Программирование на языке Ассемблер. Учебное пособие
.pdfпроцессором |
для |
этого |
физического |
адреса: |
100 16 + 30 + 70 + 25 = 1725. |
|
|
||
По умолчанию сегментный регистр DS используется для всех способов адресации, кроме способа, который используется с регистром BP. В последнем случае используют сегментный регистр SS.
Программист может формировать все имеющие силу комбинации, взяв по одному пункту из каждого столбца, либо пропустить какой-либо столбец и ничего из него не взять. Как можно заметить, BX и BP никогда не идут вместе. SI и DI также не могут быть вместе. Здесь приведен пример имеющего силу способа адресации: [BX + 5].
Значение в сегментном регистре (CS, DS, SS, ES) называется «segment» (сегмент), а значение в регистре общего назначения (BX, SI, DI, BP) называется «offset» (смещение).
Если DS содержит значение 1234h, а SI содержит значение 7890h, то это можно записать как 1234:7890. Физиче-
ский адрес будет таким: 1234h 10h + 7890h = 19BD0h.
Чтобы указать компилятору тип данных, необходимо использовать следующие префиксы:
–BYTE PTR – для байта;
–WORD PTR – для слова (два байта). Например:
BYTE PTR [BX] ; доступ к байту.
или
WORD PTR [BX] |
; доступ к слову. |
|
Эмулятор процессора Intel 8086 (Emu8086) поддержива- |
||
ет короткие префиксы: |
|
|
b. – для BYTE |
PTR; |
|
w. – для WORD |
PTR. |
|
Иногда компилятор может вычислить тип данных автоматически, но лучше задавать типы данных явно.
11
2.1. Команда MOV
Команда MOV предназначена для записи данных в регистры и выполняется следующим образом:
Копирует второй операнд (источник) в первый опе-
ранд (приемник).
Операнд-источник может быть непосредственным значением, регистром общего назначения или местоположением памяти.
Регистр-приемник может быть регистром общего назначения или местоположением памяти.
Оба операнда должны иметь одинаковый размер байта или слова.
Следующие типы операндов поддерживаются:
MOV регистр, память
MOV память, регистр
MOV регистр, регистр
MOV память, непосредственное значение (число) MOV регистр, непосредственное значение
Здесь:
в качестве регистра могут быть: AX, BX, CX, DX, AH, AL,
BL, BH, CH, CL, DH, DL, DI, SI, BP, SP;
память: [BX], [BX+SI+7], переменная, и т. п.; непосредственноезначение:5, -24, 3Fh, 10001101b, и т.п. Команда MOV не может использоваться для установки
значений регистров CS и IP.
Приведем программу, которая демонстрирует использование команды MOV:
#MAKE_COM# |
; команда компилятору для |
|
; создания СОМ-файла |
ORG 100h |
; директива, необходимая |
|
; для COM-программы |
MOV AX, 0B800h |
; установить AX в |
|
; шестнадцатиричное значение |
|
; B800h |
12
MOV DS, AX |
; копировать значение из |
|
|
; AX в DS |
|
MOV CL, 'A' |
; установить в CL ASCII-код |
|
|
; символа 'A', т.е. 41h |
|
MOV CH, 01011111b |
; установить CH в |
|
|
|
; двоичное значение |
MOV BX, 15Eh |
; установить BX в 15Eh |
|
MOV [BX], CX |
; копировать содержимое из |
|
|
; CX в память с адресом |
|
|
; B800:015E |
|
RET |
; вернуться в операционную |
|
|
; систему |
|
Вы можете ввести вышеописанную программу в редактор кода Emu8086, и нажать кнопку [Compile and Emulate] (или на-
жать клавишу F5 на клавиатуре). Окно эмулятора должно открыться с загруженной программой. Щелкните кнопку [Single Step](пошаговыйрежим)инаблюдайтезасодержимымрегистров.
В Emu8086 – точка с запятой (;) используется для комментариев. Все символы, которые следуют за «;» игнорируются компилятором.
Вы должны увидеть нечто подобное (рис. 3), когда программа закончит свою работу.
Рис. 3. Результат работы программы
Фактически вышеописанная программа записывает данные непосредственно в видеопамять, так что MOV – это очень мощная инструкция.
13
3. ПЕРЕМЕННЫЕ
Переменные хранятся в памяти по определенным адресам. Программисту проще иметь дело с именами переменных, чем с адресами в памяти. Например, переменная с именем «var1» будет более понятна в коде программы, чем адрес 5A73:235B, особенно когда количество переменных велико.
Компилятор поддерживает два типа переменных: BYTE и WORD.
Синтаксис для объявления переменных:
имя DB значение
имя DW значение
Здесь:
DB – Define Byte – определяет байт; DW – Define Word – определяет слово;
имя – может быть любой комбинацией букв или цифр, но должно начинаться с буквы. Можно объявлять безымянные переменные, которые имеют адрес, но не имеют имени;
значение – может быть любой числовой величиной, которая представлена в какой-либо из поддерживаемых систем счисления (шестнадцатеричной, двоичной или десятичной). Значение может также быть символом «?» для не инициализированных переменных.
Как было написано выше, команда MOV используется для копирования значения из источника в приемник. Рассмотрим другой пример с командой MOV:
#MAKE_COM#
ORG 100h MOV AL, var1 MOV BX, var2
RET ; завершение программы
VAR1 DB 7 var2 DW 1234h
14
Введите вышеприведенный код в редактор кода Emu8086 и нажмите клавишу F5, чтобы откомпилировать и загрузить этот код в эмулятор. Вы увидите примерно такую картину (рис. 4):
Рис. 4. Результат работы программы
Вы можете заметить команды (рис. 4), похожие на те, что используются в примере. Только переменные заменены фактическими местоположениями в памяти. Когда компилятор создает машинный код, он автоматически заменяет имена всех переменных их смещениями. По умолчанию сегмент загружен в регистр DS (в COM-файлах значение регистра DS устанавливается таким же, что и значение в регистре CS – сегменте кода).
15
На рис. 4 в таблице памяти (memory) первый столбец – это смещение, второй столбец – это шестнадцатеричное значение, третий столбец – десятичное значение, а послед-
ний столбец – это символ ASCII, соответствующий данному числу.
Компилятор не чувствителен к регистру, поэтому «VAR1» и «var1» – это одно и то же.
Смещение переменной VAR1 – это 0108h, а полный ад-
рес – 0B56:0108.
Смещение переменной var2 – это 0109h, а полный адрес – 0B56:0109. Эта переменная имеет тип WORD, поэтому занимает 2 байта. Принято младший байт записывать по меньшему адресу, поэтому 34h размещается перед 12h.
Вы можете увидеть некоторые другие инструкции после команды RET. Это случается потому, что дизассемблер не знает, где начинаются данные. Он только обрабатывает значения в памяти и понимает их как имеющие силу инструкции процессора 8086 (мы изучим их позже).
Вы можете даже написать программу, используя только директиву DB:
#MAKE_COM#
ORG 100h
DB 0A0h
DB 08h
DB 01h
DB 8Bh
DB 1Eh
DB 09h
DB 01h
DB 0C3h
DB 7
DB 34h
DB 12h
16
Введите вышеприведенный код в редактор кода Emu8086 и нажмите клавишуF5, чтобы откомпилировать и загрузить этот код в эмулятор. Вы получите тот же самый дизассемблированный код и тот же самый результат работы программы!
Как вы можете догадаться, компилятор только преобразует исходный код программы в набор байтов. Этот набор байтов называется машинным кодом. Процессор обрабатывает машинный код и выполняет его.
ORG 100h – это директива компилятора, она указывает компилятору, как обрабатывать исходный код. Эта директива очень важна при работе с переменными. Она указывает компилятору, какой исполняемый файл будет загружаться в смещение (offset) 100h (256 байтов), так что компилятор должен вычислить правильный адрес для всех переменных, когда он размещает имена переменных с их смещениями. Директивы никогда не преобразуются в какой-либо реальный машинный код. Почему исполняемый файл загружается по смещению 100h? Операционная система хранит некоторые данные о программе в первых 256 байтах, начиная от CS (сегмента кода), такие как параметры командной строки и т. д. Все это справедливо только для COM-файлов, файлы EXE загружаются по смещению 0000, и обычно используют специальный сегмент для переменных.
3.1. Массивы
Массив можно рассматривать как цепочку переменных. Текстовая строка – это пример массива байтов, в котором каждый символ представлен значением ASCII-кода (0…255).
Некоторые примеры определения массивов: a DB 48h, 65h, 6Ch, 6Ch, 6Fh, 00h b DB 'Hello', 0
b – это точная копия массива a, когда компилятор видит строку, заключенную в кавычки, он автоматически преобразу-
17
ет ее в набор байтов. На рис. 5 представлен участок памяти, показывающий расположение массивов:
Рис.5. Расположение массивов в памяти
Значение любого элемента массива можно получить, используя квадратные скобки, например:
MOV AL, a[3]
Также можно использовать какой-либо из регистров BX, SI, DI, BP, например:
MOV SI, 3 MOV AL, a[SI]
Для объявления массивов также можно использовать оператор DUP. Синтаксис для оператора DUP:
количество DUP (значение(я))
Где:
количество – количество дубликатов (любая константа); значение – выражение, которое будет дублироваться
оператором DUP. Например:
c DB 5 DUP(9)
Аэто альтернативный способ объявления: c DB 9, 9, 9, 9, 9
Еще один пример: d DB 5 DUP(1, 2)
И альтернативный способ объявления: d DB 1, 2, 1, 2, 1, 2, 1, 2, 1, 2
18
Конечно, можно использовать DW вместо DB, если требуется хранить числа более 255 или менее –128. DW не может быть использован для объявления строк.
Оператор DUP не может содержать более 1020 знаков в качестве операнда (в последнем примере 13 знаков).
3.2. Получение адреса переменной
Существует команда LEA (Load Effective Address) и альтернативный оператор OFFSET. Как OFFSET, так и LEA могут быть использованы дляполучения смещенияадресапеременной.
Команда LEA более мощная, т. к. она также позволяет получить адрес индексированных переменных. Получение адреса переменной может быть очень полезно в различных ситуациях, например, если необходимо поместить параметр в процедуру.
Первый пример:
ORG 100h |
|
|
MOV AL, |
VAR1 |
; проверить значение VAR1, |
|
|
; поместив ее в AL |
LEA BX, |
VAR1 |
; записать адрес переменной |
|
|
; VAR1 в BX |
MOV BYTE PTR [BX], 44h ; изменить содержимое
|
; переменной VAR1 |
MOV AL, VAR1 |
; проверить значение VAR1, |
RET |
; поместив ее в AL |
|
|
VAR1 DB 22h |
|
END |
|
Во втором примере используется OFFSET вместо LEA:
ORG 100h |
|
MOV AL, VAR1 |
; проверить значение |
|
; VAR1, поместив ее в AL |
MOV BX, OFFSET VAR1 |
; записать адрес VAR1 в BX |
MOV BYTE PTR [BX], 44h |
; изменить содержимое VAR1 |
19
MOV AL, VAR1 |
; проверить значение VAR1, |
RET |
; поместив ее в AL |
|
|
VAR1 DB 22h |
|
END |
|
Оба примера функционально идентичны. Следующие строки:
LEA BX, VAR1
MOV BX, OFFSET VAR1
даже компилируются в одинаковый машинный код:
MOV BX, num
num – это 16-битовое значение смещения переменной. Необходимо учитывать, что только следующие регистры
могут использоваться внутри квадратных скобок (как указатели памяти): BX, SI, DI, BP.
3.3. Константы
Константы подобны переменным, но они существуют до того, как программа откомпилирована (ассемблирована). После определения константы ее значение не может быть изменено. Для определения константы используется директива EQU:
имя EQU < любое выражение >
Например: k EQU 5 MOV AX, k
Этот пример функционально идентичен коду:
MOV AX, 5
Можно наблюдать значения переменных во время выполнения программы при помощи меню «View» эмулятора, если выбрать пункт «Variables» (рис. 6).
Чтобы наблюдать массивы, необходимо выбрать переменную и установить свойство «Elements» – размер массива.
В Ассемблере нет строгих типов данных, поэтому любые переменные могут быть представлены как массив.
20
