Системное программирование. Конспект лекций


Двухпроходный Ассемблер - 2-й проход


Обычно 2-й проход Ассемблера читает исходный модуль с самого начала и отчасти повторяет действия 1-го прохода (лексический разбор, распознавание команд и директив, подсчет адресов). Это, однако, скорее дань традиции - с тех времен, когда в вычислительных системах ощущалась нехватка (или даже полное отсутствие) внешней памяти. В те далекие времена колода перфокарт или рулон перфоленты, содержащие текст модуля, вставлялись в устройство ввода повторно. В системах с эволюционным развитием сохраняются перфокарты и перфоленты (виртуальные), так что схема работы Ассемблера - та же. В новых системах Ассемблер может создавать промежуточный файл - результат 1-го прохода, который является входом для 2-го прохода. Каждому оператору исходного модуля соответствует одна запись промежуточного файла, и формат этой записи приблизительно такой:

Текст исходного оператора нужен только для печати листинга, Ассемблер на 2-ом проходе использует только первые 4 поля записи. Первое поле позволяет исключить строки, целиком состоящие из комментария. Второе поле позволяет избежать подсчета адресов, третье - поиска мнемоники в таблицах. Основная работа 2-го прохода состоит в разборе поля операндов и генерации объектного кода.

Некоторые общие соображения, касающиеся этой работы. Объектный код команды состоит из поля кода операции и одного или нескольких полей операндов. Код операции, как правило, имеет размер 1 байт, количество, формат и семантика полей операндом определяется для каждого типа команд данной аппаратной платформы. В общем случае операндом команды может быть:

  • регистр;
  • непосредственный операнд;
  • адресное выражение.

Виды адресных выражений зависят от способов адресации вычислительной системы, некоторые (возможно, наиболее типовые) способы адресации:

  • абсолютный адрес;
  • [базовый регистр]+смещение (здесь и далее квадратные скобки означают) "содержимое того, что взято в скобки);
  • [базовый регистр]+[индексный регистр]+смещение;
  • имя+смещение;
  • литерал.

Адресное выражение моет содержать арифметические операции, соображения, касающиеся арифметики в этом случае - те же, что и в адресной арифметике языка C.


Имена в адресных выражениях должны заменяться на значения. Замена абсолютных имен (определенных в директиве EQU) очень проста - значение имени из таблицы символов просто подставляется вместо имени. Перемещаемые имена (метки и имена переменных) превращаются Ассемблером в адресное выражение вида [базовый регистр]+смещение. В таблице символов значения этих имен определены как смещение соответствующих ячеек памяти относительно начала программы. При трансляции имен необходимо, чтобы:


  • Ассемблер "знал", какой регистр он должен использовать в качестве базового;
  • Ассемблер "знал", какое значение содержится в базовом регистре;
  • в базовом регистре действительно содержалось это значение.


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

В Intel Ассемблер использует в качестве базовых сегментные регистры (DS при трансляции имен переменных, CS при трансляции меток). Для простой программы, состоящей из одной секции, Загрузчик перед выполнением заносит во все сегментные регистры сегментный адрес начала программы и Ассемблер считает все смещения относительно него. Сложная программа может состоять из нескольких секций, и в сегментном регистре может содержаться адрес той или иной секции, причем содержимое сегментного регистра может меняться в ходе выполнения программы. Загрузка в сегментный регистр адреса секции выполняется машинными командами:



MOV AX,секция

MOV сегментный_регистр,AX

Для того, чтобы Ассемблер знал, что адрес секции находится в сегментном_регистре, применяется директива:

ASSUME сегментный_регистр:секция

Далее при трансляции имен Ассемблер превращает имена в адресные выражения вида

[сегментный_регистр]+смещение в секции

Отмена использования сегментного регистра задается директивой:

ASSUME сегментный_регистр:NOTHING



Обратим внимание на то, что при трансляции команды

MOV AX,секция

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

Более гибкая система базовой адресации применяется в S/360, S/370, S/390. В качестве базового может быть использован любой регистр общего назначения. Директива:

USING относительный_адрес,регистр

сообщает Ассемблеру, что он может использовать регистр в качестве базового, и в регистре содержится адрес - 1-й операнд. Чаще всего относительный_адрес кодируется как * (обозначение текущего значения счетчика адреса), это означает, что в регистре содержится адрес первой команды, следующей за директивой USING. Занесение адреса в базовый регистр выполняется машинной командой BALR. Обычный контекст определения базового регистра:

BALR регистр,0 USING *,регистр

С такими операндами команда BALR заносит в регистр адрес следующей за собой команды.

Зная смещение именованной ячейки относительно начала программы и смещение относительно начала же программы содержимого базового регистра, Ассемблер легко может вычислить смещение именованной ячейки относительно содержимого базового регистра.

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

Директива

DROP регистр

отменяет использование регистра в качестве базового.

