На этом шаге мы рассмотрим структуру EXE-программы.
Существует два основных типа выполняемых программ. Это EXE- и COM-файлы. Операционная система (DOS) требует выполнения четырех требований для инициализации ассемблерной EXE-программы:
Первое из перечисленных требований реализуется директивой ASSUME.
Второе требование вызвано тем, что загрузочному модулю в памяти непосредственно предшествует 256-байтовая область (100H), называемая префиксом программного сегмента (PSP), который содержит определенную служебную информацию. Для нас важно то, что первой командой, находящейся в PSP, является команда выхода в DOS. Поэтому после выполнения программы нужно передать управление на начало PSP и, таким образом, выйти в DOS.
Выполняемую программу в памяти компьютера размещает специальная программа-загрузчик, которая использует регистр DS для установки адреса начальной точки PSP. Пользовательская программа должна сохранить этот адрес, поместив его в стек. Команда пересылки информации в стек начинается со служебного слова PUSH, например: PUSH AX. Команда извлечения данных из стека начинается со служебного слова POP. Таким образом, EXE-программа всегда должна содержать сегмент стека, в который обязательно помещается адрес начала PSP командой PUSH DS.
Третье требование объясняется следующим образом. Извлечение из стека помещенного значения регистра DS будет осуществляться при выполнении команды RET главной процедуры (процедуры, с которой начинается выполнение программы). Это значение будет помещено в регистр CS. Однако в процессе вычисления адреса выполняемой команды участвует пара регистров CS:IP. После выполнения программы значение регистра IP будет отлично от нуля, но нам надо, чтобы там содержался нуль, так как регистр CS уже содержит адрес начала PSP. Поэтому нужно обнулить значение регистра IP, что осуществляется размещением нуля в стеке.
Напомним, что параметр расстояние у директивы PROC основной программы должен быть установлен в FAR, что обеспечит при выполнении команды RET извлечение двух слов из стека. Если это расстояние не будет указано, то из стека будет извлечен только нуль, который помещается в регистр IP, что приведет к повторному выполнению программы (значение регистра CS не изменится и поэтому пара CS:0 адресует начало программы).
Четвертое требование очевидно. Так как программа загрузчика использует регистр DS для своих нужд, то необходимо поместить в регистр DS адрес начала сегмента данных (если он присутствует в программе).
Проиллюстрируем сказанное, рассмотрев общую структуру EXE-программы.
TITLE пример EXE-программы ;------------------ Сегмент стека ------------------------- Stacksg SEGMENT PARA STACK dw 128 DUP(?) Stacksg ENDS ;--------------------- Сегмент данных--------------------- Datasg SEGMENT PARA x DB 00 y DB 00 z DB ? Datasg ENDS ;---------------------Кодовый сегмент -------------------- Codesg SEGMENT PARA begin PROC FAR ;Пролог EXE-программы. ;Выполнение первого требования. ASSUME CS:Codesg, DS:Datasg, SS:Stacksg ;Выполнение второго требования. PUSH DS ; Записать DS в стек. ;Выполнение третьего требования. XOR AX,AX ; Установить в нуль AX. PUSH AX ; Записать AX в стек. ;Выполнение четвертого требования. MOV AX,Datasg ; Занести адрес MOV DS,AX ; Datasg в DS. ;Завершение пролога EXE-программы. . . . RET begin ENDP Codesg ENDS END begin
На следующем шаге мы рассмотрим структуру COM-программы.