Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Машинно-независимые характеристики ассемблера

Поиск

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

В разд. 2.3.1 мы обсудим таблицы и алгоритмы, необходимые для реализации литералов. В разд.2.3.2 будут рассмотрены две директивы ассемблера (EQU и ORG), основной функцией которые является определение имен. В разд. 2.3.3 коротко рассматриваются выражения в языке ассемблера. Обсуждаются различные типы таких выражений, их вычисление и использование.

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

Литералы

Очень часто бывает удобно иметь возможность записывать значения константы, используемой в качестве операнда, непосредственно в команде, где она используется. Это позволяет отказаться от использования отдельного предложения для определения константы и соответствующей ей метки. Такой операнд называется литеральным (literal), поскольку значение константы задается в виде строки символов. На рис.2.9 приведен пример использования литералов. Объектный код, сгенерированный для предложений этой программы, показан на рис.2.10. (Эта программа является модификацией программы, представленной на рис.2.5; другие изменения мы обсудим в разд. 2.3.)

 

Строка Исходное предложение

5 COPY START 0 КОПИРОВАНИЕ ФАЙЛА

10 FIRST STL RETADR СОХРАНЕНИЕ АДРЕСА ВОЗВРАТА

13 LDB #LENGTH УСТАНОВКА БАЗОВОГО РЕГИСТРА

14 BASE LENGTH

15 CLOOP +JSUB RDREC ВВОД ВХОДНОЙ ЗАПИСИ

20 LDA LENGTH ПРОВЕРКА НА EOF (LENGTH = 0)

25 COMP #0

30 JEQ ENDFIL ВЫХОД, ЕСЛИ НАШЛИ EOF

35 +JSUB WRREC ВЫВОД ВЫХОДНОЙ ЗАПИСИ

40 J CLOOP ЦИКЛ

45 ENDFIL LDA =C"EOF" ЗАНЕСЕНИЕ МАРКЕРА КОНЦА ФАЙЛА

50 STA BUFFER

55 LDA #3 УСТАНОВИТЬ LENGTH = 3

60 STA LENGTH

65 +JSUB WRREC ЗАПИСЬ EOF

70 J @RETADR ВОЗВРАТ ИЗ ПРОГРАММЫ

93 LTORG

95 RETADR RESW 1

100 LENGTH RESW 1 ДЛИНА ЗАПИСИ

105 BUFFER RESB 4096 ДЛИНА БУФЕРА – 4096 БАЙТ

106 BUFEND EQU *

107 MAXLEN EQU BUFEND-BUFFER МАКСИМАЛЬНАЯ ДЛИНА ЗАПИСИ

110 *

115 * ПОДПРОГРАММА ВВОДА ЗАПИСИ НА БУФЕР

120 *

125 RDREC CLEAR X ОБНУЛЕНИЕ СЧЕТЧИКА ЦИКЛА

130 CLEAR A ОБНУЛЕНИЕ РЕГИСТРА А

132 CLEAR S ОБНУЛЕНИЕ РЕГИСТРА S

133 +LDT #MAXLEN

135 RLOOP TD INPUT ПРОВЕРКА УСТРОЙСТВА ВВОДА

140 JEQ RLOOP ЦИКЛ ДО ПОЛУЧЕНИЯ ГОТОВНОСТИ

145 RD INPUT ЧТЕНИЕ СИМВОЛА В РЕГИСТР А

150 COMP A,S ПРОВЕРКА НА КОНЕЦ ЗАПИСИ (Х"00")

155 JEQ EXIT ВЫХОД ИЗ ЦИКЛА ПО КОНЦУ ЗАПИСИ

160 STCH BUFFER,X ЗАПИСЬ СИМВОЛА В БУФЕР

165 TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ МАКСИМАЛЬНОЙ

170 JLT RLOOP ДЛИНЫ

175 EXIT STX LENGTH ЗАПОМИНАНИЕ ДЛИНЫ ЗАПИСИ

180 RSUB ВОЗВРАТ ИЗ ПОДПРОГРАММЫ

185 INPUT BYTE X"F1" КОД УСТРОЙСТВА ВВОДА

195 *

200 * ПОДПРОГРАММА ВЫВОДА ЗАПИСИ ИЗ БУФЕРА

205 *

210 WRREC CLEAR X ОБНУЛЕНИЕ СЧЕТЧИКА ЦИКЛА

212 LDT LENGTH

215 WLOOP TD =X"05" ПРОВЕРКА УСТРОЙСТВА ВЫВОДА

220 JEQ WLOOP ЦИКЛ ДО ПОЛУЧЕНИЯ ГОТОВНОСТИ

225 LDCH BUFFER,X ЧТЕНИЕ СИМВОЛА ИЗ БУФЕРА

