
Танненбаум Е. Архітектура компютера [pdf]
.pdf
Введение в язык ассемблера |
523 |
Для компьютеров семейства Intel существует несколько ассемблеров, которые отличаются друг от друга по синтаксису. В этой книге мы будем использовать язык ассемблера Microsoft MASM. Мы будем говорить о процессоре Pentium II, но все, что мы будем обсуждать, применимо и к процессорам 386,486, Pentium и Pentium Pro. Для процессора SPARC мы будем использовать ассемблер Sun. Все это также применимо к более ранним 32-битным версиям. В книге коды операций и регистры всегда обозначаются прописными буквами, причем не только в ассемблере для Pentium II, как это обычно принято, но и в ассемблере Sun, где по соглашению используются строчные буквы.
Высказывания языка ассемблера состоят из четырех полей: поля метки, поля операции, поля операндов и поля комментариев. Метки используются для того, чтобы обеспечить символические имена для адресов памяти. Они нужны для того, чтобы можно было совершить переход к командам. Они также нужны для слов с данными, чтобы по символическому имени можно было получить доступ к тому месту, где они хранятся. Если высказывание снабжено меткой, то эта метка обычно располагается в колонке 1.
Вкаждом из трех примеров есть 4 метки: FORMULA, I, J и N. Отметим, что
вязыках ассемблера для SPARC после каждой метки нужно ставить двоеточие,
адля Motorola — нет. В компьютерах Intel двоеточия ставятся только после меток команд, но не после меток данных. Данное различие вовсе не является фундаментальным. Разработчики разных ассемблеров имеют разные вкусы. Архитектура машины никак не определяет тот или иной выбор. Единственное преимущество двоеточия состоит в том, что метку можно писать на отдельной строке, а код операции — на следующей строке в колонке 1. Это упрощает работу компилятора: без двоеточия нельзя было бы отличить метку на отдельной строке от кода операции на отдельной строке.
Внекоторых ассемблерах длина метки ограничена до 6 или 8 символов. А в большинстве языков высокого уровня длина имен произвольна. Длинные и хорошо подобранные имена упрощают чтение и понимание программы другими людьми.
Вкаждой машине содержится несколько регистров, но всем им даны совершенно разные названия. Регистры в Pentium II называются ЕАХ, ЕВХ, ЕСХ и т. д. Регистры в Motorola называются DO, Dl, D2. Регистры в машине SPARC имеют несколько названий. Здесь для их обозначения мы будем использовать %R1 и %R2.
Вполе кода операции содержится либо символическая аббревиатура этого кода (если высказывание является символической репрезентацией машинной команды), либо команда для самого ассемблера. Выбор имени — дело вкуса, и поэтому разные разработчики языков ассемблера называют их по-разному. Разработчики ассемблера Intel решили использовать обозначение MOV и для загрузки регистра из памяти, и для сохранения регистра в память. Разработчики ассемблера Motorola выбрали обозначение MOVE для обеих операций. Аразработчики ассемблера SPARC