В качестве базовых могут быть назначены несколько регистров, Ассемблер сам выбирает, какой из них использовать в каждом случае.

Выше мы говорили, что Ассемблер "знает" базовый регистр и его содержимое. Это "знание хранится в таблице базовых регистров. Обычно таблица содержит строки для всех регистров, которые могут быть базовыми и признак, используется ли регистр в таком качестве. Формат строки таблицы:



Алгоритм выполнения 2-го прохода представлен на рисунке.


Мы исходили из того, что 2-й проход использует промежуточный файл, сформированный 1-ым проходом. Если же 2-й проход использует исходный модуль, то алгоритм должен быть расширен лексическим разбором оператора, распознаванием мнемоники и подсчетом адресов - так же, как и в 1-ом проходе.

  • Начало 2-го прохода ассемблирования.
  • Начальные установки:


    • создание пустой таблицы базовых регистров;
    • открытие промежуточного файла исходного модуля;
    • установка в FASLE признака окончания


  • Признак окончания TRUE?
  • Считывание следующей записи промежуточного файла.
  • Если запись промежуточного файла описывает комментарий, переход на печать строки листинга
  • Выясняется, содержит оператор команду или директиву
  • Если оператор содержит команду, формируется байт кода операции (код выбирается из таблицы команд) в объектном коде.
  • Выделение следующего элемента из списка операндов с удалением его из списка и с проверкой, не обнаружен ли при выделении конец списка операндов?
  • Если конец не обнаружен, обрабатывается выделенный операнд. Проверяется, не превысило ли число операндов требуемого для данного типа команды (выбирается из таблицы команд)
  • Если число операндов превышает требуемое - формирование сообщения об ошибке
  • Если число операндов правильное, распознается и проверяется тип операнда.
  • Если тип операнда не распознан или недопустим для данной команды - формирование сообщения об ошибке.
  • Есть ли в команде имя?
  • Если в команде есть имя, оно ищется в таблице символов.
  • Если имя в таблице символов не найдено - формирование сообщения об ошибке.
  • Если найдено имя в таблице символов, оно переводится в "база-смещение"
  • Если имени в команде нет, выполняется разбор и интерпретация операнда с проверкой правильности его кодирования.
  • Если обнаружены ошибки в кодировании операнда - формирование сообщения об ошибке.
  • Формируется код поля операнда и заносится в объектный код команды и обрабатывается следующий элемент списка операндов.
  • Если обнаружен конец списка операндов, проверяется, не меньше ли число операндов требуемого для данного типа команды. Если число операндов соответствует требуемого, управление переходит на вывод объектного кода.
  • Если число операндов меньше требуемого - формирование сообщения об ошибке
  • Если обрабатываемый оператор является директивой, алгоритм разветвляется, в зависимости от того, какая это директива. При обработке любой директивы производится разбор и анализ ее операндов и (не показано на схеме алгоритма) возможно формирование сообщения об ошибке.
  • Обработка директивы типа DD включает в себя:


    • выделение элементов списка операндов;
    • для каждого элемента - распознавание типа и значения константы;
    • генерация объектного кода константы;
    • обработка возможных коэффициентов повторения.


  • Обработка директивы типа BSS может вестись точно так же, как и DD за исключением того, что вместо кода константы генерируются некоторые "пустые" коды. Однако, эти коды не нужны в объектном модуле, они могут не генерироваться, в этом случае должны предприниматься некоторые действия, формирующие "разрыв" в объектных кодах.
  • Обработка директивы типа USING (ASSUME) включает в себя занесение в соответствующую строку таблицы базовых регистров значения операнда-адреса и установку для данного регистра признака использования.
  • Обработка директивы типа USING (ASSUME) включает в себя занесение в соответствующую строку таблицы базовых регистров значения операнда-адреса и установку для данного регистра признака использования.
  • Обработка директивы END устанавливает признак окончания в TRUE. При обработке этой директивы в объектный модуль также может заносится стартовый адрес программы - параметр директивы.
  • Обработка прочих директив ведется по своим алгоритмам.
  • После окончания обработки команды или директивы сформированный объектный код выводится в файл объектного модуля.
  • Печать строки листинга. На эту точку также управление передается при выявлении ошибок. При наличии ошибки сообщение об ошибке печатается после строки листинга. Управление затем передается на считывание следующей записи промежуточного файла.
  • После того, как установлен признак окончания работы формируются и выводятся в объектный модуль коды литерального пула, таблицы связываний и перемещений.
  • Закрываются файлы, освобождается выделенная память.
  • Работа Ассемблера завершается

  • При рассмотрении алгоритма мы принимали во внимание только генерацию объектных кодов, соответствующих командам и константам. Мы не рассматривали то, какова общая структура объектного модуля. Эти вопросы будут рассматриваться в Теме 4.


    Содержание раздела