230 WD =X"05" ВЫВОД СИМВОЛА

235 TIXR T ЦИКЛ, ПОКА НЕ БУДУТ ВЫВЕДЕНЫ

240 JLT WLOOP ВСЕ СИМВОЛЫ

245 RSUB ВОЗВРАТ ИЗ ПОДРОГРАММЫ

255 END FIRST

 

Рис. 2.9. Программа, демонстрирующая дополнительные возможности ассемблера.

 

Строка Адрес Исходное предложение Объектный код

5 0000 COPY START 0

10 0000 FIRST STL RETADR 17202D

13 0003 LDB #LENGTH 69202D

14 BASE LENGTH

15 0006 CLOOP +JSUB RDREC 4B101036

20 000A LDA LENGTH 032026

25 000D COMP #0 290000

30 0010 JEQ ENDFIL 332007

35 0013 +JSUB WRREC 4B10105D

40 0017 J CLOOP 3F2FEC

45 001A ENDFIL LDA =С"EOF" 032010

50 001D STA BUFFER 0F2016

55 0020 LDA #3 010003

60 0023 STA LENGTH 0F200D

65 0026 +JSUB WRREC 4B10105D

70 002A J @RETADR 3E2003

93 LTORG

002D * =C"EOF" 454F46

95 0030 RETADR RESW 1

100 0033 LENGTH RESW 1

105 0036 BUFFER RESB 4096

106 1036 BUFEND EQU *

107 1000 MAXLEN EQU BUFEND-BUFFER

110 *

115 * ПОДПРОГРАММА ВВОДА ЗАПИСИ НА БУФЕР

120 *

125 1036 RDREC CLEAR X B410

130 1038 CLEAR A B400

132 103A CLEAR S B440

133 103C +LDT #MAXLEN 75101000

135 1040 RLOOP TD INPUT E32019

140 1043 JEQ RLOOP 332FFA

145 1046 RD INPUT DB2013

150 1049 COMP A,S A004

155 104B JEQ EXIT 332008

160 104E STCH BUFFER,X 57C003

165 1051 TIXR T B850

170 1053 JLT RLOOP 3B2FEA

175 1056 EXIT STX LENGTH 134000

180 1059 RSUB 4F0000

185 105C INPUT BYTE X"F1" F1

195 *

200 * ПОДПРОГРАММА ВЫВОДА ЗАПИСИ ИЗ БУФЕРА

205 *

210 105D WRREC CLEAR X B410

212 105F LDT LENGTH 774000

215 1062 WLOOP TD =X"05" E32011

220 1065 JEQ WLOOP 332FFA

225 1068 LDCH BUFFER,X 53C003

230 106B WD =X"05" DF2008

235 106E TIXR T B850

240 1070 JLT WLOOP 3B2FEF

245 1073 RSUB 4F0000

255 END FIRST

1076 * =X"05" 05

 

Рис.2.10. Объектный код для программы на рис.2.9.

 

В нашем языке ассемблера литералы задаются с помощью префикса =, за которым следует константа, задаваемая точно так же, как в предложении BYTE. Таким образом, в предложении

45 001A ENDFIL LDA =С'ЕОF' 032010

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

215 1062 WLOOP ТD =X'05' ЕЗ2011

задается 1-байтовый литерал, имеющий шестнадцатеричное значение 05. Символьная нотация для задания литералов может быть различной, однако в большинстве ассемблеров для того, чтобы упростить идентификацию литералов, используется определенный символ (у нас =).

Важно уяснить различие между литералом и непосредственным операндом. В случае непосредственного операнда его значение транслируется как составная часть машинной команды. В случае литерала его значение генерируется в виде константы в оперативной памяти, а ее адрес используется в качестве целевого адреса команды. Употребление литерала дает точно та кой же результат, как если бы программист явно определил константу и использовал в качестве операнда ее метку. (Действительно, сгенерированный объектный код для строк 45 и 215 в распечатке 5 идентичен объектному коду для соответствующих строк программы на рис.2.6.) Вам следует сравнить объектный код, сгенерированный для строк 45 и 55 (рис.2.10), для того, чтобы убедиться, что вы уяснили разницу в обработке литералов и непосредственных операндов.

Все литеральные операнды, используемые в программе, объединяются в один или несколько литеральных пулов (literals pools). Обычно литеральный пул помещается в конце программы и распечатывается в ее листинге. В литеральном пуле для каждого литерала показаны назначенный ему адрес и сгенерированное значение. Пример такого литерального пула приведен на рис.2.10 (он расположен непосредственно после предложения END). В данном случае пул состоит только из одного литерала =X'05'.

