Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Textnew2

.pdf
Скачиваний:
13
Добавлен:
06.02.2018
Размер:
1.48 Mб
Скачать

mov ecx, txtmes ; address of txtmes (и далее formax) int 80h

mov eax,1

int 80h ; function=exit for Linux

SEGMENT .data

narraydd 3,17,-5,27,0,-11,17,33,-1,9

txtmes db 'Максимальное число в массиве есть ' lenmes equ $-txtmes

cnt dd 0 SEGMENT .data

formax resb 10

Рис. 4.4.1. Поиск максимального в массиве чисел на основе адресации с масштабированием

В этой программе для доступа к i-му элементу массива narray используется задание операнда в виде [narray+4*esi], где индексный регистр esi содержит значение этого индекса, а стоящее перед ним обозначение 4* задает масштаб - размер элемента используемого массива. (В данном примере массив состоит из четырехбайтовых слов, поэтому применен масштаб 4). Согласовано с использованием масштабирования, значение индексного регистра изменяется в данном примере на единицу командой инкрементирования, а определения завершения цикла организуется на основе сравнения значения в регистре esi с первым значением индекса за пределами массива (числом 10). Читателя для сравнения следует обратиться к ранее рассмотренному примеру решения той же задачи на рис. 4.2.1, где была использован обычный индексный способ адресации. (Там приходилось сравнивать со значением 10*4 и увеличивать индексный регистра на 4.)

Способ адресации с масштабированием позволяет не только более явно задавать действия с элементами числовых массивов, но и сократить число ошибок, возникающих от расхождения математического понятия индекса со значением индексного регистра в простых способах адресации.

Упражнения.

1.Переработать программу с рис. 4.2.1, чтобы перед поиском элемента в массиве, она вводила со стандартного ввода числа в этот массив.

2.Разработать программу, которая находит минимальное число в массиве чисел и номер порядкового элемента этого массива, содержащего минимальное число.

5. ВЗАИМОСВЯЗИ ПРОГРАММНЫХ ЕДИНИЦ

71

5.1. Многомодульная разработка программ

Только самые простые программы изготавливаются из одного файла исходной программы. Более сложные программы в процессе разработки разбиваются на множество исходных модулей, как наиболее обще называют отдельные файлы исходной программы. Такое разбиение помогает справиться с общей сложностью программы и выполнять разработку по частям: вначале написать и отладить только часть программы, затем аналогичное выполнить с другой частью и т.д. Все современные языки предоставляют средства для такого исходного разбиения программы и указания связи получаемых частей. (Заметим, что Паскаль - в отличие от Турбо Паскаля и его модификаций - не позволял разбивать программу на отдельно транслируемые модули.)

Проблема связей программ, которые в совокупности после соединения составляют единую выполняемую программу, особенно четко просматривается именно с позиций программирования на ассемблере и машинных структур объектных файлов. Большинство современных систем программирования работают по принципу компиляция-компоновка-выполнение. Это значит, что непосредственным результатом обработки файла исходной программы служит файл специального служебного формата, называемый объектным модулем (или объектным файлом). В типовых системах разработки программ для операционных систем Windows, OS/2, MS-DOS имена объектных файлов традиционно имеют расширение OBJ, а в Unixсистемах - расширение имени, задаваемое единственной буквой o.

Объектный файл представляет собой полузаконченную форму исполняемого файла. Он содержит, в основном, двоичные коды машинных команд и данных, но, кроме того, вспомогательную информацию для связывания множества объектных модулей в единый исполняемый файл. Проблема компоновки объектных модулей становиться понятной только при рассмотрении проблем перехода к машинным кодам команд и данных. Сейчас же мы остановимся на внешней стороне этой проблемы, рассматривая какую дополнительную информацию и как следует задавать для последующей конкретной связи объектных модулей в исполняемую программу.

Из языков высокого уровня, подобных Алголу, читателю должно быть известно, что никакие информационные объекты нельзя использовать без их описания как данных. Такое описание задается специальными конструкциями, которые неявно задают размер этих объектов в памяти компьютера и место занимаемое ими в этой памяти. На ассемблере подобные описания, как мы уже видели, представляют собой конструкции на основе директив RESx и Dx, гдн символ x обобщенно обозначает одну из букв B, W D. Именно эти директивы явно задают место в памяти компьютера для данных.

