Ассемблер для процессоров Intel Pentium
Шрифт:
Команда call может иметь один из перечисленных ниже форматов вызова:
– прямой ближний (в пределах текущего программного сегмента);
– прямой дальний (вызов процедуры, расположенной в другом программном сегменте);
– косвенный ближний (в пределах текущего программного сегмента с использованием переменной, содержащей адрес перехода);
– косвенный дальний (вызов процедуры, расположенной в другом программном сегменте, с использованием переменной, содержащей адрес перехода).
Тип адресации при вызове процедуры зависит от используемой модели памяти. Директива .model автоматически устанавливает атрибут near или far для вызываемых
Проанализируем более подробно форматы вызовов команды call. Если используется прямой ближний вызов, то команда call помещает в стек относительный адрес точки возврата в текущем программном сегменте и модифицирует указатель адресов команд EIP так, чтобы в нем содержался относительный адрес точки перехода в том же программном сегменте.
Требуемая для вычисления этого адреса величина смещения от точки возврата до точки перехода содержится в коде самой команды, занимающем 3 байта (код операции E8h плюс смещение к точке перехода).
Команда call прямого дальнего вызова помещает в стек два слова: вначале сегментный адрес текущего программного сегмента, затем относительный адрес точки возврата в этом программном сегменте. После этого выполняется модификация регистров EIP и CS: в EIP помещается относительный адрес точки перехода в том сегменте, куда осуществляется переход, а в CS – селектор адреса для этого сегмента.
Оба эти значения извлекаются из кода команды, занимающего 5 байт (код операции 9Ah, эффективный адрес вызываемой процедуры и селектор сегмента). Для указания прямого дальнего вызова используется директива far ptr, которая говорит компилятору и компоновщику, что вызов является дальним.
В листинге 6.3 показан фрагмент программного кода, демонстрирующий дальний вызов процедуры.
Листинг 6.3. Демонстрация дальних вызовов процедур (16-разрядная версия)
Процедуры subr1 и subr2 находятся в другом сегменте команд той же программы и при вызове выводят на экран строки s1 и s2. При выполнении команды call процессор помещает в стек сначала сегментный адрес вызывающей программы, а затем относительный адрес возврата, как показано на рис. 6.7.
Рис. 6.7. Содержимое стека после вызова дальней процедуры
Поскольку процедура объявлена дальней (атрибут far), то команда ret имеет код OCBh, отличный от кода аналогичной команды для вызова ближней процедуры (0C3h), и выполняется по-другому: из вершины стека извлекаются два слова и помещаются в регистры EIP и CS, передавая тем самым управление вызывающей программе из другого сегмента команд. Для команды возврата из дальней процедуры существует специальное мнемоническое обозначение retf.
Рассмотрим косвенный ближний вызов. В этом случае адрес процедуры содержится либо в ячейке памяти, либо в регистре. Это позволяет, как и в случае косвенного ближнего перехода, модифицировать адрес вызова, а также осуществлять вызов без использования метки по известному абсолютному адресу. Следующее 16-разрядное приложение иллюстрирует механизм косвенного вызова процедуры (листинг 6.4).
Листинг 6.4. Демонстрация косвенного вызова процедуры (16-разрядная версия)
Процедуры subr1 и subr2 с атрибутом near
находятся в том же сегменте, что и вызывающая программа, а их относительные адреса – в переменных addrl и addr2 в сегменте данных. Процедуры при вызове выводят соответствующие сообщения (строки s1 и s2) на экран.Косвенный ближний вызов позволяет использовать разнообразные способы адресации процедур:
В листинге 6.5 приведен исходный текст 16-разрядного приложения, демонстрирующий один из вариантов реализации косвенного ближнего вызова. Здесь для вычисления смещения (эффективного адреса) процедуры используются регистры s1 и ВХ, причем в регистре s1 содержится адрес таблицы tbl смещений подпрограмм, а регистр ВХ содержит индекс.
Листинг 6.5. Демонстрация косвенного ближнего вызова (16-разрядная версия)
Исходный текст программы несложен, хочу лишь обратить внимание на инструкцию
add BX, 2
Эта инструкция находится в цикле
Поскольку таблица tbl содержит смещения процедур в виде слов, то для перехода к следующему элементу таблицы содержимое ВХ увеличивается на 2. Результатом выполнения подпрограммы будет вывод на экран строк:
Последним мы рассмотрим косвенный дальний вызов. Его основное отличие от косвенного ближнего вызова состоит в том, что подпрограмма находится в другом сегменте, а в ячейке памяти содержится полный адрес подпрограммы, включая сегмент и смещение.
Пример косвенного дальнего вызова приведен в листинге 6.6. Это 16-разрядное приложение, в основу которого положен исходный текст предыдущего примера.
Листинг 6.6. Демонстрация косвенного дальнего вызова (16-разрядная версия)
Листинг 6.6 (продолжение)
Программа довольно сложная, поэтому остановимся на ней подробно.
Анализ процедуры начнем со структуры таблицы tbl. Эта таблица содержит дальние адреса трех процедур (subr1, subr2 и subr3), находящихся в трех разных сегментах кода (codel, code2 и code3). Каждый элемент таблицы представляет собой двойное слово. Младшее слово двухсловного элемента содержит смещение (эффективный адрес) процедуры, старшее – адрес сегмента программного кода, в котором данная процедура находится. Таким образом, в таблице tbl зарезервировано 12 байт памяти для адресов трех процедур.
Программа заполняет 4-байтовые ячейки памяти необходимой информацией так, как это делается, например, для процедуры subr2:
После заполнения таблицы нужной информацией основная программа (находящаяся в программном сегменте codeO) в цикле next, состоящем из трех итераций, выполняет дальние косвенные вызовы каждой из процедур:
Результатом работы программы является вывод следующих трех строк на экран:
Прежде чем закончить тему адресации процедур, хочу сделать некоторые замечания. Если вы работаете с 32-разрядными приложениями (используется директива .model flat), то понятия «дальний вызов» не существует. Приложение выполняется в едином линейном адресном пространстве размером вплоть до 4 Гбайт, где данные и код перемешаны, а сегментные регистры установлены в одно и то же значение. Все вызовы и команды переходов считаются ближними (атрибут near ptr). Для таких вызовов можно применять те же режимы, что и для ближних вызовов в 16-разрядных моделях памяти (прямой ближний и косвенный ближний), но использовать при этом 32-разрядные переменные и регистры.