ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 24.12.2021

Просмотров: 12254

Скачиваний: 10

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
background image

Введение в язык ассемблера 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
решили использовать LD для первой операции и ST для второй. Очевидно, что вы-
бор названий в данном случае никак не связан с архитектурой машины.

Напротив, необходимость использовать две машинные команды для доступа

к памяти объясняется устройством архитектуры SPARC, поскольку виртуальные
адреса могут быть 32-битными (как в SPARC Version 8) и 44-битными (как в SPARC


background image

5 2 4 Глава 7. Уровень языка ассемблера

Version 9), а команды могут содержать максимум 22 бита данных. Следовательно,
чтобы передать все биты полного виртуального адреса, всегда требуется две ко-
манды. Команда

SETHI

 янкп.та

обнуляет старшие 32 бита и младшие 10 битов 64-битного регистра R1, а затем
помещает старшие 22 бита 32-битного адреса переменной I в регистр R1 в битовые
позиции с 10 по 31. Следующая команда

юсш+шхш.да

складывает R1 и младшие 10 битов адреса I (в результате чего получается полный

адрес I), вызывает данное слово из памяти и помещает его в регистр R1.

Процессоры семейства Pentium, 680x0 и SPARC — все допускают операнды

разной длины (типа byte (байт), word (слово) и long). Каким образом ассемблер

определит, какую длину использовать? И опять разработчики ассемблера приня-

ли разные решения. В Pentium II регистры разной длины имеют разные назва-

ния. Так, для перемещения 32-битных элементов используется название ЕАХ, для

16-битных — АХ, а для 8-битных — AL и АН. Разработчики ассемблера Motorola

решили прибавлять к каждому коду операции суффикс .L для типа long, .W — для

типа word и .В для типа byte. В SPARC для операндов разной длины использу-

ются разные коды операций (например, для загрузки байта, полуслова (halfword)

и слова в 64-битный регистр используются коды операций LDSB, LDSH и LDSW соот-

ветственно). Как видите, разработка языка произвольна.

Три ассемблера, которые мы рассматриваем, различаются по способу резерви-

рования пространства для данных. Разработчики языка ассемблера для Intel вы-

брали DW (Define Word — определить слово). Позднее был введен альтернативный

вариант .WORD. В Motorola используется DC (Define Constant — определить кон-

станту). Разработчики SPARC с самого начала предпочли .WORD. И слова различия

произвольны.

В поле операндов определяются адреса и регистры, которые являются операн-

дами для машинной команды. В поле операндов команды целочисленного сложе-

ния сообщается, что и к чему нужно прибавить. Поле операндов команд перехода

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

константы, ячейки памяти и т. д.

В поле комментариев приводятся пояснения о действиях программы. Они мо-

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

чужую программу, или программисту, который изначально писал программу и воз-

вратился к работе над ней через год. Программа на ассемблере без таких коммен-

тариев совершенно непонятна программистам (даже автору этой программы). Ком-

ментарии нужны только человеку. Они никак не влияют на работу программы.

Директивы

Программа на языке ассемблера должна не только определять, какие машинные

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

Команды для ассемблера называются

 псевдокомандами

 или

 директивами ассем-


background image

Введение в язык ассемблера

525

блера.

 Мы уже видели одну типичную псевдокоманду DW (см. табл. 7.2). В табл. 7.5

приведены некоторые другие псевдокоманды (директивы). Они взяты из ассемб-
лера MASM для семейства Intel.

Таблица 7.5.

 Некоторые директивы ассемблера MASM

Директива Значение

SEGMENT Начинает новый сегмент (текста, данных и т.п.) с определенными атрибутами
ENDS Завершает текущий сегмент
ALIGN Контролирует выравнивание следующей команды или данных
EQU Определяет новый символ, равный данному выражению
DB Выделяет память для одного или нескольких байтов
DD Выделяет память для одного или нескольких 16-битных полуслов
DW Выделяет память для одного или нескольких 32-битных слов
DQ Выделяет память для одного или нескольких 64-битных двойных слов
PROC Начинает процедуру
ENDP Завершает процедуру
MACRO Начинает макроопределение
ENDM Завершает макроопределение
PUBLIC Экспортирует имя, определенное в данном модуле
EXTERN Импортирует имя из другого модуля
INCLUDE Вызывает другой файл и включает его в текущий файл
IF Начинает условную компоновку программы на основе данного выражения
ELSE Начинает условную компоновку программы, если условие IF над директивой

не выполнено

ENDIF Завершает условную компоновку программы
COMMENT Определяет новый отделитель комментариев
PAGE Совершает принудительный обрыв страницы в листинге
END Завершает программу ассемблирования

