Textnew2
.pdf
|
GLOBAL wiwoda:function |
|
EXTERN printf |
|
SEGMENT .data |
txt |
db 'We are into Dll .. ',10 |
lentxt equ $-txt |
|
frmt |
db 'Text argument is <%s> k=%d',10,0 |
wiwoda: |
;;аналог функции wiwoda(char *pt, int k) |
push ebp |
|
mov ebp,esp |
|
pusha |
|
mov eax,4 mov ebx,1 mov ecx, txt mov edx, lentxt int 80h
; для вызова функции printf(frmt, pt) push dword [ebp+12]
push dword [ebp+8] push dword frmt call printf
add esp, 12 popa
mov eax, [ebp+12] lea eax, [2*eax] pop ebp
ret
Рис. 8.2.5. Программа updll.asm на ассемблере NASM
Для превращения этой исходной программы в разделяемую библиотеку нужно выполнить последовательность команд
nasm -f elf updll.asm
ld -shared -o myupdll.so updll.o
Для простейшего использования разделяемой библиотеки myupdll.so можно использовать программу, написанную на языке Си, которая приведена на рис. 8.2.6.
#include <stdio.h>
extern wiwoda(char *pt, int n); int main()
{int k;
printf("Before call my Dll\n"); k=wiwoda("Text for our DLL", 37);
191
printf("After call -- Dll, k=%d\n", k); k=wiwoda("Second text for our DLL", 20); printf("After call -- Dll, k=%d\n",k); return 0;
}
Рис. 8.2.6. Программа exdll.c для использования разделяемой библиотеки
Для превращения последней программы в исполняемый файл следует выполнить команду вызова универсальной программы разработки
gcc -o exdll.exe exdll.c путь/myupdll.so
Следует отметить, что программирование для Unix чаще всего осуществляется на языке Си, поэтому следует рассмотреть как превратить исходную программу с языка Си в разделяемую библиотеку согласно системным рекомендациям.
Документация на gcc рекомендует использовать предварительный вызов компилятора с опцией -fPIC. В частности программу с рис. 8.2.7
#include <stdio.h>
int wiwoda(char* text, int n)
{ printf("New Into DLL..ist...\n"); printf("%s\n", text);
return 2*n;
}
Рис. 8.2.7. Программа updll2.c для Linux.
следует обработать командным вывовом gcc -c -fPIC updll2.c
Результатом работы этой компиляции будет объектный файл с именем updll2.o. В данной командной строке вызывается GNU компилятор с языка Си. Он использует две опции. Первая из них (опция -с) указывает, что будет выполняться только компиляция без последующей компоновки в данном командном вызове. Вторая опция (-fPIC) заставляет компилятор генерировать командный код, выполнение которого не зависит от положения фрагмента программы (реализация этой опции зависит от возможностей конкретной архитектуры). Далее следует компоновка вызовом командной строки
ld -shared -o myd.so.1.0 updll2.o
Команда задается именем стандартного компоновщика в Linux. Первая опция этого вызова задают разделяемый объект (опция -shared). Опция -o имя задает имя результирующего файла, в данном случае создается файл с именем myd.so.1.0. Последний элемент командной строки определяет исходный файл, обрабатываемый компоновщиком.
192
Вместо отдельных вызовом компилятора и компоновщика для создания библиотеки она может быть построена вызовом одной командной строки
gcc -shared -o myd.so.1.0 updll2.c
Следует обратить внимание, что в Linux принята очень свободная система обозначения файлов, в частности собственное имя файла может включать не одну, а несколько символов точки. Достаточно ограниченное понятие расширения имени файла используется здесь только по желанию индивидуального разработчика и в большинстве случаев ни имеет общесистемного назначения.
С другой стороны для обозначения библиотек динамической компоновки в Linux принята четко сложившаяся система. Системные DLL библиотеки начинаются с префикса lib, за которым следует основная часть собственного имени. Все системные DLL библиотеки имеют общую форму имени в виде
libимя.so.гл.мл.вер
где имя - собственная часть имени, гл- главный номер версии, мл - младший номер версии, вер - вариант (patch) версии. Например, такая библиотека может иметь название libc.so.4.3.2, где главный номер версии равен 4, младший номер версии равен 3, а вариант версии равен 2. Принято очень удобное для использование соглашение, по которому изменение младшего номера (его увеличение) соответствует только добавлению новых функций в библиотеку без изменения ранее туда помещенных, так что сохраняется обратная совместимость (библиотека без каких-либо изменений поддерживает старые функции в ней). Увеличение варианта соответствует только устранению обнаруженных ошибок. Более серьезные изменения приводят к увеличению главного номера библиотеки, при котором не гарантируется точная поддержка более ранних функций.
В связи с этой системой собственное имя DLL библиотеки как правило никогда непосредственно не используется для ссылок на нее в исполняемых файлах. В качестве промежуточного звена используются обобщенное имя без номера варианта и обобщенное имя, не включающее ни один из внутренних номеров версии. Таким образом для приведенного примера libc.so.4.3.2, программы, использующие эту библиотеку будут ссылаться внутри себя только на имя libc.so, а в файловой системе должен быть файл с этим обобщенным именем, ссылающимся на действительное имя библиотеки. Такие имена файлов, представляющие собой всего лишь ссылки на действительные файлы или другие имена называются символическими ссылками в файловой системе и создаются в Unix командой ln с опцией -s. Таким образом для использования DLL библиотеки с собственным именем myd.so.1.0 следует создать символическую ссылку с именем myd.so.1, указывающей на это собственное имя и ссылку с именем myd.so, указывающую на имя myd.so.1. Создание этих ссылок выполняется командами
ln -s myd.so.1.0 myd.so.1 ln -s myd.so.1 myd.so
Для вызова процедуры из построенной библиотеки можно использовать программу, представленную исходным текстом на рис. 8.2.8.
193
#include <stdio.h>
extern int wiwoda(char* text, int n); int main()
{int k;
printf("Before DLL\n"); k=wiwoda("Text For DLL mmmm", 10); printf("After DLL mmm k=%d\n",k); return 0;
}
Рис. 8.2.8. Программа uxd2.c для Linux.
Создание исполняемой программы из файла uxd2.с может быть получено вызовом командной строки
gcc uxd2.с путь/myd.so
где путь задает точное место размещения файла обобщенной ссылки на библиотеку myd в дереве файловой системы Linux. Например, если библиотека вместе со ссылками построена в каталоге /home/sdudent, то рассматриваемая команда должна быть выдана в виде
gcc uxd2.с /home/sdudent/myd.so
У читателей может возникнуть вопрос, зачем такая сложная многоступенчатая система ссылок, не проще ли использовать при построении исполняемого файла собственное имя библиотеки. Причина этого в том, что изменение версии библиотеки в принятом варианте не требует никакого изменения в исполняемом файле, использующем эту библиотек, а только изменения файлов символических ссылок. Иное решение требовало бы перекомпоновки всех программ, использующих библиотеку, которая обновляется с соответствующим изменением полного обозначения.
8.3. Использование объектных модулей формата COFF
Старый стиль построения трансляторов для языка Си в Unix и трансляторов для 16-битной архитектуры персональных компьютеров систематически использовал следующее правило определения действительных внешних имен. Имена, записанные на языке Си дополнялись при занесении в объектные файлы дополнительным символом подчеркивания в качестве префикса имени. Например, имя подпрограммы, записываемое на языке Си как printf, в объектном файле записывалось как _printf. При использовании ассемблерных вставок в программе Си имена локальных переменных могли быть в любой момент обозначены также с помощью дополнительного предшествующего символа подчеркивания. Так локальные переменные i и j в ассемблерной вставке должны были обозначаться как _i и _j.
Такой подход сохранялся до возникновения проблемы явного указания имен для динамической компоновки, которая уже выходила за пределы программирования на конкретном языке программирования. В последних разработках фирма Microsoft
194
использует новый формат объектных файлов, называемый ею COFF (Common Object File Format), но в ассемблере NASM обозначаемый win32. Для этого формата использована не бросающаяся в глаза особенность, заключающаяся в том, что хотя имена в нем обязательно дополнены префиксными символами подчеркивания, но в директивах компоновки имена указываются с опущенным символом подчеркивания. Практически это значит, что все внешние имена для данного формата, предназначенные для использования в библиотеках динамической компоновки, должны в объектном файле иметь символы подчеркивания (иначе компоновщик не сможет их использовать).
Более того, все действительные имена системных функций ОС Windows должны строиться из имен прототипов на языке Си путем дополнения не только предшествующим символом подчеркивания, но и суффиксом вида @число_байтов_в_области_аргументов. В частности, из имени GetStdHandle при этом получается имя _GetStdHandle@4, а из имени WriteFile получается имя _WriteFile@20. Именно такие "настроенные" имена можно увидеть при просмотре объектного файла, полученного 32-битным транслятором MS из программы Си, которая использует указанные функции GetStdHandle и WriteFile.
В качестве примера рассмотрим программы wp1.asm и wp2.asm, первая из которых предназначена для использования библиотеки динамической компоновки, построенной из второй программы. Эти программы приведены на рис. 8.3.1 и 8.3.2.
extern _ExitProcess@4 SEGMENT .data
tttdb 'Text for dll---',0 SEGMENT .text
EXTERN _wiwoda ; ассемблерный аналог функции wiwoda(char *t) GLOBAL _start
_start:
push dword 37 push dword ttt call _wiwoda add esp,8
push dword 1
call _ExitProcess@4
Рис. 8.3.1. Программа wp1 вызова функции _wiwoda из DLL-библиотеки
STD_OUTPUT_HANDLE EQU -11 INVALID_HANDLE_VALUE EQU -1
extern _ExitProcess@4, _GetStdHandle@4 extern _FillConsoleOutputAttribute@20 extern _WriteConsoleOutputCharacterA@20 SEGMENT .data
195
text |
db 'Мы находимся внутри DLL!!! var 1' |
lenequ $-text |
|
lena |
dd 0 |
cbActual dd 0 hstdout dd 0 pos:
col dw 0 row dw 0 attr dd 1eh
SEGMENT .text GLOBAL _wiwoda _wiwoda:
push ebp mov ebp, esp pusha
push dword STD_OUTPUT_HANDLE call _GetStdHandle@4
mov [hstdout], eax
cmp eax, -1; значение INVALID_HANDLE_VALUE je near kon
mov word [col],20 mov word [row], 9 push dword cbActual push dword [pos] push dword len push dword [attr] push dword [hstdout]
call _FillConsoleOutputAttribute@20 push dword cbActual
push dword [pos] push dword len push dword text push dword [hstdout]
call _WriteConsoleOutputCharacterA@20 mov esi, [ebp+8]
xor edx, edx mov ecx, 80
cmm: cmp byte [esi],0 je au
inc esi inc edx
loop cmm ; вычисление числа символов в текстовом аргументе функции
196
au:inc word [row] mov [lena], edx
push dword cbActual push dword [pos] push dword [lena] push dword [attr] push dword [hstdout]
call _FillConsoleOutputAttribute@20 push dword cbActual
push dword [pos] push dword [lena] push dword [ebp+8] push dword [hstdout]
call _WriteConsoleOutputCharacterA@20 mov eax, [ebp+12]
imul eax, 20 exi: popa
pop ebp ret
kon: push dword 1
call _ExitProcess@4
GLOBAL __DllMainCRTStartup@12 __DllMainCRTStartup@12: mov eax,1
ret 12
Рис. 8.3.2. Программы wp2 с функцией _wiwoda для DLL-библиотеки
Кроме необходимости использовать в ассемблерных программах для DLL оформленных указанным способом имен, в программе для DLL требуется учесть еще одну особенность. Как уже упоминалось в разделе 8.2, каждая DLL должна содержать специальную процедуру инициализации. По умолчанию компоновщик link.exe фирмы Microsoft принимает для этой процедуры действительное имя __DllMainCRTStartup@12. При желании, можно имя такой инициализирующей процедуры переопределить, используя опцию компоновщика /ENTRY. Она записывается при вызове LINK.EXE в виде /ENTRY:инитимя, но действительное имя точки входа в такую процедуру должно быть написано на ассемблере в виде _ инитимя@12 (т.е. соответствовать описанным выше соглашениям по использованию имен системных функций для формата COFF объектных файлов Microsoft). Подробней с функциями инициализации для DLL в общем случае мы будет разбираться позже. Пока нам достаточно, что эта функция должна быть описана как получающая 12 байтов аргументов и возвращать - для нормальной работы - отличное от нуля значение.
Построение исполняемых файлов из программ wp1.asm и wp2.asm может быть задано командным файлом (или простой последовательность команд оболочки ОС)
197
nasmw -f win32 wp1.asm nasmw -f win32 wp2.asm
link /DLL /export:wiwoda wp2.obj kernel32.lib
link /subsystem:console /entry:start wp1.obj kernel32.lib wp2.lib
Рис. 8.3.3. Командный файл построения и использования DLL
Здесь вызов компоновщика с опцией DLL формирует библиотеку с неявно заданным именем wp2.DLL (собственное имя которой совпадает с именем первого или единственного объектного файла, обрабатываемого компоновщиком link). Кроме того, и это отличительная особенность рассматриваемого компоновщика фирмы MS, одновременно с библиотекой DLL автоматически формируется библиотека импорта, имеющая то же собственное имя, но в качестве расширения буквосочетание LIB. Последняя библиотека используется в последующем вызове компоновщика для формирования исполняемого файла wp1.exe. Он строиться из объектного модуля wp1.obj с помощью библиотеки импорта KERNEL32.LIB для доступа к функциям API Windows в указанном выше соглашении на действительные имена таких функций.
Вопциях компоновщика для исполняемого файла wp1.exe указывается, что программа предназначена для выполнения в консольном режиме (опция /SUBSYSTEM:CONSOLE) и что метка начала выполнения программы есть start (опция /ENTRY:start). Заметим, что действительное имя метки начала выполнения программы есть _start, но выше уже объяснялась почему имеется такое расхождение имен.
Для формирования DLL-библиотеки использован один из вариантов задания экспортируемых из библиотеки имен. Этот вариант состоит в использовании опции /EXPORT, за ключевым словом которой через двоеточие следует имя экспортируемой функции - в форме, принятой для имен языка Си. В нашем случае опция указывается в виде /EXPORT:wiwoda, хотя действительное имя функции (видимое и в объектном файле) есть _wiwoda.
Третья команда командного файла на рис. 8.3.3 может быть заменена на команду link /DLL /DEF:wp2.def wp2.obj kernel32.lib
где файл определения библиотеки может быть задана двумя строками LIBRARY wp2
EXPORTS wiwoda
но может состоять и только из второй строки с оператором EXPORTS, так как оператор LIBRARY для компоновщика фирмы MS не является обязательным.
Вданном случае очевидным образом проще первый из рассмотренных вариантов. При импорте многих имен из DLL-библиотеки можно использовать необходимое число раз опцию EXPORT в вызове программы LINK. Возможет еще промежуточный вариант, заключающийся в том, что множество опций вызова программы помещается во вспомогательных файл, например с именем wwpp.lnk, а последний используется при вызове компоновщика в виде
198
link /DLL wp2.obj kernel32.lib @ wwpp.lnk
Этот файл wwpp.lnk может содержать, например, текст /export:wiwoda
/export:funa
/export:abc
Третий вариант задания перечня экспортируемых имен пригоден только для программ, записываемых на языке Си и будет рассмотрен далее.
Когда исполняемый файл создается из исходного файла wp1.c на языке Си и должен вызывать функции из DLL-библиотеки wp2.DLL, для компоновки может быть использован командный вызов
link /subsystem:console /entry:main wp1.obj kernel32.lib wp2.lib
(При этом предполагается, что объектный файл получен вызовом транслятора cl -c wp1.c.) Текст такого файла приведен на рис. 8.3.4. Заметим, что при использовании исходного языка Си имена экспортируемых функций в этой программе и используемых данных экспорта совпадают (в данном случае вместо библиотеки можно было использовать опцию /export:wiwoda).
#include <windows.h> extern int wiwoda(char* text); void main()
{int k=1; HANDLE hout; DWORD act;
char ttt[ ]="Text for dll-MS";
hout=GetStdHandle(STD_OUTPUT_HANDLE); WriteFile(hout,"\nBegin prog\n",12,&act,NULL); wiwoda(ttt);
WriteFile(hout,"\nEnd prog\n",10,&act,NULL); Sleep(2000);
}
Рис. 8.3.4. Программа wp1.c для использования функции wiwoda из DLL, полученной из wp1.asm
Еще проще строить исполняемый файл без явного получения объектного файла, что можно добиться командным вызовом
cl wp1.c wp2.lib
В свою очередь, средствами MS получение библиотеки DLL из исходного файла на языке Си проще всего достичь вызовом
cl имяисх.c /link /DLL /EXPORT:имя_экпортируемое
Но при этом результирующий исполняемый файл, хотя и будет по своему содержанию динамической библиотекой, получит автоматически расширение имени
199
EXE. Чтобы добиться обычного в практике обозначения DLL библиотеки, следует предыдущий вызов задавать в несколько измененном виде, приведенном в следующей строке.
cl /Feимяdll.dll имяисх.c /link /DLL остальные опции и параметры
В частности из исходного файла wp3.c, приведенного на рис. 8.3.5, библиотека wp3.DLL может быть создана вызовом
cl /Fewp3.dll wp3.c /link /DLL /EXPORT:wiwoda
Заметим, что при использовании системы разработки MS для построения программ с языка Си требуется не только исполняемые файлы транслятора CL.EXE и компоновщика LINK.EXE, но и файлы C2.EXE, C1.DLL, C1XX.DLL, MSPDB50.DLL, MSPDB60.DLL, MSVCRT.DLL.
#include <windows.h> #include <stdio.h>
char attr=0x1e, attrr=0x0c, attrg=0x0a; void wiwoda(char *txt)
{HANDLE hstdout; COORD coor; DWORD cbwritten;
hstdout=GetStdHandle(STD_OUTPUT_HANDLE); coor.X=35; coor.Y=12; WriteConsoleA(hstdout,"Privetaa...",11,&cbwritten,NULL); FillConsoleOutputAttribute(hstdout, attrr, 12, coor, &cbwritten);
WriteConsoleOutputCharacterA(hstdout, txt, 12, coor, &cbwritten);
}
Рис. 8.3.5. Файл wp3.c для библиотеки DLL
Кроме того, потребуются библиотеки KERNEL32.LIB, LIBC.LIB, MSVCRT.LIB, UUID.LIB, OLDNAMES.LIB и содержимое каталога INCLUDE, где содержатся необходимые заголовочные файлы. Причем при использовании усеченной системы разработки, состоящей только из указанных файлов, необходимо задать путь доступа к заголовочным файлам и библиотекам, что можно сделать выполнением команд установки констант окружения, имеющими вид
SET INCLUDE=путь_к_каталогу_INCLUDE SET LIB=путь_к_каталогу\LIB
(Возможно также указание путей этих каталогов в опциях /Iпуть и /LIBPATH:путь при вызове программ CL.EXE и LINK.EXE соответственно.)
8.4. Динамическая компоновка времени выполнения
Динамическая компоновка времени выполнения не требует никаких ухищрений с дополнительной информацией компоновщику, но в исходном тексте программы необходимо указать как вызов соответствующей системной функции о выполнении
200