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