Встает вопрос, что нужно делать, если разработчик хочет использовать в программе одного исходного модуля данные, описанные подобными конструкциями в другом исходном модуле. Нетрудно понять, что описать их тем же самым способом в первом из указанных модулей нельзя - одни и те же данные (по смыслу их использования и, главное, по имени области их размещения) нельзя размещать в

72

разных местах памяти. Нельзя, потому что в машинных командах указываются действия над данными, расположенными по конкретным адресам и не может быть данных, расположенных по двум или более различным адресам для одной исполняемой программы. Поэтому в исходном модуле программ для данных, определенных

вдругом мести и приходится записывать буквально (с учетом английского диалекта, на котором записаны ключевые слова программ), что данные с таким-то именем

вдействительности определены и размещены в другом исходном модуле.

Такая запись на языке ассемблера NASM делается в директиве EXTERN. Последняя имеет вид

EXTERN имя_внешнего_имени

или, в общем случае,

EXTERN перечисление_внешних_имен

где перечисление_внешних_имен представляет собой перечисление через запятую набора различных внешних имен.

В свою очередь те имена, которые предполагается использовать для доступа к данным и подпрограммам из другого исходного модуля, необходимо дополнительно сопровождать соответствующей информацией для системы разработки. Эта информация на ассемблере NASM задается в директиве GLOBAL, с которой читатель уже частично знаком. Именно, все имена, которые будут использоваться как внешние (или которые только могут использоваться как внешние), следует включить в состав перечисления после служебного слова GLOBAL. Имена, перечисленные в одной из директив GLOBAL, называются глобальными. Существенной особенностью таких имен является то, что они попадают в объектный модуль, сохраняя в нем свое обозначение. В пояснение заметим, что все остальные (не глобальные) имена не сохраняются в объектном модуле, а превращаются в нем лишь в числовые смещения от начала соответствующего сегмента. Те имена исходного файла, которые, согласно сделанным пояснениям, программист должен объявить глобальными или внешними, могут быть произвольно сгруппированы в одном, нескольких или многих директивах (соответственно, GLOBAL или EXTERN).

Рассмотрим для дальнейшего изучения описанных средств, демонстрационную программу, которая образуется двумя исходными файлами prim5.asm и prim5a.asm на ассемблере. Текст этих программ приведен на рис. 5.1.1 и 5.1.2.

;Многомодульные программы (использование GLOBAL и EXTERN) ;Главная программа - файл prim5.asm

;use:> nasml2 prim5 prim5a

GLOBAL

_start

GLOBAL

sla, slb, sum

EXTERN

funa

SEGMENT

.code

_start

mov DWORD [sla], 27

 

mov WORD [slb], 19

 

call funa

73

 

mov eax, [sum]

 

; сюда следует вставить преобразование

 

; в десятичную систему и вывод результата на экран

 

mov esi,10 ; base of position digit system

 

mov ecx, 0 ; reset digit counter

pov:

mov edx, 0 ; null into left part of devident

 

div esi

; divide for next digit = rest

 

add dl, '0'

 

 

push edx

 

 

inc ecx

; step into counter

 

cmp eax, 0

 

 

jne pov

 

 

mov [cnt], ecx

 

mov ebx, numtxt

izv:

pop edx

 

 

mov byte [ebx],dl

 

inc ebx

 

 

loop izv

 

 

mov eax,4 ; N function=write

 

mov ebx,1

; N handle=1 (stdout)

 

mov ecx, numtxt ; address of text

 

mov edx,[cnt] ; number of byte

 

int 80h

 

 

mov eax, 1

 

 

int 80h ; function=exit

 

SEGMENT .data

sla

DD 0

 

slb

DW

0

sum

DD 0

 

cnt

dd 0

 

SEGMENT .bss numtxt times 10 db 0

Рис. 5.1.1. Файла prim5.asm многомодульной программы для Linux

;Многомодульные программы (использование GLOBAL и EXTERN)

;Дополнительный файл Prima.asm:

SEGMENT .data

74