Однако в некоторых случаях хотелось бы размещать литеральный пул в другом месте объектной программы. Это можно сделать с помощью директивы ассемблера LTORG (строка 93 на рис.2.10). Когда ассемблер встречает предложение LTORG, он создает литеральный пул, содержащий все литеральные операнды, которые были использованы в программе с момента обработки предыдущего предложения LTORG (или с начала программы). Литеральный пул помещается в объектную программу в то место, где было встречено предложение LTORG (см. рис.2.10). Конечно, литералы, помещенные в литеральный пул по команде LTORG, не будут повторно помещаться в литеральный пул в конце программы.

Если бы мы не использовали предложение LTORG в строке 93, то литерал =С'EOF' был бы помещен в литеральный пул в конце программы. В этом случае данный операнд получил бы адрес 1073 и мы не смогли бы использовать для него адресацию относительно счетчика команд. Конечно, все дело в том, что мы зарезервировали очень большое пространство для массива BUFFER. Расположив литеральный пул перед этим массивом, мы смогли избежать использования расширенного командного формата для ссылок на литералы. Обычно необходимость в команде, подобной LTORG, возникает тогда, когда желательно расположить литеральные операнды в непосредственной близости от команд, в которых они используются.

Большинство ассемблеров умеют распознавать повторное использование литералов и хранят только один экземпляр каждого из них. Например, литерал =X'05' используется в нашей программе в строках 215 и 230. Однако для его хранения генерируется только одна константа и операнды обеих команд ссылаются на адрес этой константы.

Простейший способ распознать повторное использование литерала заключается в сравнении определяющих их строк символов (в нашем случае это строка =Х'05'). Однако в некоторых случаях лучше сравнивать не определения, а сгенерированные коды. Например, литералы =C'EOF' и =X'454F46' определяют идентичные значения. Если бы ассемблер умел распознавать их эквивалентность, то он мог бы не заводить дублирующую константу. В то же время выигрыш от подобного способа обработки литералов обычно не столь велик, чтобы оправдать дополнительные усложнения ассемблера.

Если для распознавания повторного использования литералов используется символьная строка, то следует быть внимательными при обработке литералов, значение которых зависит от месторасположения в программе. Предположим, что мы разрешаем использовать литералы, которые ссылаются на текущее значение счетчика размещений (часто обозначаются с помощью символа *). Такие литералы иногда бывают полезны для загрузки базового регистра. Например, предложения

LDB = *

BASE *

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

Однако такое обозначение может вызвать трудности распознавания повторно используемых литералов. Если литерал =* появился бы в нашей программе в строке 13, то его значение было бы 0003. Если тот же операнд был бы задан в строке 55, то он определял бы операнд, имеющий значение 0020. В этом случае литеральные операнды имеют идентичные имена, нo различные значения. Следовательно, оба эти операнда должны быть помещены в литеральный пул. Те же самые проблемы возникают, если литерал ссылается на какой-либо другой объект, значение которого изменяется между двумя точками программы.

Теперь мы готовы к тому, чтобы описать, как ассемблер обрабатывает литеральные операнды. Основной структурой данных, используемой при обработке литералов, является таблица литералов LITTAB. Для каждого использованного в программе литерала эта таблица содержит следующую информацию: имя литерала, его значение, длину и назначенный ему адрес. Очень часто LITTAB организуется в виде хеш-таблицы, в которой в качестве ключа используется имя литерала или его значение.

Если во время первого просмотра ассемблер встречает литеральный операнд, то он пытается найти его в LITTAB. Если литерал уже присутствует в таблице, то никаких дополнительных действий не требуется. Если литерала в таблице еще нет, то он помещается в LITTAB (значение адреса пока остается неопределенным). Когда во время первого просмотра встречается предложение LTORG или конец программы, ассемблер просматривает таблицу литералов и назначает каждому литералу его адрес (за исключением тех литералов, которым адреса были назначены ранее). После назначения адреса для очередного литерала счетчик размещений продвигается на длину литерального операнда.

Во время второго просмотра адреса литеральных операндов извлекаются из LITTAB и используются для генерации объектного кода. Значения литеральных операндов в литеральном пуле заносятся в соответствующие места объектной программы точно так же, как если бы эти значения были сгенерированы с помощью предложений BYTE или WORD. Если значение литерального операнда представляет собой программный адрес (например, значение счетчика размещений), то ассемблер должен сгенерировать для него соответствующую запись-модификатор в объектном представлении.

Для того чтобы убедиться, что вы поняли, как создается и используется таблица литералов, вам следует применить описанную нами процедуру к исходной программе на рис.2.9. Объектный код и литеральные пулы, которые вы получите, должны быть такими, как показано на рис.2.10.

2.3.2. Средства определения имен

