Содержание

Проектирование ассемблера

1.Процедура проектирования и разработки программных систем
2.Проект ассемблера: цели и требования
3.Внешний проект ассемблера
4.Проект архитектуры ассемблера


1. Процедура проектирования и разработки программных систем

Проект (projectus - брошенный вперёд) - система документов, определяющая процесс создания какого-либо продукта (его бумажная модель). Здесь за основу принят структурный подход с его методом пошаговой детализации, что вполне целесообразно в применении к программным системам (ПС) малого и даже среднего размера (до 10 т. операторов). Основные составляющие проекта и этапы разработки ПС:

1. Цели и требования

1) Назначение;
2) Определение пользователя;
3) Подробное перечисление функций;
4) Публикации;
5) Эффективность;
6) Совместимость;
7) Конфигурация ТО и БПО;
8) Безопасность;
9) Обслуживание;
10) Установка;
11) Надёжность;
12) Мобильность.

Следует стремиться к использованию измеряемых категорий и чётких критериев.

2. Внешний проект

Содержит описание ожидаемого поведения ПС с точки зрения пользователя в форме внешних спецификаций. Проектируемый продукт на этом этапе рассматривается как чёрный ящик. Общая идея этого этапа состоит в достаточно детальном определении входов и выходов ПС, включая определение входных и выходных языков, структуры входных и выходных файлов, содержания экранных форм и т.д. Иногда этот этап называют созданием прототипа ПС. В последующем внешний проект служит основой создания проекта архитектуры ПС и одновременно служит источником информации для технических писателей, составляющих комплекс документации для пользователя.

Основные изобразительные средства этого этапа: чертежи, схемы, рисунки, словесные описания, формализованные описания (математические выкладки, БНФ, расширенная БНФ (РБНФ), синтаксические диаграммы Вирта).

РБНФ позволяет заменить рекурсию итерацией. К метасимволам БНФ добавлены скобки факторизации "{" и "}", в которые заключают повторяющуюся часть грамматической конструкции, в том числе и нуль раз. Пример.
<Составной оператор> ::= begin {<Оператор> ; } <Оператор> end .

Cинтаксические диаграммы Вирта впервые были использованы при определении синтаксиса АЯ Pascal. Это графическая форма РБНФ.

3. Проект архитектуры

Под архитектурой понимается распределение функций в системе. Для ПС это означает разбиение её на структурные и функциональные модули, в качестве которых могут быть единицы компиляции (файлы), процедуры, объекты, типы (классы) и т.д. Проект архитектуры включает:
- определение метода преобразования данных,
- определение основных структур данных,
- разработка иерархической схемы вызовов модулей,
- составление спецификаций модулей.

4. Проектирование модулей

На этом этапе выполняется проектирование и разработка отдельных модулей ПС. Методология проектирования и разработки подсистемы в общих чертах повторяет методологию проектирования и разработки системы в целом.

5. Кодирование

Запись программы на языке разработки.

6. Комплексная сборка, тестирование и отладка

ПС среднего размера обычно отлаживают по мере сборки. Есть два классических метода сборки - нисходящий и восходящий. Нисходящий метод базируется на использовании заглушек - процедур, которые в очень упрощённой форме моделируют поведение реальных процедур нижних уровней ПС. По мере продвижения разработки ПС заглушки заменяются реальными процедурами. Восходящий подход базируется на использовании драйверов - программных единиц, моделирующих поведение реальных программных единиц вызывающего уровня. На практике используют всевозможные комбинации этих двух классических методов. ПС малого размера отлаживают обычно методом большого скачка, т.е. сразу всю систему целиком, в некоторых случаях предварительно отлаживая лишь процедуры самых нижних уровней.

Проект должен содержать необходимую систему тестов для проведения отладки системы на всех уровнях, включая внешнее тестирование. Этот вид тестирования относится ко всей системе в целом.

7. Сопровождение

Сопровождение ПС предполагает внесение в неё изменений на этапе её эксплуатации. Такая необходимость может быть вызвана изменением условий применения или обнаружением ошибок. Важность этого этапа вытекает из структуры трудозатрат на обеспечение жизненного цикла ПС среднего размера: проектирование - 15%, кодирование - 10%, тестирование и отладка - 25%, сопровождение - 50%. В этой связи разумно предъявить достаточно жёсткие требования к качеству сопровождающей систему документации.

2. Проект ассемблера: цели и требования

