Структура программы на языке ассемблера для получения исполняемого файла формата 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