BAULA1 (1110624), страница 12
Текст из файла (страница 12)
Текстовый файл, содержащий первый (головной) модуль нашей программы, мы, не долго думая, назовём p1.asm, а файл второго модуля с процедурой суммирования – p2.asm. Ниже приведён текст первого модуля.
; p1.asm
; Ввод массива, вызов внешней процедуры
include io.asm
St segment stack
dw 64 dup (?)
St ends
N equ 1000
Data segment public
A dw N dup (?)
public A,N; Входные точки
extrn Summa:word; Внешняя переменная
Diagn db 'Переполнение!',13,10,'$'
Data ends
Code segment public
assume cs:Code,ds:Data,ss:St
Start:mov ax,Data
mov ds,ax
mov cx,N
sub bx,bx; индекс массива
L: inint A[bx];Ввод массива A
add bx,type A
loop L
extrn Sum:far; Внешнее имя
call Sum; Процедура суммирования
outint Summa
newline
; А теперь вызов с ошибкой
mov A,7FFFh; Maxint
mov A+2,1; Для переполнения
call Sum
outint Summa; Сюда возврата не будет
newline
finish ; Вообще-то не нужен
public Error; Входная точка
Error:lea dx,T
outstr
finish
Code ends
end Start; головной модуль
В нашем головном модуле три входные точки с именами A,N и Error и два внешних имени: Sum, которое имеет тип дальней метки, и Summa, которое имеет тип слова. Работу программы подробно рассмотрим после написания текста второго модуля с именем p2.asm.
Comment * модуль p2.asm
Суммирование массива, контроль ошибок
include io.asm не нужен – нет ввода/вывода
Используется стек головного модуля
В конечном end не нужна метка Start
*
Data segment public
Summa dw ?
public Summa; Входная точка
extrn N:abs; Внешняя константа
extrn A:word; Внешний адрес
Data ends
Code segment public
assume cs:Code,ds:Data
public Sum; Входная точка
Sum proc far
push ax
push cx
push bx; сохранение регистров
xor ax,ax; ax:=0
mov cx,N
xor bx,bx; индекс 1-го элемента
L: add ax,A[bx]
jno L1
; Обнаружена ошибка
pop bx
pop cx
pop ax
extrn Error:near
jmp Error
L1: add bx,type A
loop L
mov Summa,ax
pop bx
pop cx
pop ax; восстановление регистров
ret
Code ends
end
Наш второй модуль не является головным, поэтому в его директиве end нет метки первой команды программы. Модуль p2.asm имеет три внешних имени A,N и Error и две входные точки с именами Sum и Summa. Так как второй модуль не производит никаких операций ввода/вывода, то он не подключает к себе файл io.asm. Оба наших модуля используют общий стек объёмом 64 слова, что, наверное, достаточно, так как стековый кадр процедуры Sum невелик.
Разберём работу нашей программы. После ввода массива A головной модуль вызывает внешнюю процедуру Sum. Это статическая связь модулей по управлению, дальний адрес процедуры Sum будет известен головному модулю до начала счёта. Этот адрес будет расположен в формате i32 на месте операнда Sum команды call Sum .
Между основной программой и процедурой установлены следующие (нестандартные) соглашения о связях. Суммируемый массив знаковых чисел расположен в головном модуле и имеет общедоступное имя A. Длина массива является общедоступной константой с именем N, описанной в головном модуле. Вычисленная сумма массива помещается в общедоступную переменную с именем Summa, описанную во втором модуле. Всё это примеры статических связей по данным. Наша программа не содержит динамических связей по данным, в качестве примера такой связи можно привести передачу параметра по ссылке в процедуру другого модуля. Действительно, адрес переменной становится известным процедуре только во время счёта программы, когда он передан ей вызывающей программой (обычно в стеке).
В том случае, если при суммировании массива обнаружена ошибка (переполнение), второй модуль передаёт управление на общедоступную метку с именем Error, описанную в головном модуле. Остальные имена являются локальными в модулях, например, обратите внимание, что в обоих модулях используется одинаковая метка c именем L.
Здесь необходимо отметить важную особенность использования внешних адресов. Рассмотрим, например, команду
L: add ax,A[bx]
во втором модуле. При получении из этого предложения языка Ассемблера машинной команды необходимо знать, по какому сегментному регистру базируется наш внешний адрес A. На это во втором модуле (а только его и видит во время перевода программа Ассемблера, первый модуль недоступен!) указывает местоположение директивы
extrn A:word; Внешний адрес
внутри второго модуля. Эта директива располагается в сегменте с именем Data, а директива
assume cs:Code,ds:Data
определяет, что во время счёта на этот сегмент будет установлен регистр ds. Следовательно, адрес A соответствует области памяти в том сегменте, на который указывает регистр ds.1 Как видим, директива assume нам снова пригодилась.
Продолжим рассмотрение работы нашей программы. Получив управление, процедура Sum сохраняет в стеке используемые регистры (эта часть соглашения о связях выполняется), и накапливает сумму всех элементов массива A в регистре ax. При ошибке переполнения процедура восстанавливает значения регистров и передаёт управление на метку Error в головном модуле. В нашем примере второй вызов процедуры Sum специально сделан так, чтобы вызвать ошибку переполнения. Заметим, что переход на внешнюю метку Error – это тоже статическая связь по управлению, так как адрес метки известен до начала счёта. В то же время возврат из внешней процедуры по команде ret является динамической связью по управлению, так как конкретный адрес возврата в другой модуль будет помещён в стек только во время счёта программы.
Программа Ассемблера не в состоянии перевести каждый исходный модуль в готовый к счёту фрагмент программы на машинном языке, так как, во-первых, не может определить внешние адреса модуля, а, во-вторых, не знает будущего расположения сегментов модуля в памяти. Говорят, что Ассемблер переводит исходный модуль на специальный промежуточный язык, который называется объектным языком. Следовательно, программа Ассемблер преобразует входной модуль в объектный модуль. Полученный объектный модуль оформляется в виде файла, имя этого файла обычно совпадает с именем исходного файла на языке Ассемблер, но имеет другое расширение. Так, наши исходные файлы p1.asm и p2.asm будут переводиться (или, как чаще говорят, компилироваться или транслироваться) в объектные файлы с именами p1.obj и p2.obj.
Рассмотрим теперь, чего не хватает в объектном модуле, чтобы быть готовым к счёту фрагментом программы на машинном языке. Например, самая первая команда всех наших программ
mov ax,Data
должна переводится в машинную команду пересылки формата mov ax,i16 , однако значение константы i16, которая равна физическому адресу начала сегмента Data в памяти, делённому на 16, неизвестна программе Ассемблера и поле операнда i16 в команде пересылки остаётся незаполненным. Таким образом, в объектном модуле некоторые адреса остаются неизвестными (неопределёнными). До начала счёта программы, однако, все такие адреса обязательно должны получить конкретные значения.
Объектный модуль, получаемый программой Ассемблера, состоит из двух частей: тела модуля и паспорта (или заголовка) модуля. Тело модуля состоит из сегментов, в которых находятся команды и переменные нашего модуля, а паспорт содержит описание структуры объектного модуля. В этом описании содержатся следующие данные об объектном модуле.
-
Сведения обо всех сегментах модуля (длина сегмента, его спецификация).
-
Сведения обо всех общедоступных (экспортируемых) именах модуля, с каждым таким именем связан его тип (abs,byte,word,near и т.д.) и адрес (входная точка) внутри какого-либо сегмента модуля (для константы типа abs это просто целое число).
-
Сведения о местоположении и типе всех внешних адресов модуля.
-
Сведения о местоположении всех остальных незаполненных адресов в модуле, для каждого такого адреса содержится информация о способе его заполнения перед началом счёта.
-
Другая информация, необходимая для сборки программы из модулей.
На рис. 10.1 показано схематическое изображение объектных модулей p1.obj и p2.obj, полученных программой Ассемблера, для каждого модуля изображены его сегменты, входные точки и внешние адреса. Вся эта информация содержится в паспортах объектных модулей.2
p1.obj | p2.obj | ||
St segment stack | Data segment public extrn A:word extrn N:abs public Summa:word | ||
public A:word public N:abs extrn Summa:word | A N Summa | A N Summa | |
Extrn Sum:far public Error:near | Sum Error | Sum Error | Code segment public public Sum:far extrn Error:near |
Рис. 10.1. Схематический вид объектных модулей с внешними адресами и входными точками. |
Обратимся теперь к проблеме сборки программы из модулей. Как мы уже упоминали, эту работу выполняет специальная системная программа, которая называется редактором внешних связей. Из этого названия хорошо видно одно из назначений этой программы – устанавливать связи между внешними адресами и входными точками модулей. Рассмотрим схему работы редактора внешних связей на нашем примере.
10.2. Схема работы редактора внешних связей.
Целью работы редактора внешних связей является построение из объектных модулей почти готового к счёту программного модуля, который называется загрузочным модулем. Загрузочный модуль всё ещё не является полностью готовой к счёту программой на машинном языке, в этом модуле остаются незаполненными некоторые поля. Например, наша команда
mov ax,Data