EXTERN sla, slb, sum

GLOBAL funa

SEGMENT .code ; may be other segment, for example, .codes

;procedure funa funa:

push eax push ebx mov eax,[sla] mov ebx, 0 mov bx, [slb] add eax, ebx

mov [sum], eax pop ebx

pop eax ret

;end procedure funa

Рис. 5.1.2. Файла prim5a.asm многомодульной программы для Linux

В первом модуле этой программы, названном для определенности prim5.asm, определены именованные области данных sla, slb и sum. Причем в предположении их использования в другом модуле эти имена объявлены директивой GLOBAL как глобальные. В этом же исходном модуле используется имя funa, которому в программе исходного модуля не соответствует никакой метки и, вообще никакого конкретного места внутри программы. Если бы в программу исходного модуля не была вставлена директива EXTERN funa, то при ее трансляции появилась бы ошибка, что имя funa не определено. Последняя же директива поясняет компилятору, что программист совсем не ошибся, а место в программе, обозначаемое меткой funa, будет находится где-то в другом исходном модуле и окончательное согласование ее использования выполняет уже компоновщик.

Во втором модуле программы, названном для определенности prim5a.asm, имя funa определено как метка (начинающая по существу процедуру). Чтобы сделать это имя доступным для использования компоновщику, в этом втором модуле имя funa указано в директиве GLOBAL. Если такое указание не сделано в этом модуле, то при его компиляции ошибка об этом не появится - для глобального имени все равно, будет оно использоваться извне или нет. Но при выполнении компоновки обоих получающихся объектных модулей появится сообщение, что в программе модуля prim5.o сделана ссылка на несуществующее внешнее имя.

Кроме того, в исходном модуле prim5a.asm используются имена областей данных sla, slb и sum, которым в этом модуле не соотнесено никакое место в памяти. В то же время этот модуль содержит директиву EXTERN, в которой указанные имена перечисляются обозначаясь таким образом как внешние. Если директиву EXTERN опустить, ошибка появится уже при компиляции исходного модуля prim5a.asm, так

75

как компилятор имеет все основания полагать, что программист забыл о каком-ли- бо определении или объявлении упоминаемых имен.

Заметим, что в языках высоко уровня делается терминологическое отличие определения имен и объявления имен. Более ясно эта проблема смотрится на ассемблере. При определении имени ему оно соотносится с конкретным местом в памяти. Программист может явно (буквально, "пальцем") показать, какие данные или команды идут перед местом, обозначенным этим именем, а какие - после него (в действительности имени соотносится совершенно конкретное смещение в сегменте данных или команд, соответственно). При объявлении имени (директивой EXTERN в NASM) лишь объявляется, что где-то в другом участке результирующей программы будет определено место, которое будет обозначаться этим именем. Таким образом, объявление имени содержит скорее намерение в дальнейшем определить имя, чем его действительное определение (другое дело, что модуль, в котором это имя определено, может быть изготовлен по времени гораздо раньше - компилятор не знает и не может знать об этом).

Существенной деталью, использования директивы GLOBAL является необходимость ее указания до места использования перечисленных в ней имен (иначе возникают ошибки на стадии компоновки). (Возможно, эта особенность будет устранена в следующих версиях NASM, так как рациональной причины оставлять эту особенность не видно.) Для ассемблеров MASM и TASM не следует размещать директиву EXTRN в сегменте, отличном от того, в котором действительно будут определены перечисленные в ней имена. В старых версиях операционных систем такое требование было связано с устремлением обеспечить, по возможности, максимальную степень контроля над именами в ходе разработки. (Сейчас, при одном сегменте изменяемых данных и одном сегменте машинных команд большого смысла в подобном требовании нет.)

Если порознь выполнить компиляцию исходных модулей, приведенных на рис. 5.1.1 и 5.1.2, используя рекомендованные выше соглашения, то получаться объектные модули prim5.o и prim5a.o. Для связывания (компоновки) их в единую выполняемую программу следует использовать вызов компоновщика в виде команды

ld -o prim5.exe prim5.o prim5a.o

Автоматизировать процесс разработки данной программы можно с помощью командного файла, текст которого приведен на рис. 5.1.3.

