Структура программы на языке ассемблера для получения исполняемого файла формата EXE

Операционная система MS DOS предъявляет некоторые обязательные требования к структуре ASM-программы, предназначенной для последующего создания EXE-файла:

l Программа может использовать 4 сегмента памяти, начальные адреса которых должны быть загружены в регистры микропроцессора CS, SS, DS и ES, а сами сегменты в явном виде определены в программе в виде операторных скобок: имя_сегмента segment ... имя_сегмента ends (версии MS DOS 4.0 и выше допускают более простое указание сегментов в программе: имя_сегмента).

l В программе должно быть указание, какие сегментные регистры закрепляются за используемыми сегментами памяти; при исполнении программы сегментные регистры CS, SS, ES в соответствии с этими указаниями загружаются автоматически.

l Сегмент данных DS в EXE-программе не может быть загружен автоматически, поскольку он используется программным загрузчиком для формирования начального адреса служебной области памяти — префикса программного сегмента (PSP), непосредственно предшествующего любой исполняемой программе EXE. Регистр сегмента данных DS должен быть инициирован принудительно — для этого следует в самом начале ASM-программы записать в стек вектор-адрес возврата к служебной области PSP: содержимое регистра DS и нулевое смещение, а затем в регистр DS загрузить адрес сегмента данных. PSP — это группа служебных слов в оперативной памяти, формируемая для каждой загружаемой программы пользователя и занимающая обычно 256 байтов (100h). При запуске программы пользователя в ОЗУ автоматически формируется PSP, и ее начальный адрес помещается в регистр DS.

l Обеспечение после завершения выполнения программы возврата к префиксу программного сегмента; проще всего это можно сделать, оформив обращение к исполняемой программе в виде обращения к процедуре (главной процедуре, обязательно с атрибутом far) и поместив в конце программы команду возврата ret (выход из программы можно выполнить также, используя прерывание 20H DOS или функцию 4Ch прерывания 21H DOS, но управление при этом передается не в PSP, а непосредственно в резидентную часть программы COMMAND.COM).

Типовая структура ASM-программы включает в себя:

1. Имя программы.

TITLE prog.ASM

Может присутствовать комментарий назначения программы.

2. Инициализацию стековой памяти в сегменте стека:

STACKSEG segment stack

DW N dup(?) ; меньше 32 слов в стеке обычно задавать не следует

STACKSEG ends

3. Инициализацию всех переменных в сегменте данных:

DATASEG segment

; задаются имена всех констант и переменных,

; их начальные значения и резервируется память под них

DATASEG ends

4. Назначение сегментных регистров в сегменте кодов:

CODESEG segment

Assume CS:codeseg, DS:dataseg, SS:stackseg

5. Организацию главной программной процедуры far:

MAIN proc far

6. Запись адреса префикса программного сегмента (PSP) в стек:

push DS

sub AX, AX

push AX

7. Инициализацию содержимого регистра сегмента данных:

mov AX, dataseg

mov DS, AX

; при указании в команде в качестве операнда символического

; имени сегмента (dataseg) происходит пересылка начального адреса

; этого сегмента — неверно указывать offset dataseg

8. Текст программы пользователя в сегменте кодов:

основной текст программы

9. Восстановление адреса PSP в DS:

ret

10. Тексты процедур; если имеются процедуры near, используемые в данной программе, то записываются тексты этих процедур.

11. Закрытие главной процедуры main, сегмента кодов и выход из программы:

MAIN endp

CODESEG ends

end MAIN

Итак, обобщенная структура программы:

title prog.asm

stackseg segment

; задание поля памяти для стека

stackseg ends

dataseg segment

; задание полей памяти для данных и определение всех констант и переменных

dataseg ends

codeseg segment

assume CS:codeseg, DS:dataseg, SS:stackseg

main proc far

push DX

sub AX, AX

push AX

mov AX, dataseg

mov DS, AX

; основной текст программы

; ...

ret

; тексты ближних процедур

main endp

codeseg ends

end main

Рассмотрим программу расчета сложных процентов.

Капитал Q вкладывается в некоторое мероприятие, обеспечивающее ежегодный прирост капитала D%. Задача: определить текущую величину капитала в течение первых N лет. Вот соответствующая ASM-программа для создания исполняемого EXE-файла.

TITLE RASCHET.ASM ; расчет сложных процентов  
STACKSG SEGMENT STACK 'STACK'  
  DW 64 DUP(?)  
