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

Textnew2

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

|FUNA| NEAR32 | Определено вне| 0000004a|

Для имен, определенных с помощью директивы GLOBAL, также строится специальная, уже несколько иная запись в объектном файла, она имеет вид |Внешнее имя| Атрибут | Определено здесь| Отн. адрес места определения|

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

Например, если процедура FUNA размещается в кодах объектного файла со смещением 00000240 от начала этого кода, то в листинге для программного модуля, содержащего FUNA, это изображается в виде

FUNA Near32 FLAT:00000240

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

|FUNA| NEAR32 | Определено здесь| 00000240|

Если теперь при компоновке двоичные коды модуля, содержащего процедуру FUNA, оказываются при последовательной раскладке кодов всех соединяемых модулей, начиная с адрес 00003200 общего двоичного кода, а двоичные коды модуля, содержащего рассмотренный выше вызов этой функции, начиная с адреса 00002160, то действительный адрес начала процедуры FUNA будет 00003440 (вычисляясь как результат сложения базовый адрес модуля + смещение, т.е. 3200 + 240), а действительный адрес начала тех 4-х байтов машинного кода команды CALL FUNA, которые следует заполнить этим действительным адресом 3440, равен 000021Aa (2160 + 4a). Все числовые значения адресов в данном примере приведены в шестнадцатеричном коде, как и принято в системном программировании, а главное отвечает двоичному кодированию адресов в аппаратуре компьютера.

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

181

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

Напрашивается рекомендация разработчикам системного обеспечения сделать как-то так, чтобы различные программы могли использовать единственный экземпляр программы, в частности той же printf, в оперативной памяти. Эта рекомендация уже выполнена и в результате получилась динамическая компоновка. Идея динамической компоновки в том, чтобы не включать машинные коды широко используемых программ в исполняемый файл, а построить последний особым образом, который позволяет произвести настройку машинных кодов вызова в последний момент - уже при выполнении программы. При этом возникло два варианта, один называется динамической компоновкой времени загрузки (Load Time Dynamic Linking), а второй - динамической компоновкой времени выполнения (Run Time Dynamic Linking).

8.2. Динамическая компоновка времени загрузки

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

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

182

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

Процедуры, которые динамически подключаются (компонуются) операционной системой, предварительно собираются в специальные наборы - библиотеки динамической компоновки (Dynamic Linking Library, сокращенно DLL). Идея предварительного соединения объектных модулей в библиотеку здесь аналогична использованной для библиотек объектных модулей, имеющих расширение LIB и широко используемых при обычной статической компоновке. Но библиотеки динамической компоновки имеют гораздо более сложную структуру, в чем можно убедиться собрав для примера пару процедур (вводя для них специальные дополнительные описания, как будет показано в дальнейшем изложении) в библиотеку DLL и сделав для сравнения обычную статическую библиотеку из этих же процедур.

Библиотеки DLL создает системный компоновщик - почти также как он создает исполняемые файлы. Для того, чтобы компоновщику создавать исполняемый файл (с расширением EXE) или библиотеку DLL (с расширением DLL) требуются специальные указания. Для задания таких указаний сложилось два основных подхода: в одном они задаются как опции при вызове компоновщика, в другом включаются в состав вспомогательного файла определения компоновки с расширением DEF, который задается в качестве соответствующего файла-параметра при вызове компоновщика. Стандартный вызов компоновщика в командной строке по второму варианту используется в продукции Inprise/Borland и имеет вид

Имя_компоновшика опции обфайлы, результат, карта, библиотеки, def-файл

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

Имя компоновщика в указанных системах для 16-битного варианта есть TLINK, а для 32-битного - TLINK32. Все компоненты - файлы в командной строке вызова компоновщика, как правило, задаются без расширений, если они имеют стандартные расширения. Явное задание имен файлов вместе с расширениями встречается очень редко. Заметим, что файл карты загрузки имеет расширение MAP, статические библиотеки - расширение LIB. Несколько другой подход используется в Unix, он будет рассматриваться далее.

