Структура exe-программ
Операционная система (MS-DOS) требует выполнения четырех обязательных действий для инициализации ассемблерной EXE-программы:
1. Указания соответствия между сегментами и сегментными регистрами.
2. Сохранения в стеке адреса, находящегося в регистре DS.
3. Записи в стек нуля.
4. Загрузки в регистр DS адреса начала сегмента данных.
Первое из перечисленных действий реализуется директивой ASSUME (см. разд.5.1). Пример записи директивы ASSUME для EXE-программ:
ASSUME CS:codesg,DS:datasg,SS:stacksg,ES:nothing
Для того чтобы разобраться с остальными действиями необходимо рассмотреть, что происходит при загрузке операционной системой EXE-программы в память (см. рис.6.1). Исполнительному модулю в памяти непосредственно предшествует область, которая называется префиксом программного сегмента (англ. Program Segment Prefix (PSP)). Эта область размещается перед первым сегментом исполнительного модуля, имеет размер 256 байт (100H байт) и содержит различную служебную информацию об исполняемой программе (например, аргументы командной строки, с которыми программа была запущена на выполнение). При его загрузке (для адресации начала PSP) операционная система использует сегментный регистр DS, а после загрузки префикса и всех сегментов управление передается именно на PSP, откуда уже происходит вызов главной процедуры сегмента кода. Следовательно, главная процедура сегмента кода должна обязательно иметь тип FAR (см. разд.5.2), поскольку она вызывается не из того сегмента в котором была определена.
Подробно структуру префикса программного сегмента рассматривать не будем. Однако отметим, что в самом его начале (по смещению 0000H) находится двоичный код команды INT 20H, которая завершает выполнение всей программы и передает управление обратно операционной системе. Поэтому после выполнения программы нужно передать управление на начало PSP и, таким образом, выйти в MS-DOS.
Для этого необходимо запомнить точку возврата в стеке, что и делается с помощью второго (сохраняется сегментная составляющая) и третьего (сохраняется смещение) действий.
В дальнейшем при завершении выполнения программы (завершения выполнения главной процедуры командой RET) из стека будет извлечено первое значение (ноль) и помещено в регистр указатель команд IP и второе значение (адрес начала PSP) и помещено в сегментный регистр CS. Получившаяся пара СS:IP будет как раз указывать на начало PSP и обеспечивать выход в MS-DOS.
Если главная процедура будет иметь тип NEAR, то при выполнении команды RET из стека будет извлекаться только одно значение (ноль) и помещаться в регистр указатель команд IP, а регистр CS модифицироваться не будет. Тем самым мы будем передавать управление на начало сегмента кода (пара CS:0 адресует начало сегмента кода, то есть начало главной процедуры), и программа зациклится.
Если в главной процедуре используется стек, то перед выполнением команды RET стек должен быть возвращен в исходное состояние (иначе будет потеряна точка передачи управления операционной системе).
Четвертое требование вытекает из того, что операционная система использует регистр DS для своих нужд и поэтому для начала выполнения собственно программы необходимо поместить в регистр DS адрес начала сегмента данных (если он присутствует в программе). Как уже отмечалось в разд.2, запись в сегментные регистры возможна только через промежуточный регистр общего назначения.
Очевидно также, что EXE-программа всегда должна обязательно содержать сегмент стека, поскольку именно там сохраняется точка возврата в операционную систему.
Пример оформления EXE-программы приведен на рис.6.2.
Рис. 6.1. Состояние памяти при загрузке EXE-программы
;---------------------Сегмент данных---------------------
datasg SEGMENT PARA 'Data'
x DB 24
y DB 24H
z DB ?
datasg ENDS
;---------------------Сегмент кода------------------------
сodesg SEGMENT PARA 'Code'
main PROC FAR
;Выполнение первого требования
ASSUME CS:codesg,DS:datasg,SS:stacksg,ES:nothing
;Выполнение второго требования
PUSH DS ;Записать DS в стек
;Выполнение третьего требования
XOR AX,AX ;Обнулить регистр AX
PUSH AX ;Записать значение регистра AX в стек
;Выполнение четвертого требования
MOV AX,datasg
MOV DS,AX ;Занести адрес datasg в DS
;Текст программы
RET
main ENDP
codesg ENDS
;------------------Сегмент стека-------------------------
stacksg SEGMENT PARA STACK 'Stack'
DW 128 DUP(?) ;Запись в стек неинициализированных значений
stacksg ENDS
END main
Рис. 6.2. Пример оформления EXE-программы