Ассемблер для процессоров Intel Pentium
Шрифт:
Точкой входа в процедуру считается директива proc. В директиве proc после имени процедуры не ставится двоеточие, хотя имя считается меткой и указывает на первую команду процедуры. Имя процедуры можно указать в команде перехода, и тогда будет осуществлен переход на первую команду процедуры.
Директива proc может принимать один из двух параметров: near или far. Параметр near указывает на то, что процедура является ближней, a far указывает на то, что процедура дальняя. Если параметр отсутствует, то считается, что процедура имеет тип near (поэтому параметр near обычно и не указывается).
К ближней (near)
Следует отметить, что в языке ассемблера имена и метки, описанные в процедуре, должны быть уникальными и не должны совпадать с другими именами в программе. В языке ассемблера имеется возможность создавать вложенные процедуры, то есть процедуры внутри процедур, но особых преимуществ это не дает и используется относительно редко.
Можно обойтись и без явного определения процедуры, пометив первую строку программы некоторой меткой, как проиллюстрировано в следующем фрагменте программного кода:
В этом случае отсутствуют директивы ргос и endp и говорят, что подпрограмма (процедура) определяется неявно. Подобные записи процедур используются редко, поскольку значительно затрудняют анализ исходных текстов и отладку программ. Мы не будем применять такое определение процедур, а воспользуемся директивами ргос и endp, как было сказано в начале главы. Вызов процедуры выполняется с помощью команды call, которая передает управление процедуре, сохранив в стеке адрес возврата в вызывающую программу. Процедура должна завершаться командой ret, которая извлекает из стека адрес возврата и возвращает управление команде, следующей за командой call.
Рассмотрим более подробно механизмы работы команд call и ret. Особое значение в механизме вызова процедуры и возврата из нее имеет стек. Поскольку в стеке хранится адрес возврата, то процедура, использующая его для хранения промежуточных результатов, должна к моменту выполнения команды ret восстановить стек в том состоянии, в котором он находился перед ее вызовом. В этом случае говорят, что процедура должна восстановить, или очистить, стек.
В момент вызова процедуры команда call помещает в стек адрес команды, следующей непосредственно за call, уменьшая значение указателя стека SP (ESP). Команда ret вызываемой процедуры использует этот адрес для возврата в вызывающую программу, автоматически увеличивая при этом указатель вершины стека.
Типы адресации (near или far) команд ret и call должны соответствовать друг другу. Вызываемая процедура может вызвать с помощью команды call следующую процедуру и т. д., поэтому стек должен иметь достаточный размер для того, чтобы хранить в нем все записываемые данные.
Следует сказать, что команда ret не анализирует состояние или содержимое стека. Она извлекает из вершины стека слово или двойное слово, в зависимости от типа адресации, полагая, что это адрес возврата, по которому передается управление. Если к моменту выполнения команды ret указатель стека окажется смещенным в ту или иную сторону, содержимое вершины стека может представлять все что угодно, поэтому передача управления по этому адресу приведет к краху программы.
Команда call
– прямой ближний (в пределах текущего программного сегмента);
– прямой дальний (вызов процедуры, расположенной в другом программном сегменте);
– косвенный ближний (в пределах текущего программного сегмента с использованием переменной, содержащей адрес перехода);
– косвенный дальний (вызов процедуры, расположенной в другом программном сегменте, с использованием переменной, содержащей адрес перехода).
Тип адресации при вызове процедуры зависит от используемой модели памяти. Директива .model автоматически устанавливает атрибут near или far для вызываемых процедур, при этом модели tiny, smal1 и compact устанавливают атрибут near, а модели medium, large и huge – атрибут far. Ассемблер генерирует far-вызовы для моделей medium, large и huge автоматически. Для 32-разрядных приложений, использующих модель fIat, все вызовы процедур считаются ближними (near).
Проанализируем более подробно форматы вызовов команды 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.