Межсегментные вызовы

Примеры программ, рассматриваемых ранее, состояли из одного шага ассемблирования. Возможно, однако, выполнение программного модуля, состоящего из нескольких ассемблированных программ. В этом случае программу можно рассматривать, как состоящую из основной программы и одной или более подпрограмм. Причины такой организации программ состоят в следующем:

uбывает необходимо скомпоновать программы, написанные на разных языках, например, для объединения мощности языка высокого уровня и эффективности Ассемблера;

uпрограмма, написанная в виде одного модуля, может оказаться слишком большой для ассемблирования;

uотдельные части программы могут быть написаны разными группами программистов, ассемблирующих свои модули раздельно;

uввиду возможно большого размера выполняемого модуля, может появиться необходимость перекрытия частей программы в процессе выполнения.

Каждая программа ассемблируется отдельно и генерирует собственный уникальный объектный (OBJ) модуль. Программа компоновщик (LINK) затем компонует объектные модули в один объединенный выполняемый (EXE) модуль.

Обычно выполнение начинается с основной программы, которая вызывает одну или более подпрограмм. Подпрограммы, в свою очередь, могут вызывать другие подпрограммы.

Существует много разновидностей организации подпрограмм, но любая организация должна быть «понятна» и Ассемблеру, и компоновщику, и этапу выполнения. Следует быть внимательным к ситуациям, когда, например, подпрограмма 1 вызывает подпрограмму 2, которая вызывает подпрограмму 3 и, которая в свою очередь вызывает подпрограмму 1. Такой процесс, известный как рекурсия, может использоваться на практике, но при неаккуратном обращении может вызвать любопытные ошибки при выполнении.

Команды CALL используются для внутрисегментных вызовов, то есть, для вызовов внутри одного сегмента. Внутрисегментный CALL может быть короткий (в пределах от +127 до -128 байт) или длинный (превышающий указанные границы). В результате такой операции «старое» значение в регистре IP запоминается в стеке, а «новый» адрес перехода загружается в этот регистр.

Например, внутрисегментный CALL может иметь следующий объектный код: E82000. Шест.E8 представляет собой код операции, которая заносит 2000 в виде относительного адреса 0020 в регистр IP. Затем процессор объединяет текущий адрес в регистре CS и относительный адрес в регистре IP для получения адреса следующей выполняемой команды. При возврате из процедуры команда RET восстанавливает из стека старое значение в регистре IP и передает управление таким образом на следующую после CALL команду.

Вызов в другой кодовый сегмент представляет собой межсегментный (длинный) вызов. Данная операция сначала записывает в стек содержимое регистра CS и заносит в этот регистр адрес другого сегмента, затем записывает в стек значение регистра IP и заносит новый относительный адрес в этот регистр.

Таким образом в стеке запоминаются и адрес кодового сегмента и смещение для последующего возврата из подпрограммы.

Например, межсегментный CALL может состоять из следующего объектного кода:

9A 0002 AF04

Шест.9A представляет собой код команды межсегментного вызова которая записывает значение 0002 в виде 0200 в регистр IP, а значение AF04 в виде 04AF в регистр CS. Комбинация этих адресов указывает на первую выполняемую команду в вызываемой подпрограмме:

Кодовый сегмент 04AF0

Смещение в IP 0200

Действительный адрес 04CF0

При выходе из вызванной процедуры межсегментная команда возврата REP восстанавливает оба адреса в регистрах CS и IP и таким образом передает управление на следующую после CALL команду.