4.9.2. Директива INCLUDE
Встречая директиву INCLUDE, ассемблер весь текст, хранящийся в указанном файле, подставит в программу вместо этой директивы. В общем случае обращение к директиве INCLUDE (включить) имеет следующий вид:
INCLUDE <�имя файла>
Директиву INCLUDE можно указывать любое число раз и в любых местах программы. В ней можно указать любой файл, причем название файла записывается по правилам операционной системы.
Директива полезна, когда в разных программах используется один и тот же фрагмент текста; чтобы не выписывать этот фрагмент в каждой программе заново, его записывают в какой-то файл, а затем подключают к программам с помощью данной директивы.
4.9.3. Структура EXE- и COM- программы
Следует отметить, что какой-либо фиксированной структуры программы на языке Ассемблера нет, но для небольших EXE-программ с трехсегментной структурой типична следующая структура:
;Определение сегмента стека
STAK SEGMENT STACK
DB 256 DUP (?)
STAK ENDS
;Определение сегмента данных
DATA SEGMENT
SYMB DB '#' ;Описание переменной с именем SYMB
;типа Byte и со значением «#»
. . . ;Определение других переменных
DATA ENDS
;Определение сегмента кода
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STAK
;Определение подпрограммы
PROC1 PROC
. . . ;Текст подпрограммы
PROC1 ENDP
START: ;Точка входа в программу START
XOR AX,AX
MOV BX,data ;Обязательная инициализация
MOV DS,BX ;регистра DS в начале программы
CALL PROC1 ;Пример вызова подпрограммы
. . . ;Текст программы
MOV AH,4CH ;Операторы завершения программы
INT 21H
CODE ENDS
END START
В общем случае, взаимное расположение сегментов программы может быть любым, но чтобы сократить в командах число ссылок вперед и избежать проблем с префиксами для них, рекомендуется сегмент команд размешать в конце текста программы.
Сегмент стека в приведенной структуре описан с параметром STACK, поэтому в самой программе нет необходимости загружать сегментный регистр SS. Сегментный регистр CS тоже нет необходимости загружать, как уже отмечалось ранее. В связи с этим в начале программы загружается лишь регистр DS.
Относительно сегмента стека нужно сделать следующее замечание. Даже если сама программа не использует стек, описывать в программе сегмент стека все равно надо. Дело в том, что стек программы использует операционная система при обработке прерываний.
Необходимо также заметить, что все предложения, по которым ассемблер заносит что-либо в формируемую программу (инструкции, директивы определения данных и т.д.) обязательно должны входить в какой-либо программный сегмент, размещать их вне программных сегментов нельзя. Исключение составляют директивы информационного характера, например, директивы EQU, директивы описания типов структур и записей. Кроме того, не рекомендуется размещать в сегменте данных инструкции, а в сегменте кода – описание переменных из-за возникающих в этом случае проблем с сегментированием.
Типичная структура COM-программы аналогична структуре EXE-программы, с той лишь разницей, что, как уже отмечалось выше, COM-программа содержит лишь один сегмент – сегмент кода, который включает в себя инструкции процессора, директивы и описания переменных.
;Определение сегмента кода
CODE SEGMENT
ASSUME CS:CODE,DS:CODE,SS:CODE
ORG 100H ;Начало необходимое для COM-программы
;Определение подпрограммы
PROC1 PROC
. . . ;Текст подпрограммы
PROC1 ENDP
START:
. . . ;Текст программы
MOV AH,4CH ;Операторы завершения программы
INT 21H
;===== Data =====
BUF DB 6 ;Определение переменной типа Byte
. . . ;Определение других переменных
CODE ENDS
END START
4.10. Модификация адресов
Чаще всего в инструкциях процессора указываются не только точные адреса (имена) ячеек памяти, но и некоторые регистры в квадратных скобках, например MOV AX,A[BX]. В этом случае команда работает с, так называемым, исполнительным адресом который вычисляется по формуле AИСП=(A+[BX]) mod 216, где [BX] обозначает содержимое регистра BX. То есть процессор, прежде чем выполнить команду, прибавит к адресу А, указанному в команде (см. пример), текущее содержимое BX, получит некоторый новый адрес и из ячейки с этим адресом возьмет второй операнд. Если в результате суммирования получилась сумма большая 65535, то от нее берутся только последние 16 бит (на это указывает mod в приведенной формуле).
Подобная замена адреса из команды на исполнительный адрес называется модификацией адреса, а регистр, участвующий в модификации – регистром-модификатором. В качестве регистра-модификатора можно использовать не любой регистр, а лишь один из следующих: BX, BP, SI и DI.
Модификация адреса очень широко используется в ассемблерных программах для реализации переменных с индексами (массивов).
4.11. Сегментные регистры по умолчанию
Анализ реальных программ на языке Ассемблера, позволяет сделать вывод, что в них, в большинстве случаев, указываются адреса лишь из трех областей памяти – сегмента кода, сегмента данных и сегмента стека. Например, в командах перехода всегда указываются адреса других команд, т.е. ссылки на сегмент кода. В командах работающих со стеком указываются адреса из сегмента стека. В остальных же командах (пересылках, арифметических и т.д.) указываются, как правило, адреса из сегмента данных. С учетом этой особенности реальных программ принят ряд соглашений, которые позволяют во многих командах не указывать явно сегментные регистры, а подразумевать их по умолчанию. Для этого необходимо, чтобы начальные адреса сегментов памяти находились в определенных регистрах, а именно: регистр CS должен указывать на начало сегмента кода, регистр DS – на начало сегмента данных, а регистр SS – на начало сегмента стека. Если эти правила соблюдены, то справедливо следующее:
адреса переходов всегда сегментируются по регистру CS;
во всех остальных инструкциях: если адрес в команде не модифицируется или если он модифицируется, но среди модификаторов нет регистра BP, то этот адрес считается ссылкой в сегмент данных и сегментируется по регистру DS; если адрес модифицируется по регистру BP, то он считается ссылкой в сегмент стека и поэтому по умолчанию сегментируется по регистру SS.
В случае если приходится работать с сегментами памяти, отличными от сегментов кода, стека или данных, в командах, содержащих ссылку на эти сегменты, необходимо явно указывать сегментный регистр.
5. Команды пересылки
5.1. Команда MOV
Команда MOV – основная команда пересылки данных, которая пересылает один байт или слово данных из памяти в регистр, из регистра в память или из регистра в регистр. Команда MOV может также занести число (непосредственный операнд) в регистр или память. В действительности команда MOV это целое семейство машинных команд микропроцессора. На приведенном ниже рисунке представлены различные способы, которыми в микропроцессоре можно пересылать данные из одного места в другое. Каждый прямоугольник означает здесь регистр или ячейку памяти. Стрелки показывают пути пересылки данных, которые допускает микропроцессор. Необходимо также помнить, что все команды микропроцессора могут указывать только один операнд памяти.
Из рисунка видно, что запрещены пересылки из одной ячейки памяти в другую, из одного сегментного регистра в другой, запись непосредственного операнда в память. Это обусловлено тем, что в персональном компьютере отсутствуют соответствующие машинные команды. Если по алгоритму необходимо произвести одно из таких действий, то оно обычно реализуется в две команды, пересылкой через какой-нибудь несегментный регистр. Кроме того, командой MOV нельзя менять содержимое сегментного регистра CS. Это связано с тем, что регистровая пара CS:IP определяет адрес следующей выполняемой команды, поэтому изменение любого из этих регистров есть ничто иное, как операция перехода. Команда же MOV не реализует переход.
Примеры использования команды пересылки:
MOV Data,DI
MOV BX,CX
MOV DI,Index
MOV Start_Seg,DS
MOV ES,Buffer
MOV Days,356
MOV DI,0
5.2. Команда обмена данных XCHG
Команда XCHG меняет местами содержимое двух операндов. Порядок следования операндов не имеет значения. В качестве операндов могут выступать регистры (кроме сегментных) и ячейки памяти.
Примеры использования команды XCHG:
XCHG BL,BH
XCHG DH,Char
XCHG AX,BX
5.3. Команды загрузки полного указателя LDS и LES
Эти команды загружают полный указатель из памяти и записывают его в выбранную пару «сегментный регистр : регистр». При этом первое слово из адресуемой памяти загружается в регистр первого операнда, второе в регистр DS, если выполняется команда LDS, или в регистр ES если выполняется команда LES.
Примеры использования команд:
LDS BX,[BP+4]
LES DI,TablePtr
5.4. Команда перекодировки XLAT
Команда XLAT заменяет содержимое регистра AL байтом из таблицы перекодировки (максимальная длинна – 256 байт), начальный адрес которой относительно сегмента DS находится в регистре BX.
Алгоритм выполнения команды XLAT состоит из двух этапов:
содержимое регистра AL прибавляется к содержимому регистра BX;
полученный результат рассматривается как смещение относительно регистра DS. По данному адресу выбирается байт и помещается в регистр AL.
XLAT всегда использует в качестве смещения начала таблицы содержимое регистра BX, поэтому перед выполнением команды необходимо поместить в BX смещение таблицы.
Пример использования команды XLAT:
MOV BX,OFFSET Talbe
MOV AL,2
XLAT
...
Table DB ‘abcde’
5.5. Команды работы со стеком
Как уже было указано ранее, процессор адресует стек с помощью регистровой пары SS:SP. Помещение объектов в стек приводит к автоматическому декременту указателя стека, а извлечение – к инкременту, т.е. он «растет» в сторону меньших адресов памяти.
Для сохранения и восстановления различных 16-битовых данных в стеке используются команды PUSH (протолкнуть) и POP (вытолкнуть). За кодами операций PUSH и POP следует операнд, который необходимо поместить (извлечь) в (из) стек. В качестве операнда может выступать регистр или ячейка памяти, которую можно адресовать, используя известные способы адресации.
Замечание: Команда POP CS недопустима (восстановление из стека в регистр CS осуществляется по команде RET).
Для помещения в стек и извлечения из стека регистра флагов используются специальные команды PUSHF и POPF соответственно.
Стек удобен для передачи информации в подпрограммы и из них. Для этого подпрограмма может использовать BP как указатель на область стека. Ниже приведен фрагмент программы, демонстрирующий использование BP для доступа к параметрам, переданным через стек.
CODE SEGMENT
...
PROC1 PROC
MOV BP,SP ;загрузка в BP текущего адреса стека
MOV BX,[BP+4];выборка из стека 1 параметра (ca)
...
MOV BX,[BP+2];выборка из стека 2 параметра (ll)
...
RET 4 ;Возврат с удалением 4 слов из стека
PROC1 ENDP
START:
...
MOV AX,’ca’ ;Загрузка в AX символов
MOV CX,’ll’ ;Загрузка в CX символов
PUSH AX ;Сохранение AX в стек
PUSH CX ;Сохранение CX в стек
CALL PROC1
...
CODE ENDS
5.6. Команды ввода-вывода
Все устройства ЭВМ принято делить на внутренние (центральный процессор ЦП, оперативная память ОП) и внешние (внешняя память, клавиатура, дисплей и т. д.). Под вводом-выводом понимается обмен информацией между ЦП и любым внешним устройством. В ЭВМ передача информации между ЦП и внешним устройством, как правило, осуществляется через порты. Порт – некоторый регистр размером в байт, находящийся вне ЦП (два соседних порта могут рассматриваться как порт размером в слово). Обращение к портам происходит по номерам. Все порты нумеруются от 0 до 0FFFFh. С каждым внешним устройством связан свой порт или несколько портов их адреса заранее известны.
Запись и чтение порта осуществляется при помощи следующих команд:
Чтение (ввод): IN AL, n или IN AX, n
Запись (вывод):OUT n, AL или OUT n, AX
Номер порта n в этих командах может быть задан либо непосредственно, либо регистром DX (IN AX,DX).
Сценарий ввода вывода через порты существенно зависит от специфики того внешнего устройства, с которым ведется обмен, но обычно ЦП связан с внешним устройством через два порта: первый – порт данных, второй – порт управления и достаточно типичной является следующая процедура обмена:
ЦП записывает в порт управления соответствующую команду, а порт данных – выводимые данные;
внешнее устройство, считав эту информацию, записывает в порт управления команду «занято» и начинает непосредственно вывод (например, печать);
ЦП переходит либо в режим ожидания, опрашивая в цикле порт управления, либо занимается другой работой – до тех пор пока в порте управления не сменится сигнал «занято»;
внешнее устройство заканчивает вывод и записывает в порт управления сигнал об успешном завершении или об ошибке;
ЦП анализирует полученную информацию и продолжает свою работу.
6. Арифметические команды
Все арифметические команды устанавливают флаги CF, AF, SF, ZF, OF и PF в зависимости от результат операции.
Двоичные числа могут иметь длину 8 и 16 бит. Значение старшего (самого левого бита) задает знак числа: 0 – положительное, 1 – отрицательное. Отрицательные числа представляются в так называемом дополнительном коде, в котором для получения отрицательного числа необходимо инвертировать все биты положительного числа и прибавить к нему 1. Пример:
Положительное:
|
24=18h=
|
00011000b
|
|
Инверсное:
|
|
11100111b
|
|
Отрицательное:
|
|
11101000b
|
=E8h=-24
|
Проверка:
|
24-24=0
|
00011000b
11101000b
(1)00000000b
|
|
6.1. Команды арифметического сложения ADD и ADC
Команда ADD выполнят целочисленное сложение двух операндов, представленных в двоичном коде. Результат помещается на место первого операнда, второй операнд не изменяется. Команда корректирует регистр флагов в соответствии с результатом сложения. Существуют две формы сложения: 8-битовое и 16-битовое. В различных формах сложения принимают участие различные регистры. Компилятор следит за тем, чтобы операнды соответствовали друг другу. На следующих рисунках иллюстрируются различные варианты команды ADD.
Команда сложения с переносом ADC – это та же команда ADD, за исключением того, что в сумму включается флаг переноса CF, который прибавляется к младшему биту результата. Для любой формы команды ADD существует аналогичная ей команда ADC. Команда ADC часто выполняется как часть многобайтной или многословной операции сложения.
Примеры использования команд ADD и ADC:
ADD AL,12h
ADD Count,1
ADC BX,4
ADD AX,BX
ADC Count,DI
|