
10.9. Директивы определения данных
Определение байтов, слов и двойных слов. Для начала мы изучим три директивы: DB (define byte), DW (define word), DD (define doubleword). Первые две нам уже знакомы по мини-Ассемблеру. Например,
DB 15, –3, 7 (в отличие от debug можно вводить и отрицательные числа)
Метки. Метка — это символическое имя адреса с данными или командой. Метка может состоять из симолов: A-Z, a-z, _, @, $, ?, 0-9. Метка не должна начинаться символами 0-9. Длина метки может составлять 255 символов, но Ассемблер различает только первые 32 символа. От директивы объявления данных метка отделяется пробелами, от команды — двоеточием. Метка с двоеточием может стоять в отдельной строке.
Использование меток. Рассмотрим пример.
num DW 12
……
mov ax, num ; поместить в регистр AX число 12
Проанализируем синтаксис команды mov ax, num. Числовое значение num — это адрес. Допустим, адрес (смещение) равен 4. Но в AX загружается не адрес num, а содержимое слова по этому адресу! В отладчике мы увидим команду mov ax, [0004]. Поэтому хотелось бы записать на языке Ассемблера эту команду иначе: mov ax, [num]. И MASM и TASM транслируют такую команду правльно. Более того, в TASM есть режим IDEAL, который требует именно такую форму записи команды: mov ax, [num]. Встретив команду mov ax, num в режиме IDEAL ассемблер TASM выдает предупреждение: Pointer expression needs brackets (Ссылка на адрес в памяти не заключена в квадратные скобки). Но сложилась традиция опускать квадратные скобки. Будем ей следовать.
А как загрузить адрес? Это можно сделать двумя способами. Один вам уже знаком: lea si, num. Второй способ — новый: mov si, OFFSET num. OFFSET — это функция времени ассемблирования. На этапе ассемблирования вычисляется смещение num и формируется код команды. В отладчике (на этапе выполнения) мы увидим: mov si, 0004. Директивы и функции Ассемблера рекомендуется набирать прописными буквами. Ассемблеру это безразлично, а читателю программы позволяет сразу видеть, что на каком этапе делается.
Цепочка символов. Определить цепочку символов можно двояко:
DB 'Hello'
DB "Hello"
т.е. в качестве ограничителей строки могут быть либо одинарные либо двойные кавычки. Но недопустимо эти кавычки смешивать: DB 'Hello".
К цепочке изображаемых символов можно добавить управляющие символы. Добавим в конце строки символы "возврат каретки" и "перевод строки":
DB "Hello", 0Dh, 0Ah, '$'
Если вместо 0Dh поместить Dh, то Ассемблер выдал бы сообщение об ошибке: Illegal use of register (неверное использование регистра). Dh было бы воспринято как имя регистра DH. Число должно предваряться десятичной цифрой.
Резервирование памяти. Если начальное значение выделяемой области памяти несущественно, то можно вместо конкретных чисел поместить вопросительные знаки:
DW ?, 6, ?, ? ; Зарезервировано четыре слова, определено второе.
В программе, работающей под управлением отладчика, эти вопросительные знаки заменяются нулями. Но если программа запускается автономно, то содержимое этих ячеек памяти может оказаться любым.
Оператор дублирования. Если некоторая последовательность данных многократно повторяется, то для сокаращения записи можно воспользоваться оператором дублирования DUP (duplicate — дублировать). Примеры:
DB 6 DUP (1) ; эквивалентно DB 1,1,1,1,1,1
DW 2 DUP (1,2,?) ; эквивалентно DW 1,2,?,1,2,?
Итак, перед оператором DUP ставится коэффициент повторения, отделенный от DUP не менее, чем одним пробелом. В скобках указывается последовательность, подлежащая дублированию. Возможны вложенные DUP:
DB 2 DUP( 5, 3 DUP(1,0),2) ; эквивалентно DB 5,1,0,1,0,1,0,2,5,1,0,1,0,1,0,2
Атрибутные операторы. Как вы помните, debug "не понимал" команду mov [300],5. Действительно, неясно, число 5 записывается в байт по адресу 300 или в слово по адресу 300? Разница существенная: байт с адресом 301 сохранит свое значение или обнулиться? Поэтому для уточнения действия команды нужно было использовать атрибутный опреатор: mov word ptr [300],5. Теперь рассмотрим пример на языке Ассемблера.
num DW 0FFFFh
……
mov num,5
Здесь атрибутный оператор не нужен. Ассемблер знает, что num — адрес слова, поэтому генерирует правильный код команды. После выполнения команды в байте с адресом num будет храниться число 5, а в байте с адресом num + 1 — нуль. Но как поступить, если нужно обратиться к байту слова? Вот здесь понадобится атрибутный опреатор: mov byte ptr num+1,5. Адрес num + 1 вычисляется на этапе ассемблирования. В результате: num DW 5FFh.
В этом примере можно было обойтись без атрибутного оператора, но тогда слову num надо было дать альтернативное имя:
numb LABEL BYTE ; LABEL — метка
num DW 0FFFFh
……
mov numb+1,5
Еще об атрибутном операторе. В debug была допустимой команда mov ah, [400]. Операнд-приемник — регистр, имеющий размер байта. Поэтому по адресу 400 выбирается байт. Теперь введем команду: mov ah, num. Последует сообщение об ошибке: Operand types do not match (Несоответствие типов операндов). Попытаемся исправить ошибку: mov byte ptr ah, num. Вновь последует то же самое сообщение об ошибке. Правильно так: mov ah, byte ptr num.
10.10. Подпрограммы
Подпрограммы в программах на языке Ассемблера имеют формат
имя PROC
<тело подпрограммы>
ret
имя ENDP
Вызов подпрограммы осуществляется командой call имя.
В зависимости от используемой модели памяти генерируются дальние или ближние вызовы и возвраты. Пока мы используем только малую модель памяти. Так как для этой модели программный код заключен в одном сегменте, то генерируются ближние вызовы и возвраты. Примеры подпрограмм появятся далее.
10.11. Командная строка
Обычно мы запускаем программу на выполнение, набирая ее имя в командной строке и заканчивая ввод нажатием ENTER. Но в командной строке можно вводить и дополнительную информацию, которая составляет так называемый "хвост командной строки". Пример: tasm.exe prim.asm. Здесь в хвосте командной строки мы указываем, какую программу нужно ассемблировать.
Хвост командной строки располагается в PSP начиная со смещения 80h. Рассмотрим пример. Пусть в командной строке введено:
D:\>prim.exe ab c
Посмотрим в отладчике, как выглядит хвост командной строки в PSP
Для этого вызовем отладчик так:
td prim.exe ab c
Перейдем в окно CPU (F10/View/CPU). Перейдем в панель данных (Shift+Tab). Отобразим данные, начиная со смещения 80h (Alt+F10/Goto/80). Мы увидим:
ds:0080 05 20 61 62 20 63 0D 00
Сегментная часть адреса находится в DS, т.к. выполнение программы еще не началось. 05 — количество символов в командной строке: пробел, 'a', 'b', пробел, 'c'. Строка заканчивается управляющим символом 0D (в количество символов не входит). Теперь из программы можно обратиться к этой области памяти и извлечь из нее необходимую информацию.