nasm -f elf $1.asm -l $1.lst nasm -f elf $2.asm -l $2.lst ld -o $1.exe $1.o $2.o

Рис. 5.1.3. Командный файла nasml2 для изготовления программы из двух исходных моделей в Linux

Применение такого командного файл особенно удобно при многократных ошибках в ее составлении и необходимости многократного запуска системы разработки.

76

Если данный командный файл назван nasml2, то его использования для данной задачи заключается в вызове с помощью командной строки

nasml2 prim5 prim5a

При выполнении командного файла первый и второй аргумент этого вызова используются вместо формальных аргументов $1 и $2 командного файла. Этот же командный файл можно использовать для разработки любой программы, состоящей из двух исходных модулей.

В ассемблерах MASM и TASM вместо директивы EXTERN используется директива EXTRN. Кроме отсутствия в ее названии одной буквы (для сокращения) в директиве EXTRN необходимо для каждого имени указывать его атрибут. Эти атрибуты задаются служебными словами BYTE, WORD, DWORD, NEAR, FAR и должны следовать за именем через разделяющее двоеточие. Для аналога нашего примера в указанных ассемблерах следует записать

EXTRN funa: NEAR

EXTRN sla: DWORD , slb:BYTE, sum: DWORD

Таким образом, в указанных ассемблерах внутри директивы EXTRN задаются не только внешние имена, но и характеристики размера памяти, связанной с каждым из этих имен.

Вместо директивы GLOBAL в указанных ассемблерах имеется соответствующая директива PUBLIC, применение которой ничем кроме ключевого слова не отличается от уже рассмотренной.

5.2. Использование библиотек объектных модулей

Когда объектных модулей, необходимых для компоновки программы, оказывается много, программисту становится неудобным перечислять их все при вызове компоновщика. Ситуация становится почти катастрофической, когда таких модулей многие десятки и даже сотни.

Выход был найден с помощью специализированных наборов объектных модулей, хранимых как отдельные файлы и называемых обычно библиотеками объектных модулей. В ОС Unix для аналогичных целей вначале использовался еще термин архив объектных модулей, но к настоящему времени, в связи с широким использованием архивирования как метода сжатого сохранения любых файлов и каталогов файлов, последняя терминология оказывается несколько двусмысленной (по крайней мере, неоднозначной).

Применение библиотек требует использования служебных утилит обслуживания, называемых обычно библиотекарями или архиваторами (последний термин характерен для Unix и, как уже объяснялось, несколько устарел).

Утилита обслуживания библиотек в Unix называется ar и ее вызовы имеют в упрощенной форме общей вид

ar опции модификаторы имя_библиотеки объектные_файлы

где компонент объектные_файлы представляет собой перечисление имен файлов, разделяемых пробелами. Заметим, что данная утилита может применяться для

77

сохранения в виде компактного набора не только объектных файлов, но и любых других файлов, но именно библиотеки объектных файлов являются мощным вспомогательным средством при разработке сложных программ.

Основными опция данной утилиты, которые мы рассмотрим, являются опции -r, -d, -m, -t. Первая из них задает добавление нового объектного файла или файлов к библиотеке, вторая опция задает удаление указанных в перечне файлов из библиотеки, третья опция задает перемещение указанных в перечне файлов из библиотеки в текущий каталог, а опция -t приказывает выдать на стандартный вывод перечень файлов, содержащихся в библиотеке. Модификаторы призваны повысить эффективность использования и построения библиотек и записываются непосредственно за опцией. Модификатор, задаваемый буквой 'c', предназначен для указания, что библиотеку нужно создать. Стандартное расширение имени библиотек объектных модулей в Unix содержит единственную букву 'a' (строчную латинскую букву).

Следующий пример демонстрирует использование библиотеки объектных модулей. Сами модули формируются из исходных файлов с именами stwrite, stread, stexit, содержащих, соответственно, процедуры stdread, stdwrite, stdexit и приведенные на рис. 5.2.1, 5.2.2 и 5.2.3.

GLOBAL stdread SEGMENT .text

stdread: ;<adress into ecx, max len into edx, actlen ->eax > ;--- read(1, buf, len) == <3>(ebx, ecx, edx)

