St segment common
dw 64 dup (?)
St ends
N equ 1000
Data segment public
A dw N dup (?)
S dw ?
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 S; синоним имени Summa
Newline
Finish
public Error; Входная точка
Error:lea dx,T
Outstr
Finish
Code ends
end Start; головной модуль
Comment * модуль p2.asm
Суммирование массива, контроль ошибок
include io.asm не нужен – нет ввода/вывода
Стек головного модуля не увеличивается
В конечном end не нужна метка Start
*
M equ 1000
Data segment common
B dw M dup (?)
Summa dw ?
Data ends
Code segment public
assume cs:Code,ds:Data
public Sum; Входная точка
Sum proc far
push ax
push cx
push bx; сохранение регистров
xor ax,ax
mov cx,M
xor bx,bx; индекс 1-го элемента
L: add ax,B[bx]
jno L1
; Обнаружена ошибка
pop bx
pop cx
pop ax
extrn Error:near
jmp Error
L1: add bx,type B
loop L
mov Summa,ax
pop bx
pop cx
pop ax; восстановление регистров
Ret
Code ends
End
Теперь сегменты данных будут накладываться друг на друга (в головном модуле сегмент данных немного длиннее, так что длина итогового сегмента данных будет равна максимальной длине накладываемых сегментов). Как видим, почти все имена в модулях теперь являются локальными, однако из-за наложения сегментов данных друг на друга получается, что имя A является синонимом имени B (это имена одной и той же области памяти – нашего массива). Аналогично имена S и Summa также будут обозначать одну и ту же переменную в сегменте данных.
Можно сказать, что при наложении друг на друга сегментов разных модулей получаются неявные статические связи по данным (очевидно, что накладывать друг на друга кодовые сегменты почти всегда бессмысленно). Вследствие этого можно (как в нашем примере) резко сократить число явных связей по данным (то есть входных точек и внешних адресов). Надо, однако, заметить, что такой стиль модульного программирования является весьма опасным: часто достаточно ошибиться в расположении хотя бы одной переменной в накладываемых сегментах, чтобы программа стала работать неправильно.[51] Например, рассмотрите, что будет, если поменять в одном из накладываемых сегментов местами массив и переменную для хранения суммы этого массива (никакой диагностики об ошибке при этом, естественно, не будет).
Заметим, что во всех предыдущих примерах нам было всё равно, в каких именно конкретных областях памяти будут располагаться сегменты нашей программы во время счёта. Более того, считается хорошим стилем так писать программы, чтобы их сегменты на этапе счёта могли располагаться в любых свободных областях оперативной памяти компьютера. Однако очень редко может понадобиться расположить определённый сегмент с явно заданного программистом адреса оперативной памяти. Для обеспечения такой возможности на языке Ассемблер служит параметр at <адрес сегмента> директивы segment. Здесь <адрес сегмента> является адресом начала сегмента в оперативной памяти, делённым на 16. В качестве примера рассмотрим такое описание сегмента с именем Interrupt_Vector.
Interrupt_Vector Segment at 0
Divide_by_Zero dd ?
Trace_Program dd ?
Fatal_Interrupt dd ?
Int_Command dd ?
Into_Command dd Code:Error
Interrupt_Vector ends
Этот сегмент во время счёта программы будет накладываться на начало вектора прерываний, а переменные этого сегмента будут обозначать конкретные адреса процедур обработки прерываний. Так заданный сегмент данных может облегчить написание собственных процедур-обработчиков прерываний.
Рассмотрим теперь второй этап работы редактора внешних связей – настройку всех внешних имён на соответствующие им входные точки в других модулях. На этом этапе редактор внешних связей начинает просматривать паспорта всех модулей и читать оттуда их внешние имена. Эта работа начинается с головного модуля, для всех его внешних имён ведётся поиск соответствующих им входных точек в других модулях. Если такой поиск оказывается безуспешным, то редактор внешних связей фиксирует ошибку: неразрешённое (в смысле ненайденное) внешнее имя.
Для некоторого внешнего имени могут существовать и несколько входных точек в разных модулях. При этом многие редакторы внешних связей такую ошибку не фиксируют и берут первое встреченное внешнее имя, так что программисту надо быть осторожным и обеспечить уникальность входных имён у всех модулей. К большому сожалению, некоторые редакторы внешних связей (в том числе и в Ассемблере MASM-4.0) не проверяют соответствие типов у внешнего имени и входной точки. Таким образом, например, внешнее имя-переменная размером в слово может быть связано с входной точкой – переменной размером в байт или вообще с меткой. При невнимательном программировании это может привести к серьёзным ошибкам, которые трудно найти при отладке программы.
Когда для некоторого внешнего имени найдена соответствующая входная точка, то устанавливается связь: адрес входной точки записывается в соответствующее поле внешнего имени. Например, для команды
call Sum; Формат i32=seg:off= callseg:off
на место поля off запишется смещение начала процедуры суммирования в объединённом после склеивания сегменте кода, а поле seg пока останется незаполненным, его значение (адрес начала сегмента кода, делённый на 16) будет известно только после размещения программы в оперативной памяти перед началом счёта. Аналогично, на место команды
mov cx,N
запишется команда
mov cx,1000
Итак, если для каждого внешнего имени найдена входная точка в другом объектном модуле, то редактор внешних связей нормально заканчивает свою работу, выдавая в качестве результата загрузочный модуль. Загрузочный модуль, как и объектный, состоит из тела модуля и паспорта. Тело загрузочного модуля содержит все его сегменты,[52] а в паспорте собраны необходимые для дальнейшей работы данные:
· информация обо всех сегментах (длина и класс сегмента), в частности, данные о сегменте стека;
· информация обо всех ещё неопределённых полях в сегментах модуля;
· информация о расположении входной точки программы (в нашем примере – метки Start);
· другая необходимая информация.
На рис. 10.2 показан схематический вид загрузочного модуля, полученного для первого варианта нашего примера (со склеиваемыми сегментами). Внутри сегмента кода показаны незаполненные поля (они подчёркнуты). Метку Start можно рассматривать как единственную входную точку загрузочного модуля.
p.exe | ||
St segment stack | ||
Data segment | ||
Code segment Start: mov ax,Data . . . call Code:Sum | ||
Рис. 10.2. Схематический вид загрузочного модуля, незаполненные поля подчёркнуты. | ||
Вот теперь всё готово для запуска программы на счёт. Осталось только поместить нашу программу в оперативную память и передать управление на её начало (в нашем примере – на метку Start). Эту работу делает служебная программа, которая называется статическим загрузчиком (далее мы познакомимся и с другим видом загрузчика – динамическим загрузчиком). Сейчас мы рассмотрим схему работы статического загрузчика.
Дата добавления: 2015-10-05; просмотров: 705;