Библиотеки DLL и исполняемые файлы, вызывающие процедуры из таких библиотек, можно писать на любом языке программирования, небольшими дополнительными директивами поддерживающими такую возможность. Для систем программирования Unix собственно в языках программирования целенаправленно не вводится никаких средств указания использования DLL или размещения разрабатываемых процедур в DLL. Так сделано для того, чтобы программы при желании или необходимости можно было бы использовать в любом варианте: статической или

183

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

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

Вбиблиотеку DLL поместим единственную процедуры с именем wiwoda, которая будет сообщать о своем выполнении. В Windows исходный файл для процедуры wiwoda с может имеет содержание, приведенное на рис. 8.2.1.

STD_OUTPUT_HANDLE EQU -11 INVALID_HANDLE_VALUE EQU -1

extern ExitProcess, GetStdHandle extern CloseHandle

extern FillConsoleOutputAttribute extern WriteConsoleOutputCharacterA GLOBAL wiwoda

SEGMENT CODE USE32 CLASS=CODE wiwoda: ;----;procedure

push ebp mov ebp, esp pusha

push dword STD_OUTPUT_HANDLE

call GetStdHandle ;STDCALL, STD_OUTPUT_HANDLE mov [hstdout], eax

cmp eax, INVALID_HANDLE_VALUE ; -1 je near kon

mov word [col],20 mov word [row], 9 push cbActual push dword [pos] push dword len push dword [attr]

push dword [hstdout]

call FillConsoleOutputAttribute push cbActual

push dword [pos]

184

push dword len push text

push dword [hstdout]

call WriteConsoleOutputCharacterA mov esi, [ebp+8]

xor edx, edx mov ecx, 80

cmm: cmp BYTE [esi],0 je au

inc esi inc edx loop cmm

au: inc word [row] mov [lena], edx push cbActual push dword [pos] push dword [lena] push dword [attr]

push dword [hstdout]

call FillConsoleOutputAttribute push cbActual

push dword [pos] push dword [lena] push dword [EBP+8] push dword [hstdout]

call WriteConsoleOutputCharacterA mov eax, [ebp+12]

imul eax, 20 popa

pop ebp ret

kon: push dword 1 call ExitProcess

;;wiwoda endp

..start:

init: mov eax,1 ret 12

SEGMENT DATA USE32

text

db 'Мы находимся внутри DLL!!! var 1'

len

equ $-text

lena

dd 0

cbActual dd 0

185

hstdout dd 0

pos: ;; label for struct COORD col dw 0

row dw 0 attr dd 1eh

8.2.1. Исходная программа wpd.asm для Windows

Из текста программы видно, что никаких дополнений и особенностей в ней по сравнению с программой для обычного приложения (процедуры которого не входят в состав DLL) практически нет. Точнее нет, за исключением некоторой дополнительной процедуры с именем, которая возвращает единичное значение в регистре eax и которую можно вынести в отдельный исходный и объектный файл. Такая ситуация оказывается очень удобной, так как позволяет один и тот же исходный текст использовать и для DLL-библиотек и для обычных статических библиотек. (Заметим, что это не характерно для последних прикладных технологий фирмы Microsoft, в которых взять крен на включение таких особенностей непосредственно в исходный текст программы.) Особенности появляются при изготовлении результирующего исполняемого файла на этапе компоновки программы. Поэтому компиляция данного программного примера должна выполняться обычным образом, например с помощью вызова

nasmw -f obj wpd.asm

эта исходная программа преобразуется в объектный файл wpd.obj. В результате компиляции получается обычный объектный файл.

Для компоновки этого файла нужно позаботится о определении библиотеки DLL. С этой целью мы подготовим файл wpd.def. Название такого файла могло быть любым, но в большинстве случаев удобно называть его также как исходный файл библиотеки (или один из исходных, если их несколько).

Файл определения модуля для MS Windows может иметь содержимое, приведенное на рис. 8.2.2.

library wpdo exports wiwoda

Рис. 8.2.2. Файл определения wpd.def для Windows

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

186