До сих пор в рассмотренных нами ассемблерных программах мы имели дело только с одним способом определения символических имен пользователя. Эти имена появлялись в качестве меток команд или областей данных. Значением такой метки является адрес, назначаемый предложению, в котором она определена. Большинство ассемблеров предоставляет программисту дополнительные средства для определения имен и задания их значений. К числу обычно используемых директив ассемблера относится директива EQU (от EQUate). В общем виде это предложение записывается следующим образом:

имя EQU значение

Данное предложение определяет некоторое имя (т. е. вносит его в SYMTAB) и присваивает ему значение. Значение может задаваться в виде константы пли выражения, в котором могут использоваться константы и ранее определенные имена. Образование и использование выражений мы обсудим в следующем разделе.

Одним из общих применений EQU является введение символических имен вместо числовых значений, чтобы упростить чтение программы. Например, в строке 133 программы на рис.2.5 для того, чтобы загрузить в регистр Т число 4096, мы использовали предложение

+LDT #4096

Данная величина представляет собой максимальную длину записи, которую мы можем прочитать с помощью подпрограммы RDREC. Однако смысл этого предложения далеко не очевиден. Если мы включим в нашу программу предложение

MAXLEN EQU 4096

то сможем записать строку 133 как

+LDT #MAXLEN

Когда ассемблер встретит предложение EQU, он занесет МАХLEN в таблицу имен (со значением 4096). Во время трансляции команды LDT ассемблер найдет MAXLEN в SYMTAB и использует его значение в качестве операнда. Получаемый при этом объектный код будет таким же, как и в предыдущей версии, однако исходная программа легче для понимания. Кроме того, если понадобится изменить значение величины, задающей максимальную длину записи, то проще сделать это в одном предложении EQU, чем искать все вхождения 4096 в исходной программе.

Другое общее применение EQU заключается в определении мнемонических имен для регистров. Мы предположили, что наш ассемблер распознает стандартные мнемонические имена регистров - А, X, L и т. д. Однако представим себе, что ассемблер требует использовать для задания регистров их номера (например, в команде RMO). Тогда мы должны были бы писать RMO 0, 1 вместо RMO А, X. В этом случае программист мог бы включить в свою программу следующую последовательность предложений:

А EQU 0

X EQU 1

L EQU 2

.

.

.

В результате обработки этих предложений имена А, X, L,... были бы занесены в SYMTAB и имели бы значения 0, 1, 2,.... После этого команда, подобная RMO А, X, была бы допустима. Ассемблер, обрабатывая такое предложение, должен был бы просмотреть SYMTAB и при трансляции использовать для имен А и X соответствующие им значения 0 и 1.

На машине, подобной УУМ, задание для регистров пользовательских имен большого смысла не имеет. Гораздо проще использовать символические имена, встроенные в ассемблер. К тому же стандартные имена (base, index и т. п.) отражают способ использования регистров. Рассмотрим, однако, машину, имеющую регистры общего назначения (например, System/370). Обычно для обозначения этих регистров используются номера вида 0, 1, 2,... или имена вида R0, R1, R2,.... В конкретной программе некоторые из них могут быть использованы как базовые регистры, другие - как индексные, третьи - как сумматоры и т. д. Более того, в различных программах функциональное распределение регистров может быть различным. В этом случае программист может определить имена, отражающие функциональное назначение регистров конкретной программы, с помощью следующих предложений:

BASE EQU 1

COUNT EQU 2

INDEX EQU 3

Имеется еще одна общеупотребительная директива ассемблера, позволяющая косвенно задавать значения символическим именам. Обычно эта директива называется ORG (от ORiGin). Она имеет следующий вид:

ORG значение

где значение представляет собой константу или выражение, в котором могут быть использованы константы и ранее определенные имена. Если во время ассемблирования программы встречается данное предложение, то заданное им значение заносится в счетчик размещений (LOCCTR). Поскольку для определения значений имен используется LOCCTR, то предложение ORG распространяет свое влияние на все последующие определения меток.

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

STAB

100 элементов

SYMBOL VALUЕ FLAGS

100 элементов

   
     
     
     
     

...

...

...

В этой таблице SYMBOL представляет собой 6-байтовое поле для хранения имен; VALUE - 1-словное поле для хранения значения, присвоенного имени; FLAGS - 2-байтовое поле для спецификации типа и другой информации.

Мы могли бы зарезервировать пространство для этой таблицы с помощью предложения

STAB RESB 1100

Естественно, что мы хотим иметь возможность ссылаться на элементы таблицы с помощью индексной адресации (помещая в индексный регистр величину смещения требуемого элемента относительно начала таблицы). Так как нам нужен доступ к отдельным полям элементов, то мы должны определить метки SYMBOL, VALUE и FLAGS. С помощью предложений EQU это можно сделать следующим образом:

