ЯЗЫК МАКРОАССЕМБЛЕРА 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 - destination index, индекс приемника; 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 Сегментирование, базирование и индексирование адресов Поскольку сегментирование адресов - это разновидность модификации адресов, то в ПК адрес, указываемый в команде, в общем случае модифи- цируется по трех рег