Сразу же дадим имя разрабатываемому продукту - BAS (Базовый ассемблер). Учитывая учебный характер проекта, определим следуюшие цели и требования:
1) Назначение. Ассемблирование программ в процессе изучения студентами методов низкоуровнего программирования на примере учебной ЦВМ.
2) Пользователи. Студенты ФИПМ ВлГУ.
3) Функции. Перевод исходного модуля (ИМ) на языке ассемблера в формат абсолютного загрузочного модуля (АМ) для учебной ЦВМ с выводом протокола ассемблирования (листинга).
4) Публикации. 1) BAS: описание языка ассемблера для учебной ЦВМ. 2) BAS: инструкция по установке и эксплуатации.
5) Конфигурация. Стандартный состав IBM PC, MS DOS любой версии.
6) Установка. Методом копирования EXE-файла с установочной дискеты на жёсткий диск PC.

3. Внешний проект ассемблера

1. Входы и выходы

Вход: ИМ. Выходы: АМ и листинг. Все файлы текстовые. Установим стандарт на расширение физических имён файлов: ИМ - *.bsm,АМ - *.abm, листинг - *.lst.

2. Определение синтаксиса BAS

<Программа> ::= { <Оператор> | <Комментарий> | <Пустая строка> }
<Оператор> ::= [<Метка>] " " {" "}<Код операции> [ " " {" "}<Операнд>[ , <Операнд>]] <Комментарий>
<Метка> ::= <Имя>
<Код операции> ::= <Имя>
<Операнд> ::= <Имя> | <Целое число>
<Комментарий> ::= { " " } ; <Строка из любых неуправляющих литер кодовой таблицы ЦВМ>

И так далее до полного определения синтаксиса языка.

3. Семантика BAS

Здесь должны быть даны сведения о структуре команд ЦВМ и способах адресации, система команд ЦВМ с указанием мнемоник кодов операций, ограничения на значения операндов операторов BAS. Мы ограничимся только описанием псевдокоманд (п/к) BAS - операторов, которые используются для управления ассемблером и в машинные инструкции не переводятся.

П/к start: Единственный операнд команды определяет адрес первого размещаемого объекта (машинной команды или данного) программы. Его значение должно быть в пределах 0..4095. Операнд должен задаваться только целым числом. Метка этой команды одновременно является именем всей программы. По умолчанию - "NoName". П/к start обязательно должна присутствовать в программе и быть единственной. Предшествовать этой команде могут только комментарии.

П/к end: Определяет конец ИМ. Никакой текст после команды end ассемблером не обрабатывается и в листинге не отображается. Единственный операнд этой команды рассматривается как адрес точки входа в программу. Его значение должно быть в пределах 0..4095. Это обязательная команда программы и должна быть в ней единственной.

П/к word: Определяет инициализированную переменную в памяти ЦВМ. Операнд определяет значение переменной. Допустимый диапазон значений операнда: -2^23..(2^23 - 1).

П/к resb: Резервирует блок памяти заданной длины. Длина блока определяется операндом команды и может быть в диапазоне значений 0..4096.

4. Организация АМ

<АМ> ::= <Заголовок> { <Текст> } <Концевик>
<Заголовок> ::= H <ИМ (6)> <НАМ (6)> <ДМ (6)>
<Текст> ::= T <АЗТ (6)> <ДТ (2)> <Байты кода программы (2*ДТ)>
<Концевик> ::= E <АТВ (6)>

Каждый элемент ИМ типов H, T и E размещается в отдельной строке текстового файла. Обозначения: ИМ - имя модуля, НАМ - начальный адрес модуля, ДМ - длина модуля, АЗТ - адрес загрузки текста, ДТ - длина текста, АТВ - адрес точки входа в программу. Все эти поля записываются в символьном шестнадцатеричном формате. В скобках указаны длины полей. Мы сознательно используем символьный формат представления АМ как более наглядный, хотя на практике для записи кода используют исключительно бинарный формат.

5. Организация листинга

Здесь необходимо детально определить структуру листинга как документа (см. пример ниже).

6. Пример входных и выходных данных BAS

Файл inc.bsm:

; var = var + 1
inc    start   20
       lda     var
       add     c1
       sta     var
       hlt
;
var    resb    3
c1     word    1
       end     inc

Файл inc.abm:

Hinc   000014000010
T0000140A00001E1800210C001EFF
T00002103000001
E000014

Файл inc.lst:

    Базовый учебный ассемблер BAS 1.0
Первый просмотр:
  Таблица символов:
     a     = 000     x     = 001
     l     = 002     inc   = 014
     var   = 01E     c1    = 021
Второй просмотр:
Адр.   Код          #         Оператор
                    1    ; var = var + 1
