Жанр: Учеба
Ассемблер и программирование для IBM PC
...ся необходимость перекрытия частей программы в процессе
выполнения.
Каждая программа ассемблируется отдельно и генерирует собственный
уникальный объектный (OBJ) модуль. Программа компоновщик (LINK) затем
компонует объектные модули в один объединенный выполняемый (EXE) модуль.
Обычно выполнение начинается с основной программы, которая вызывает одну
или более подпрограмм. Подпрограммы, в свою очередь, могут вызывать другие
подпрограммы.
На рис.21.1 показаны два примера иерархической структуры основной
подпрограммы и трех подпрограмм. На рис.21.1.(а) основная программы
вызывает подпрограммы 1, 2 и 3. На рис.21.1.(б) основная программа
вызывает подпрограммы 1 и 2, а подпрограмма 1 вызывает подпрограмму 3.
Существует много разновидностей организации подпрограмм, но любая
организация должна быть "понятна" и ассемблеру, и компоновщику, и этапу
выполнения. Следует быть внимательным к ситуациям, когда, например, под
программа 1 вызывает подпрограмму 2, которая вызывает подпрограмму 3 и,
которая в свою очередь вызывает подпрограмму 1. Такой процесс, известный
как рекурсия, может использоваться на практике, но при неаккуратном
обращении может вызвать любопытные ошибки при выполнении.
__________________________________________________________________________
a) ------------¬ б) ------------¬
¦ Основная ¦ ¦ Основная ¦
¦ программа ¦ ¦ программа ¦
L-----T------ L-----T------
----------+---------¬ -----+----¬
-------¬ -------¬ -------¬ -------¬ -------¬
¦ П/П 1 ¦ ¦ П/П 2 ¦ ¦ П/П 3 ¦ ¦ П/П 1 ¦ ¦ П/П 2 ¦
L-------- L-------- L-------- L---T---- L--------
-------¬
¦ П/П 3 ¦
L--------
__________________________________________________________________________
Рис.21.1. Иерархия программ.
МЕЖСЕГМЕНТНЫЕ ВЫЗОВЫ
________________________________________________________________
Команды CALL в предыдущих главах использовались для внутрисегментных
вызовов, т.е. для вызовов внутри одного сегмента. Внутрисегментный CALL
может быть короткий (в пределах от +127 до -128 байт) или длинный (
превышающий указанные границы). В результате такой операции "старое"
значение в регистре IP запоминается в стеке, а "новый" адрес перехода
загружается в этот регистр.
Например, внутрисегментный CALL может иметь следующий объектный код:
E82000. Шест.E8 представляет собой код операции, которая заносит 2000 в
виде относительного адреса 0020 в регистр IP. Затем процессор объединяет
текущий адрес в регистре CS и относительный адрес в регистре IP для
получения адреса следующей выполняемой команды. При возврате из процедуры
команда RET восстанавливает из стека старое значение в регистре IP и
передает управление таким образом на следующую после CALL команду.
Вызов в другой кодовый сегмент представляет собой межсегментный
(длинный) вызов. Данная операция сначала записывает в стек содержимое
регистра CS и заносит в этот регистр адрес другого сегмента, затем
записывает в стек значение регистра IP и заносит новый относительный адрес
в этот регистр.
Таким образом в стеке запоминаются и адрес кодового сегмента и
смещение для последующего возврата из подпрограммы.
Например, межсегментный CALL может состоять из следующего объектного
кода:
9A 0002 AF04
Шест.9A представляет собой код команды межсегментного вызова которая
записывает значение 0002 в виде 0200 в регистр IP, а значение AF04 в виде
04AF в регистр CS. Комбинация этих адресов указывает на первую выполняемую
команду в вызываемой подпрограмме:
Кодовый сегмент 04AF0
Смещение в IP 0200
Действительный адрес 04CF0
При выходе из вызванной процедуры межсегментная команда возврата REP
восстанавливает оба адреса в регистрах CS и IP и таким образом передает
управление на следующую после CALL команду.
АТРИБУТЫ EXTRN и PUBLIC
________________________________________________________________
Рассмотрим основную программу (MAINPROG), которая вызывает
подпрограмму (SUBPROG) с помощью межсегментного CALL, как показано на
рис.21.2.
Команда CALL в MAINPROG должна "знать", что SUBPROG существует вне
данного сегмента (иначе ассемблер выдаст сообщение о том, что
идентификатор SUBPROG не определен). С помощью директивы EXTRN можно
указать ассемблеру, что ссылка на SUBPROG имеет атрибут FAR,
т.е.определена в другом ассемблерном модуле. Так как сам ассемблер не
имеет возможности точно определить такие ссылки, он генерирует "пустой"
объектный код для последующего заполнения его при компановке:
9A 0000 ---- E
Подпрограмма SUBPROG содержит директиву PUBLIC, которая указывает
ассемблеру и компоновщику, что другой модуль должен "знать" адрес SUBPROG.
В последнем шаге, когда оба модуля MAINPROG и SUBPROG будут успешно
ассемблированы в объектные модули, они могут быть скомпонованы следующим
образом:
Запрос компоновщика LINK: Ответ:
Object Modules [.OBJ]: B:MAINPROG+B:SUBPROG
Run File [filespec.EXE]: B:COMBPROG (или другое имя)
List File [NUL.MAP]: CON
Libraries [.LIB]: [return]
Компоновщик устанавливает соответствия между адресами EXTRN в одном
объектном модуле с адресами PUBLIC в другом и заносит необходимые
относительные адреса. Затем он объединяет два объектных модуля в один
выполняемый. При невозможности разрешить ссылки компоновщик выдает
сообщения об ошибках. Следите за этими сообщениями прежде чем пытаться
выполнить программу.
__________________________________________________________________________
---------------------------------¬
¦ EXTRN SUBPROG:FAR ¦
¦ MAINPROG: . ¦
¦ . ¦
¦ CALL SUBPROG ¦
¦ . ¦
¦ . ¦
+--------------------------------+
¦ PUBLIC SUBPROG ¦
¦ SUBPROG: . ¦
¦ . ¦
¦ . ¦
¦ RET ¦
L---------------------------------
__________________________________________________________________________
Рис.21.2. Межсегментный вызов.
Директива EXTRN
-----------------
Директива EXTRN имеет следующий формат:
EXTRN имя:тип [, ... ]
Можно определить более одного имени (до конца строки) или закодировать
дополнительные директивы EXTRN. В другом ассемблерном модуле
соответствующее имя должно быть определено и идентифицировано как PUBLIC.
Тип элемента может быть ABS, BYTE, DWORD, FAR, NEAR, WORD. Имя может быть
определено через EQU и должно удовлетворять реальному определению имени.
Директива PUBLIC
------------------
Директива PUBLIC указывает ассемблеру и компоновщику, что адрес
указанного идентификатора доступен из других программ. Директива имеет
следующий формат:
PUBLIC идентификатор [, ... ]
Можно определить более одного идентификатора (до конца строки) или
закодировать дополнительные директивы PUBLIC. Идентификаторы могут быть
метками (включая PROC-метки), переменными или числами. Неправильными
идентификаторами являются имена регистров и EQU-идентификаторы,
определяющие значения более двух байт.
Рассмотрим три различных способа компановки программ.
ПРОГРАММА: ИСПОЛЬЗОВАНИЕ ДИРЕКТИВ EXTRN и PUBLIC ДЛЯ МЕТОК
_______________________________________________________________
__________________________________________________________________________
page 60,132
TITLE CALLMULL1 (EXE) Вызов подпрограммы умнож.
EXTRN SUBMUL:FAR
;-----------------------------------------------
0000 STACKSG SEGMENT PARA STACK 'Stack'
0000 40 [ ???? ] DW 64 DUP(?)
0080 STACKSG ENDS
;-----------------------------------------------
0000 DATASG SEGMENT PARA 'Data'
0000 0140 QTY DW 0140H
0002 2500 PRICE DW 2500H
0004 DATASG ENDS
;-----------------------------------------------
0000 CODESG SEGMENT PARA 'Code'
0000 BEGIN PROC FAR
ASSUME CS:CODESG,DS:DATASG,SS:STACKSG
0000 1E PUSH DS
0001 2B C0 SUB AX,AX
0003 50 PUSH AX
0004 B8 ---- R MOV AX,DATASG
0007 8E D8 MOV DS,AX
0009 A1 0002 R MOV AX,PRICE ;Загрузить стоимость
000C 8B 1E 0000 R MOV BX,QTY ; и количество
0010 9A 0000 ---- E CALL SUBMUL ;Вызвать подпрограмму
0015 CB RET
0016 BEGIN ENDP
0016 CODESG ENDS
END BEGIN
Segments and Groups:
N a m e Size Align Combine Class
CODESG . . . . . . . . . . . . 0016 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=0016
PRICE. . . . . . . . . . . . . L WORD 0002 DATASG
QTY. . . . . . . . . . . . . . L WORD 0000 DATASG
SUBMUL . . . . . . . . . . . . L FAR 0000 External
page 60,132
TITLE SUBMUL Подпрограмма для умножения
;-----------------------------------------------
0000 CODESG SEGMENT PARA 'Code'
0000 SUBMUL PROC FAR
ASSUME CS:CODESG
PUBLIC SUBMUL
0000 F7 E3 MUL BX ;AX-стоимость, BX-количество
0002 CB RET ;Произведение в DX:AX
0003 SUBMUL ENDP
0003 CODESG ENDS
END SUBMUL
Segments and groups:
N a m e Size Align Combine Class
CODESG . . . . . . . . . . . . 0003 PARA NONE 'CODE'
Symbols:
N a m e Type Value Attr
SUBMUL . . . . . . . . . . . . F PROC 0000 CODESG Clobal Length=0003
__________________________________________________________________________
LINK
IBM Personal Computer Linker
Version 2.30 (C) Copyright IBM Corp 1981, 1985
Object Modules: B:CALLMUL1+B:SUBMUL1
Run File: [B:CALLMUL1.EXE]: "return"
List File:[NUL.MAP]: CON
Libraries [.LIB]: "return"
Start Stop Length Name Class
00000H 00015H 0016H CODESG CODE "--Примечание: 2 кодовых
00020H 00022H 0003H CODESG CODE "-- сегмента
00030H 00033H 0004H DATASG DATA
00040H 000BFH 0080H STACKSG STACK
Program entry point at 0000:0000
__________________________________________________________________________
Рис. 21.3. Использование директив EXTRN и PUBLIC.
Программа на рис.21.3 состоит из основной программы CALLMUL1 и
подпрограммы SUBMUL1. В основной программе определены сегменты для стека,
данных и кода. В сегменте данных определены поля QTY и PRICE. В кодовом
сегменте регистр AX загружается значением PRICE, а регистр BX - значением
QTY, после чего происходит вызов подпрограммы. Директива EXTRN в основной
программе определяет SUBMUL как точку входа в подпрограмму.
Подпрограмма содержит директиву PUBLIC (после ASSUME), которая
указывает компоновщику, что точкой входа для выполнения является метка
SUBMUL. Подпрограмма выполняет умножение содержимого регистра AX (цена) на
содержимое регистра BX (количество). Результат умножения вырабатывается в
регистровой паре DX:AX в виде шест.002E 4000.
Так как подпрограмма не определяет каких-либо данных, то ей не
требуется сегмент данных. Если бы подпрограмма имела сегмент данных, то
только она одна использовала бы свои данные.
Также в подпрограмме не определен стековый сегмент, так как она
использует те же стековые адреса, что и основная программа. Таким образом,
стек определенный в основной программе является доступным и в
подпрограмме. Для компоновщика необходимо обнаружить по крайней мере один
стек и определение стека в основной программе является достаточным.
Рассмотрим теперь таблицы идентификаторов, вырабатываемые после
каждого ассемблирования. Обратите внимание, что SUBMUL в таблице
идентификаторов для основной программы имеет атрибуты FAR и External
(внешний), а для подпрограммы - F (для FAR) и Global (глобальный). Этот
последний атрибут указывает, что данное имя доступно из вне подпрограммы,
т.е. глобально.
Карта компановки (в конце листинга) отражает организацию программы в
памяти. Заметьте, что здесь имеются два кодовых сегмента (для каждого
ассемблирования) с разными стартовыми адресами. Последовательность
расположения кодовых сегментов соответствует последовательности указанных
для компановки объектных модулей (обычно основная программа указывается
первой). Таким образом, относительный адрес начала основной программы -
шест.00000, а подпрограммы - шест.00020.
При трассировке выполнения программы можно обнаружить, что команда
CALL SUBMUL имеет объектный код
9A 0000 D413
Машинный код для межсегментного CALL - шест.9A. Эта команда сохраняет в
стеке регистр IP и загружает в него значение 0000, сохраняет в стеке
значение шест.13D2 из регистра CS и загружает в него шест.D413. Следующая
выполняемая команда находится по адресу в регистровой паре CS:IP т.е.
13D40 плюс 0000. Обратите внимание, что основная программа начинается по
адресу в регистре CS, содержащему шест.13D2, т.е. адрес 13D20. Из карты
компановки видно, что подпрограмма начинается по относительному адресу
шест.0020. Складывая эти два значения, получим действительный адрес
кодового сегмента для подпрограммы:
Адрес в CS 13D20
Смещение в IP 0020
Действительный адрес 13D40
Компоновщик определяет это значение точно таким же образом, и подставляет
его в операнд команды CALL.
ПРОГРАММА: ИСПОЛЬЗОВАНИЕ ДИРЕКТИВЫ PUBLIC В КОДОВОМ СЕГМЕНТЕ
________________________________________________________________
__________________________________________________________________________
page 60,132
TITLE CALLMUL2 (EXE) Вызов подпрограммы умнож.
EXTERN SUBMUL:FAR
;----------------------------------------------
0000 STACKSG SEGMENT PARA STACK 'Stack'
0000 40 [????] DW 64 DUP(?)
0080 STACKSG ENDS
;----------------------------------------------
0000 DATASG SEGMENT PARA 'Data'
0000 0140 QTY DW 0140H
0002 2500 PRICE DW 2500H
0004 DATASG ENDS
;----------------------------------------------
0000 CODESG SEGMENT PARA PUBLIC 'Code'
0000 BEGIN PROC FAR
ASSUME CS:CODESG,DS:DATASG,SS:STACKSG
0000 1E PUSH DS
0001 2B C0 SUB AX,AX
0003 50 PUSH AX
0004 B8 ---- R MOV AX,DATASG
0007 8E D8 MOV DS,AX
0009 A1 0002 R MOV AX,PRICE ;Загрузить стоимость
000C 8B 1E 0000 R MOV BX,QTY ; и количество
0010 9A 0000 ---- E CALL SUBMUL ;Вызвать подпрограмму
0015 CB RET
0016 BEGIN ENDP
0016 CODESG ENDS
END BEGIN
________________________________________________________________________
Segments and Group:
N a m e Size Align Combine Class
CODESG . . . . . . . . . . . . .0016 PARA PUBLIC 'CODE'
DATASG . . . . . . . . . . . . .0004 PARA NONE 'DATA'
STACKSG. . . . . . . . . . . . .0080 PARA STACK 'STACK'
Symbols:
N a m e Type Value Attr
BEGIN. . . . . . . . . . . . . F PROC 0000 CODESG Lenght=0016
PRICE. . . . . . . . . . . . . L WORD 0002 DATASG
QTY. . . . . . . . . . . . . . L WORD 0000 DATASG
SUBMUL . . . . . . . . . . . . L FAR 0000 External
page 60,132
TITLE SUBMUL2 Вызываемая подпрограмма умножения
;----------------------------------------------
0000 CODESG SEGMENT PARA PUBLIC 'CODE'
0000 SUBMUL PROC FAR
ASSUME CS:CODESG
PUBLIC SUBMUL
0000 F7 E3 MUL BX ;AX-стоимость, BX-количество
0002 CB RET ;Произведение в DX:AX
0003 SUBMUL ENDP
0003 CODESG ENDS
END SUBMUL
Segments and Groups:
N a m e Size Align Combine Class
CODESG. . . . . . . . . . . . . 0003 PARA PUBLIC 'CODE'
Symbols:
N a m e Type Value Attr
SUBMUL. . . . . . . . . . . . .F PROC 0000 CODESG Global Length=0003
LINK
IBM Personal Computer Linker
Version 2.30 (C) Copyright IBM Corp 1981, 1985
Object Modules: B:CALLMUL2+B:SUBMUL2
Run File: [B:CALLMUL2.EXE]: "return"
List File: [NUL.MAP]: CON
Libraries [.LIB]: "return"
Start Stop Length Name Class
00000H 00022H 0023H CODESG CODE "-- Примечание: 1 сегмент кода
00030H 00033H 0004H DATASG DATA
00040H 000BFH 0080H STACKSG STACK
Program entry point at 0000:0000
__________________________________________________________________________
Рис.21.4. Кодовый сегмент, определенный как PUBLIC.
Следующий пример на рис.21.4 представляет собой вариант программы на
рис.21.3. Имеется одно изменение в основной программе и одно - в
подпрограмме. В обоих случаях в директиве SEGMENT используется атрибут
PUBLIC:
CODESG SEGMENT PARA PUBLIC 'CODE'
Рассмотрим результирующую карту компоновки и объектный код команды
CALL.
Из таблицы идентификаторов (в конце каждого листинга ассемблирования)
следует: обобщенный тип кодового сегмента CODESG - PUBLIC (на рис.21.3
было NONE). Но более интересным является то, что карта компановки в конце
листинга показывает теперь только один кодовый сегмент! Тот факт, что оба
сегмента имеют одни и те же имя (CODESG), класс ('CODE') и атрибут PUBLIC,
заставил компоновщика объединить два логических кодовых сегмента в один
физический кодовый сегмент. Кроме того, при трассировке выполнения
программы можно обнаружить, что теперь команда вызова подпрограммы имеет
следующий объектный код:
9A 2000 D213
Эта команда заносит шест.2000 в регистр IP и шест.D213 в регистр CS. Так
как подпрограмма находится в общем с основной программой кодовом сегменте,
то в регистре CS устанавливается тот же стартовый адрес - шест.D213. Но
теперь смещение равно шест.0020:
Адрес в CS: 13D20
Смещение в IP: 0020
Действительный адрес: 13D40
Таким образом, кодовый сегмент подпрограммы начинается, очевидно, по
адресу шест.13D40. Правильно ли это? Карта компановки не дает ответа на
этот вопрос, но можно определить адрес по листингу основной программы,
которая заканчивается на смещении шест.0016. Так как кодовый сегмент для
подпрограммы определен как SEGMENT, то он должен начинаться на границе
параграфа, т.е. его адрес должен нацело делиться на шест.10 или правая
цифра адреса должна быть равна 0. Компоновщик размещает подпрограмму на
ближайшей границе параграфа непосредственно после основной программы -
этот относительный адрес равен шест.00020. Поэтому кодовый сегмент
подпрограммы начинается по адресу 13D20 плюс 0020 или 13D40.
__________________________________________
| Основная программа... | Подпрограмма |
| (не используемый участок) | |
|___________________________|______________|
| | |
13D20 13D30 13D40
Рассмотрим, каким образом компоновщик согласует данные, определенные в
основной программе и имеющие ссылки из подпрограммы.
ПРОГРАММА: ОБЩИЕ ДАННЫЕ В ПОДПРОГРАММЕ
________________________________________________________________
__________________________________________________________________________
page 60,132
TITLE CALLMUL3 (EXE) Вызов подпрограммы
; для умножения
EXTRN SUBMUL:FAR
PUBLIC QTY,PRICE
;-------------------------------------------------
0000 STACKSG SEGMENT PARA STACK 'Stack'
0000 40 [????] DW 64 DUP(?)
0080 STACKSD ENDS
;-------------------------------------------------
0000 DATASG SEGMENT PARA PUBLIC 'Data'
0000 0140 QTY DW 0140H
0002 2500 PRICE DW 2500H
0004 DATASG ENDS
;-------------------------------------------------
0000 CODESG SEGMENT PARA PUBLIC 'Code'
0000 BEGIN PROC FAR
ASSUME CS:CODESG,DS:DATASG,SS:STACKSG
0000 1E PUSH DS
0001 2B C0 SUB AX,AX
0003 50 PUSH AX
0004 B8 ---- R MOV AX,DATASG
0007 8E D8 MOV DS,AX
0009 9A 0000 ---- E CALL SUBMUL ;Вызвать подпрограмму
000E CB RET
000F BEGIN ENDP
000F CODESG ENDS
END BEGIN
_____________________________________________________________________
Segments and Groups:
N a m e Size Align Combine Class
CODESG . . . . . . . . . . . . 000F PARA PUBLIC 'CODE'
DATASG . . . . . . . . . . . . 0004 PARA PUBLIC 'DATA'
STACKSG. . . . . . . . . . . . 0080 PARA STACK 'STACK'
Symbols:
N a m e Type Value Attr
BEGIN. . . . . . . . . . . . . F PROC 0000 CODESG Length=000F
PRICE. . . . . . . . . . . . . L WORD 0002 DATASG Global
QTY. . . . . . . . . . . . . . L WORD 0000 DATASG Global
SUBMUL . . . . . . . . . . . . L FAR 0000 External
page 60,132
TITLE SUBMUL Подпрограмма для умножения
EXTRN QTY:WORD,PRICE:WORD
;-------------------------------------------------
0000 CODESG SEGMENT PARA PUBLIC 'CODE'
0000 SUBMUL PROC FAR
ASSUME CS:CODESG
PUBLIC SUBMUL
0000 A1 0000 E MOV AX,PRICE
0003 8B 1E 0000 E MOV BX,QTY
0007 F7 E3 MUL BX ;Произведение в DX:AX
0009 CB RET
000A SUBMUL ENDP
000A CODESG ENDS
END SUBMUL
_____________________________________________________________________
Segments and Groups:
N a m e Size Align Combine Class
CODESG . . . . . . . . . . . . 000A PARA PUBLIC 'CODE'
Symbols:
N a m e Type Value Attr
PRICE. . . . . . . . . . . . . V WORD 0000 External
QTY. . . . . . . . . . . . . . V WORD 0000 External
SUBMUL . . . . . . . . . . . . F PROC 0000 CODESG Global Length=000A
_____________________________________________________________________
LINK
IBM Personal Computer Linker
Version 2.30 (C) Copyright IBM Corp 1981, 1985
Object Modules: B:CALLMUL3+B:SUBMUL3
Run File: [B:CALLMUL3.EXE]: "return"
List File: [NUL.MAP]: CON
Libraries [.LIB]: "return"
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.5. Общие данные в подпрограмме.
Наличие общих данных предполагает возможность обработки в одном
ассемблерном модуле данных, которые определены в другом ассемблерном
модуле. Изменим предыдущий пример так, чтобы области QTY и PRICE
по-прежнему определялись в основной программе, но загрузка значений из
этих областей в регистры BX и AX выполнялась
...Поділитися у соцмережах