что если требуется включить в состав DLL несколько функций, которые должны быть доступны снаружи, то имена их перечисляются после строки со служебным словом EXPORTS - каждое на своей строке или через пробелы в одной или нескольких строках. Теперь создать библиотеку можно командным вызовом

TLINK32 -Tpd wpd,wpdo,,import32, wpd.def

или в более поздних пакетах разработки программ Borland/Inprise (начиная с версии 6)

ILINK32 -Tpd wpd, wpdo,,IMPORT32,wpd.def

(В последних пакетах вместо компоновщика TLINK32.EXE используется более мощный, хотя и "более расточительный" компоновщик ILINK32.EXE)

В результате работы компоновщика для нашего примера получается файл wpdo.DLL. Причем расширение def в последнем элементе можно было не использовать. Как видим, единственное отличие при построении DLL библиотеки связано с использование файла определения (далее будет показан другой вариант построения DLL библиотеки). Заметим, что настоятельно рекомендуется использовать одинаковые названия результирующего исполняемого файла и в вызове компоновщика, и в файле определения.

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

extern ExitProcess, Sleep EXTERN wiwoda

SEGMENT CODE USE32 CLASS=CODE

..start:

push dword 37 push ttt

call wiwoda ; C, offset ttt, 37 add esp, 8

mov [rez], eax push dword 3000

call Sleep ;STDCALL, 3000 push dword 0

call ExitProcess ;STDCALL, 0

SEGMENT DATA USE32

ttt

db 'My text for DLL var1',0

rez

dd 0

 

Рис. 8.2.3. Исходная программа wexd.asm для Windows

Опять же в тексте программы не видно никаких особенностей, определяющих, что процедуру следует использовать из библиотеки DLL, а именно из такой библиотеки с именем wpdo.dll. Относительно процедуры wiwoda в тексте программы

187

указано только то, что она внешняя, а поэтому реально может находиться в библиотеке статической компоновки и в составе отдельного объектного модуля, а может быть в составе библиотеки динамической компоновки - все это уточняется позже. Файл wexd.asm обрабатывается компилятором в результате вызова командной строки

nasmw -f obj wexd.asm

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

name wexd imports wpdo.wiwoda

Рис. 8.2.4. Файл определения wexd.def для Windows

Файл определения исполняемого файла EXE содержит два специализированных оператора: оператор именования приложения (со служебным словом NAME) и оператор импорта (со служебным словом IMPORTS). Оператор наименования содержит в качестве операнда имя результирующего файла, а оператор импорта - перечисление имен функций, используемых из DLL библиотек, причем перед именем каждой такой функции должно обязательно стоять имя библиотеки, в которой эта функция находится. Имена функций и библиотеки при этом разделяются символом точки - без дополнительных пробелов. Разделителями в перечислении имен функций могут быть как пробельные символы (пробелы и символы табуляции), так и символы перевода на новую строку. Обычно - для наглядности - используется последний вариант.

Получение исполняемого файла wexd.exe достигается командным вызовом TLINK32 wexd,,,IMPORT32, wexd.def

(В последнем элементе этого вызова - обозначении файла определения - расширение не обязательно, если он имеет стандартное такое расширение.) Запустив на выполнение образованный компоновщиком файл wexd.exe можно убедиться, что все работает правильно и действительно используется процедура wiwoda, которой нет в составе исполняемого файла wexd.exe, но которая содержится в библиотеке wpdo.dll.

Использование файлов определения модуля оказывается неудобным при значительном числе экспортируемых имен. В таком случае предпочтительней оказывается применение библиотек импорта. Библиотеки импорта в системах разработки Inprise/Borland (как и в ряде других систем разработок) создаются с помощью утилиты, которая преобразует DLL-библиотеку в библиотеку импорта. Обычно такая утилита называется IMPLIB. Она используют два аргумента: название создаваемой библиотеки импорта и название исходной библиотеки DLL. Например из библиотеки wpdo.dll библиотека импорта wpdo.lib может быть построена командным вызовом

IMPLIB WPDO.LIB WPDO.DLL

188

При использовании библиотеки импорта wpdo.lib (вместо файла wexd.def определения модуля) построение исполняемого файла wexd.exe с помощью компоновщика TLINK32 может быть выполнено вызовом