014                 2    inc    start   20
014  00 00 1E       3           lda     var
017  18 00 21       4           add     c1
01A  0C 00 1E       5           sta     var
01D  FF             6           hlt
                    7    ;
01E                 8    var    resb    3
021  00 00 01       9    c1     word    1
024                10           end     inc
Статистика:
  *** Ошибок не обнаружено ***
 Адрес загрузки программы: 014
 Пусковой адрес программы: 014
 Длина программы:          010
 Имя файла входа: inc.bsm

4. Проект архитектуры ассемблера

Мы рассмотрим эту тему несколько шире, чем этого обычно требует содержание программного проекта. Это связано с тем, что попутно мы должны изучить сами принципы ассемблирования и понять как организован ассемблер. Так как на данном этапе мы ешё не обладаем опытом проектирования таких программ, то будем вынуждены затронуть здесь и кодирование, чего в реальных проектах, естественно, не делают. С другой стороны в учебных целях мы существенно сократим рутинную часть проекта и во многих случаях ограничимся лишь примерами.

1. Метод преобразования данных

Ассемблер строится по классической двухпроходной схеме. В ходе первого прохода строится таблица символов, определяемых программистом. В ходе второго просмотра генерируются командды и заносятся в АМ. Попутно в ходе второго прохода формируется файл листинга.

2. Структуры данных ассемблера

Основные структуры данных первого прохода:
файл ИМ;

var count, dcount: Integer; { Cчётчик адреса и его приращение }
const  mtmk = 21;   {Длина таблицы машинных команд (ТМК) }
       Tmk: array[1..mtmk] of    {ТМК}
         record
            m: string[6];   {Мнемоника команды}
            c: string[2];   {Шестнадцатеричный код команды}
            f: Integer      {Формат команды}
         end =
        ((m: 'add'; c: '18'; f: 1),
            . . .
         (m: 'nop'; c: 'FE'; f: 4));
const  mts = 100;   {Длина таблицы символов (ТС)}
var    ts: array[1..mts] of  {ТС}
         record
            n: string[6];   {Имя}
            v: Integer      {Значение}
         end;
const mtpk = 4;   {Длина таблицы псевдокоманд}
      tpk: array of[1..mtpk] of string[5] =
      ("start", "end", "word", "resb");

Основные структуры данных второго прохода:
файл ИМ; ТМК; ТС; ТПК; строка для сборки команд; файл АМ; файл листинга; буферы файлов АМ и листинга.

3. Алгоритм первого прохода

procedure Pass1;
begin
{Инициализация переменных};
repeat
   {Ввод очередного оператора ИМ}; dcount := 0;
   if {Это комментарий} then Continue;
   {Выделение метки и кода операции};
   if {Метка есть} then
      {Поместить метку в ts с значением count};
   {Поиск кода операции в tpk};
   if {Найдена} then
      {Обработка п/к с определением dcount}
   else
      begin
      {поиск кода операции в tmk};
      if {Найдена} then
         {Определение dcount}
      else
         {Сообщение об ошибке "Неопределённый код операции"}
      end;
   count := count + dcount;
until {Код операции = 'end'}
end; {Pass1}

4. Алгоритм второго прохода

procedure Pass2;
begin
{Инициализация переменных};
repeat
   {Ввод очередного оператора ИМ}; dcount := 0;
   if {Это комментарий} then Continue;
   {Выделение кода операции};
   {Поиск кода операции в tpk};
   if {Найдена} then
      {Обработка п/к с определением dcount}
   else
      begin
      {поиск кода операции в tmk};
      if {Найдена} then
         {Генерация машинной команды и определение dcount}
      else
         {Сообщение об ошибке "Неопределённый код операции"}
      end;
   {Размещение команды в АМ};
   {Формирование строки листинга и её вывод};
   count := count + dcount;
until {Код операции = 'end'};
{Определение и вывод статистики ассемблирования};
end; {Pass2}

5. Схема вызовов процедур

Анализ алгоритмов первого и второго проходов ассемблера позволяет определить основные процедуры данного шага детализации. Это могут быть:
RdLine - ввод очередной строки ИМ с проверкой неожиданного окончания входного файла;
GetLex - лексический анализатор для определения имён и констант;
PutInTs - помещение имени в ТС;
PoiskInTpk - поиск в ТПК;
ExecPk1 - обработка п/к на первом проходе;
ExecPk2 - обработка п/к на втором проходе;
PoiskInTmk - поск в таблице машинных команд;
Error - обработка ошибок ассемблирования;
GenCode - генерация кода машинной команды;
PutInAM - размещение сгенерированной команды в АМ;
PrintLine - вывод строки листинга.