Директива SEGMENT начинает новый сегмент, а директива ENDS завершает его.

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

Директива ALIGN переводит следующую строку (обычно данные) в адрес, кото-

рый делим на аргумент данной директивы. Например, если текущий сегмент уже
содержит 61 байт данных, тогда следующим адресом после

 ALIGN 4

 будет адрес 64.

Директива EQU дает символическое название некоторому выражению. Напри-

мер, после записи

BASE EQU 1000

символ BASE можно использовать вместо 1000. Выражение, которое следует за

 EQU,

может содержать несколько символов, соединенных арифметическими и другими
операторами, например:

LIMIT EQU 4 * BASE + 2000

Большинство ассемблеров, в том числе MASM, требуют, чтобы символ был

определен в программе до появления в некотором выражении.


background image

5 2 6 Глава 7. Уровень языка ассемблера

В,Ш v^ ръетсределэдэт тосштсъ для  о д ш л там. не-

скольких переменных размером 1, 2,4 и 8 байтов соответственно. Например,

TABLE D8 11.  2 3 . 49

выделяет пространство для 3 байтов и присваивает им начальные значения 11, 23 и
49 соответственно. Эта директива, кроме того, определяет символ TABLE, равный
тому адресу, где хранится число 11.

Директивы PROC и ENDP определяют начало и конец процедур языка ассембле-

ра. Процедуры в языке ассемблера выполняют ту же функцию, что и в языках
программирования высокого уровня. Директивы MACRO и ENDM определяют начало
и конец макроса. О макросах мы будем говорить ниже.

Далее идут директивы PUBLIC и EXTERN. Программы часто пишут в виде совокуп-

ности файлов. Часто процедуре, находящейся в одном файле, нужно вызвать про-
цедуру или получить доступ к данным, определенным в другом файле. Чтобы та-
кие отсылки между файлами стали возможными, обозначение (имя), которое нужно
сделать доступным для других файлов, экспортируется с помощью директивы
PUBLIC. Чтобы ассемблер не ругался по поводу использования символа, который не
определен в данном файле, этот символ может быть объявлен внешним (EXTERN),
это сообщит ассемблеру, что символ определен в каком-то другом файле. Символы,
которые не определены ни в одной из этих директив, используются только в пре-

делах одного файла. Поэтому даже если символ F00 используется в нескольких

файлах, это не вызовет никакого конфликта, поскольку этот символ локален по
отношению к каждому файлу.

Директива INCLUDE приказывает ассемблеру вызвать другой файл и включить

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

Многие языки ассемблера, в том числе MASM, поддерживают условную ком-

поновку программы. Например, программа

WORDSIZE EQU 16

IF WORDSIZE GT 16

WSIZE: DW32

ELSE
WSIZE: DW 16
ENDIF

выделяет в памяти одно 32-битное слово и вызывает его адрес WSIZE. Этому слову
придается одно из значений: либо 32, либо 16 в зависимости от значения WORDSIZE
(в данном случае 16). Такая конструкция может использоваться в программах

для 16-битных машин (как 8088) или для 32-битных машин (как Pentium II). Если

в начале и в конце машинозависимого кода поставить IF и ENDIF, а затем изменить
одно определение, WORDSIZE, программу можно автоматически установить на один
из двух размеров. Применяя такой подход, можно сохранять одну такую исходную
программу для нескольких разных машин. В большинстве случаев все машиноза-
висимые определения, такие как WORDSIZE, сохраняются в одном файле, причем для
разных машин должны быть разные файлы. Путем включения файла с нужными
определениями программу можно легко перекомпилировать на разные машины.

Директива COMMENT позволяет пользователю изменять символ комментария на

что-либо отличное от точки с запятой. Директива PAGE используется для управле-
ния листингом программы. Наконец, директива END отмечает конец программы.


background image

Макросы 527

В ассемблере MASM есть еще много директив. Другие ассемблеры для Pentium II

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

Макросы

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

Альтернативный подход — оформить эту последовательность в процедуру и вы-

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

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

может сильно снизить скорость работы программы. Макросы являются простым
и эффективным решением этой проблемы.

Макроопределение, макровызов

и макрорасширение

Макроопределение — это способ дать имя куску текста. После того как макрос
был определен, программист может вместо куска программы писать имя макроса.
В сущности, макрос — это обозначение куска текста. В листинге 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).

Когда ассемблер наталкивается на макроопределение в программе, он сохраня-

ет его в таблице макроопределений для последующего использования. Всякий раз,
когда в программе в качестве кода операции появляется макрос (в нашем примере
SWAP), ассемблер замещает его телом макроса. Использование имени макроса в ка-
честве кода операции называется макровызовом, а его замещение телом макроса

называется макрорасширением.