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

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6281
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

630 Часть IV. Динамически подключаемые библиотеки

что идентификатор, на который мы ссылаемся, импортируется из LIB-файла DLLмодуля. Вот почему я настоятельно рекомендую пользоваться ключевым словом __declspec(dllimport) для импортируемых функций и идентификаторов данных. Именно его подставляет за вас операционная система, когда вы вызываете любую из стандартных Windows-функций.

Разрешая ссылки на импортируемые идентификаторы, компоновщик создает в конечном ЕХЕ-модуле раздел импорта (imports section). В нем перечисляются DLL, необходимые этому модулю, и идентификаторы, на которые есть ссылки из всех используемых DLL.

Воспользовавшись утилитой DumpBin.exe (с ключом -imports), мы можем увидеть содержимое раздела импорта. Ниже показан фрагмент полученной с ее помощью таблицы импорта Calc.exe.

C:\Windows\System32>DUMPBIN -imports Calc.exe

Microsoft (R) COFF/PE Dumper Version 8.00.50727.42

Copyright (C) Microsoft Corporation. All rights reserved.

Dump of file calc.exe

File Type: EXECUTABLE IMAGE

Section contains the following imports:

SHELL32.dll

10010CC Import Address Table 1013208 Import Name Table FFFFFFFF time date stamp

FFFFFFFF Index of first forwarder reference

766EA0A5

110 ShellAboutW

 

 

ADVAPI32.dll

1001000 Import Address Table

101313C Import Name Table FFFFFFFF time date stamp

FFFFFFFF Index of first forwarder reference 77CA8229 236 RegCreateKeyW

77CC802D 278 RegSetValueExW

77CD632E 268 RegQueryValueExW 77CD64CC 22A RegCloseKey

...

ntdll.dll

1001250 Import Address Table

101338С Import Name Table FFFFFFFF time date stamp

Глава 19. DLL - основы.docx 631

FFFFFFFF Index of first forwarder reference

77F0850D 548 WinSqmAddToStream KERNEL32.dll

1001030 Import Address Table

101316C Import Name Table FFFFFFFF time date stamp

FFFFFFFF Index of first forwarder reference

77E01890 24F GetSystemTimeAsFileTime 77E47B0D 1AA GetCurrentProcessId 77E2AA46 170 GetCommandLineW 77E0918D 230 GetProfilelntW

Header contains the following bound import information: Bound to SHELL32.dll [4549BDB4] Thu Nov 02 10:43:16 2006 Bound to ADVAPI32.dll [4549BCD2] Thu Nov 02 10:39:30 2006 Bound to 0LEAUT32.dll [4549BD95] Thu Nov 02 10:42:45 2006 Bound to ole32.dll [4549BD92] Thu Nov 02 10:42:42 2006 Bound to ntdll.dll [4549BDC9] Thu Nov 02 10:43:37 2006 Bound to KERNEL32.dll [4549BD80] Thu Nov 02 10:42:24 2006 Bound to GDI32.dll [4549BCD3] Thu Nov 02 10:39:31 2006 Bound to USER32.dll [4549BDE0] Thu Nov 02 10:44:00 2006 Bound to msvcrt.dll [4549BD61] Thu Nov 02 10:41:53 2006

Summary

2000 .data

2000 .reloc

16000 .rsrc

13000 .text

Как видите, в разделе есть записи по каждой DLL необходимой Calc.exe: Shell32.dll, AdvAPI32.dll, OleAut32.dll, O1e32.dll, Ntdll.dll, Kernel32.dll, GDI32.dll, User32.dll и MSVCRXdll. Под именем DLL-модуля выводится список идентификаторов, импортируемых программой Саlc.exe. Например, Calc.exe обращается к следующим функциям из Kernel32.dll: GetCurrentProcessId, GetCommandLineW, GetProfileIntW и др.

Число слева от импортируемого идентификатора называется «подсказкой» (hint) и для нас несущественно. Крайнее левое число в строке для идентификатора сообщает адрес, по которому он размещен в адресном пространстве процесса. Такой адрес показывается, только если было проведено связывание (binding) исполняемого модуля, но об этом — в главе 20.

632 Часть IV. Динамически подключаемые библиотеки

Выполнение ЕХЕ-модуля

При запуске ЕХЕ-файла загрузчик операционной системы создает для его процесса виртуальное адресное пространство и проецирует на него исполняемый модуль. Далее загрузчик анализирует раздел импорта и пытается спроецировать все необходимые DLL на адресное пространство процесса. Поскольку в разделе импорта указано только имя DLL (без пути), загрузчику приходится самому искать ее на дисковых устройствах в компьютере пользователя. Поиск DLL осуществляется в следующей последовательности.

1.Каталог, содержащий ЕХЕ-файл.