push ebx

mov eax,3 ; N function=read mov ebx,0 ; 0 handle=0 (stdin) int 80h

pop ebx ret

Рис. 5.2.1. Процедура stdread в файле stread.asm для собственной библиотеки Linux

GLOBAL stdwrite SEGMENT .text

stdwrite: ;<adress into ecx, max len into edx> ;--- write(1, buf, len) == <4>(ebx, ecx, edx)

push eax push ebx

mov eax,4 ; N function=write mov ebx,1 ; N handle=1 (stdout) int 80h

pop ebx pop eax

78

ret

Рис. 5.2.2. Процедура stdwrite в файле stwrite.asm для собственной библиотеки Linux

GLOBAL stdexit SEGMENT .text

stdexit:

mov eax,1

int 80h ; function=exit ret

Рис. 5.2.3. Процедура stdexit в файле stexit.asm для собственной библиотеки Linux

Формирование объектных файлов проще всего выполнить с помощью командного файла, содержимое которого приведено на рис. 5.2.4? и имеющего для дальнейшего использования имя nasmc.

nasm -f elf $1.asm -l $1.lst

Рис. 5.2.4 Командный файл nasmc для раздельной трансляции исходных модулей

Далее с помощью командного вызова

ar -rc myliba.a stwrite.o stread.o stexit.o

нужно построить библиотеку с именем myliba.a . Использование библиотеки предполагает программа, приведенная на рис. 5.2.5.

GLOBAL _start

EXTERN stdread, stdwrite, stdexit SEGMENT .text

_start:

;--- stdread(buf, 80) == call stdread < ecx, edx >, result into eax mov ecx, buf ; address of buf

mov edx,80 ; number of byte

call stdread ; arguments into ECX, EDX - адрес области для данных и ее дли-

на

; число прочитанных байтов в регистре EAX mov [len],eax

mov byte [buf+2],'!'

;--- stdwrite(buf, [len]) == call stdwrite < ecx, edx > mov ecx, buf ; address of buf

mov edx,[len] ; number of byte

call stdwrite ; arguments into ECX, EDX - адрес области данных и их длина call stdexit

79

SEGMENT .bss buf resb 80

SEGMENT .data lendd 0

Рис. 5.2.5. Пример prim5e.asm использования процедур из собственной библиотеки

Изготовление исполняемой программы из исходного текста на рис. 5.2.5 может осуществляться двумя командными вызовами, приведенными в следующих строках

./nasmc prim5e

ld -o prime.exe prim5e.o myliba.a

Процесс изготовления исполняемой программы примера можно автоматизировать, применяя командный файл, текст которого дан на рис. 5.2.6

./nasmc stwrite

./nasmc stread

./nasmc stexit

ar -rc myliba.a stwrite.o stread.o stexit.o ar -t myliba.a

./nasmc prim5e

ld -o prime.exe prim5e.o myliba.a

Рис. 5.2.6. Командный файл komfil для построения и использования библиотеки

Заметим, что имена процедур в объектных модулях библиотеки никак не связаны (кроме возможных человеческих ассоциаций разработчика) с именами соответствующих объектных файлов. Более того, один библиотечный файл может содержать множество процедур и, в общем случае, любых внешних имен, используемых в дальнейшем из этой библиотеки. Установление связей между компонентами будущей исполняемой программы в процессе компоновки осуществляется исключительно по внешним именам в модулях, занесенных в библиотеку, а имена объектных модулей существенны только для приказов вставки или удаления объектных модулей в (из) библиотеки. В частности, процедуры stdread, stdwrite, stdexit целесообразно поместить в один исходный файл и, как следствие, в один объектный файл. В общем случае разнесение процедур по различным объектным модулям приводит к тому, что к машинным кодам компонуемой программы подключаются машинные коды только тех объектных модулей библиотеки, которые действительно необходимы, а остальные не подключаются.

При компоновки исполняемой программы при вызове компоновщика может быть указано столько библиотек, сколько необходимо. Для этого их следует просто включить в перечень обрабатываемых компоновщиком файлов, компоновщик сам определяет, что с ними делать, используя их внутренний формат.

80

Соседние файлы в предмете Операционные системы