На данном этапе разработки можно составить такую схему вызовов:

BAS(
   Pass1( RdLine, GetLex, PutInTs, PoiskInTpk, ExecPk1, PoiskInTmk, Error),
   Pass2( RdLine, GetLex, PoiskInTpk, ExecPk2, PoiskInTmk, GenCode, PutInAM, PutLine)
)

6. Обработка псевдокоманд

Обозначим через Arg значение операнда текущего оператора п/к. На первом проходе необходимо выполнить следующие операции:
start: count := Arg;
end: ;
word: dcount := 3;
resb: dcount := Arg;

На втором проходе необходимо выполнить следующие операции:
start: count := Arg; Определение имени программы и её длины. Открытие файла АМ. Формирование заголовка АМ.
end: Определение адреса точки входа в программу и формирование концевика АМ. Закрытие файла АМ.
word: dcount := 3; Генерация в Code кода константы с значением Arg.
resb: dcount := Arg; Установка признака формирования новой записи текста АМ.

7. Спецификации процедур

Составим для примера спецификацию процедуры GenCode.

Спецификация модуля GenCode - "Генерация машинной команды"

1. Заголовок: procedure GenCode(Index: Integer);
2. Входы: Index - индекс записи в ТМК, соответствующей текущему оператору;
3. Выходы: нет;
4. Глобальные имена: ts, tmk, Code;
5. Функция: построение в поле строковой переменной Code шестнадцатеричного кода команды, соответствующей текущему оператору ИМ. Предполагается, что указатель текущей литеры входа установлен на первую литеру после мнемонического кода операции текущего оператора.
7. Используемые процедуры: GetLex, PoiskInTs, ToInt, ToHex;
8. Особые случаи: нет.

8. Генерация машинных команд

Рассмотрим алгоритм генерации кода команды на примере команды 1-го формата. Команды этого формата имеют длину три байта. Код операции занимает биты 23..16; бит индексной адресации - бит 15, адрес операнда - биты 11..0.

procedure GenCode(Index: Integer);
var Arg1,Arg2: Integer; {Значения аргументов оператора}
...
begin
Code := tmk[Index].c;   {Установка кода операции}
case tmk[Index].f of
1: begin                {Генерация кода команды 1-го формата}
   GetLex(LexType,Str); {Чтение первого операнда}
   case LexType of
      NAME:   PoiskInTS(Str,Arg1); {Получение из ts значения имени}
      NUMBER: ToInt(Str,Arg1);     {Преобразование числа в целый тип}
   else
      begin Arg1 := 0; Error(17) end {Ошибка в операнде}
   end; {case}
   GetLex(LexType, Str);   {Чтение запятой}
   if Str = ',' then
      begin
      GetLex(LexType,Str); {Чтение второго операнда}
      case LexType of
         NAME:   PoiskInTS(Str,Arg2); {Получение из ts значения имени}
         NUMBER: ToInt(Str,Arg2);     {Преобразование числа в целый тип}
      else
         begin Arg2 := 0; Error(17) end {Ошибка в операнде}
      end; {case}
      end
   else
      Arg2 := 0;    {Значение по умолчанию}
   Code := Code + ToHex(Arg2<<15 + Arg1,4); {Генерация кода}
   end;              {Конец генерации кода команды 1-го формата}
2: ...
end; {case tmk[Index].f}
end; {GenCode}

9. Обработка ошибок

Наличие ошибок в программе пользователя не может считаться исключением. Каков бы ни был файл ИМ программа BAS должна достойно завершить свою работу. При трансляции программы, в которой могут содержаться ошибки, возникает три проблемы:
- обнаружение ошибки;
- определение местоположения ошибки;
- восстановление программы с целью продолжения её трансляции.

Различают следующие виды ошибок:
- синтаксические;
- семантические (например, повторное определение метки в программе);
- ошибки ограничения реализации (например, переполнение таблицы символов).
             
Способы восстановления:
- игнорирование (например, пропуск оператора с неправильным кодом операции);
- обнуление (например, выход значения операнда из допустимых пределов).

Способы организации обработки:
- трансляция до первой ошибки;
- прекращение обработки, если серьёзность ошибки превышает установленный уровень;
- включение сообщений об ошибках в конец листинга;
- вывод сообщений об ошибках вместе со строками, в которых они были обнаружены.


Copyright г Барков Валерий Андреевич, 2000