2.Основной каталог Windows (по данным GetWindowsDirectory).

3.Системный каталог Windows (по данным GetSystemDirectory).

4.Текущий каталог процесса.

5.Каталоги, указанные в переменной окружения PATH.

Обратите внимание на то, что текущий каталог приложения просматривается

после каталогов Windows. Это новшество, введенное в Windows XP SP2, должно помешать загрузке записанных злоумышленниками в текущей каталог приложения поддельных системных DLL вместо настоящих, расположенных в «законных» каталогах Windows. В электронной документации MSDN говорится, что DWORD-

значение в разделе HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager позволяет изменять порядок просмотра каталогов, но лучше не трогайте его, чтобы вредоносные программы не смогли воспользоваться этой уязвимостью. Имейте в виду, что есть и другие факторы, влияющие на поиск DLL загрузчиком (подробнее см. в главе 20).

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

Если найти файл DLL не удается, загрузчик выводит следующее сообщение.

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

Глава 19. DLL - основы.docx 633

модуле, проверяя наличие указанного идентификатора в соответствующей DLL. He обнаружив его (что происходит крайне редко), загрузчик выводит следующее сообщение:

Если же идентификатор найден, загрузчик отыскивает его RVA и прибавляет к виртуальному адресу, по которому данная DLL размещена в адресном пространстве процесса, а затем сохраняет полученный виртуальный адрес в разделе импорта ЕХЕ-модуля. И с этого момента ссылка в коде на импортируемый идентификатор приводит к выборке его адреса из раздела импорта вызывающего модуля, открывая таким образом доступ к импортируемой переменной, функции или функции-члену С++-класса. Вот и все — динамические связи установлены, первичный поток процесса начал выполняться, и приложение наконец-то работает!

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

Оглавление

 

Г Л А В А 2 0 DLL: более сложные методы программирования...................................

634

Явная загрузка DLL и связывание идентификаторов................................................

634

Явная загрузка DLL ...........................................................................................................

635

Явная выгрузка DLL..........................................................................................................

639

Явное подключение экспортируемого идентификатора ......................................

642

Функция входа/выхода........................................................................................................

643

Уведомление DLL_PROCESS_ATTACH .......................................................................

644

Уведомление DLL_PROCESS_DETACH .......................................................................

646

Уведомление DLL_THREAD_ATTACH ..........................................................................

648

Уведомление DLL_THREAD_DETACH ..........................................................................

649

Как система упорядочивает вызовы DllMain............................................................

650

Функция DllMain и библиотека С/С++ ..........................................................................

653

Отложенная загрузка DLL ...................................................................................................

654

Программа-пример DelayLoadApp................................................................................

659

Переадресация вызовов функций...................................................................................

666

Известные DLL .......................................................................................................................

667

Перенаправление DLL..........................................................................................................

669

Модификация базовых адресов модулей .....................................................................

670

Связывание модулей...........................................................................................................

677

Г Л А В А 2 0

DLL: более сложные методы программирования

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

Явная загрузка DLL и связывание идентификаторов

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

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

На рис. 20-1 показано, как приложение явно загружает DLL и связывается с ней.

Глава 20. DLL - более сложные методы программирования.docx 635

Сборка DLL-модуля Сборка EXE-модуля

1) Заголовок с экспортируемыми прототипа- 6) Заголовок с экспортируемыми прототипами, ми, структурами и идентификаторами. структурами и идентификаторами.

2) Файлы с исходным кодом на С/С++ с реа- 7) Файлы с исходным кодом на С/С++, ссылаюлизацией экспортируемых функций и перещимся на импортированные функции и переменменных. ные.

3) Компилятор генерирует .obj-файл для ка- 8) Компилятор генерирует .obj-файл для каждого ждого из файлов с кодом на С/С++. из файлов с кодом на C/C++.

4) Компоновщик генерирует DLL из .obj- 9) Компоновщик комбинирует .obj-модули и раз-

файла.

решает ссылки на импортированные функции и

5) Компоновщик также генерирует .lib-файл,

переменные, используя .lib-файл. В результате

если найдена хотя бы одна экспортируемая

получается .ехе-файл (с таблицей импортирован-

функция или переменная.

ных функций—списком необходимых DLL и

 

идентификаторов).

Рис. 20-1. Так DLL создается и явно связывается с приложением

Явная загрузка DLL

В любой момент поток может спроецировать DLL на адресное пространство процесса, вызвав одну из двух функций:

HMODULE LoadLibrary(PCTSTR pszDLLPathName);

636 Часть IV. Динамически подключаемые библиотеки

HMOOULE LoadLibraryEx(

PCTSTR pszDLLPathName,

HANDLE hFile,

DWORD dwFlags);