STACKSG ENDS    
DATASG SEGMENT 'DATA' ; задание переменных  
VVQ DB ' Введите величину начального капитала (до 64 000)'  
  DB 10,13,'$'  
  DB 10,13,'Введите процент годового прироста'  
  DB 10,13,'$'  
VVN DB 10,13,'Введите количество расчетных лет'  
  DB 10,13,'$'  
Q0 DW ?  
D DW ?  
D1 DW ?  
N DW ?  
I DW  
Q DW ?  
BUF DB 5, 0, 0, 0, 0, 0, 0, 0  
VIV1 DB ' год капитал'  
  DB 10,13,'$'  
SRB DB 14 DUP(0), '$'  
SR DB 6 DUP(0), '$'  
SRK DB 10, 13, '$'  
FT10 DW  
TEN DW  
STO DW  
DATASG ENDS    
CODESG SEGMENT 'CODE'  
MAIN PROC FAR  
    ASSUME CS:CODESG, DS:DATASG, SS:STACKSG  
    PUSH DS  
    SUB AX, AX  
    PUSH AX  
    MOV AX, DATASG  
    MOV DS, AX  
    MOV AH, 9 ; запрос на ввод Q  
    MOV DX, offset VVQ  
    INT 21H  
    MOV AH, 0Ah ; ввод Q  
    MOV DX, offset BUF  
    INT 21H  
    CALL STR2BIN  
    MOV Q0, DI  
    MOV AH, 9 ; запрос на ввод D  
    MOV DX, offset VVD
    INT 21H
    MOV AH, 0AH ; ввод D
    MOV DX, offset BUF
    INT 21H
    CALL STR2BIN
    MOV D, DI
    MOV AH, 9 ; запрос на ввод N
    MOV DX, offset VVN
    INT 21H
    MOV AH, 0AH ; ввод N
    MOV DX, offset BUF
    INT 21H
    CALL STR2BIN
    MOV N, DI
    MOV AX, D
    MOV D1, AX
    ADD D1, 100 ; расчет D1 = (1 + D/100) * 100
    MOV AX, Q0 ; присвоение Q = Q0
    MOV Q, AX
    MOV AH, 9
    MOV DX, offset VIV1
    INT 21H
  RST: MOV AX, Q ; расчет Q = Q * D1
    MUL D1  
    DIV STO  
    MOV Q, AX  
    MOV AX, I  
    CALL BIN2STR  
    MOV AH, 9 ; вывод года  
    MOV DX, offset SR  
    INT 21H  
    MOV AH, 9 ; вывод пробела  
    MOV DX, offset SRB  
    INT 21H  
    MOV AX, Q ; вывод прибыли  
    CALL BIN2STR  
    MOV AH, 9  
    MOV DX, offset SR  
    INT 21H  
    MOV AH, 9 ; перевод строки  
    MOV DX, offset SRK  
    INT 21H  
    INC I ; I = I + 1  
    MOV AX, I ; сравнение I с N  
    CMP AX, N  
    JLE RST ; условный переход по I <= N  
    RET    
  BIN2STR PROC NEAR  
    MOV SI, offset SR+5 ; процедура перевода двоичного  
  PR2: SUB DX, DX ; кода в код ASCII с предварительным  
    MOV [SI], DL ; обнулением поля SR  
    DEC SI  
    CMP SI, offset SR  
    JA PR2  
    MOV CX, 10  
    MOV SI, offset SR+5  
  PR1: XOR DX, DX  
    DIV CX  
    OR DL, 30H  
    MOV [SI], DL  
    DEC SI  
    CMP AX,0  
    JNE PR1  
    RET    
  BIN2STR ENDP    
  STR2BIN PROC NEAR ; процедура перевода ASCII-кодов  
    MOV FT10, 1 ; в двоичный код  
    XOR DI, DI  
    MOV CX, 10  
    LEA SI, BUF + 1  
    XOR BH, BH  
    MOV BL, [BUF + 1]  
  PR3: MOV AL, [SI+BX]  
    AND AX, 0FH  
    MUL FT10  
    ADD DI, AX  
    MOV AX, FT10  
    MUL TEN  
    MOV FT10, AX  
    DEC BX  
    JNZ PR3  
    RET    
  STR2BIN ENDP    
  MAIN ENDP    
  CODESG ENDS    
    END MAIN  
           

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

10 print “Расчет сложных процентов“

20 input “Введите Q, D, N“, Q, D, N

30 D1 = 1 + D/100

40 I = 1

50 Q = Q * D1

60 print I, Q

70 I = I + 1

80 if I <= N then goto 50

90 end