TLINK32 wexd,,,IMPORT32 wpdo.lib

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

Если исходная программа для построения DLL библиотеки строится на языке Си, то ее компиляция в системе разработки Borland/Inprise выполняется обычным образом с опцией -с (вызовом bcc32 -c имяисх.с ). Последующую компоновку можно задать командой

ilink32 -Tpd c0d32 имяисх, имяисх,,IMPORT32 cw32, имяисх.def

где c0d32 - имя специального загрузочного модуля в формате объектного файла для построения DLL, а библиотека cw32 (с полным именем cw32.LIB) необходима только при использовании функций из стандартной библиотеки языка Си (которые почти всегда используются, так как к ним относится, в частности, и функция printf).

Вместо компоновщиков TLINK32.EXE или ILINK32.EXE от разработчика Borland/Inprise можно и даже - в учебном процессе - целесообразно использовать свободно распространяемый компоновщик ALINK.EXE, уже рассматривавшийся выше. В отличие от компоновщиков от Borland/Inprise и Microsoft этот компоновщик не рассчитан на применение файлов определения модуля. Для достижения ставящихся перед ним целей необходимо передать информацию об экспорте исключительно через объектный файл. Для этого могут быть использованы специальные утилиты, но простейшим путем является указание в самой исходной программе информации об экспортируемых объектах. С этой целью в язык NASM введены директивы экспорта вида

EXPORT имя_объекта

Вчастности, программу на рис. 8.2.1 можно дополнить строкой текста EXPORT wiwoda

что позволит использовать модифицированный файл для построения библиотеки динамической компоновки. Само указание такой динамической компоновки задается с дополнительной опцией -DLL, так что вместо вызова компоновщика TLINK32.EXE или ILINK32.EXE следует использовать командный вызов

alink -oPE -dll -o wpdo.dll wpd.obj C:\UTIL\win32.lib

Заметим, что опцию -o принудительного именования выходного файла в подобном вызове компоновщика можно не указывать. Тогда в нашем примере вместо файла автоматически образуется файл wpd.dll библиотеки динамической компоновки.

189

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

IMPORT имя_объекта имя_библиотеки_dll

Вчастности для использования подпрограммы wiwoda - вне определяющей эту подпрограммы библиотеки wpdo.dll - в соответствующем ассемблерном файле нужно записать

import wiwoda wpdo.dll

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

call [имя_подпрограммы]

Вчастности, вызов подпрограммы wiwoda внутри ассемблерной программы должен быть записан в виде

call [wiwoda]

(Уточним еще раз, что это требует применение директивы ассемблера IMPORT.) Первый из упомянутых выше способов требует применения вспомогательной утилиты для формирования библиотеки импорта. Кроме уже называвшейся утилиты IMPLIB из директивы от Borland/Inprise для этих целей можно использовать свободно распостраняемую утилиту ALIB.EXE, входящую в современный комплект компоновщика ALINK и созданную тем же разработчиком (Copyright 1998/9 Anthony A.J. Williams.). В этом случае для построения исполняемого файла потребуются командные вызовы

alib wpdo.dll

alink -oPE -subsys console wexd.obj wpdo.lib C:\util\win32.lib

Уточним еще раз, что при использовании библиотеки импорта не нужно вводить в исходный ассемблерный файл - указание о библиотеке и поэтому достаточно воспользоваться объектным файлом wexd.obj, получаемым из программы на рис. 8.2.3.

Воперационной системе Unix ассемблерные программы можно компилировать с помощью стандартного ассемблера, имеющего имя as, или с помощью свободно распространяемого ассемблера nasm. Мы используем последний вариант, так как синтаксис стандартного ассемблера существенно отличается от привычного для пользователей Microsoft, Borland/Inprise и Intel. (Наиболее существенные отличия последнего ассемблера - отсутствие директивы ASSUME, обязательного помещения имен областей данных в квадратные скобки для задания их значений и использование просто имен для задания адресов именованных объектов.).

На рис. 8.2.5 приведена программа, которая обеспечивает вывод

190

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