Обе функции ищут образ DLL-файла (в каталогах, список которых приведен в предыдущей главе) и пытаются спроецировать его на адресное пространство вызывающего процесса. Значение типа HINSTANCE, возвращаемое этими функциями, сообщает адрес виртуальной памяти, по которому спроецирован образ файла. Если спроецировать DLL на адресное пространство процесса не удалось, функции возвращают NULL. Дополнительную информацию об ошибке можно получить вызовом GetLastError.

Очевидно, вы обратили внимание на два дополнительных параметра функции LoadLibraryEx: hFile и dwFlags. Первый зарезервирован для использования в будущих версиях и должен быть NULL. Во втором можно передать либо 0, либо комбинацию флагов DONT_RESOLVE_DLL_REFERENCES, LOAD_LIBRARY_ AS_DATAFILE, LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE, LOAD_LIBRARY_AS_IMAGE_RESOURCE, LOAD_WITH_ALTERED_SEARCH_PATH и LOAD_IGNORE_CODE_AUTHZ_LEVEL о которых мы сейчас и поговорим.

DONT_RESOLVE_DLL_REFERENCES

Этот флаг указывает системе спроецировать DLL на адресное пространство вызывающего процесса. Проецируя DLL. система обычно вызывает из нее специальную функцию DllMain (о ней — чуть позже) и с ее помощью инициализирует библиотеку. Так вот, данный флаг заставляет систему проецировать DLL, не обращаясь к DllMain.

Кроме того, DLL может импортировать функции из других DLL. При загрузке библиотеки система проверяет, использует ли она другие DLL; если да, то загружает и их. При установке флага DONT_RESOLVE_DLL_REFERENCES дополнительные DLL автоматически не загружаются. Так что вызывать экспортируемые этими DLL функции без риска получить код, использующий неинициализированные внутренние структуры либо еще не загруженную DLL Словом, по возможности лучше избегать использования этого флага. Подробнее см. пост «LoadLibraryEx (DONT_RESOLVE_DLL_REFERENCES) is fundamentally flawed» в блоге Рэймонда Чена (Raymond Chen) по адресу http://blogs.msdn.com/oldnewthing/archive/2005/02/14/372266.aspx.

LOAD_LIBRARY_AS_DATAFILE

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

Глава 20. DLL - более сложные методы программирования.docx 637

ла. Если флаг LOAD_LIBRARY_AS_DATAFILE не указан, атрибуты защиты устанавливаются такими, чтобы кол из данного файла можно было выполнять. Например, если вызвать функцию GetProcAddress на DLL, загруженной с этим фла-

гом, то GetProcAddress вернет NULL, а GetLastError — ERROR_MOD_NOT_FOUND.

Этот флаг может понадобиться по нескольким причинам. Во-первых, его стоит указать, если DLL содержит только ресурсы и никаких функций. Тогда DLL проецируется па адресное пространство процесса, после чего при вызове функций, загружающих ресурсы, можно использовать значение HMODULE, возвращенное функцией LoadLibraryEx. Во-вторых, он пригодится, если вам нужны ресурсы, содержащиеся в каком-нибудь EXE-файле. Обычно загрузка такого файла приводит к запуску нового процесса, но этого не произойдет, если его загрузить вызовом LoadLibraryEx в адресное пространство вашего процесса. Получив значение HMODULE/HINSTANCE для спроецированного EXE-файла, вы фактически получаете доступ к его ресурсам. Так как в EXE-файле нет DllMain, при вызове LoadLibraryEx для загрузки EXE-файла нужно указать флаг

LOAD_LIBRARY_AS_DATAFILE.

LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE

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

LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE, если только другим программам не требуется возможность записи в ваш файл.

LOAD_LIBRARY_AS_IMAGE_RESOURCE

Этот флаг также похож на LOAD_LIBRARY_AS_DATAFILE, но отличается тем, что при загрузке DLL ее RVA (о RVA см. главу 19) изменяется операционной системой. Это позволяет непосредственно использовать RVA, не преобразуя их с учетом адреса, по которому DLL загружена в память. Это особенно удобно при анализе РЕ-разделов в DLL.

LOAD_WITH.ALTERED.SEARCH.PATH

Этот флаг изменяет алгоритм, используемый LoadLibraryEx при поиске DLLфайла. Обычно поиск осуществляется так, как я рассказывал в главе 19. Однако, если данный флаг установлен, функция ищет файл, в зависимости от значения переданного ей параметра pszDLLPathName, по одному из следующих алгоритмов.

1.Если pszDLLPathName не содержит символа \, поиск идет по алгоритму, описанному в главе 19.

2.Если в pszDLLPathName есть символ \, действия LoadLibraryEx зависят от того, какой путь ей передан: полный или относительный.

Соседние файлы в предмете Программирование на C++