ЯЗЫК МАКРОАССЕМБЛЕРА IBM PC (Справочное пособие) Составитель: В.Н.Пильщиков (МГУ,
ВМК) (январь 1992 г.) В пособии рассматривается язык макроассеблера для персональных
ЭВМтипа IBM PC (язык MASM, версия 4.0). Пособие состоит из 4 глав. В главе
1 рассмотрены особенности пер-сональных компьютеров типа IBM PC и приведены
начальные сведения оязыке MASM. В главе 2 описывается система команд этих компьютеров.Глава
3 посвящена посвящена собственно языку MASM. В главе 4 приведеныпримеры
фрагментов программ и полных программ на MASM для решения раз-личных задач.
В пособии не рассматриваются вопросы, связанные с обработкой дво-ично-десятичных
чисел и работой арифметического сопроцессора 8087 или80287. Под термином
"ПК" в пособии понимается персональный компьютер типаIBM PC c микропроцессором
8088/8086, 80186 или 80286. ГЛАВА 1. ОСОБЕННОСТИ ПК. ВВЕДЕНИЕ В MASM. 1.1. ОПЕРАТИВНАЯ
ПАМЯТЬ. РЕГИСТРЫ. 1.1.1 Оперативная память Объем оперативной памяти ПК
- 2^20 байтов (1 Мб). Байты нумеруютсяначиная с 0, номер байта называется его адресом.
Для ссылок на байтыпамяти используются 20-разрядные адреса: от 00000 до
FFFFF (в 16-рич-ной системе). Байт содержит 8 разрядов (битов), каждый из которых
может прини-мать значение 1 или 0. Разряды нумеруются справа налево от 0 до
7: ----------------- | | | | | | | | | ----------------- 7 6 5 4 3 2 1 0 Байт
- это наименьшая адресуемая ячейка памяти. В ПК используютсяи более крупные ячейки
- слова и двойные слова. Слово - это два сосед-них байта, размер слова - 16
битов (они нумеруются справа налево от 0до 15). Адресом слова считается адрес
его первого байта (с меньшим ад-ресом); этот адрес может быть четным и нечетным.
Двойное слово - этолюбые четыре соседних байта (два соседних слова), размер такой
ячейки- 32 бита; адресом двойного слова считается адрес его первого байта.
Байты используются для хранения небольших целых чисел и символов,слова - для хранения
целых чисел и адресов, двойные слова - для хране-ния "длинных" целых чисел
и т.н. адресных пар (сегмент:смещение). 1.1.2 Регистры Помимо ячеек оперативной
памяти для хранения данных (правда, крат-ковременного) можно использовать
и регистры - ячейки, входящие в сос-тав процессора и доступные из машинной программы.
Доступ к регистрамосуществляется значительно быстрее, чем к ячейкам памяти,
поэтому ис-пользование регистров заметно уменьшает время выполнения программ.
Все регистры имеют размер слова (16 битов), за каждым из них зак-реплено определенное
имя (AX, SP и т.п.). По назначению и способуиспользования регистры можно
разбить на следующие группы: - регистры общего назначения (AX, BX, CX, DX,
BP, SI, DI, SP); - сегментные регистры (CS, DS, SS, ES); - счетчик команд (IP);
- регистр флагов (Flags).(Расшифровка этих названий: A - accumulator, аккумулятор;
B - base,база; C - counter, счетчик; D - data, данные; BP - base pointer,
ука-затель базы; SI - source index, индекс источника; DI - destinationindex, индекс
приемника; SP - stack pointer, указатель стека; CS -code segment, сегмент
команд; DS - data segment, сегмент данных; SS -stack segment, сегмент стека; ES
- extra segment, дополнительный сег-мент; IP - instruction pointer, счетчик команд.)
Регистры общего назначения можно использовать во всех арифметичес-ких и
логических командах. В то же время каждый их них имеет опреде-ленную специализацию
(некоторые команды "работают" только с определен-ными регистрами). Например,
команды умножения и деления требуют, чтобыодин из операндов находился в регистре
AX или в регистрах AX и DX (взависимости от размера операнда), а команды управления
циклом исполь-зуют регистр CX в качестве счетчика цикла. Регистры BX и
BP очень час-то используются как базовые регистры, а SI и DI - как индексные.
Ре-гистр SP обычно указывает на вершину стека, аппаратно поддерживаемогов ПК.
Регистры AX, BX, CX и DX конструктивно устроены так, что возможеннезависимый доступ
к их старшей и младшей половинам; можно сказать,что каждый из этих регистров
состоит из двух байтовых регистров, обо-значаемых AH, AL, BH и т.д. (H - high,
старший; L - low, младший): ----------- ----------- ----------- ----------- AX
| AH | AL | BX | BH | BL | CX | CH | CL | DX | DH | DL | ----------- -----------
----------- ----------- 15 8 7 0Таким образом, с каждым из этих регистров можно
работать как с единымцелым, а можно работать и с его "половинками". Например,
можно запи-сать слово в AX, а затем считать только часть слова из регистра
AH илизаменить только часть в регистре AL и т.д. Такое устройство регистровпозволяет
использовать их для работы и с числами, и с символами. Все остальные регистры
не делятся на "половинки", поэтому считатьили записать их содержимое (16 битов)
можно только целиком. Сегментные регистры CS, DS, SS и ES не могут быть операндами
ника-ких команд, кроме команд пересылки и стековых команд. Эти регистры
ис-пользуются только для сегментирования адресов (см. 1.4). Счетчик команд
IP всегда содержит адрес (смещение от начала про-граммы) той команды, которая должна
быть выполнена следующей (началопрограммы хранится в регистре CS). Содержимое
регистра IP можно изме-нить только командами перехода. 1.1.3 Флаги И, наконец,
в ПК имеется особый регистр флагов. Флаг - это бит,принимающий значение 1 ("флаг
установлен"), если выполнено некотороеусловие, и значение 0 ("флаг сброшен")
в противном случае. В ПК ис-пользуется 9 флагов, каждому из них присвоено определенное
имя (ZF, CFи т.д.). Все они собраны в регистре флагов (каждый флаг
- это один изразрядов регистра, часть его разрядов не используется): -------------------------------------------------
Flags | x| x| x| x|OF|DF|IF|TF|SF|ZF| x|AF|
x|PF| x|CF| ------------------------------------------------- 15 14 13 12
11 10 9 8 7 6 5 4 3 2 1 0 Некоторые флаги принято называть флагами условий; они
автоматичес-ки меняются при выполнении команд и фиксируют те или иные свойства
ихрезультата (например, равен ли он нулю). Другие флаги называются фла-гами состояний;
они меняются из программы и оказывают влияние на даль-нейшее поведение
процессора (например, блокируют прерывания). Флаги условий: CF (carry flag) -
флаг переноса. Принимает значение 1, если присложении целых чисел появилась единица
переноса, не "влезающая" в раз-рядную сетку, или если при вычитании чисел
без знака первое из них бы-ло меньше второго. В командах сдвига в CF заносится
бит, вышедший заразрядную сетку. CF фиксирует также особенности команды умножения.
OF (overflow flag) - флаг переполнения. Устанавливается в 1, еслипри сложении
или вычитании целых чисел со знаком получился результат,по модулю превосходящий
допустимую величину (произошло переполнениемантиссы и она "залезла" в знаковый
разряд). ZF (zero flag) - флаг нуля. Устанавливается в 1, если результаткоманды
оказался равным 0. SF (sign flag) - флаг знака. Устанавливается в 1, если
в операциинад знаковыми числами получился отрицательный результат. PF (parity flag)
- флаг четности. Равен 1, если результат очеред-ной команды содержит четное
количество двоичных единиц. Учитываетсяобычно только при операциях ввода-вывода.
AF (auxiliary carry flag) - флаг дополнительного переноса. Фикси-рует особенности
выполнения операций над двоично-десятичными числами. Флаги состояний: DF
(direction flag) - флаг направления. Устанавливает направлениепросмотра строк
в строковых командах: при DF=0 строки просматриваются"вперед" (от начала к концу),
при DF=1 - в обратном направлении. IF (interrupt flag) - флаг прерываний. При
IF=0 процессор переста-ет реагировать на поступающие к нему прерывания, при
IF=1 блокировкапрерываний снимается. TF (trap flag) - флаг трассировки. При TF=1
после выполнения каж-дой команды процессор делает прерывание (с номером 1),
чем можно вос-пользоваться при отладке программы для ее трассировки. 1.2. ПРЕДСТАВЛЕНИЕ
ДАННЫХ. АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ Здесь рассматривается машинное представление
целых чисел, строк иадресов. Представление двоично-десятичных чисел, используемых
доста-точно редко, не рассматривается. Что касается вещественных чисел,
то вПК нет команд вещественной арифметики (операции над этими числами реа-лизуются
программным путем или выполняются сопроцессором) и потому нетстандартного
представления вещественных чисел. Кроме того, рассматри-ваются некоторые особенности
выполнения арифметических операций. Шестнадцатиричные числа записываются
с буквой h на конце, двоичныечисла - с буквой b (так принято в MASM). 1.2.1 Представление
целых чисел. В общем случае под целое число можно отвести любое число
байтов,однако система команд ПК поддерживает только числа размером в байт ислово
и частично поддерживает числа размером в двойное слово. Именноэти форматы
и будут рассмотрены. В ПК делается различие между целыми числами без знака (неотрица-тельными)
и со знаком. Это объясняется тем, что в ячейках одного и то-го
же размера можно представить больший диапазон беззнаковых чисел,чем неотрицательных
знаковых чисел, и если известно заранее, что неко-торая числовая величина
является неотрицательной, то выгоднее рассмат-ривать ее как беззнаковую, чем как
знаковую. Целые числа без знака. Эти числа могут быть представлены в виде байта,
слова или двойногослова - в зависимости от их размера. В виде байта представляются
целыеот 0 до 255 (=2^8-1), в виде слова - целые от 0 до 65535 (=2^16-1),
ввиде двойного слова - целые от 0 до 4 294 967 295 (=2^32-1). Числа за-писываются
в двоичной системе счисления, занимая все разряды ячейки.Например, число 130
записывается в виде байта 10000010b (82h). Числа размером в слово хранятся в
памяти в "перевернутом" виде:младщие (правые) 8 битов числа размещаются в первом
байте слова, астаршие 8 битов - во втором байте (в 16-ричной системе: две правыецифры
- в первом байте, две левые цифры - во втором байте). Например,число
130 (=0082h) в виде слова хранится в памяти так: ----------- | 82 | 00 | -----------(Отметим,
однако, что в регистрах числа хранятся в нормальном виде: -----------
AX | 00 | 82 | ----------- AH AL ) "Перевернутое" представление используется
и при хранении в памятицелых чисел размером в двойное слово: в первом его байте
размещаютсямладшие 8 битов числа, во втором байте - предыдущие 8 битов и т.д.
На-пример, число 12345678h хранится в памяти так: --------------------- | 78
| 56 | 34 | 12 | ---------------------Другими словами, в первом слове двойного
слова размещаются младшие(правые) 16 битов числа, а во втором слове - старшие
16 битов, причемв каждом из этих двух слов в свою очередь используется "перевернутое"представление.
Такое необычное представление чисел объясняется тем, что
в первыхмоделях ПК за раз можно было считать из памяти только один байт и чтовсе
арифметические операции над многозначными числами начинаются сдействий над младшими
цифрами, поэтому из памяти в первую очередь надосчитывать младшие цифры,
если сразу нельзя считать все цифры. Учитываяэто, в первых ПК и стали размещать
младшие цифры числа перед старшимицифрамми, а ради преемственности такое представление
чисел сохранили впоследующих моделях ПК. Конечно, "перевернутое" представление
неудобно для людей, однакопри использовании языка ассемблера это неудобство
не чувствуется: вMASM все числа записываются в нормальном, неперевернутом
виде (см. ни-же). Целые числа со знаком. Эти числа также представляются в виде
байта, слова и двойного сло-ва. В виде байта записываются числа от -128 до 127,
в виде слова -числа от -32768 до 32767, а в виде двойного слова - числа от-2147483648
до 2147483647. При этом числа записываются в дополнитель-ном коде: неотрицательное
число записывается так же, как и беззнаковоечисло (т.е. в прямом
коде), а отрицательное число -x (x>0) представля-ется беззнаковым числом 2^8-x
(для байтов), 2^16-x (для слов) или2^32-x (для двойных слов). Например, дополнительным
кодом числа -6 яв-ляется байт FAh (=256-6), слово FFFAh или двойное слово
FFFFFFFAh. Приэтом байт 10000000b (=80h) трактуется как -128, а не как +128
(слово8000h понимается как -32678), поэтому левый бит дополнительного кодавсегда
играет роль знакового: для неотрицательных чисел он равен 0,для отрицательных
- 1. Знаковые числа размером в слово и двойное слово записываются в па-мяти в
"перевернутом" виде (при этом знаковый бит оказывается в пос-леднем байте ячейки).
Но в MASM эти числа, как и беззнаковые, записы-ваются в нормальной форме.
Иногда число-байт необходимо расширить до слова, т.е. нужно полу-чить такое же по
величине число, но размером в слово. Существует дваспособа такого расширения
- без знака и со знаком. В любом случае ис-ходное число-байт попадает во второй
(до "переворачивания") байт сло-ва, а вот первый байт заполняется по-разному:
при расширении без знакав него записываются нулевые биты (12h -> 0012h), а при
расширении сознаком в первый байт записываются нули, если число-байт было неотрица-тельным,
и записывается восемь двоичных единиц в противном случае (81h-> FF81h).
Другими словами, при расширении со знаком в первом байтеслова копируется
знаковый разряд числа-байта. Аналогично происходит расширение числа-слова до двойного
слова. 1.2.2 Особенности выполнения арифметических опреаций В ПК имеются
команды сложения и вычитания целых чисел размером вслово и байт. Специальных команд
для сложения и вычитания двойных словнет, эти операции реализуются через
команды сложения и вычитания слов. Сложение и вычитание беззнаковаых чисел производится
по модулю 2^8для байтов и 2^16 для слов. Это означает, что если в результате
сложе-ния появилась единица переноса, не вмещающаяся в разрядную сетку,
тоона отбрасывается. Например, при сложении байтов 128 и 130 получаетсячисло 258
= 100000010b, поэтому левая двоичная единица отбрасывается иостается число 2
= 10b, которое и объявляется результатом сложения.Ошибка здесь не фиксируется,
но в флаг переноса CF записывается 1 (ес-ли переноса не было, в CF заносится 0).
"Поймать" такое искажение сум-мы можно только последующим анализом флага CF.
Искажение результата происходит и при вычитание из меньшего числабольшего. И здесь
не фиксируется ошибка, однако первому числу дается"заем единицы" (в случае
байтов это число увеличивается на 256, дляслов - на 2^16), после чего и производится
вычитание. Например, вычи-тание байтов 2 и 3 сводится к вычитанию чисел 256+2=258
и 3, в резуль-тате чего получается неправильная разность 255 (а не -1).
Для тогочтобы можно было обнаружить такую ситуацию, в флаг переноса CF зано-сится
1 (если заема не было, в CF записывается 0). Сложение и вычитание знаковых
целых чисел производится по тем жеалгоритмам, что и для беззнаковых чисел (в этом
одно из достоинств до-полнительного кода): знаковые числа рассматриваются как
соответствую-щие беззнаковые числа, произодится операция над этими беззнаковыми
чи-слами и полученный результат интерпретируется как знаковое число. Нап-ример,
сложение байтовых чисел 1 и -2 происходит так: берутся их до-полнительные
коды 1 и (256-2)=254, вычисляется сумма этих величин1+254=255 и она трактуется
как знаковое число -1 (255=256-1). Если притаком сложении возникла единица переноса,
то она, как обычно, отбрасы-вается, а флаг CF получает значение 1. Однако
в данном случае это от-сечение не представляет интерес - результат операции будет
правильным,например: 3+(-2) => 3+254(mod 256) = 257(mod 256) = 1. Зато здесь
воз-можна иная неприятность: модуль суммы (ее мантисса) может превзойтидопустимую
границу и "залезть" в знаковый разряд, испортив его. Напри-мер, при сложении
байтовых чисел 127 и 2 получается величина 129 == 100001001b, представляющая
дополнительный код числа -127 (=256-129).Хотя результат здесь получился и неправильным,
процессор не фиксируетошибку, но зато заносит 1 в флаг переполнения OF
(если "переполнениямантиссы" не было, в OF записывается 0). Анализируя затем
этот флаг,можно "поймать" такую ошибку. Таким образом, сложение (вычитание) знаковых
и беззнаковых чиселпроизводится по одному и тому же алгоритму. При этом
ПК не "знает",какие числа (со знаком или без) он складывает; в любом случае он скла-дывает
их как беззнаковые числа и в любом случае формирует флаги CF иOF. А
вот как интерпретировать слагаемые и сумму, на какой из этихфлагов обращать внимание
- это личное дело автора программы. Что касается умножения и деления знаковых
и беззнаковых чисел, тоони выполняются по разным алгоритмам, разными машинными
командами. Од-нако и у этих операций есть ряд особенностей. При умножении
байтов(слов) первый сомножитель обязан находиться в регистре AL (AX), ре-зультатом
же умножения является слово (двойное слово), которое зано-сится в регистр AX
(регистры DX и AX). Тем самым при умножении сохра-няются все цифры произведения.
При делении байтов (слов) первый опе-ранд (делимое) должен быть словом (двойным
словом) и обязан находитьсяв регистре AX (регистрах DX и AX). Результатом
деления являются двевеличины размером в байт (слово) - неполное частное (div) и
остаток отделения (mod); неполное частное записывается в регистр AL (AX), а ос-таток
- в регистр AH (DX). 1.2.3 Представление символов и строк На символ отводится
один байт памяти, в который записывается кодсимвола - целое от 0 до 255.
В ПК используется система кодировки ASCII(American Standard Code for Information
Interchange). Она, естествен-но, не содержит кодов русских букв, поэтому в нашей
стране применяетсянекоторый вариант этой системы с русскими буквами (обычно
это альтер-нативная кодировка ГОСТа). Некоторые особенности этих систем кодировки:
- код пробела меньше кода любой буквы, цифры и вообще любого графи-чески представимого
символа; - коды цифр упорядочены по величине цифр и не содержат пропусков,т.е.
из неравенства код('0')+; например, для доступа к байту с числом -5
надо указать выражениеG+1, для доступа к байту с 10h - выражение G+2 и т.д.
Если в директиве DB перечислены только символы, например: S DB 'a','+','b'тогда
эту директиву можно записать короче, заключив все эти символы водни кавычки: S
DB 'a+b' И, наконец, если в директиве описывается несколько одинаковых кон-стант
(переменных), то можно воспользоваться конструкцией повторения k DUP(a,b,...,c)которая
эквивалентна повторенной k раз последовательности a,b,...,c.Например,
директивы V1 DB 0,0,0,0,0 V2 DW ?,?,?,?,?,?,?,?,?,'a',1,2,1,2,1,2,1,2можно записать
более коротко таким образом: V1 DB 5 DUP(0) V2 DW 9 DUP(?), 'a', 4 DUP(1,2)
1.3. ПРЕДСТАВЛЕНИЕ КОМАНД. МОДИФИКАЦИЯ АДРЕСОВ. 1.3.1 Структура команд. Исполнительные
адреса Машинные команды ПК занимают от 1 до 6 байтов. Код операции (КОП)
занимает один или два первых байта команды. ВПК столь много различных операций,
что для них не хватает 256 различ-ных КОПов, которые можно представить в
одном байте. Поэтому некоторыеоперации объединяются в группу и им дается один и
тот же КОП, во вто-ром же байте этот КОП уточняется. Кроме того, во втором байте
указыва-ются типы и способ адресации операндов. Остальные байты команды указы-вают
на операнды. Команды могут иметь от 0 до 3 операндов, у большинства команд
-один или два операнда. Размер операндов - байт или слово (редко -двойное слово).
Операнд может быть указан в самой команде (это т.н.непосредственный операнд),
либо может находиться в одном из регистровПК и тогда в команде указывается
этот регистр, либо может находиться вячейке памяти и тогда в команде тем или иным
способом указывается ад-рес этой ячейки. Некоторые команды требуют, чтобы операнд
находился вфиксированном месте (например, в регистре AX), тогда операнд
явно неуказывается в команде. Результат выполнения команды помещается в ре-гистр
или ячейку памяти, из которого (которой), как правило, беретсяпервый операнд.
Например, большинство команд с двумя операндами реали-зуют действие op1 := op1
op2где op1 - регистр или ячейка, а op2 - непосредственный операнд, ре-гистр
или ячейка. Адрес операнда разрешено модифицировать по одному или двум регист-рам.
В первом случае в качестве регистра-модификатора разрешено ис-пользовать регистр
BX, BP, SI или DI (и никакой иной). Во втором слу-чае один из модификаторов
обязан быть регистром BX или BP, а другой -регистром SI или DI; одновременная
модификация по BX и BP или SI и DIнедопустима. Регистры BX и BP обычно используются
для хранения базы(начального адреса) некоторого участка памяти (скажем,
массива) и по-тому называются базовыми регистрами, а регистры SI и DI часто содержатиндексы
элементов массива и потому называются индексными регистрами.Однако
такое распределение ролей необязательно, и, например, в SI мо-жет находиться база
массива, а в BX - индекс элемента массива. В MASM адреса в командах записываются
в виде одной из следующихконструкции: A, A[M] или A[M1][M2],где A - адрес,
M - регистр BX, BP, SI или DI, M1 - регистр BX или BP,а M2 - регистр SI или DI.
Во второрм и третьем варианте A может отсут-ствовать, в этом случае считается,
что A=0. При выполнении команды процессор прежде всего вычисляет т.н. ис-полнительный
(эффективный) адрес - как сумму адреса, заданного в ко-манде, и текущих
значений указанных регистров-модификаторов, причемвсе эти величины рассматриваются
как неотрицательные и суммированиеведется по модулю 2^16 ([r] означает содержимое
регистра r): A : Aисп = A A[M] : Aисп = A+[M] (mod 2^16) A[M1][M2]:
Aисп = A+[M1]+[M2] (mod 2^16) Полученный таким образом 16-разрядный адрес определяет
т.н. смеще-ние - адрес, отсчитанный от начала некоторого сегмента (области)
памя-ти. Перед обращением к памяти процессор еще добавляет к смещению на-чальный
адрес этого сегмента (он хранится в некотором сегментном реги-стре), в результате
чего получается окончательный 20-разрядный ад-рес, по которому и происходит
реальное обращение к памяти (см. 1.4). 1.3.2 Форматы команд В ПК форматы машинных
команд достаточно разнообразны. Для примераприведем лишь основные форматы
команд с двумя операндами. 1) Формат "регистр-регистр" (2байта): -------------
---------------- | КОП |d|w| | 11 |reg1|reg2| ------------- ----------------
7 2 1 0 7 6 5 3 2 0Команды этого формата описывают обычно действие reg1:=reg1reg2
илиreg2:=reg2reg1. Поле КОП первого байта указывает на операцию (), ко-торую
надо выполнить. Бит w определяет размер операндов, а бит d ука-зывает, в какой
из регистров записывается результат: w = 1 - слова d = 1 - reg1:=reg1reg2
= 0 - байты = 0 - reg2:=reg2reg1Во втором байте два левых бита фиксированы (для
данного формата), атрехбитовые поля reg1 и reg2 указывают на регистры, участвующие
в опе-рации, согласно следующей таблице: reg w=1 w=0 reg w=1 w=0 -----------------
----------------- 000 AX AL 100 SP AH 001 CX CL 101 BP CH 010 DX DL
110 SI DH 011 BX BL 111 DI BH 2) Формат "регистр-память" (2-4 байта): -------------
------------- ------------------- | КОП |d|w| |mod|reg|mem| |адрес (0-2 байта)|
------------- ------------- -------------------Эти команды описывают операции
reg:=regmem или mem:=memreg. Бит wпервого байта определяет размер операндов
(см. выше), а бит d указыва-ет, куда записывается результат: в регистр (d=1)
или в ячейку памяти(d=0). Трехбитовое поле reg второго байта указывает операнд-регистр(см.
выше), двухбитовое поле mod определяет, сколько байтов в командезанимает
операнд-адрес (00 - 0 байтов, 01 - 1 байт, 10 - 2 байта), атрехбитовое поле
mem указывает способ модификации этого адреса. В сле-дующей таблице указаны
правила вычисления исполнительного адреса в за-висимости от значений полей mod
и mem (a8 - адрес размером в байт, a16- адрес размером в слово): mem mod |
00 01 10 ------------------------------------------------------- 000 | [BX]+[SI]
[BX]+[SI]+a8 [BX]+[SI]+a16 001 | [BX]+[DI] [BX]+[DI]+a8 [BX]+[DI]+a16 010 | [BP]+[SI]
[BP]+[SI]+a8 [BP]+[SI]+a16 011 | [BP]+[DI] [BP]+[DI]+a8 [BP]+[DI]+a16
100 | [SI] [SI]+a8 [SI]+a16 101 | [DI] [DI]+a8 [DI]+a16 110 | a16 [BP]+a8 [BP]+a16
111 | [BX] [BX]+a8 [BX]+a16 Замечания. Если в команде не задан адрес, то он
считается нулевым.Если адрес задан в виде байта (a8), то он автоматически расширяется
сознаком до слова (a16). Случай mod=00 и mem=110 указывает на отсутствиерегистров-модификаторов,
при этом адрес должет иметь размер слова (ад-ресное выражение
[BP] ассемблер транслирует в mod=01 и mem=110 приa8=0). Случай mod=11 соответствует
формату "регистр-регистр". 3) Формат "регистр-непосредственный операнд"
(3-4 байта): ----------- ------------- -------------------------- | КОП |s|w|
|11|КОП"|reg| |непосред.операнд (1-2 б)| ----------- ------------- --------------------------Команды
этого формата описывают операции reg:=regimmed (immed
- не-посредственный операнд). Бит w указывает на размер операндов, а полеreg
- на регистр-операнд (см. выше). Поле КОП в первом байте определя-ет лишь класс
операции (например, класс сложения), уточняет же опера-цию поле КОП" из второго
байта. Непосредственный операнд может зани-мать 1 или 2 байта - в зависимости
от значения бита w, при этом опе-ранд-слово записывается в команде в "перевернутом"
виде. Ради экономиипамяти в ПК предусмотрен случай, когда в операции над
словами непос-редственный операнд может быть задан байтом (на этот случай указывает1
в бите s при w=1), и тогда перед выполнением операции байт автомати-чески
расширяется (со знаком) до слова. 4) Формат "память-непосредственный операнд"
(3-6 байтов): ----------- -------------- -------------- ------------------ | КОП
|s|w| |mod|КОП"|mem| |адрес (0-2б)| |непоср.оп (1-2б)| ----------- --------------
-------------- ------------------Команды этого формата описывают операции
типа mem:=memimmed. Смыслвсех полей - тот же, что и в предыдущих форматах. Помимо
рассмотренных в ПК используются и другие форматы команды сдвумя операндами;
так, предусмотрен специальный формат для команд,один из операндов которых фиксирован
(обычно это регистр AX). Имеютсвои форматы и команды с другим числом операндов.
1.3.3 Запись команд в MASM Из сказанного ясно, что одна и та же операция
в зависимости от ти-пов операдов записывается в виде различных машинных команд:
например,в ПК имеется 28 команд пересылки байтов и слов. В то же время в MASMвсе
эти "родственные" команды записываются единообразно: например, всекоманды
пересылки имеют одну и ту же символьную форму записи: MOV op1,op2 (op1:=op2)Анализируя
типы операндов, ассемблер сам выбирает подходящую машиннуюкоманду. В общем
случае команды записываются в MASM следующим образом: метка: мнемокод операнды
;комментарийМетка с двоеточием, а также точка с запятой и комментарий могут
отсут-ствовать. Метка играет роль имени команды, ее можно использовать в ко-мандах
перехода на данную команду. Комментарий не влияет на смысл ко-манды, а лишь
поясняет ее. Мнемонические названия операций полностью перечислены в главе 2.
Операнды, если есть, перечисляются через запятую. Основные правилазаписи операндов
следующие. Регистры указываются своими именами, например: MOV AX,SI ;оба
операнда - регистры Непосредственные операнды задаются константными выражениями
(ихзначениями являются константы-числа), например: MOV BH,5 ;5 - непосредственный
операнд MOV DI,SIZE X ;SIZE X (число байтов, занимаемых перемен- ;ной X) -
непосредственный операнд Адреса описываются адресными выражениями (например, именами
пере-менных), которые могут быть модифицированы по одному или двум регист-рам;
например, в следующих командах первые операнды задают адреса: MOV X,AH MOV
X[BX][DI],5 MOV [BX],CL При записи команд в символьной форме необходимо внимательно
сле-дить за правильным указанием типа (размера) операндов, чтобы не былоошибок.
Тип обычно определяется по внешнему виду одного из них, напри-мер: MOV
AH,5 ;пересылка байта, т.к. AH - байтовый регистр MOV AX,5 ;пересылка слова, т.к.
AX - 16-битовый регистр ;(операнд 5 может быть байтом и словом, по нему ;нельзя
определить размер пересылаемой величины) MOV [BX],300 ;пересылка слова, т.к.
число 300 не может быть ;байтом Если по внешнему виду можно однозначно определить
тип обоих опе-рандов, тогда эти типы должны совпадать, иначе ассемблер зафиксируетошибку.
Примеры: MOV DS,AX ;оба операнда имеют размер слова MOV CX,BH
;ошибка: регистры CX и BH имеют разные размеры MOV DL,300 ;ошибка: DL - байтовый
регистр, а число 300 не ;может быть байтом Возможны ситуации, когда по внешнему
виду операндов нельзя опреде-лить тип ни одного из них, как, например, в команде
MOV [BX],5Здесь число 5 может быть и байтом, и словом, а адрес из регистра
BXможет указывать и на байт памяти, и на слово. В подобных ситуациях ас-семблер
фиксирует ошибку. Чтобы избежать ее, надо уточнить тип одногоиз операндов с помощью
оператора с названием PTR: MOV BYTE PTR [BX],5 ;пересылка байта MOV WORD
PTR [BX],5 ;пересылка слова(Операторы - это разновидность выражений языка MASM,
аналогичные функ-циям.) Оператор PTR необходим и в том случае, когда надо изменить
тип,предписанный имени при его описании. Если, например, X описано как имяпеременной
размером в слово: X DW 999и если надо записать в байтовый регистр AH
значение только первогобайта этого слова, тогда воспользоваться командой MOV AH,Xнельзя,
т.к. ее операнды имеют разный размер. Эту команду следует за-писать
несколько иначе: MOV AH,BYTE PTR XЗдесь конструкция BYTE PTR X означает адрес X,
но уже рассматриваемыйне как адрес слова, а как адрес байта. (Напомним, что с
одного и тогоже адреса может начинаться байт, слово и двойное слово; оператор
PTRуточняет, ячейку какого размера мы имеем в виду.) И еще одно замечание. Если
в символьной команде, оперирующей сословами, указан непосредственный операнд размером
в байт, как, напри-мер, в команде MOV AX,80hто возникает некоторая неоднозначность:
что будет записано в регистрAX - число 0080h (+128) или 0FF80h (-128)?
В подобных ситуациях ассем-блер формирует машинную команду, где операнд-байт
расширен до слова,причем расширение происходит со знаком, если операнд был записан
какотрицательное число, и без знака в остальных случаях. Например: MOV AX,-128
; => MOV AX,0FF80h (A:=-128) MOV AX,128 ; => MOV AX,0080h (A:=+128) MOV
AX,80h ; => MOV AX,0080h (A:=+128) 1.4. СЕГМЕНТИРОВНИЕ 1.4.1 Сегменты памяти. Сегментные
регистры. Первые модели ПК имели оперативную память объемом 2^16 байтов(64Кб)
и потому использовали 16-битовые адреса. В последующих моделяхпамять была
увеличена до 2^20 байтов (1Мб=1000Кб), для чего уже необ-ходимы 20-битовые
адреса. Однако в этих ПК ради сохранения преемствен-ности были сохранены 16-битовые
адреса: именно такие адреса хранятся врегистрах и указываются в командах,
именно такие адреса получаются врезультате модмфикации по базовым и индексным регистрам.
Как же удает-ся 16-битовыми адресами ссылаться на 1Мб памяти? Эта проблема
решается с помощью сегментирования адресов (неявногобазирования адресов).
В ПК вводится понятие "сегмент памяти". Так на-зывается любой участок памяти размером
до 64Кб и с начальным адресом,кратным 16. Абсолютный (20-битовый) адрес
A любой ячейки памяти можнопредставить как сумму 20-битового начального адреса
(базы) B сегмента,которому принадлежит ячейка, и 16-битового смещения D - адреса
этойячейки, отсчитанного от начала сегмента: A=B+D. (Неоднозначность выбо-ра
сегмента не играет существенной роли, главное - чтобы сумма B и Dдавала нужный
адрес.) Адрес B заносится в некоторый регистр S, а в ко-манде, где должен быть
указан адрес A, вместо него записывается параиз регистра S и смещения D (в MASM
такая пара, называемая адресной па-рой или указателем, записывается как S:D).
Процессор же устроен так,что при выполнении команды он прежде всего по паре S:D
вычисляет абсо-лютный адрес A как сумму содержимого регистра S и смещения D
и толькозатем обращается к памяти по этому адресу A. Вот так, заменяя в коман-дах
абсолютные адреса на адресные пары, и удается адресовать всю па-мять 16-битовыми
адресами (смещениями). В качестве регистра S разрешается использовать не любой
регистр, атолько один из 4 регистров, называемых сегментными: CS, DS, SS и
ES. Всвязи с этим одновременно можно работать с 4 сегментами памяти: началоодного
из них загружается в регистр CS и все ссылки на ячейки этогосегмента указываются
в виде пар CS:D, начало другого заносится в DS ивсе ссылки на его ячейки задаются
в виде пар DS:D и т.д. Если одновре-менно надо работать с большим числом
сегментов, тогда нужно своевре-менно спасать содержимое сегментных регистров
и записывать в них на-чальные адреса пятого, шестого и т.д. сегментов. Отметим,
что используемые сегменты могут быть расположены в памятипроизвольным образом:
они могут не пересекаться, а могут пересекатьсяи даже совпадать. Какие сегменты
памяти использовать, в каких сегмент-ных регистрах хранить их начальные адреса
- все это личное дело авторамашинной программы. Как и все регистры ПК, сегментные
регистры имеют размер слова. По-этому возникает вопрос: как удается разместить
в них 20-битовые на-чальные адреса сегментов памяти? Ответ такой. Поскольку
все эти адресакратны 16 (см. выше), то в них младшие 4 бита (последняя 16-ричнаяцифра)
всегда нулевые, а потому эти биты можно не хранить явно, а лишьподразумевать.
Именно так и делается: в сегментном регистре всегдахранятся только первые
16 битов (первые четыре 16-ричные цифры) на-чального адреса сегмента (эта величина
называется номером сегмента илипросто сегментом). При вычислении же абсолютного
адреса A по паре S:Dпроцессор сначала приписывает справа к содержимому
регистра S четыренулевых бита (другими словами, умножает на 16) и лишь затем прибавляетсмещение
D, причем суммирование ведется по модулю 2^20: Aабс = 16*[S]+D
(mod 2^20)Если, например, в регистре CS хранится величина 1234h, тогда адреснаяпара
1234h:507h определяет абсолютный адрес, равный 16*1234h+507h =12340h+507h
= 12847h. 1.4.2 Сегментные регистры по умолчанию Согласно описанной схеме сегментирования
адресов, замену абсолют-ных адресов на адресные пары надо производить
во всех командах, имею-щих операнд-адрес. Однако разработчики ПК придумали
способ, позволяю-щий избежать выписывания таких пар в большинстве команд. Суть
его втом, что заранее договариваются о том, какой сегментный регистр на ка-кой
сегмент памяти будет указывать, и что в командах задается толькосмещение: не указанный
явно сегментный регистр автоматически восста-навливается согласно этой
договоренности. И только при необходимостинарушить эту договоренность надо полностью
указывать адресную пару. Что это за договоренность? Считается, что регистр
CS всегда указывает на начало области памя-ти, в которой размещены команды программы
(эта область называется сег-ментом команд или сегментом кодов), и потому
при ссылках на ячейкиэтой области регистр CS можно не указывать явно, он подразумевается
поумолчанию. (Отметим попутно, что абсолютный адрес очередной команды,подлежащей
выполнению, всегда задается парой CS:IP: в счетчике командIP всегда
находится смещение этой команды относительно адреса из реги-стра CS.) Аналогично
предполагается, что регистр DS указывает на сег-мент данных (область памяти
с константами, переменными и другими вели-чинами программы), и потому во всех
ссылках на этот сегмент регистр DSможно явно не указывать, т.к. он подразумевается
по умолчанию. РегистрSS, считается, указывает на стек - область памяти, доступ
к которойосуществляется по принципу "последним записан - первым считан" (см.1.7),
и потому все ссылки на стек, в которых явно не указан сегментныйрегистр,
по умолчанию сегментируются по регистру SS. Регистр ES счита-ется свободным,
он не привязан ни к какому сегменту памяти и его можноиспользовать по своему усмотрению;
чаще всего он применяется для дос-тупа к данным, которые не поместились
или сознательно не были размеще-ны в сегменте данных. С учетом такого распределения
ролей сегментных регистров машинныепрограммы обычно строятся так: все команды
программы размещаются в од-ном сегменте памяти, начало которого заносится
в регистр CS, а вседанные размещаются в другом сегменте, начало которого заносится
в ре-гистр DS; если нужен стек, то под него отводится третий сегмент памя-ти,
начало которого записывается в регистр SS. После этого практическиво всех
командах можно указывать не полные адресные пары, а лишь сме-щения, т.к. сегментные
регистры в этих парах будут восстанавливатьсяавтоматически. Здесь, правда,
возникает такой вопрос: как по смещению определить,на какой сегмент памяти оно
указывает? Точный ответ приведен ниже (см.1.4.3), а в общих чертах он такой: ссылки
на сегмент команд могут бытьтолько в командах перехода, а ссылки практически
во всех других коман-дах (кроме строковых и стековых) - это ссылки на сегмент
данных. Нап-ример, в команде пересылки MOV AX,Xимя X воспринимается как ссылка
на данное, а потому автоматически вос-станавливается до адресной пары DS:X.
В команде же безусловного пере-хода по адресу, находящемуся в регистре BX, JMP
BXабсолютный адрес перехода определяется парой CS:[BX]. Итак, если в ссылке на
какую-то ячейку памяти не указан явно сег-ментный регистр, то этот регистр берется
по умолчанию. Явно же сегмен-тные регистры надо указывать, только если по каким-то
причинам регистрпо умолчанию не подходит. Если, например, в команде пересылки
нам надосослаться на стек (скажем, надо записать в регистр AH байт стека,
по-меченный именем X), тогда нас уже не будет устраивать договоренность отом,
что по умолчанию операнд команды MOV сегментируется по региструDS, и потому мы
обязаны явно указать иной регистр - в нашем случае ре-гистр SS, т.к. именно он
указывает на стек: MOV AH,SS:XОднако такие случаи встречаются редко и потому в
командах, как прави-ло, указываются только смещения. Отметим, что в MASM сегментный
регистр записывается в самой коман-де непосредственно перед смещением (именем
переменной, меткой и т.п.),однако на уровне машинного языка ситуация несколько
иная. Имеется 4специальные однобайтовые команды, называемые префиксами замены
сегмен-та (обозначаемые как CS:, DS:, SS: и ES:). Они ставятся перед коман-дой,
операнд-адрес которой должен быть просегментирован по регистру,отличному от
регистра, подразумеваемому по умолчанию. Например, приве-денная выше символическая
команда пересылки - это на самом деле двемашинные команды: SS: MOV AH,X
1.4.3 Сегментирование, базирование и индексирование адресов Поскольку сегментирование
адресов - это разновидность модификацииадресов, то в ПК адрес, указываемый
в команде, в общем случае модифи-цируется по трех регистрам - сегментному, базовому
и индексному. В це-лом, модификация адреса производится в два этапа. Сначала
учитываютсятолько базовый и индексный регистры (если они, конечно, указаны
в ко-манде), причем вычисление здесь происходит в области 16-битовых адре-сов;
полученный в результате 16-битовый адрес называется исполнитель-ным (эффективным)
адресом. Если в команде не предусмотрено обращение кпамяти (например, она загружает
адрес в регистр), то на этом модифика-ция адреса заканчивается и используется
именно исполнительный адрес(он загружается в регистр). Если же нужен доступ
к памяти, тогда навтором этапе исполнительный адрес рассматривается как смещение
и к не-му прибавляется (умноженное на 16) содержимое сегментного регистра,указанного
явно или взятого по умолчанию, в результате чего получаетсяабсолютный
(физический) 20-битовый адрес, по которому реально и проис-ходит обращение
к памяти. Отметим, что сегментный регистр учитывается только в "последний"момент,
непосредственно перед обращением к памяти, а до этого работаведется только с
16-битовыми адресами. Если учесть к тому же, что сег-ментные регистры, как правило,
не указываются в командах, то можно вобщем-то считать, что ПК работает с
16-битовыми адресами. Как уже сказано, если в ссылке на ячейку памяти не указан
сегмент-ный регистр, то он определяется по умолчанию. Это делается по следую-щим
правилам. 1) В командах перехода адрес перехода сегментируется по региструCS
и только по нему, т.к. абсолютный адрес команды, которая должнабыть выполнена
следующей, всегда определяется парой CS:IP (попытка из-менить в таких командах
сегментный регистр будет безуспешной). Отметим, что сегментиорвание по регистру
CS касается именно адресаперехода, а не адреса той ячейки, где он может находиться.
Например, вкоманде безусловного перехода по адресу, находящемуся в ячейке
X: JMP Xимя X сегментируется по регистру DS, а вот адрес перехода, взятый изячейки
X, уже сегментируется по регистру CS. 2) Адреса во всех других командах,
кроме строковых (STOS, MOVS,SCAS и CMPS), по умолчанию сегментируются: - по регистру
DS, если среди указанных регистров-модификаторов нет регистра BP; - по регистру
SS, если один из модификаторов - регистр BP. Таким образом, адреса вида A,
A[BX], A[SI], A[DI], A[BX][SI] иA[BX][DI] сегментируются по регистру DS, а адреса
A[BP], A[BP][SI] иA[BP][DI] - по регистру SS, т.е. адреса трех последних видов
использу-ются для доступа к ячейкам стека. 3) В строковых командах STOS, MOVS,
SCAS и CMPS, имеющих два опе-ранда-адреса, на которые указывают индексные
регистры SI и DI, один изоперандов (на который указывает SI) сегментируется по
регистру DS, адругой (на него указывает DI) - по регистру ES. 1.4.4 Программные
сегменты. Директива ASSUME Рассмотрим, как сегментирование проявляется в программах
на MASM. Для того чтобы указать, что некоторая группа предложений программына
MASM образуют единый сегмент памяти, они оформляются как программ-ный сегмент:
перед ними ставится директива SEGMENT, после них - дирек-тива ENDS, причем
в начале обеих этих директив должно быть указано од-но и то же имя, играющее роль
имени сегмента. Программа же в целомпредставляет собой последовательность таких
программных сегментов, вконце которой указывается директива конца программы
END, например: DT1 SEGMENT ;программный сегмент с именем DT1 A DB 0 B DW ? DT1
ENDS ; DT2 SEGMENT ;программный сегмент DT2 C DB 'hello' DT2 ENDS ; CODE SEGMENT
;программный сегмент CODE ASSUME CS:CODE, DS:DT1, ES:DT2 BEG: MOV AX,DT2 MOV
DS,AX MOV BH,C ... CODE ENDS END BEG ;конец текста программы Предложения программного
сегмента ассемблер размещает в одном сег-менте памяти (в совокупности
они не должны занимать более 64Кб) начи-ная с ближайшего свободного адреса, кратного
16. Номер (первые 16 би-тов начального адреса) этого сегмента становится
значением имени сег-мента. В MASM это имя относится к константным выражениям, а
не адрес-ным, в связи с чем в команде MOV AX,DT2второй операнд является непосредственным,
поэтому в регистр AX будетзаписано начало (номер) сегмента DT2, а не
содержимое начальной ячейкиэтого сегмента. Имена же переменных (A, B, C) и метки
(BEG) относятся к адреснымвыражениям, и им ставится в соответствие адрес их
ячейки относительно"своего" сегмента: имени A соответствует адрес 0, имени B -
адрес 1,имени C - адрес 0, а метке BEG - адрес 0. Все ссылки на предложения одного
программного сегмента ассемблерсегментирует по умолчанию по одному и тому
же сегментному регистру. Покакому именно - устанавливается специальной директивой
ASSUME. В нашемпримере эта директива определяет, что все ссылки на сегмент CODE
долж-ны, если явно не указан сегментный регистр, сегментироваться по регис-тру
CS, все ссылки на DT1 - по регистру DS, а все ссылки на DT2 - порегистру ES.
Встретив в тексте программы ссылку на какое-либо имя (например, наимя C в команде
MOV AX,C), ассемблер определяет, в каком программномсегменте оно описано (у
нас - в DT2), затем по информации из директивыASSUME узнает, какой сегментный
регистр поставлен в соответствие этомусегменту (у нас - это ES), и далее образует
адресную пару иэ данногорегистра и смещения имени (у нас - ES:0), которую и
записывает в фор-мируемую машинную команду. При этом ассемблер учитывает используемое
вПК соглашение о сегментных регистрах по умолчанию: если в адресной па-ре,
построенной им самим или явно заданной в программе, сегментный ре-гистр совпадает
с регистром по умолчанию, то в машинную команду зано-сится лишь смещение.
Если, скажем, в нашем примере встретится командаMOV CX,B, тогда по имени В ассемблер
построит пару DS:1, но раз опе-ранд-адрес команды MOV по умолчанию сегментируется
по регистру DS, тозаписывать этот регистр в машинную команду излишне
и ассемблер записы-вает в нее только смещение 1. Таким образом, директива ASSUME
избавляет программистов от необхо-димости выписывать полные адресные пары не
только тогда, когда исполь-зуются сегментные регистры по умолчанию (как в случае
с именем B), нотогда, когда в машинной команде нужно было бы явно указать сегментныйрегистр
(как в случае с именем C). В MASM сегментный регистр в ссылкена
имя требуется указывать лишь тогда, когда имя должно по каким-либопричинам сегментироваться
по регистру, отличному от того, что постав-лен в соответствие всему
сегменту, в котором это имя описано. Однако все это справедливо только при соблюдении
следующих усло-вий. Во-первых, директива ASSUME должна быть указана перед
первой ко-мандой программы. В противном случае ассемблер, просматривающий
текстпрограммы сверху вниз, не будет знать, как сегментировать имена из ко-манд,
расположенных до этой директивы, и потому зафиксирует ошибку.Во-вторых, в директиве
ASSUME следует каждому сегменту ставить в соот-ветствие сегментный регистр:
если ассемблеру встретится ссылка на имяиз сегмента, которому не соответствует
никакой сегментный регистр, тоон зафиксирует ошибку. Правда, в обоих случаях
можно избежать ошибки,но для этого в ссылках необходимо явно указывать сегментный
регистр. 1.4.5 Начальная загрузка сегментных регистров Директива ASSUME сообщает
ассмеблеру о том, по каким регистрам ондолжен сегментировать имена из каких
сегментов, и "обещает", что вэтих регистрах будут находиться начальные адреса
этих сегментов. Одна-ко загрузку этих адресов в регистры сама директива не осуществляет.Сделать
такую загрузку - обязанность самой программы, с загрузки сег-ментных
регистров и должно начинаться выполнение программы. Делаетсяэто так.
Поскольку в ПК нет команды пересылки непосредственного операнда всегментный регистр
(а имя, т.е. начало, сегмента - это непосредствен-ный операнд), то такую загрузку
приходится делать через какой-то дру-гой, несегментный, регистр (например,
AX): MOV AX,DT1 ;AX:=начало сегмента DT1 MOV DS,AX ;DS:=AXАналогично загружается
и регистр ES. Загружать регистр CS в начале программы не надо: он, как и
счетчиккоманд IP, загружается операционной системой перед тем, как начинаетсявыполнение
программы (иначе нельзя было бы начать ее выполнение). Чтоже касается
регистра SS, используемого для работы со стеком, то он мо-жет быть загружен так
же, как и регистры DS и ES, однако в MASM преду-смотрена возможность загрузки
этого регистра еще до выполнения прог-раммы (см. 1.7). 1.4.6 Ссылки вперед Встречая
в символьной команде ссылку назад - имя, которое описанов тексте программы
до этой команды, ассемблер уже имеет необходимуюинформацию об имени и потому может
правильно оттранслировать эту ко-манду. Но если в команде встретится ссылка
вперед, т.е. имя, котороене было описано до команды и которое, наверное, будет
описано позже,то ассемблер в большинстве случаев не сможет правильно оттранслироватьэту
команду. Например, не зная, в каком программном сегменте будетописано
это имя, ассемблер не может определить, по какому сегментномурегистру надо сегментировать
имя, и потому не может определить, надоили нет размещать перед соответствующей
машинной командой префикс за-мены сегмента и, если надо, то какой
именно. В подобной ситуации ассемблер действует следующим образом: если вкоманде
встретилась ссылка вперед, то он делает некоторое предположе-ние относительно
этого имени и уже на основе этого предположения фор-мирует машинную команду.
Если затем (когда встретится описание имени)окажется, что данное предположение было
неверным, тогда ассемблер пы-тается исправить сформированнную им ранее машинную
команду. Однако этоне всегда удается: если правильная машинная команда должна
заниматьбольше места, чем машинная команда, построенная на основе предположе-ния
(например, перед командой надо на самом деле вставить префикс за-мены сегмента),
тогда ассемблер фиксирует ошибку (как правило, этоошибка номер 6: Phase
error between passes.) Какие же предположения делает ассемблер, встречая ссылку
вперед?Во всех командах, кроме команд перехода (о них см. 1.5), ассемблерпредполагает,
что имя будет описано в сегменте данных и потому сегмен-тируется по регистру
DS. Это следует учитывать при составлении прог-раммы: если в команде встречается
ссылка вперед на имя, которое описа-но в сегменте, на начало которого
указывает сегментный регистр, отлич-ный от DS, то перед таким именем автор программы
должен написать соот-вествующмй префикс. Пример: code segment assume cs:code
x dw ? beg: mov ax,x ;здесь вместо cs:x можно записать просто x mov cs:y,ax
;здесь обязательно надо записать cs:y ... y dw ? code ends 1.5. ПЕРЕХОДЫ В систему
команд ПК входит обычный для ЭВМ набор команд перехода:безусловные и условные
переходы, переходы с возвратами и др. Однако вПК эти команды имеют некоторые
особенности, которые здесь и рассматри-ваются. Абсолютный адрес команды, которая
должна быть выполнена следующей,определяется парой CS:IP, поэтому выполнение
перехода означает измене-ние этих регистров, обоих или только одного (IP).
Если изменяетсятолько счетчик команд IP, то такой переход называется внутрисегментнымили
близким (управление остается в том же сегменте команд), а если ме-няются
оба регистра CS и IP, то это межсегментный или дальний переход(начинают выполняться
команды из другого сегмента команд). По способуизменения счетчика команд
переходы делятся на абсолютные и относитель-ные. Если в команде перехода указан
адрес (смещение) той команды, ко-торой надо передать управление, то это абсолютный
переход. Однако вкоманде может быть указана величина (сдвиг), которую надо
добавить ктекущему значению регистра IP, чтобы получился адрес перехода, и тогдаэто
будет относительный переход; при этом сдвиг может быть положитель-ным и
отрицательным, так что возможен переход вперед и назад. По вели-чине сдвига относительные
переходы делятся на короткие (сдвиг задаетсябайтом) и длинные (сдвиг
- слово). Абсолютные же переходы делятся напрямые и косвенные: при прямом переходе
адрес перехода задается в са-мой команде, а при косвенном - в команде указывается
регистр или ячей-ка памяти, в котором (которой) находится адрес перехода.
1.5.1 Безусловные переходы. В MASM все команды безусловного перехода обозначаются
одинаково: JMP opно в зависимости от типа операнда, ассемблер формирует
разные машинныекоманды. 1) Внутрисегментный относительный короткий переход. JMP
i8 (IP:=IP+i8)Здесь i8 обозначает непосредственный операнд размеров в байт, которыйинтерпретируется
как знаковое целое от -128 до 127. Команда прибавляетэто
число к текущему значению регистра IP, получая в нем адрес (смеще-ние) той команды,
которая должна быть выполнена следующей. Регистр CSпри этом не меняется.
Необходимо учитывать следующую особенность регистра IP. Выполнениелюбой команды
начинается с того, что в IP заносится адрес следующей заней команды, и только затем
выполняется собственно команда. Для коман-ды относительного перехода это означает,
что операнд i8 прибавляетсяне к адресу этой команды, а к адресу команды,
следующей за ней, поэто-му, к примеру, команда JMP 0 - это переход на следующую
команду про-граммы. При написании машинной программы сдвиги для относительных
перехо-дов приходится вычислять вручную, однако MASM избавляет от этого не-приятного
занятия: в MASM в командах относительного перехода всегдауказывается метка
той команды, на которую надо передать управление, иассемблер сам вычисляет
сдвиг, который он и записывает в машинную ко-манду. Отсюда следует, что в MASM
команда перехода по метке восприни-мается не как абсолютный переход, а как относительный.
По короткому переходу можно передать управление только на ближай-шие
команды программы - отстоящие от команды, следующей за командойперехода, до 128
байтов назад или до 127 байтов вперед. Для переходана более дальние команды
используется 2) Внутрисегментный относительный длинный переход. JMP i16 (IP:=IP+i16)Здесь
i16 обозначает непосредственный операнд размером в слово, кото-рый рассматривается
как знаковое целое от -32768 до 32767. Этот пере-ход аналогичен
короткому переходу. Отметим, что, встретив команду перехода с меткой, которой была
по-мечена одна из предыдущих (по тексту) команд программы, ассемблер вы-числяет
разность между адресом этой метки и адресом команды перехода ипо этому сдвигу
определяет, какую машинную команду относительного пе-рехода - короткую или
длинную - надо сформировать. Но если метка ещене встречалась в тексте программы,
т.е. делается переход вперед, тогдаассемблер, не зная еще адреса метки, не может
определить, какую именномашинную команду относительного перехода формировать,
поэтому он навсякий случай выбирает команду длинного перехода. Однако эта машиннаякоманда
занимает 3 байта, тогда как команда короткого перехода - 2байта,
и если автор программы на MASM стремится к экономии памяти изнает заранее, что
переход вперед будет на близкую метку, то он долженсообщить об этом ассемблеру,
чтобы тот сформировал команду короткогоперехода. Такое указание делается с помощью
оператора SHORT: JMP SHORT LДля переходов назад оператор SHORT не нужен: уже
зная адрес метки, ас-семблер сам определит вид команды относительного перехода.
3) Внутрисегментный абсолютный косвенный переход. JMP r16 (IP:=[r]) или JMP
m16 (IP:=[m16])Здесь r16 обозначает любой 16-битовый регистр общего назначения,
а m16- адрес слова памяти. В этом регистре (слове памяти) должен находитьсяадрес,
по которому и будет произведен переход. Например, по командеJMP BX осушествляется
переход по адресу, находящемуся в регистре BX. 4) Межсегментный абсолютный
прямой переход. JMP seg:ofs (CS:=seg, IP:=ofs)Здесь seg - начало (первые 16
битов начального адреса) некоторого сег-мента памяти, а ofs - смещение в этом
сегменте. Пара seg:ofs определя-ет абсолютный адрес, по которому делается переход.
В MASM эта паравсегда задается конструкцией FAR PTR , которая "говорит", чтонадо
сделать переход по указанной метке, причем эта метка - "дальняя",из другого
сегмента. Отметим, что ассемблер сам определяет, какой этосегмент, и сам подставляет
в машинную команду его начало, т.е. seg. 5) Межсегментный абсолютный косвенный
переход. JMP m32 (CS:=[m32+2], IP:=[m32])Здесь под m32 понимается адрес
двойного слова памяти, в котором нахо-дится пара seg:ofs, задающая абсолютный
адрес, по которому данная ко-манда должна выполнить переход. Напомним, что в ПК
величины размером вдвойное слово хранятся в "перевернутом" виде, поэтому смещение
ofs на-ходится в первом слове двойного слова m32, а смещение seg - во второмслове
(по адресу m32+2). Команды межсегментного перехода используются тогда,
когда командыпрограммы размещены не в одном сегменте памяти, а в нескольких (напри-мер,
команд столь много, что в совокупности они занимают более 64Кб,т.е. более
максимального размера сегмента памяти). При переходе из од-ного такого сегмента
в другой необходимо менять не только счетчик ко-манд IP, но и содержимое регистра
CS, загружая в последний начальныйадрес второго сегмента. Такое одновременное
изменение обоих этих ре-гистров и делают команды межсегментного перехода.
При записи в MASM команд перехода следует учитывать, что они могутвосприниматься
неоднозначно. Скажем, как воспринимать команду JMP A- как переход по метке
A или как переход по адресу, хранящемуся вячейке с именем A? Кроме того, какой
это переход - внутрисегментныйили межсегментный? Ответ зависит от того, как описано
имя A, и от то-го, когда описано имя A - до или после команды перехода. Пусть
A описано до команды перехода ("ссылка назад"). Если именемA помечена некоторая
команда текущего сегмента команда (т.е. A - мет-ка), тогда ассемблер формирует
машинную команду внутрисегментного от-носительного перехода. Если же A -
имя переменной, тогда ассемблерформирует машинную команду косвенного перехода -
внутрисегментного,если A описано в директиве DW, или межсегментного, если A описано
вдирективе DD. В случае же, если имя A описано после команды перехода ("ссылкавперед"),
ассемблер всегда формирует машинную команду внутрисегментно-го относительного
длинного перехода. С учетом этого имя A обязательнодолжно метить команду
из текущего сегмента команд, иначе будет зафик-сирована ошибка. Если такая
трактовка ссылки вперед не удовлетворяетавтора программы, тогда он обязан с
помощью оператора SHORT или PTRуточнить тип имени A: JMP SHORT A ;внутрисегментный
короткий переход по метке JMP WORD PTR A ;внутрисегментный косвенный переход
JMP DWORD PTE A ;межсегментный косвенный переход Отметим, что переход по метке
A из другого сегмента команд всегдадолжен указываться с помощью FAR PTR (независимо
от того, описана мет-ка A до или после команды перехода): JMP FAR PTR A
;межсегментный переход по метке 1.5.2 Условные переходы. Практически во всех командах
условного перехода проверяется значе-ние того или иного флага (например,
флага нуля ZF) и, если он имеетопределенное значение, выполняется переход по адресу,
указанному в ко-манде. Значение флага должно быть установлено предыдущей
командой, на-пример, командой сравнения CMP op1,op2которая вычисляет разность
op1-op2, однако результат никуда не записы-вает, а только меняет флаги, на которые
и будет реагировать командаусловного перехода. В MASM команды условного перехода
имеют следующую форму: Jxx opгде xx - одна или несколько букв, в сокращенном
виде отражающие прове-ряемое условие (обычно в предположении, что перед этой
командой нахо-дится команда сравнения). Примеры некоторых мнемоник: JE - переход
"по равно" (jump if equal) JL - переход "по меньше" (jump if less) JNL - переход
"по неменьше" (jump if not less) Особеностью всех машинных команд условного
перехода является то,что они реализуют внутрисегментный относительный короткий
переход,т.е. добавляют к счетчику команд IP свой операнд, рассматриваемый какзнаковое
число от -128 до 127. В MASM этот операнд всегда должен запи-сываться
как метка, которую ассемблер заменит на соответствующий сдвиг(см. выше). Такая
особенность команд условного перехода вызывает неудобствопри переходах на "дальние"
команды. Например, если надо сделать пере-ход при A M (обход команды JMP)
JMP L ;меньше --> L (длинный переход) M: ... 1.5.3 Команды управление циклом
В ПК есть несколько команд, упрощающих программирование циклов сзаранее известным
числом повторений. Применение этих команд требует,чтобы к началу цикла в регистр
CX было занесено число шагов цикла. Са-ми команды размещаются в конце цикла,
они уменьшают значение CX на 1и, если CX еще не равно 0, передают управление
на начало цикла. Напри-мер, найти S - сумму элементов массива X из 10 чисел-слов
можно так: MOV AX,0 ;начальное значение суммы (накапливается в AX) MOV SI,0
;начальное значение индексного регистра MOV CX,10 ;число повторений цикла L: ADD
AX,X[SI] ;AX:=AX+X[i] ADD SI,2 ;SI:=SI+2 LOOP L ;CX:=CX-1; if CX0 then goto L
MOV S,AX ;S:=AX Помимо команды LOOP есть еще две "циклические" команды - LOOPZ
иLOOPNZ (они имеют синонимичные названия LOOPE и LOOPNE), которых кромерегистра
CX проверяют еще и флаг нуля ZF; например, команда LOOPZ "вы-ходит" из цикла,
если CX=0 или ZF=1. Эту команду можно, например, ис-пользовать при поиске в массиве
первого нулевого элемента, где должнобыть предусмотрено два условия выхода
из цикла: либо будет найден ну-левой элемент (ZF=1, если перед LOOPZ поставить
команду сравнения оче-редного элемента с 0), либо будет исчерпан весь мсассив
(CX=0) Отметим, что все эти "циклические" команды реализуют короткий от-носительный
переход, как и команды условного перехода, поэтому их мож-но использовать
только для циклов с небольшим числом команд. В MASM есть еще две команды перехода
- CALL (переход с возвратом)и RET (возврат из подпрограммы), они рассматриваются
в 1.7. 1.6. СТРОКОВЫЕ ОПЕРАЦИИ В ПК под строкой понимается последовательность
соседних байтов илислов. В связи с этим все строковые команды имеют две разновидности
-для работы со строками из байтов (в мнемонику операций входит буква
B)и для работы со строками из слов (в мнемонику входит W). Имеются следующие операции
над строками: - пересылка элементов строк (в память, из памяти, память-память);
- сравнение двух строк; - просмотр строки с целью поиска элемента, равного
заданному. Каждая из этих операций выполняется только над одним элементомстроки,
однако одновременно происходит автоматическая настройка наследующий или
предыдущий элемент строки. Имеются специальные командыповторения (REP и др.), которые
заставляют следующую за ними строковуюкоманду многократно повторяться (до
2^16 раз), в связи с чем такая па-ра команд позволяет обработать всю строку,
причем намного быстрее, чемзапрограммированный цикл. Кроме того, строки можно просматривать
вперед (от их начала к кон-цу) и назад. Направление просмотра зависит
от флага направления DF,значение которого можно менять с помощью команд STD
(DF:=1) и CLD(DF:=0). При DF=0 все последующие строковые команды программы просмат-ривают
строки вперед, а при DF=1 - назад. В строковых командах операнды явно
не указываются, а подразумева-ются. Если команда работает с одной строкой, то
адрес очередного, об-рабатываемого сейчас элемента строки задается парой регистров
DS и SIили парой ES и DI, а если команда работает с двумя строками, то адресэлемента
одной из них определяется парой DS:SI, а адрес элемента дру-гой -
парой ES:DI. После выполнения операции значение регистра SIи/или DI увеличивается
(при DF=0) или уменьшается (при DF=1) на 1 (длябайтовых строк) или на 2 (для
строк из слов). Начальная установка всех этих регистров, а также флага DF должнабыть
выполнена до начала операции над строкой. Если сегментный регистрDS уже имеет
нужное значение, тогда загрузить регистр SI можно с по-мощью команды LEA SI,Если
же надо загрузить сразу оба регистра DS и SI, тогда можно вос-пользоваться
командой LDS SI,m32которая в регистр SI заносит первое слово, а в регистр DS
- второеслово из двойного слова, имеющего адреc m32 (таким образом, по адресуm32+2
должен храниться сегмент, а по адресу m32 - смещение начальногоили конечного
элемента строки). Начальную загрузку регистров ES и DIобычно осуществляют одной
командой LES DI,m32которая действует аналогично команде LDS. Перечислим вкратце
строковые команды ПК. Команда загрузки элемента строки в аккумулятор (LODSB
или LODSW)пересылает в регистр AL или AX очередной элемент строки, на которыйуказывает
пара DS:SI, после чего увеличивает (при DF=0) или уменьшает(при DF=1)
регистр SI на 1 или 2. Команда записи аккумулятора в строку (STOSB или STOSW)
заносит со-держимое регистра AL или AX в тот элемент строки, на который указываетпара
ES:DI, после чего изменяет регистр DI на 1 или 2. Команда пересылки строк
(MOVSB или MOVSW) считывает элемент первойстроки, определяемый парой DS:SI,
в элемент второй строки, определяе-мый парой ES:DI, после чего одновременно меняет
регистры SI и DI. Команда сравнения строк (CMPSB или CMPSW) сравнивает очередныеэлементы
строк, указываемые парами DS:SI и ES:DI, и результат сравне-ния (равно,
меньше и т.п.) фиксирует в флагах, после чего меняет реги-стры SI и DI.
Команда сканирования строки (SCASB или SCASW) сравнивает элементстроки, адрес которого
задается парой ES:DI, со значением регистра ALили AX и результат сравнения
фиксирует в флагах, после чего меняет со-держимое регистра DI. Перед любой строковой
командой можно поставить одну из двух ко-манд, называемых "префиксами
повторения", которая заставит многократноповториться эту строковую команду. Число
повторений (обычно это длинастроки) должно быть указано в регистре CX. Префикс
повторения REPZ(синонимы - REPE, REP) сначала заносит 1 в флаг нуля ZF, после
чего,постоянно уменьшая CX на 1, заставляет повторяться следующую за нимстроковую
команду до тех пор, пока в CX не окажется 0 или пока флаг ZFне изменит свое
значение на 0. Другой префикс повторения REPNZ (сино-ним - REPNE) действует аналогично,
но только вначале устанавливаетфлаг ZF в 0, а при при изменении его
на 1 прекращает повторение стро-ковой команды. Пример. Пусть надо переписать 10000
байтов начиная с адреса A вдругое место памяти начиная с адреса B. Если оба
этих имени относятсяк сегменту данных, на начало которого указывает регистр DS,
тогда этупересылку можно сделать так: CLD ;DF:=0 (просмотр строки вперед) MOV
CX,1000 ;CX - число повторений MOV AX,DS MOV ES,AX ;ES:=DS LEA SI,A ;ES:SI - "откуда"
LEA DI,B ;DS:DI - "куда" REP MOVSB ;пересылка CX байтов 1.7. СТЕК. ПОДПРОГРАММЫ.
1.7.1 Стек В ПК имеются специальные команды работы со стеком, т.е. областьюпамяти,
доступ к элементам которой осуществляется по принципу "послед-ним
записан - первым считан". Но для того, чтобы можно было воспользо-ваться этими
командами, необходимо соблюдение ряда условий. Под стек можно отвести область
в любом месте памяти. Размер ее мо-жет быть любым, но не должен превосходить 64Кб,
а ее начальный адресдолжен быть кратным 16. Другими словами, эта область должна
быть сег-ментом памяти; он называется сегментом стека. Начало этого сегмента(первые
16 битов начального адреса) должно обязательно храниться всегментном
регистре SS. Хранимые в стеке элементы могут иметь любой размер, однако следуетучитывать,
что в ПК имеются команды записи в стек и чтения из неготолько слов.
Поэтому для записи байта в стек его надо предварительнорасширить до слова, а запись
или чтение двойных слов осуществляютсяпарой команд. В ПК принято заполнять
стек снизу вверх, от больших адресов кменьшим: первый элемент записывается в конец
области, отведенной подстек, второй элемент - в предыдущую ячейку области
и т.д. Считываетсявсегда элемент, записанный в стек последним. В связи с этим
нижняяграница стека всегда фиксирована, а верхняя - меняется. Слово памяти,в котором
находится элемент стека, записанный последним, называетсявершиной стека.
Адрес вершины, отсчитанный от начала сегмента стека,обязан находиться в указателе
стека - регистре SP. Таким образом, аб-солютный адрес вершины стека определяется
парой SS:SP. ----- ----- ----- SS:SP | | SS:SP | | SS:SP | | | ----- запись
| ----- чтение | ----- | | | =======> ---->| b | =======> | | | | ----- в стек
----- из стека | ----- ----->| a | | a | ---->| a | ----- ----- ----- Значение
0 в регистре SP свидетельствует о том, что стек полностьюзаполнен (его вершина
"дошла" до начала области стека). Поэтому дляконтроля за переполнением стека надо
перед новой записью в стек прове-рять условие SP=0 (сам ПК этого не делает).
Для пустого стека значениеSP должно равняться размеру стека, т.е. пара SS:SP
должна указывать набайт, следующий за последним байтом области стека. Контроль
за чтениемиз пустого стека, если надо, обязана делать сама программа. Начальная
установка регистров SS и SP может быть произведена в са-мой программе, однако
в MASM предусмотрена возможность автоматическойзагрузки этих регистров. Если в
директиве SEGMENT, начинающей описаниесегмента стека, указать параметр STACK,
тогда ассемблер (точнее, за-грузчик) перед тем, как передать управление на первую
команду машиннойпрограммы, загрузит в регистры SS и SP нужные значения. Например,
еслив программе сегмент стека описан следующим образом: ST SEGMENT STACK
DB 256 DUP(?) ;размер стека - 256 байтов ST ENDSи если под этот сегмент была выделена
область памяти начиная с абсо-лютного адреса 12340h, тогда к началу выполнения
программы в регистреSS окажется величина 1234h, а в регистре SP - величина
100h (=256).Отметим, что эти значения соответствуют пустому стеку. 1.7.2 Основные
стековые команды При соблюдении указанных требований в программе можно использоватькоманды,
предназначенные для работы со стеком. Основными из них явля-ются
следующие. Запись слова в стек: PUSH opЗдесь op обозначает любой 16-битовый
регистр (в том числе и сегмент-ный) или адрес слова памяти. По этой команде значение
регистра SPуменьшается на 2 (вычитание происходит по модулю 2^16), после
чегоуказанное операндом слово записывается в cтек по адресу SS:SP. Чтение слова
из стека: POP opСлово, считанное из вершины стека, присваивается операнду op
(регист-ру, в том числе сегментному, но не CS, или слову памяти), после чегозначение
SP увеличивается на 2. Переход с возвратом: CALL opЭта команда записывает
адрес следующей за ней команды в стек и затемделает переход по адресу, определяемому
операндом op. Она используетсядля переходов на подпрограммы с запоминанием
в стеке адреса возврата. Имеются следующие разновидности этой команды (они аналогичны
вари-антам команды безусловного перехода JMP): - внутрисегментный относительный
длинный переход (op - непосредст-венный операнд размером в слово, а
в MASM - это метка из текущего сег-мента команд или имя близкой процедуры (см.
ниже)); в этом случае встек заносится только текущее значение счетчика команд IP,
т.е. смеще-ние следующей команды; - внутрисегментный абсолютный косвенный переход
(op - адрес словапамяти, в которой находится адрес (смещение) той команды,
на которую ибудет сделан переход); и здесь в стек записывается только смещение
ад-реса возврата; - межсегментный абсолютный прямой переход (op - непосредственныйоперанд
вида seg:ofs, а в MASM - это FAR PTR или имя дальнейпроцедуры (см.
ниже)); здесь в стек заносится текущие значение регист-ров CS и IP (первым в стек
записывается содержимое CS), т.е. абсолют-ный адрес возврата, после чего меняются
регистры CS и IP; - межсегментный абсолютный косвенный переход (op - адрес
двойногослова, в котором находится пара seg:ofs, задающая абсолютный адрес пе-рехода);
и здесь в стеке спасается содержимое регистров CS и IP. Переход (возврат)
по адресу из стека: RET opИз стека считывается адрес и по нему производится
переход. Если указаноперанд (а это должно быть неотрицательное число), то после
чтения ад-реса стек еще очищается на это число байтов (к SP добавляется это
чис-ло). Команда используется для возврата из подпрограммы по адресу, за-писанному
в стек по команде CALL при вызове подпрограммы, и одновре-менной очистки стека
от параметров, которые основная программа занеслав стек перед обращением к
подпрограмме. Команда RET имеет две разновидности (хотя в MASM они записываютсяи
одинаково): в одном случае из стека считывается только одно слово -смещение
адреса возврата, а во втором - из стека считывается пара seg:ofs, указывающая абсолютный
адрес возврата. Как ассемблер определяет,какой из этих двух случаев имеет
место, объяснено ниже. В ПК стек в основном используется для организации подпрограмм
ипрерываний. Подпрограммы рассматриваются ниже, а прерывания - в главе3.
Однако, даже если программе не нужен стек, она все равно должна от-вести под
него место. Дело в том, что стеком будет неявно пользоватьсяоперационная система
при обработке прерываний, которые возникают (нап-ример, при нажатии клавиш
на клавиатуре) в то время, когда выполняетсяпрограмма. Для нужд ОС рекомендуется
выделять в стеке 64 байта. 1.7.3 Подпрограммы Типичная схема огранизации подпрограмм,
обычно используемая транс-ляторами с языков высокого уровня для реализации
процедур и функций (вчастности, рекурсивных), следующая. При обращении к
подпрограмме в стек заносятся параметры для нее иадрес возрата, после чего делается
переход на ее начало: PUSH param1 ;запись 1-го параметра в стек ... PUSH paramk
;запись последнего (k-го) параметра в стек CALL subr ;переход в возратом
на подпрограмму(Замечание: если необходимо вычислить параметр или если его размер
от-личен от слова, тогда для записи параметра в стек нужно, конечно, нес-колько
команд, а не одна.) Состояние стека после выполнения этих ко-манд обращения
к подпрограмме показано на рис. a | | |--------------| | | | лок.величины |0 -
перейти к sgn1 mov ax,-1 ;ax:=-1 sgn1: ret ;дальний возврат sign endp ... Возможный
пример обращения к этой процедуре: ;cx:=sign(var) mov ax,var call sign ;дальний
вызов mov cx,ax