SYMBOL EQU STAB

VALUE EQU STAB+6

FLAGS EQU STAB+9

Теперь для ссылки на поле VALUE мы можем использовать предложения вида

LDA VALUE,X

Однако такой способ определения лишь задает метки, но не дает наглядного представления о структуре таблицы.

С помощью предложения ORG мы можем определить те же имена следующим образом:

STAB RESB 1100

ORG STAB

SYMBOL RESB 6

VALUE RESW 1

FLAGS RESB 2

ORG STAB + 1100

Первое предложение ORG заносит в счетчик размещений начальный адрес таблицы STAB. В следующем предложении метке SYMBOL будет присвоено текущее значение LOCCTR (т. е. тот же адрес, что и STAB). Затем LOCCTR будет продвинут и его новое значение (STAB + 6) будет присвоено метке VALUE. Аналогично будет определено значение метки FLAGS. В результате эти метки получат такие же значения, как и при определении с помощью EQU, но теперь структура STAB стала очевидной.

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

Определения, задаваемые предложениями EQU и ORG, содержат ограничения, являющиеся общими для всех директив ассемблера, в которых определяются имена. В случае EQU все метки, используемые в правой части предложения (т. е. все термы, используемые для определения значения нового имени), должны быть уже определены. Таким образом, последовательность предложений

ALPHA RESW 1

BETA EQU ALPHA

является допустимой, тогда как последовательность

BETA EQU ALPHA

ALPHA RESW 1

недопустима. Причина этого ограничения кроется в самом процессе распознавания имен. Так, во втором случае мы не сможем присвоить значение метке ВЕТА (так как метка ALPHA еще не определена), а алгоритм работы нашего двухпросмотрового ассемблера требует, чтобы все имена были определены во время первого просмотра.

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

ORG ALPHA

ВYТЕ1 RESB 1

ВYТЕ2 RESB 1

ВYТЕЗ RESB 1

ORG

ALPHA RESW 1

нe может быть обработана. В данном случае ассемблер не знает (во время первого просмотра), какое значение следует установить в счетчик размещений в ответ на первое предложение ORG. В результате невозможно во время первого просмотра вычислить адреса для меток BYTE1, BYTE2, BYTE3.

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

ALPHA EQU BETA

BETA EQU DELTA

DELTA RESW 1

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

Выражения

Во всех предложениях языка ассемблера, рассмотренных нами ранеев качестве операндов использовались отдельные термы (константы, метки и т. п.). В большинстве ассемблеров наряду с одиночными термами разрешается использовать выражения. Каждое такое выражение вычисляется ассемблером во время трансляции, а затем полученное значение используется в виде адреса или непосредственного операнда.

Обычно допускаются арифметические выражения, которые строятся по стандартным правилам с помощью операций +,-,*,/. Деление чаще всего определяется как целочисленное. В выражениях можно использовать константы, метки и специальные термы. Одним из таких общеупотребительных специальных термов является терм, ссылающийся на текущую величину счетчика размещений (часто обозначается как *). Значением этого терма является адрес, который будет присвоен очередной команде или области данных. Так, на рис.2.9 предложение

106 BUFEND EQU *

устанавливает в качестве значения BUFEND адрес байта, расположенного непосредственно после поля, отведенного под буфер.

В разд. 2.2 мы обсуждали проблемы, связанные с перемещением программ. Мы видели, что в объектной программе имеются адреса двух типов: относительные (их значения могут быть вычислены суммированием начального адреса программы с некоторым постоянным смещением) и абсолютные (не зависящие от начального адреса программы). Аналогично могут быть относительными или абсолютными термы и выражения. Константа - абсолютный терм. Метки команд и областей данных, а также ссылка на текущее значение счетчика размещений - относительные термы. Имена, определенные в предложениях EQU (или других подобных директивах ассемблера)могут быть как абсолютными, так и относительными в зависимости от выражений, определяющих их значения.

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

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

Хотя приведенные выше правила могут показаться произвольными, они на самом деле имеют глубокий смысл. К числу выражений, удовлетворяющих данным определениям, относятся только те выражения, значения которых не теряют смысла при перемещении программ. Относительный терм или выражение представляет собой некоторое значение, которое может быть записано в виде (S+r), где S - начальный адрес программы, а r - величина смещения относительно начального адреса. По этому обычно относительный терм задает некоторую точку внутри программы. Когда относительные термы комбинируются в пары, каждая из которых включает термы с противоположными знаками, величины S, определяющие начальный адрес программы, взаимно исключаются. В результате получается абсолютное значение.

Рассмотрим, например, программу на рис.2.9. В выражении

