0003 50 PUSH AX
0004 B8 ---- R MOV A,DATASG
0007 8E D8 MOV DS,AX
0009 FF 36 0002 R PUSH PRICE
000D FF 36 0000 R PUSH QTY
0011 9A 0000 ---- E CALL SUBMUL ;Вызвать подпрограмму
0016 CB RET
0017 BEGIN ENDP
0017 CODESG ENDS
END BEGIN
_____________________________________________________________________
Segments and Groups:
N a m e Sise Align Combine Class
CODESG . . . . . . . . . . . . 0017 PARA NONE 'CODE'
DATASG . . . . . . . . . . . . 0004 PARA NONE 'DATA'
STACKSG. . . . . . . . . . . . 0080 PARA STACK 'STACK'
Symbols:
N a m e Type Value Attr
BEGIN. . . . . . . . . . . . . F PROC 0000 CODESG Length=0017
PRICE. . . . . . . . . . . . . L WORD 0002 DATASG
QTY. . . . . . . . . . . . . . L WORD 0000 DATASG
SUBMUL . . . . . . . . . . . . L FAR 0000 External
_____________________________________________________________________
page 60,132
TITLE SUBMUL Вызываемая подпрограмма умножения
0000 CODESG SEGMENT PARA PUBLIC 'Code'
0000 SUBMUL PROC FAR
ASSUME CS:CODESG
PUBLIC SUMBUL
0000 55 PUSH BP
0001 8P EC MOV BP,SP
0003 8B 46 08 MOV AX,[BP+8] ;Стоимость
0006 8B 5E 06 MOV BX,[BP+6] ;Количество
0009 F7 E3 MUL BX ;Произведение в DX:AX
000B 5D POP BP
000F SUMBUL ENDP
000F CODESG ENDS
END
_____________________________________________________________________
Segments and Groups:
N a m e Size Align Combine Class
CODESG . . . . . . . . . . . . 000F PARA PUBLIC 'CODE'
Symbols:
N a m e Type Value Attr
SUBMUL . . . . . . . . . . . . F PROC 0000 CODESG Global Length=000F
_____________________________________________________________________
LINK
IBM Personal Computer Linker
Version 2.30 (C) Copyright IBM Corp 1981, 1985
Object Modules: B:CALLMUL4+B:SUBMUL4
Run File: [B:CALLMUL4.EXE]:
List File: [NUL.MAP]: CON
Libraries [.LIB]:
Start Stop Length Name Class
00000H 00019H 001AH CODESG CODE
00030H 00033H 0004H DATASG DATA
00040H 000BFH 0080H STACKSG STACK
PROGRAM entry point at 0000:0000
__________________________________________________________________________
Рис.21.6. Передача параметров.
Другим способом обеспечения доступа к данным из вызываемой
подпрограммы является передача параметров. В этом случае вызывающая
программа физически передает данные через стек. Каждая команда PUSH должна
записывать в стек данные размером в одно слово из памяти или из регистра.
Программа, приведенная на рис.21.6, прежде чем вызвать подпрограмму
SUBMUL заносит в стек значения из полей PRICE и QTY. После команды CALL
стек выглядит следующим образом:
... | 1600 | D213 | 4001 | 0025 | 0000 | C213 |
6 5 4 3 2 1
1. Инициализирующая команда PUSH DS заносит адрес сегмента в стек. Этот
адрес может отличаться в разных версиях DOS.
2. Команда PUSH AX заносит в стек нулевой адрес.
3. Команда PUSH PRICE заносит в стек слово (2500).
4. Команда PUSH QTY заносит в стек слово (0140).
5. Команда CALL заносит в стек содержимое регистра CS (D213)
6. Так как команда CALL представляет здесь межсегментный вызов,то в стек
заносится также содержимое регистра IP (1600).
Вызываемая программа использует регистр BP для доступа к параметрам в
стеке, но прежде она запоминает содержимое регистра BP, записывая его в
стек. В данном случае, предположим, что регистр BP содержит нуль, тогда
нулевое слово будет записано в вершине стека (слева).
Затем программа помещает в регистр BP содержимое из регистра SP, так
как в качестве индексного регистра может использоваться регистр BP, но не
SP. Команда загружает в регистр BP значение 0072. Первоначально регистр SP
содержал размер пустого стека, т.е. шест.80. Запись каждого слова в стек
уменьшает содержимое SP на 2:
| 0000 | 1600 | D213 | 4001 | 0025 | 0000 | C213 |
| | | | | | |
SP: 72 74 76 78 7A 7C 7E
Так как BP теперь также содержит 0072, то параметр цены (PRICE) будет
по адресу BP+8, а параметр количества (QTY) - по адресу BP+6. Программа
пересылает эти величины из стека в регистры AX и BX соответственно и
выполняет умножение.
Перед возвратом в вызывающую программу в регистре BP
восстанавливается первоначальное значение, а содержимое в регистре SP
увеличивается на 2, с 72 до 74.
Последняя команда RET представляет собой "длинный" возврат в
вызывающую программу. По этой команде выполняются следующие действия:
- Из вершины стека восстанавливается значение регистра IP (1600)
- Содержимое регистра SP увеличивается на 2, от 74 до 76.
- Из новой вершины стека восстанавливается значение регистра CS
(D213).
- Содержимое регистра SP увеличивается на 2 от 76 до 78.
Таким образом осуществляется корректный возврат в вызывающую
программу. Осталось одно небольшое пояснение. Команда RET закодирована как
RET 4
Параметр 4 представляет собой число байт в стеке использованных при
передаче параметров (два слова в данном случае). Команда RET прибавит этот
параметр к содержимому регистра SP, получив значение 7C. Таким образом, из
стека исключаются ненужные больше параметры. Будьте особенно внимательны
при восстановлении регистра SP - ошибки могут привести к непредсказуемым
результатам.
КОМПОНОВКА ПРОГРАММ НА BASIC-ИНТЕРПРЕТАТОРЕ И АССЕМБЛЕРЕ
________________________________________________________________
В руководстве по языку BASIC для IBM PC приводятся различные методы
связи BASIC-интерпретатора и программ на ассемблере. Для этого имеются две
причины: сделать возможным использование BIOS-прерываний через
ассемблерные модули и создать более эффективные программы. Цель данного
раздела - дать общий обзор по данному вопросу; повторять здесь технические
подробности из руководства по языку BASIC нет необходимости.
Для связи с BASIC ассемблерные программы кодируются, транслируются и
компонуются отдельно. Выделение памяти для подпрограмм на машинном языке
может быть либо внутри, либо вне 64 Кбайтовой области памяти, которой
ограничен BASIC. Выбор лежит на программисте.
Существует два способа загрузки машинного кода в память:
использование оператора языка BASIC - POKE или объединение скомпонованного
модуля с BASIC-программой.
Использование BASIC-оператора POKE
------------------------------------
Хотя это и самый простой способ, но он удобен только для очень
коротких подпрограмм. Способ заключается в том, что сначала определяется
объектный код ассемблерной программы по LST-файлу или с помощью отладчика
DEBUG. Затем шестнадцатиричные значения кодируются непосредственно в
BASIC-программе в операторах DATA. После этого с помощью BASIC-оператора
READ считывается каждый байт и оператором POKE заносится в память для
выполнения.
Компановка ассемблерных модулей
---------------------------------
С большими ассемблерными подпрограммами обычно проще иметь дело, если
они оттранслированы и скомпонованы как выполнимые (EXE) модули. Необходимо
организовать BASIC-программу и выполнимый модуль в рабочую программу. При
работе с BASIC-программой не забывайте пользоваться командой BSAVE (BASIC
save) для сохранения программы и BLOAD - для загрузки ее перед
выполнением.
Прежде чем кодировать BASIC- и ассемблерную программы, необходимо
решить, каким из двух способов они будут связаны. В языке BASIC возможны
два способа: функция USR и оператор CALL. В обоих способах регистры DS, ES
и SS на входе содержат указатель на адресное пространство среды BASIC.
Регистр CS содержит текущее значение, определенное последним оператором
DEF SEG (если он имеется). Стековый указатель SP указывает на стек,
состоящий только из восьми слов, так что может потребоваться установка
другого стеке в подпрограмме. В последнем случае необходимо на входе
сохранить значение указателя текущего стека, а при выходе восстановить
его. В обоих случаях при выходе необходимо восстановить значение
сегментных регистров и SP и обеспечить возврат в BASIC с помощью
межсегментного возврата RET.
Скомпонуйте ваш ассемблированный объектный файл так, что бы он
находился в старших адресах памяти. Для этого используется параметр HIGH
при ответе на второй запрос компоновщика, например, B:имя/HIGH. Затем с
помощью отладчика DEBUG необходимо загрузить EXE-подпрограмму и по команде
R определить значения в регистрах CS и IP: они показывают на стартовый
адрес подпрограммы. Находясь в отладчике укажите имя (команда N) BASIC и
загрузите его командой L.
Два способа связи BASIC-программы и EXE-подпрограммы - использование
операторов USR или CALL. Работая в отладчике, необходимо определить
стартовый адрес EXE-подпрограммы и, затем, указать этот адрес или в
операторе USRn или в CALL. В руководстве по языку BASIC для IBM PC
детально представлено описание функции USRn и оператора CALL с различными
примерами.
Программа: Компоновка BASIC и ассемблера
------------------------------------------
__________________________________________________________________________
LOAD"D:BASTEST.BAS
LIST
010 CLEAR ,32768!
020 ' для BLOAD
030 ' для DEFSEG
040 ' для точки входа в CALL
050 ' для вызова ASM-модуля
060 FOR N = 1 TO 5
070 INPUT "Hours "; H
080 INPUT "Rate "; R
090 W = H * R
100 PRINT "Wage = " W
110 NEXT N
120 END
_____________________________________________________________________
TITLE LINKBAS Ассемблерная подпрограмма, вызываемая из BASIC
CODESG SEGMENT PARA 'CODE'
ASSUME CS:CODESG
CLRSCRN PROC FAR
PUSH BP ;Сохранить BP
MOV BP,SP ;База списка параметров
MOV AX,0600H ;Функция прокрутки
MOV BH,07 ; всего
MOV CX,0000 ; экрана
MOV DX,184FH
INT 10H
POP BP
RET ;Завершить подпрограмму
CLRSCRN ENDP
CODESG ENDS
END
__________________________________________________________________________
Рис.21.7. Основная программа на языке BASIC и подпрограмма на
ассемблере.
Рассмотрим теперь простой пример компановки программы для
BASIC-интерпретатора и подпрограммы на ассемблере. В этом примере
BASIC-программа запрашивает ввод значений времени и расценки и выводит на
экран их произведение - размер зарплаты. Цикл FOR-NEXT обеспечивает
пятикратное выполнение ввода и затем программа завершается. Пусть BASIC-
программа вызывает ассемблерный модуль, который очищает экран.