Ассемблер для процессоров Intel Pentium
Шрифт:
Рассмотренные нами модификации команды jmp являются классическими для 16-разрядных приложений и берут свое начало от «времен MS-DOS», когда отдельный сегмент кода не мог использовать пространство памяти больше чем 64 Кбайта, а для создания больших программ требовалось определенным образом компоновать несколько сегментов кода и данных.
Любое современное приложение является 32-разрядным и оперирует с линейным пространством адресов размером до 4 Гбайт. При разработке ассемблерных программ, как упоминалось в главе 3, используется модель памяти flat, а это означает,
При запуске 32-разрядного приложения все сегментные регистры устанавливаются в одно и то же значение. Для программистов, работающих с программами в DOS, 32-разрядное Windows-приложение может напоминать СОМ-файл, поскольку в таком файле можно работать только со смещениями. В 32-разрядных приложениях все метки и переходы считаются ближними (near ptr) в диапазоне адресов 4 Гбайт.
Команду jmp можно использовать не только для безусловного перехода в сегменте программного кода, но и для организации ветвлений. Для этого можно применить один из ее форматов, показанных далее:
jmp reg16
jmp reg32
jmp word ptr [reg16]
jmp dword ptr [reg32]
Здесь reg16 (reg32) – один из 16– или 32-разрядных регистров. Для первых двух форматов команд из списка адрес, по которому передается управление, должен находиться в одном из этих регистров.
Если используется 32-разрядный регистр (reg32), то адрес команды, на которую передается управление, также является 32-разрядным. Этот формат команды jmp характерен для 32-разрядных Windows-приложений.
Последние два формата команды jmp используют механизм косвенной адресации, при этом регистр содержит адрес ячейки памяти, в которой находится адрес команды, получающей управление. Проиллюстрируем вышеизложенное примерами:
. . .
.code
. . .
L1:
xor EDX, EDX
. . .
lea ESI, L1
jmp ESI
. . .
В этом примере в регистр ESI помещается смещение метки L1, после чего с помощью команды jmp ESI управление передается на эту метку.
. . .
.data
label_offset DD LI
.code
. . .
L1:
xor EDX, EDX
lea ESI, label_offset
jmp dword ptr [ESI]
. . .
В этом примере в регистр ESI помещается адрес переменной label_offset, в то время как сама переменная label_offset содержит адрес метки L1. Команда jmp dword ptr [ESI] в этом случае передает управление на метку L1.
Как видно из примеров, использование в качестве операндов регистров или ячеек памяти придает команде безусловного перехода большую гибкость, чем применение прямого смещения, что позволяет создавать ветвления и переходы в программе. Далее мы рассмотрим несколько примеров таких ветвлений.
Следующее 16-разрядное приложение, исходный текст которого показан в листинге 5.1, выводит на экран строки s1 , s2 и s3.
Листинг 5.1. Вывод трех символьных строк на экран
.model small
.stack 100h
.data
s1 DB Odh, Oah,
s2 DB Odh, Oah, «String 2$»
s3 DB Odh, Oah, «String 3$»
sarray label word ; массив, в котором хранятся адреса строк
DW s1 ; s1 иs2
DW s2
DW s3
num DW 0 ; индекс в адресе перехода команды jmp
label_array label word ; массив адресов меток
DW LI ; адрес метки LI
DW L2 ; адрес метки L2
DW L3 ; адрес метки L3
.code
start:
mov AX, @data
mov DS, AX
mov ES, AX
;
mov CX, 3 ; счетчик цикла -> CX
lea DI, label_array ; адрес массива меток
next:
mov s1 , DI
mov BX, num ; индекс перехода -> BX
shl BX, 1 ; умножить на 2 для правильной адресации
; меток в массиве label_array
add SI, BX ; сформировать адрес перехода
; для команды jmp
jmp word ptr [SI] ; перейти по адресу, находящемуся
; в регистре s1 (L1 или L2)
wedge:
inc num ; инкремент индекса переходов
loop next ; повторить цикл
;
L1: ; фрагмент кода при переходе на метку L1
lea DX, s1
mov АН, 9h
int 21h
jmp wedge ; вернуться в цикл
L2: ; фрагмент кода при переходе на метку L2
lea DX, s2
mov АН, 9h
int 21h
jmp wedge
L3: ; фрагмент кода при переходе на метку L3
lea DX, s3
mov AH, 9h
int 21h
;
mov AH, 1h ; ожидать ввода любого символа
int 21h
;
mov AX, 4c00h ; завершение программь
int 21h
end start
end
В этой программе продемонстрирована техника использования команды безусловного перехода jmp для организации трех ветвлений по адресам, определяемым метками LI, L2 и L3. Адрес перехода команды jmp формируется в регистре s1 следующим образом: вначале в s1 загружается базовый адрес массива меток label array, после чего к нему прибавляется смещение, кратное двум (метки LI – L3 имеют двухбайтовый адрес). Затем из сформированного таким образом адреса извлекается смещение одной из меток и выполняется переход на соответствующую ветвь программы. Например, для получения смещения метки L2 необходимо к адресу labelarray прибавить значение 2 (индекс num = 1). После выполнения программы на экране должны отобразиться строки:
String 1
String 2
String 3
Как видно из примера, команду безусловного перехода jmp можно применить для организации ветвлений в программе в зависимости от значения каких-либо параметров. Рассмотрим еще один, довольно сложный пример, в котором команда jmp используется для организации ветвлений и фактически моделируется логическая структура высокого уровня switch ... case языка C++ (или оператор case языка Pascal), обладающая очень большими вычислительными возможностями. В языке ассемблера довольно сложно реализовать такую структуру, и один из вариантов реализации, который мы рассмотрим, базируется на использовании команды jmp.