107 MAXLEN EQU BUFEND - BUFFER

обе метки BUFEND и BUFFER - относительные термы, каждый из которых определяет внутренний адрес программы. Однако само выражение (разность между двумя адресами) является величиной абсолютной и определяет длину в байтах области данных, отведенной под буфер.

Значения таких выражений, как BUFEND + BUFFER, 100 - BUFFER или 3 * BUFFER, не представляют ни абсолютную величину, ни внутренний адрес программы. Зависимость этих значений от адреса начальной загрузки такова, что она не имеет связи с чем-либо в самой программе. Поскольку маловероятно, чтобы от таких выражений была какая-либо польза, они рассматриваются как ошибочные.

Для того чтобы определить тип выражения, мы должны иметь информацию о типе всех имен, определенных в программе. Эта информация хранится в таблице имен в виде признака, определяющего тип значения (абсолютное или относительное). Так, для программы на рис.2.10 некоторые из элементов таблицы имен могли бы иметь вид

ИМЯ ТИП ЗНАЧЕНИЕ
RETADR R  
BUFFER R  
BUFEND R  
MAXLEN A  

С помощью этой информации ассемблер может легко определить тип каждого выражения и сгенерировать в объектной программе записи-модификаторы для относительных выражений.

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

Программные блоки

Во всех примерах, которые мы видели до сих пор, ассемблируемая программа обрабатывалась как единое целое. Хотя логически исходная программа содержит подпрограммы, области данных и т. п., она рассматривалась ассемблером как неделимый объект, который переводился в единственный блок объектного кода. Внутри этой объектной программы сгенерированные машинные команды и данные располагались в том же порядке, в котором они были написаны в исходной программе.

Многие ассемблеры предоставляют более гибкие средства обработки исходной программы и соответствующего ей объектного кода. Одни из этих средств позволяют располагать сгенерированные машинные команды и данные в объектной программе в порядке, отличном от порядка, в котором расположены соответствующие им исходные предложения. Другие позволяют создавать несколько независимых частей объектной программы, Каждая из этих частей сохраняет свою индивидуальность и обрабатывается загрузчиком отдельно от других частей. Мы используем термин программные блоки (program blocks) для обозначения сегментов, расположенных в пределах одного объектного модуля, и термин управляющие секции (control sections) для обозначения сегментов, которые транслируются в независимые объектные модули. (Данная терминология, к сожалению, не является общепризнанной. Фактически в некоторых системах одни и те же средства языка ассемблера используются для обеспечения обеих этих логически различных функций.) В этом разделе мы рассмотрим как используются и обрабатываются ассемблером программные блоки. Разд. 2.3.5 посвящен обсуждению управляющих секций.

На рис.2.11 показано, как с помощью программных блоков может быть записана наша программа. В данном случае используются три блока. Первый (непоименованный) программный блок содержит выполняемые команды. Второй (с именем CDATA) содержит все области данных, длина которых не превышает нескольких слов. Третий (с именем CBLKS) состоит из областей данных, занимающих значительный объем оперативной памяти. Некоторые из возможных доводов и пользу подобного разделения обсуждаются в данном разделе.

 

Строка Исходное предложение

5 COPY START 0 КОПИРОВАНИЕ ФАЙЛА

10 FIRST STL RETADR СОХРАНЕНИЕ АДРЕСА ВОЗВРАТА

15 CLOOP +JSUB RDREC ВВОД ВХОДНОЙ ЗАПИСИ

20 LDA LENGTH ПРОВЕРКА НА EOF (LENGTH = 0)

25 COMP #0

30 JEQ ENDFIL ВЫХОД, ЕСЛИ НАШЛИ EOF

35 JSUB WRREC ВЫВОД ВЫХОДНОЙ ЗАПИСИ

40 J CLOOP ЦИКЛ

45 ENDFIL LDA =C"EOF" ЗАНЕСЕНИЕ МАРКЕРА КОНЦА ФАЙЛА

50 STA BUFFER

55 LDA #3 УСТАНОВИТЬ LENGTH = 3

60 STA LENGTH

65 JSUB WRREC ЗАПИСЬ EOF

70 J @RETADR ВОЗВРАТ ИЗ ПРОГРАММЫ

92 USE CDATA

95 RETADR RESW 1

100 LENGTH RESW 1 ДЛИНА ЗАПИСИ

103 USE CBLKS

105 BUFFER RESB 4096 ДЛИНА БУФЕРА – 4096 БАЙТ

106 BUFEND EQU * АДРЕС ПЕРВОГО БАЙТА ПОСЛЕ БУФЕРА

107 MAXLEN EQU BUFEND-BUFFER МАКСИМАЛЬНАЯ ДЛИНА ЗАПИСИ

