
- •Язык ассемблера и программирования Глава 1. Директивы языка ассемблера Директива segment
- •Директива group
- •Директива assume
- •Assume сегментный_регистр : имя [, ...]
- •Директива Proc
- •Директива macro
- •Оформление программ в виде отдельных модулей
- •Передача параметров в подпрограмму
- •Описание
- •Директивы упрощенного описания сегментов Директива model
- •Вызов процедур с использованием кадра стека
- •Глава 2. Вывод информации на терминал в текстовом режиме Вывод символьной строки. Модуль puts.
Оформление программ в виде отдельных модулей
В этом случае программа собирается в виде отдельных модулей. Признаком программного модуля является директива END [имя_модуля]. Она располагается в конце модуля. При этом только головной модуль имеет в директиве END имя.
На Рис. 1.6. приведена программа, состоящая из двух модулей CALLMUL1 и SUBMUL. Модуль CALLMUL1 является головным и вызывает модуль SUBMUL. Все вызываемые модули в вызывающем должны быть описаны с помощью директивы EXTRN. Головной модуль должен обеспечить возврат в MS-DOS. Все модули транслируются независимо и объединяются в единый проект на этапе компоновки. Вызываемые модули могут предварительно помещаться в библиотеку.
TITLE CALLMUL1 (EXE)
EXTRN SUBMUL: FAR
;======================================================
STACKSG SEGMENT PARA STACK ‘Stack’
dw 64 dup(?)
STACKSG ENDS
;======================================================
DATASG SEGMENT PARA ‘Data’
QTY dw 0140h
PRICE dw 2500h
REZ dd ?
DATASG ENDS
;======================================================
CODESG SEGMENT para ‘Code’
BEGIN PROC FAR
ASSUME cs:codesg,ds:datasg,ss:stacksg
push ds
sub ax,ax
push ax
mov ax,datasg
mov ds,ax
mov ax,price ;загрузить стоимость
mov bx,qty ;и количество
call submul ;вызвать подпрограмму
mov WORD ptr REZ,ax ; результат умножения
mov WORD ptr REZ+2,dx
ret
BEGIN ENDP
CODESG ENDS
END BEGIN
TITLE SUBMUL Подпрограмма для умножения
;=====================================================
CODESG SEGMENT PARA ‘Code’
SUBMUL PROC FAR
ASSUME cs:codesg
PUBLIC submul
mul bx ;ax-стоимость
;bx-количество
;произведение в dx:ax
ret
SUBMUL ENDP
CODESG ENDS
END
Рис. 1.6.
Передача параметров в подпрограмму
При вызове подпрограммы необходимо решить вопрос о передачи параметров вызываемой функции. Передача параметров может осуществляться через регистры процессора. Это самый простой способ. Однако, он имеет ряд существенных недостатков:
при таком способе передачи необходимо повсеместно отслеживать содержимое регистров до и после вызова подпрограммы;
число переданных параметров ограничивается числом регистров общего назначения, которых, как правило, не хватает.
В связи с этим часто параметры в вызываемую процедуру передаются через стек. В дальнейшем, при передаче параметров будем использовать соглашение, принятое для языка СИ.
На Рис. 1.7 показан вид стека перед выполнением первой команды процедуры Test, если ее вызов из С++ имел следующий вид:
int i,j;
.
.
.
i=25;
j=4;
Test(i,j,1) ;
|
|
SP |
Адрес возврата |
SP+2 |
25(i) |
SP+4 |
4(j) |
SP+6 |
1 |
|
|
Рис. 1.7. Вид стека перед началом выполнения процедуры Test.
На следующем рисунке показан вид стека после выполнения следующих строк вызываемой процедуры:
...
push bp
mov bp,sp
...
|
|
|
SP |
BP при вызове |
BP |
SP+2 |
Адрес возврата |
BP+2 |
SP+4 |
25(i) |
BP+4 |
SP+6 |
4(j) |
BP+6 |
SP+8 |
1 |
BP+8 |
|
|
|
Рис. 1.8. Вид стека после выполнения PUSH и MOV.
Место для автоматически размещаемых переменных резервируется путем вычитания нужного количества байтов из SP. Например, для автоматического размещения массива из 100 байт в процедуре Test должны использоваться следующие операторы:
...
push bp
mov bp,sp
sub sp,100
...
|
|
|
SP |
|
BP-100 |
|
|
|
...
|
|
|
SP+100 |
BP при вызове |
BP |
SP+102 |
Адрес возврата |
BP+2 |
SP+104 |
25(i) |
BP+4 |
SP+106 |
4(j) |
BP+6 |
SP+108 |
1 |
BP+8 |
|
|
|
Рис. 1.9. Состояние стека после размещения 100-байтного массива.
Для адресации автоматически размещаемых переменных используется отрицательное смещение относительно регистра BP. Например,
mov BYTE PTR [bp-100], 0
установит значение первого байта 100-байтного массива в 0.
На рис. 1.10 приведена программа написанная на языке СИ, а на рис. 1.11 десамблируемый код этой программы. При этом следует иметь ввиду, что вызовы функций, написанных на языке СИ, производятся не непосредственно из операционной системы, а из специальной программы - загрузчика(Startup Code). При завершении программы управление передается функции завершения(Exit Code). Эти две функции находятся в модуле COx.OBJ, который включается в исполняемый модуль при компоновке.
int test(int, int);
void main(void)
{
int i,j,k;
i=2;
j=4;
k=test(i,j);
}
int test(int i, int j)
{
int k;
k=i*j;
return k;
}
Рис. 1.10
;_main
push bp
mov bp,sp
sub sp,0006
mov word ptr [bp-02], 0002
mov word ptr [bp-04], 0004
push word ptr [bp-04]
push word ptr [bp-02]
call _test
pop cx
pop cx
mov [bp-06], ax
mov sp, bp
pop bp
ret
;_test
push bp
mov bp, sp
sub sp, 0002
mov ax, [bp+04]
imul word ptr [bp+06]
mov [bp-02], ax
mov sp, bp
pop bp
ret
Рис. 1.11.
Для автоматического размещения переменных используется директива LOCAL, которая проводит распределение в стеке поименованных локальных переменных. Дитектива LOCAL имеет следующий формат:
LOCAL аргумент [,аргумент]... [=идентификатор]
Каждый аргумент имеет следующий синтаксис:
имя [ счетчик 1] [:сложный_тип [:счетчик 2 ]]
Здесь сложный_тип представляет тип данных аргумента. Если тип не задан в явной форме используется тип WORD для 16-битовой модели или DWORD для 32-битовой.
Параметр счетчик2 указывает, сколько элементов такого типа опредедяет данный аргумент. Например, описание аргумента
LOCAL tmp:DWORD:4
означает, что аргумент tmp состоит из четырех двойных слов.
По умолчанию счетчик2 имеет значение 1 для всех типов элементов , кроме типаBYTE .Поскольку операции со стеком не работают с отдельными байтами , то при выборе типа BYTE счетчик2 получает значение 2. Для того чтобы аргумент помещался в стек в виде одного байта ,необходимо задать значение счетчика в явном виде:
LOCAL realbyte: BYTE:1
Счетчик1 определяет количество элементов с данным именем, Общий размер места ,выделяемого в стеке ,является произведением счетчика1 на счетчик2 и на размер одного элемента, определяемого типом элемента. Значение счетчика1 ,принимаемого по умолчанию -1.
Если в конце списка аргументов указан символ равенства и идентификатор ,то Turbo Assembler вычисляет общий размер блока аргументов в байтах и присваивает результат этому идентификатору. Это значение можно использовать в качестве аргумента команды RET , особенно если не используется соглашение о поддержке языков высокого уровня .В этом случае перед возвратом в вызывающую процедуру выполняется очистка стека от всех аргументов (‘это соответствует соглашениям языка PASCAL о вызовах процедур).