5 2 6 Глава 7. Уровень языка ассемблера
В,Ш v^ ръетсределэдэт тосштсъ для одшлт скольких переменных размером 1, 2,4 и 8 байтов соответственно. Наприме
TABLE D8 11 . 23 . 49
выделяет пространство для 3 байтов и присваивает им начальные значения 49 соответственно. Эта директива, кроме того, определяет символ TABLE, тому адресу, где хранится число 11.
Директивы PROC и ENDP определяют начало и конец процедур языка асс ра. Процедуры в языке ассемблера выполняют ту же функцию, что и в программирования высокого уровня. Директивы MACRO и ENDM определяют и конец макроса. О макросах мы будем говорить ниже.
Далее идут директивы PUBLIC и EXTERN. Программы часто пишут в виде со ности файлов. Часто процедуре, находящейся в одном файле, нужно вызва цедуру или получить доступ к данным, определенным в другом файле. Чт кие отсылки между файлами стали возможными, обозначение (имя), которое сделать доступным для других файлов, экспортируется с помощью дир PUBLIC. Чтобы ассемблер не ругался по поводу использования символа, кото определен в данном файле, этот символ может быть объявлен внешним (E это сообщит ассемблеру, что символ определен в каком-то другом файле. Си которые не определены ни в одной из этих директив, используются только делах одного файла. Поэтому даже если символ F00 используется в неск файлах, это не вызовет никакого конфликта, поскольку этот символ лока отношению к каждому файлу.
Директива INCLUDE приказывает ассемблеру вызвать другой файл и вк его в текущий файл. Такие включенные файлы часто содержат определени росы и другие элементы, необходимые для разных файлов.
Многие языки ассемблера, в том числе MASM, поддерживают условну поновку программы. Например, программа
WORDSIZE EQU 16
IF WORDSIZE GT 16 WSIZE: DW32 ELSE
WSIZE: DW 16 ENDIF
выделяет в памяти одно 32-битное слово и вызывает его адрес WSIZE. Этом придается одно из значений: либо 32, либо 16 в зависимости от значения W (в данном случае 16). Такая конструкция может использоваться в прог для 16-битных машин (как 8088) или для 32-битных машин (как Pentium II в начале и в конце машинозависимого кода поставить IF и ENDIF, а затем из одно определение, WORDSIZE, программу можно автоматически установить н из двух размеров. Применяя такой подход, можно сохранять одну такую ис программу для нескольких разных машин. В большинстве случаев все маш

Макросы
В ассемблере MASM есть еще много директив. Другие ассемблеры для Pe содержат другой набор директив, поскольку они определяются не в соотв с архитектурой машины, а по желанию разработчиков ассемблера.
Макросы
Программистам на языке ассемблера часто приходится повторять одни цепочки команд по несколько раз. Проще всего писать нужные команды раз, когда они требуются. Но если последовательность достаточно длинн если ее нужно повторять очень много раз, то это становится утомительны Альтернативный подход — оформить эту последовательность в процедур зывать ее в случае необходимости. У такой стратегии тоже есть свои недо поскольку в этом случае каждый раз придется выполнять специальную к вызова процедуры и команду возврата. Если последовательности команд кие (например, всего две команды), но используются часто, то вызов про может сильно снизить скорость работы программы. Макросы являются п
и эффективным решением этой проблемы.
Макроопределение, макровызов и макрорасширение
Макроопределение — это способ дать имя куску текста. После того как был определен, программист может вместо куска программы писать имя м В сущности, макрос — это обозначение куска текста. В листинге 7.1 приведе грамма на языке ассемблера для Pentium II, которая дважды меняет мест держимое переменных р и q. Эти последовательности команд можно опр как макросы (листинг 7.2). После определения макроса каждое имя SWAP в п ме замещается следующими четырьмя строками:
MOV EAX.P
MOV EBX.Q
MOV Q.EAX
MOV P.EBX
Программист определил SWAP как обозначение для этих четырех операт Хотя разные языки ассемблера используют немного разные записи для
ления макросов, все они состоят из одних и тех же базовых частей:
1.Заголовок макроса, в котором дается имя определяемого макроса.
2.Текст, в котором приводится тело макроса.
3.Директива, которая завершает определение (например, ENDM).

528 |
Глава7. Уровень языка ассемблера |
Листинг7 . 1 . Коднаязыкеассемблера,вкоторомпеременныериqдважды меняются местами (без использования макроса)
MOV EAX.P
MOV EBX.Q
MOV Q.EAX
MOV Р.ЕВХ
MOV EAX.P
MOV EBX.Q
MOV Q.EAX
MOV P.EBX
Листинг 7.2. Тот же код с использованием макроса
SWAP MACRO
MOV EAX.P
MOV EBX.Q
MOV Q.EAX
MOV P.EBX ENDM
SWAP
SWAP
Макрорасширение происходит во время процесса ассемблирования, а не мя выполнения программы. Этот момент очень важен. Программы, приве в листингах 7.1 и 7.2, произведут один и тот же машинный код. По програ машинном языке невозможно определить, использовались ли макросы пр рождении. После завершения макрорасширения ассемблер отбрасывает ма ширения. В полученной программе никаких признаков макросов не остает
Макровызовы не следует путать с вызовами процедур. Основное ра состоит в том, что макровызов — это команда ассемблеру заменить имя м телом макроса. Вызов процедуры — это машинная команда, которая вст в объектную программу и которая позднее будет выполнена, чтобы вызва цедуру. В табл. 7.6 сравниваются макровызовы и вызовы процедур.
Таблица 7.6. Сравнение макровызовов и вызовов процедур
|
Макровызов |
Вызов проце |
Когда совершается вызов |
Во время ассемблирования Во время выпо |
|
программы? |
|
|
Вставляетсялителомакроса |
Да |
Нет |
илипроцедурывобъектнуюпрограмму |
|
|
каждыйраз,когдасовершаетсявызов? |
|
|
Команда вызова процедуры |
Нет |
Да |
вставляется в объектную программу, |
|
|


5 3 0 Глава 7. Уровень языка ассемблера
и Р2 — это формальные параметры. Во время расширения макроса кажды Р1 внутри тела макроса замещается первым фактическим параметром, Р2 замещается вторым фактическим параметром. В макровызове
CHANGE P.Q
Р — это первый фактический параметр, a Q — это второй фактический п Таким образом, программы в листингах 7.3 и 7.4 идентичны.
Расширенные возможности
Большинство макропроцессоров содержат целый ряд расширенных особе которые упрощают работу программиста на языке ассемблера. В этом ра рассмотрим несколько расширенных особенностей MASM. Во всех асс есть одна проблема: дублирование меток. Предположим, что макрос соде манду условного перехода и метку, к которой совершается переход. Есл вызывается два и более раз, метка будет дублироваться, что вызовет оши этому программист должен приписывать каждому вызову в качестве п отдельную метку. Другое решение (оно применяется в MASM) — объяв ку локальной (LOCAL), при этом ассемблер автоматически будет порождат метку при каждом расширении макроса. В некоторых ассемблерах номер ки автоматически считаются локальными.
MASM и большинство других ассемблеров позволяют определять внутри других макросов. Эта особенность очень полезна в сочетании с у компоновкой программы. Обычно один и тот же макрос определятся в о тях оператора IF:
Ml |
MACRO |
|
IF |
WORDSIZE GT 16 M2 |
MACRO |
ENDM
ELSE
M2 MACRO
ENDM
ENDIF tNDM
В любом случае макрос М2 б>дет определен, но определение зависит о какой машине ассемблируется программа: на 16-битной или на 32-битн Ml не вызывается, макрос М2 вообще не будет определен.
Наконец, одни макросы могут вызывать другие макросы, в том числе са Если макрос рекурсивный, то есть вызывает самого себя, он должен пе самому себе параметр, который изменяется при каждом расширении, а та верять этот параметр и завершать рекурсию, когда параметр достигает оп ного значения. В противном случае получится бесконечный цикл.

Процесс ассемблирования
Ассемблер должен сохранять таблицу всех имен макросов, в которой ка имя сопровождается указателем на определение этого макроса, чтобы его мо было получить в случае необходимости. В одних ассемблерах предусмотрен дельная таблица для имен макросов, а другие содержат общую таблицу, в кот находятся не только имена макросов, но и все машинные команды и директи
Когда встречается макроопределение, создается новый элемент таблицы с нем макроса, числом параметров и указателем на другую таблицу — таблицу ма определений, где будет храниться тело макроса. Список формальных параме тоже создается в это время. Затем считывается тело макроса и сохраняется в лице макроопределений. Формальные параметры, которые встречаются в цикла, указываются специальным символом.
Ниже приведен пример внутреннего представления макроса CHANGE. В к стве символа возврата каретки используется точка с запятой, а в качестве сим формального параметра — амперсант.
MOV EAX.&P1;MOV EBX.&P2:M0V &P2EAX;M0V &P1.EBX:
В таблице макроопределений тело макроса представляет собой просто цеп символов.
Во время первого прохода ассемблирования отыскиваются коды операц макросы расширяются. Всякий раз, когда встречается макроопределение, он храняется в таблице макросов. При вызове макроса ассемблер временно при навливает чтение входных данных из входного устройства и начинает считы сохраненное тело макроса. Формальные параметры, извлеченные из тела мак замещаются фактическими параметрами, которые предоставляются вызо Амперсант перед параметрами позволяет ассемблеру узнавать их.
Процесс ассемблирования
В следующих разделах мы опишем, как работает ассемблер. И хотя на каждой шине есть свой определенный ассемблер, отличный от других, процесс ассем рования по сути один и тот же.
Двухпроходной ассемблер
Поскольку программа на языке ассемблера состоит из ряда операторов, на пер взгляд может показаться, что ассемблер сначала должен читать оператор, з транслировать его на машинный язык и, наконец, переносить полученный ма ный язык в файл, а соответствующий кусок листинга — в другой файл. Этот
цесс будет повторяться до тех пор, пока вся программа не будет оттранслиро

5 3 2 Глава 7. Уровень языка ассемблера
ссылки, поскольку символ L используется еще до того, как он определен было сделано обращение к символу, определение которого появится позд Опережающие ссылки можно разрешать двумя способами. Во-первых лер действительно может прочитать программу дважды. Каждое прочтени ной программы называется проходом, а транслятор, который читает и программу дважды, называется двухпроходным транслятором. На перво де собираются и сохраняются в таблице все определения символов, в то метки. К тому времени как начнется второй проход, значения символов уж ны, поэтому никакой опережающей ссылки не будет, и каждый операто читать и ассемблировать. При этом требуется дополнительный проход п
ной программе, но зато такая стратегия относительно проста.
При втором подходе программа на языке ассемблера читается один р образуется в промежуточную форму, и эта промежуточная форма сохр в таблице в памяти. Затем совершает второй проход, но уже не по исход грамме, а по таблице. Если физической памяти (или виртуальной памят точно для этого подхода, то будет сэкономлено время, затрачиваемое на ввода-вывода. Если требуется вывести листинг, тогда нужно сохранить тью исходное выражение, включая комментарии. Если листинг не нужен межуточную форму можно сократить, оставив только голые команды.
Еще одна задача первого прохода — сохранить все макроопределения рить вызовы по мере их появления. Следовательно, в одном проходе прои определение символов, и расширение макросов.
Первый проход
Главная функция первого прохода — построить таблицу символов, в кот держатся значения всех имен. Символом может быть либо метка, либо з которому с помощью директивы приписывается определенное имя:
BUFSIZE EQU 8192
Приписывая значение символьному имени в поле метки команды, а должен знать, какой адрес будет иметь эта команда во время выполнения мы. Для этого ассемблер во время процесса ассемблирования сохраняе
адресакоманд (ILC —InstructionLocationCounter) (специальнуюперем Эта переменная устанавливается на 0 в начале первого прохода и увели после каждой обработанной команды на длину этой команды (табл. 7.7.). написан для Pentium П. Мы не будем давать примеры для SPARC и Moto скольку различия между языками ассемблера не очень важны и одного будет достаточно. Кроме того, язык ассемблера для SPARC неудобочитае