110 *

115 * ПОДПРОГРАММА ВВОДА ЗАПИСИ НА БУФЕР

120 *

123 USE

125 RDREC CLEAR X ОБНУЛЕНИЕ СЧЕТЧИКА ЦИКЛА

130 CLEAR A ОБНУЛЕНИЕ РЕГИСТРА А

132 CLEAR S ОБНУЛЕНИЕ РЕГИСТРА S

133 +LDT #MAXLEN

135 RLOOP TD INPUT ПРОВЕРКА УСТРОЙСТВА ВВОДА

140 JEQ RLOOP ЦИКЛ ДО ПОЛУЧЕНИЯ ГОТОВНОСТИ

145 RD INPUT ЧТЕНИЕ СИМВОЛА В РЕГИСТР А

150 COMP A,S ПРОВЕРКА НА КОНЕЦ ЗАПИСИ (Х"00")

155 JEQ EXIT ВЫХОД ИЗ ЦИКЛА ПО КОНЦУ ЗАПИСИ

160 STCH BUFFER,X ЗАПИСЬ СИМВОЛА В БУФЕР

165 TIXR T ЦИКЛ ДО ДОСТИЖЕНИЯ МАКСИМАЛЬНОЙ

170 JLT RLOOP ДЛИНЫ

175 EXIT STX LENGTH ЗАПОМИНАНИЕ ДЛИНЫ ЗАПИСИ

180 RSUB ВОЗВРАТ ИЗ ПОДПРОГРАММЫ

183 USE CDATA

185 INPUT BYTE X"F1" КОД УСТРОЙСТВА ВВОДА

195 *

200 * ПОДПРОГРАММА ВЫВОДА ЗАПИСИ ИЗ БУФЕРА

205 *

208 USE

210 WRREC CLEAR X ОБНУЛЕНИЕ СЧЕТЧИКА ЦИКЛА

212 LDT LENGTH

215 WLOOP TD =X"05" ПРОВЕРКА УСТРОЙСТВА ВЫВОДА

220 JEQ WLOOP ЦИКЛ ДО ПОЛУЧЕНИЯ ГОТОВНОСТИ

225 LDCH BUFFER,X ЧТЕНИЕ СИМВОЛА ИЗ БУФЕРА

230 WD =X"05" ВЫВОД СИМВОЛА

235 TIXR T ЦИКЛ, ПОКА НЕ БУДУТ ВЫВЕДЕНЫ

240 JLT WLOOP ВСЕ СИМВОЛЫ

245 RSUB ВОЗВРАТ ИЗ ПОДРОГРАММЫ

252 USE CDATA

253 LTORG

255 END FIRST

 

Рис. 2.11. Пример программы с несколькими программными блоками.

 

Директива ассемблера USE указывает на то, какие части исходной программы принадлежат к тем или иным блокам, Вначале предполагается, что все предложения исходной программы являются частью непоименованного (задаваемого по умолчанию) блока. Если не использовано ни одного предложения USE, то считается, что вся программа принадлежит одному этому блоку. Предложение USE в строке 92 сигнализирует о начале блока с именем СDАТА. К этому блоку относятся все последующие предложения исходной программы вплоть до следующего предложения USE в строке 103которое начинает блок с именем CBLKS. Предложение USE может также указывать на продолжение ранее начатого блока. Так, предложение в строке 123 продолжает непоименованный блок, а предложение в строке 183 - блок СDАТА.

Как вы видите, каждый блок фактически может содержать несколько отдельных сегментов исходной программы. Ассемблер перегруппирует (логически) эти сегменты так, чтобы собрать вместе отдельные части каждого блока. Затем каждому из этих блоков будут назначены адреса в объектной программе в порядке появления блоков в исходной программе. Результат будет в точности таким же, как если бы программист собственноручно перегруппировал предложения исходной программы, собрав вместе предложения, принадлежащие каждому из блоков.

Такая логическая перегруппировка осуществляется ассемблером во время первого просмотра. Для этого с каждым программным блоком связывается свой счетчик размещений. При объявлении нового блока связанный с ним счетчик размещений устанавливается в 0. Текущее значение счетчика размещений сохраняется при переключении на другой блок и восстанавливается, когда возобновляется ранее определенный блок. Таким образом, во время первого просмотра каждая метка программы получает адрес относительно начала содержащего ее блока. Когда метка заносится в таблицу имен, то вместе с присвоенным ей адресом заносится имя или номер блока, которому она принадлежит. По завершению первого просмотра значение счетчика размещений каждого блока указывает на длину этого блока. Затем ассемблер может назначить каждому блоку начальный адрес в объектной программе (начиная с относительного адреса 0).

