Программа организуется как набор подпрограмм, которые могут находиться в различных текстовых файлах и объектных модулях, объединяемых компилятором tasm.exe и сборщиком tlink.exe в загрузочный модуль. Среди этих подпрограмм выделяется главное – это подпрограмма, на которую передаёт управление загрузчик. Главная подпрограмма вызывает в процессе работы подпрограммы с помощью команды CALL, которые, в свою очередь, возвращают ей выполнение с помощью команды RET.
Команды CALL и RET. Команда CALL выполняет вызов процедуры, адрес которой может задаваться смещением, либо сегментом и смещением. В последнем случае вызов называется межсегментным. Алгоритм работы команды CALL:
1) если вызов межсегментный, то CS запоминается в стек и CS устанавливается равным адресу сегмента подпрограммы;
2) счётчик команд IP запоминается в стек и IP устанавливается равным смещению операнда.
(При записи в стек из SP предварительно вычитается 2.)
Команда возврата RET просто извлекает из стека значение регистра IP. Если возврат длинный, то применяется команда RETF. В этом случае после извлечения IP производится извлечение CS. Команда может иметь операнд:
ret cnt ;cnt – произвольная константа
после возврата из процедуры увеличивается SP на cnt.
Подпрограммы. Программа разбивается на небольшие модули, состоящие из групп команд, которые называются процедурами или подпрограммами и записываются следующим образом:
name1 proc
… ;группа команд
ret
name1 endp
name2 proc far
… ;группа команд
retf
name2 endp
Здесь name1 и name2 – произвольные имена, proc – директива, обозначающая начало процедуры, endp – директива, обозначающая конец процедуры. Если к директиве proc добавляется слово far, то подпрограмма имеет дальний адрес. Если указано слово near, то процедура должна находиться в одном сегменте с вызывающей процедурой. Слово near может быть опущено, ибо оно подразумевается по умолчанию.
Пример 1. Напишем программу, состоящую из главного модуля и подпрограммы, выводящей символ из регистра DL на экран:
<1> title display.exe
<2> mycode segment
<3> assume cs:mycode
<4> main proc far
<5> push es ;для
<6> mov ax,0 ;возврата
<7> push ax ;в операционную систему
<8> mov dl,’a’
call display ;вывод ‘a’
<1>
<2> mov dl,’b’
<3> call display ;вывод ‘b’
<4> mov dl,’c’
<5> call display ;вывод ‘c’
<6> ret ;выход в MS-DOS
<7> main endp
<8> display proc
<9> mov ah,2
<10> int 21h
<11> ret
<12> display endp
<13> mycode ends
<14> end main ;на главную подпрограмму
Здесь выход в MS-DOS осуществляется с помощью команды ret, которая передаёт управление на команду с адресом ES:0 (По этому адресу находится команда int 20h – вызов прерывания, производящего выход.)
Внешние подпрограммы. Подпрограммы называются внешними, если их объектные модули находятся в отдельных файлах или библиотеках. Для вызова внешней подпрограммы необходимо описать эту подпрограмму с помощью одной из директив
extrn имя:far
extrn имя:near
Доступ к подпрограмме или к другим данным вызываемого модуля обеспечивается директивой
public имя
Пример 2. Главный модуль и вызываемую подпрограмму из примера 1 запишем в два файла. Добавим вызываемую подпрограмму Kbin ввода символа с клавиатуры. Получим следующий ниже текст. Первый, displ.asm:
<1> title displ.exe
<2> mycode segment
<3> assume cs:mycode
<4> extrn display:near
<5> extrn kbin:near
<6> ;— модуль mylib.obj —
<7> go: mov dl,’a’
<8> call display
<9> mov dl,’b’
<10> call display
<11> mov dl,’c’
<12> call display
<13> call kbin ;ожидание 1C
<14> ret
<15> mycode ends
<16> end go
Вызываемые подпрограммы запишем во втором файле mylib.asm:
<1> title mylib.obj
<2> subr segment
<3> public display,kbin
<4> assume cs:subr
<5> ;— подпрограммы —
<6> display proc
<7> mov ah,2
<8> int 21h
<9> ret
<10> display endp
<11> kbin proc near
<12> mov ah,1
<13> int 21h
<14> ret
<15> kbin endp
subr ends
<1>
<2> end
Компиляция и сборка выполняются после ввода команд:
tasm displ
tasm mylib
tlink mylib+displ
В результате будет создан загрузочный модуль displ.exe, который после загрузки выведет символы abc и, после ввода ctrl/c, закончит работу.