
- •Юрий Магда Ассемблер для процессоров Intel Pentium
- •Аннотация
- •Юрий Магда Ассемблер для процессоров Intel Pentium Введение
- •Структура книги
- •От издательства
- •Глава 1 Базовая архитектура процессоров Intel x86
- •Глава 2 Основы создания приложений на языке ассемблера
- •2.1. Ассемблирование исходного текста
- •2.2. Компоновка программ
- •Глава 3 Синтаксис языка ассемблера
- •3.1. Представление данных в компьютере
- •3.2. Первичные элементы языка ассемблера
- •3.3. Программная модель процессора Intel Pentium
- •Глава 4 Структура программы на языке ассемблера
- •4.1. Организация сегментов
- •4.2. Директивы управления сегментами и моделями памяти макроассемблера masm
- •4.3. Структура программ на ассемблере masm
- •Глава 5 Организация вычислительных циклов
- •5.1. Условные переходы и ветвления
- •5.2. Команда безусловного перехода jmp
- •5.3. Организация циклов
- •5.4. Оптимизация кода в процессорах Intel Pentium
- •Конец ознакомительного фрагмента.
5.2. Команда безусловного перехода jmp
При выполнении команды безусловного перехода jmp, независимо от состояния флагов процессора, программа продолжает выполняться с новой ветви. При этом новый адрес команды загружается в регистр‑счетчик команд EIP и выполнение программного кода продолжается с этого адреса.
В языке ассемблера новое место, откуда продолжается выполнение программы, в большинстве случаев обозначается меткой, которую процессор преобразует в исполнительный адрес. Если переход происходит в текущий сегмент, то смещение метки загружается непосредственно в регистр‑счетчик команд El Р. Если же метка находится в другом сегменте кода, то адрес сегмента дополнительно загружается в регистр CS. Для преобразования метки в вид сегментхмещение используются три формата команды jmp:
jmp short целевой_адрес
jmp near ptr целевой_адрес
jmp far ptr целевой_адрес
Здесь целевой_адрес – адрес команды, которая будет выполняться после перехода. Вот несколько примеров команды jmp:
jmp labell ; адрес команды, которая будет выполняться при
; переходе, находится в текущем сегменте команд
jmp near ptr labell ; адрес следующей команды находится
; в текущем сегменте команд
jmp short labell ; адрес команды, которая будет выполняться
; при переходе, находится в диапазоне
; ‑128 – +127
jmp far ptr labell ; адрес команды, которая будет
; выполняться при переходе, находится
; в другом сегменте
Рассмотрим операторы, указанные перед целевым адресом. Оператор short указывает на то, что нужно сделать переход на метку в диапазоне от ‑128 до +127, начиная от адреса следующей команды. В этом случае к содержимому регистра указателя команд EIP прибавляется 8‑разрядное целое число.
Оператор near ptr указывает на метку в текущем сегменте, при этом к регистру указателя команд EIP прибавляется 16‑разрядное смещение. Наконец, оператор far ptr указывает, что необходимо сделать переход на метку в другом сегменте. В этом случае сегментная часть адреса метки загружается в регистр CS, a смещение – в El Р.
Рассмотренные нами модификации команды jmp являются классическими для 16‑разрядных приложений и берут свое начало от «времен MS‑DOS», когда отдельный сегмент кода не мог использовать пространство памяти больше чем 64 Кбайта, а для создания больших программ требовалось определенным образом компоновать несколько сегментов кода и данных.
Любое современное приложение является 32‑разрядным и оперирует с линейным пространством адресов размером до 4 Гбайт. При разработке ассемблерных программ, как упоминалось в главе 3, используется модель памяти flat, а это означает, что программа занимает непрерывную область адресов, в которой размещаются данные и код. По этой причине любые команды адресуются 32‑разрядным смещением в пространстве адресов программы.
При запуске 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, «String 1$»
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.
Пример представляет собой 32‑разрядную процедуру (она называется easel). В качестве входного параметра процедура принимает целое число из диапазона 0‑2, а в регистре ЕАХ возвращает адрес строки, соответствующий значению параметра. Принципы организации процедур мы рассмотрим в следующих главах, сейчас же акцентируем наше внимание на работе программного кода процедуры easel, не вникая в детали ее взаимодействия с другими частями программы.
Для извлечения единственного параметра используется регистр EBP, a сам параметр для выполнения дальнейших манипуляций помещается в регистр ЕВХ. Исходный текст процедуры представлен в листинге 5.2.
Листинг 5.2. Ассемблерный аналог конструкции case
.686
.model flat
option casemap: none
.data
s1 DB «String 1», 0
s2 DB «String 2», 0
s3 DB «String 3», 0
err DB «Incorrect parameter!», 0
label_array label dword ; массив меток, в котором будут
; находиться смещения
; меток LI, L2 и L3
DD 3 DUP (?)
.code
_case_l proc
push EBP mov EBP, ESP
mov EBX, dword ptr [EBP+8] ; извлекаем параметр (номер строки)
; и сохраняем его в регистре ЕВХ
lea ESI, label_array ; адрес массива меток ‑> ESI
mov [ESI], offset LI ; заполняем массив меток смещениями
mov [ESI+4], offset L2 ; меток LI, L2 и L3 mov [ESI+8], offset L3
lea EAX, err_exit ; сохраняем в регистре ЕАХ смещение
; метки для выхода из процедурь
; в случае ошибки
shl ЕВХ, 2 ; поскольку для адресации
; используются двойные слова,
; умножаем номер строки на 4
сmp ЕВХ, 8 ; значение учетверенного параметра
; не должно превышать 8 (номер строки
; лежит в диапазоне 0‑2)
jle next1 ; верхнее значение меньше 8? Если
; да, следующая проверка
jmp EAX ; нет, параметр превышает значение 2,
; выйти из процедуры с ошибкой
next1 :
cmp EBX, 0 ; параметр не является отрицательным
; числом? Если
jge get_string ; нет, продолжить выполнение
; процедуры
jmp EAX ; да, параметр вне диапазона, выйти
; с ошибкой
get_string: ; параметр находится в нужном
; диапазоне, получить адрес
; соответствующей строки и выйти из
; процедуры
cmovge EAX, [ESI][EBX]
jmp EAX
L1: ; сюда передается управление при
; значении входного параметра,
; равном 0
lea EAX, s1 ; адрес строки s1 ‑> EAX
jmp exit ; выход из процедурь
L2: ; сюда передается управление при
; значении входного параметра,
; равном 1
lea EAX, s2 ; адрес строки s2 ‑> EAX
jmp exit ; выход из процедурь
L3: ; сюда передается управление при
; значении входного параметра,
; равном 2
lea EAX, s3 ; адрес строки s3 ‑> EAX
jmp exit ; выход из процедурь
err_exit: ; сюда передается управление
; при возникновении ошибки
lea EAX, err ; адрес сообщения об ошибке ‑> ЕАХ
exit:
pop EBP
ret
_case_l endp
end
Анализ работы процедуры начнем со строк
lea ESI, label_array
mov [ESI], offset L1
mov [ESI+4], offset L2
mov [ESI+8], offset L3
Как и в предыдущем примере, вначале заполняем массив меток смещениями используемых ветвей программы. Поскольку 32‑разрядные приложения работают со смещениями, равными двойному слову, то наш массив labelarray состоит из трех двойных слов, в которых и сохраняются смещения меток LI, L2 и L3. Все эти действия и выполняют четыре команды, показанные выше.
Следующая команда помещает в регистр ЕАХ смещение метки, куда должно передаваться управление в случае ошибки:
lea EAX, err_exit
Для передачи управления в нашей процедуре используется команда
jmp EAX
Она принимает в качестве операнда регистр (в данном случае – ЕАХ), содержащий смещение команды, куда передается управление.
С помощью следующей команды устанавливается смещение одной из меток (LI, L2 или L3), в которую должно передаваться управление при корректном значении параметра процедуры:
sh1 EBX, 2
Фрагмент программного кода, в котором выполняется проверка параметра на принадлежность диапазону 0‑2, думаю, понятен и в объяснениях не нуждается. Если полученный параметр корректен, то выполняется команда
cmovge EAX, [ESI][EBX]
Остановимся на работе этой инструкции ассемблера более подробно. Описание группы команд, к которой принадлежит cmovge, приводится далее в этой главе, но в нашем случае эта инструкция выполняет две функции:
– анализирует результат предыдущей операции (флаг SF);
– если SF = 1, то в регистр ЕАХ помещается смещение одной из меток (LI, L2 или L3). Само смещение находится по адресу, равному сумме адресов массива label_агтау (регистр ESI) и индекса строки (регистр ЕВХ).
Подобную процедуру при желании можно усовершенствовать и использовать для организации ветвлений в программах на ассемблере.