Во время второго просмотра для генерации объектного кода ассемблеру для каждого имени нужно знать значение адреса относительно начала объектной программы (а не относительно начала блока). Эту информацию можно легко получить из SYMTAB: достаточно просто прибавить к адресу метки начальный адрес ее блока.

 

Строка Адрес Исходное предложение Объектный код

5 0000 0 COPY START 0

10 0000 0 FIRST STL RETADR 172063

15 0003 0 CLOOP JSUB RDREC 4B2021

20 0006 0 LDA LENGTH 032060

25 0009 0 COMP #0 290000

30 000C 0 JEQ ENDFIL 332006

35 000F 0 JSUB WRREC 4B203B

40 0012 0 J CLOOP 3F2FEE

45 0015 0 ENDFIL LDA =С"EOF" 032055

50 0018 0 STA BUFFER 0F2056

55 001B 0 LDA #3 010003

60 001E 0 STA LENGTH 0F2048

65 0021 0 JSUB WRREC 4B2029

70 0024 0 J @RETADR 3E203F

92 0000 1 USE CDATA

95 0000 1 RETADR RESW 1

100 0003 1 LENGTH RESW 1

103 0000 2 USE CBLKS

105 0000 2 BUFFER RESB 4096

106 1000 2 BUFEND EQU *

107 1000 MAXLEN EQU BUFEND-BUFFER

110 *

115 * ПОДПРОГРАММА ВВОДА ЗАПИСИ НА БУФЕР

120 *

123 0027 0 USE

125 0027 0 RDREC CLEAR X B410

130 0029 0 CLEAR A B400

132 002B 0 CLEAR S B440

133 002D 0 +LDT #MAXLEN 75101000

135 0031 0 RLOOP TD INPUT E32038

140 0034 0 JEQ RLOOP 332FFA

145 0037 0 RD INPUT DB2032

150 003A 0 COMP A,S A004

155 003C 0 JEQ EXIT 332008

160 003F 0 STCH BUFFER,X 57A02F

165 0042 0 TIXR T B850

170 0044 0 JLT RLOOP 3B2FEA

175 0047 0 EXIT STX LENGTH 13201F

180 004A 0 RSUB 4F0000

183 0006 1 USE CDATA

185 0006 1 INPUT BYTE X"F1" F1

195 *

200 * ПОДПРОГРАММА ВЫВОДА ЗАПИСИ ИЗ БУФЕРА

205 *

208 004D 0 USE

210 004D 0 WRREC CLEAR X B410

212 004F 0 LDT LENGTH 772017

215 0052 0 WLOOP TD =X"05" E3201B

220 0055 0 JEQ WLOOP 332FFA

225 0058 0 LDCH BUFFER,X 53A016

230 005B 0 WD =X"05" DF2012

235 005E 0 TIXR T B850

240 0060 0 JLT WLOOP 3B2FEF

245 0063 0 RSUB 4F0000

252 0007 1 USE CDATA

253 LTORG

0007 1 * =C"EOF" 454F46

000A 1 * =X"05" 05

255 END FIRST

 

Рис.2.12. Объектный код для программы на рис.2.11.

 

На рис.2.12 показано, как этот процесс применяется к нашей модельной программе. В столбце "Адрес" приведены относительный адрес (внутри блока) каждого предложения и номер соответствующего ему блока (0 = непоименованный блок, 1 = CDATA, 2 = CBLKS). По существу это та же информация, которая хранится в SYMTAB для каждого имени. Обратите внимание, что для значения MAXLEN (строка 107) номер блока не указан. Это означает, что MAXLEN является абсолютной меткой, значение которой не связано ни с одним из блоков.

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

Имя блока Номер блока Адрес Длина
(непоименованный)      
CDATA     000B
CBLKS      
         

Рассмотрим теперь команду

20 0006 0 LDA LENGTH 032060

В SYMTAB значение ее операнда (метка LENGTH) определено как относительный адрес 0003, принадлежащий программному блоку 1 (CDATA). Начальный адрес CDATA равен 0066. Таким образом, требуемый целевой адрес для этой команды будет 0003 + 0066 = 0069. Команда должна ассемблироваться с использованием адресации относительно счетчика команд. Во время исполнения данной команды счетчик команд будет содержать адрес следующей команды (строка 25). Относительный адрес этой команды равен 0009 и принадлежит непоименованному блоку. Поскольку этот блок начинается с адреса 0000, то, следовательно, значение этого адреса будет просто равно 0009. Итак, требуемое смещение вычисляется: 0069 - 0009 = 60. Вычисление других адресов осуществля



Поделиться:


Последнее изменение этой страницы: 2017-02-17; просмотров: 212; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 52.15.49.90 (0.012 с.)