Плагины
Вы можете подключать свои собственные DLL-модули (плагины)
к отладчику. Это может вам понадобиться по двум причинам:
Вы хотите расширить функциональность отладчика по своему усмотрению.
У вас есть компилятор, отладочная информация
которого не поддерживается стандартными загрузчиками,
входящими в состав отладчика, и вы хотите написать загрузчик,
который читает ее.
Подключение
Плагины подключаются к отладчику через четыре экспортируемые плагином функции:
PLUGIN_Init обязательна | Вызывается в момент загрузки
плагина. Кроме того, по ее наличию отладчик отличает модуль плагина от
остальных не являющихся плагинами.
|
|---|
| PLUGIN_Shutdown | Вызывается перед самой
выгрузкой модуля из памяти отладчика. Может использоваться
плагином для освобождения своих ресурсов.
|
|---|
| PLUGIN_Prop | Вызывается из окна управления отладчиком.
Может использоваться для редактирования внутренних опций плагина.
Опции предпочтительно сохранять в реестр.
|
|---|
| PLUGIN_LoadDebugInfo | Вызывается для загрузки
отладочных символов из модуля, загружаемого в отладочный процесс.
Должна возвращать TRUE, если символы прочитаны.
|
|---|
Сами плагины могут вызывать функции отладчика, которые объявлены в файле zdapi.h,
копию которого можно скопировать из окна управления плагинами отладчика.
Подробнее об этих функциях отладчика читайте здесь.
Порядок загрузки плагинов
Все плагины должны находиться в одной директории,
которая определяется в опциях плагинов, открывающихся клавишей ALT+F5.
Плагины загружаются в момент загрузки самого отладчика
по следующиму алгоритму: отладчик открывает директорию плагинов,
и последовательно загружает каждый *.dll модуль из директории.
Если этот DLL-модуль имеет функцию PLUGIN_Init,
то он считается плагином и остается в памяти, иначе он из нее выгружается.
Кроме того, плагины могут быть перезагружены после смены пути
плагинов. В этом случае все загруженные модули сначала выгружаются,
после чего из нового пути загружаются другие модули.
Принцип использования плагина
Примеры работающих плагинов с исходными файлами на Си вы можете
скачать отсюда.
Здесь же будет описан краткий алгоритм работы. Он прост:
первой в вашем плагине вызывается функция PLUGIN_Init (),
которая используется для инциализации ваших переменных.
Она же может установить заголовок вашего модуля,
который будет использован отладчиком в качестве комментария к модулю
в окне управления плагинами. В это строку можно также выводить форматы,
загрузку которых поддерживает плагин, или же просто его назначение.
Далее отладчик обращается к плагинам в момент загрузки любого из
модулей отлаживаемого процесса. Он последовательно вызывает функции
PLUGIN_LoadDebugInfoдля всех плагинов поочередно, до тех пор пока
функция одного из них не возвратит TRUE, что означает, что символы из
модуля DLL загружены нормально. Если ни одна функция не возвратит
TRUE, то формат отладки считается неопознанным и отладочная информация
становится недоступна.
Функция, вызываемая отладчиком в качестве загрузчика,
объявлена следующим образом:
BOOL PLUGIN_LoadDebugInfo(BYTE*FileRoot, BYTE*DebugRoot);
В момент вызова параметр #FileRoot указывает на область памяти,
куда загружен файл модуля.
Обратите внимание, что это - не указатель памяти модуля в
отлаживаемом процессе, а лишь область, куда загружена точная копия
файла модуля. Cам DLL-модуль, как исполняемая часть отлаживаемой программы
находится всегда в контексте отлаживаемого процесса, а его секции
обычно смещены в памяти относительно их положения в файле.
Эти две причины делают неудобным извлечение символов прямо из него,
гораздо удобнее извлекать их прямо из файла.
Параметр #DebugRoot указывает на область памяти,
где находятся данные отладки. Этот параметр передается для удобства,
так как его можно самостоятельно вычислить из первого, зная формат
заголовка исполняемого файла, который приводится ниже.
Формат исполняемых файлов
Если Вы хотите написать загрузчик отладочной информации,
то вам, возможно, понадобится информация о формате самого
исполняемого файла. Это может потребоваться только в очень редких случаях,
когда по каким-либо причинам отладочная информация хранится не
в стандартном месте. В остальных же случаях можно использовать
указатель #DebugRoot, который указывает на отладочные данные в модуле.
В следущей таблице представлена структура заголовка стандартного
исполняемого *.exe или *.dll файла. Поля, которые могут оказаться полезными,
выделены жирным шрифтом. Более подробная информация на этот счет
доступна в MSDN, а все структуры объявлены в файле winnt.h.
| Тип данных | Имя | Полезное содержание
|
|---|
| IMAGE_DOS_HEADER | DosHeader | Указатель NT заголовка
|
|---|
| пропуск переменной длины...
| | DWORD | Signature | 'PE'
|
|---|
| IMAGE_FILE_HEADER | FileHeader | Кол-во секций и характеристики модуля
|
|---|
| IMAGE_OPTIONAL_HEADER32 | OptionalHeader | Точка входа, база образа, размеры, версия и другое
|
|---|
| IMAGE_DATA_DIRECTORY | Export | Экспортируемые символы
|
|---|
| IMAGE_DATA_DIRECTORY | Import | Импортируемые символы
|
|---|
| IMAGE_DATA_DIRECTORY | Resource | Ресурсы
|
|---|
| IMAGE_DATA_DIRECTORY | Exception | Exception Directory
|
|---|
| IMAGE_DATA_DIRECTORY | Security | Security Directory
|
|---|
| IMAGE_DATA_DIRECTORY | BaseReloc | Base Relocation Table
|
|---|
| IMAGE_DATA_DIRECTORY | Debug | Директория отладочных символов
|
|---|
| IMAGE_DATA_DIRECTORY | Copyright/Architecture | Architecture Specific Data
|
|---|
| IMAGE_DATA_DIRECTORY | GlobalPtr | RVA of GP
|
|---|
| IMAGE_DATA_DIRECTORY | TLS | TLS Directory
|
|---|
| IMAGE_DATA_DIRECTORY | LoadConfig | Load Configuration Directory
|
|---|
| IMAGE_DATA_DIRECTORY | BoundImport | Bound Import Directory in headers
|
|---|
| IMAGE_DATA_DIRECTORY | IAT | Import Address Table
|
|---|
| IMAGE_DATA_DIRECTORY | DelayImport | Delay Load Import Descriptors
|
|---|
| IMAGE_DATA_DIRECTORY | COM_Descriptor | COM Runtime descriptor
|
|---|
| пропуск переменной длины (обычно нулевой)...
| | IMAGE_SECTION_HEADER | Sections []
| Секции разных данных, включая ресурсы и секции кода (обычно одна).
Каждая секция имеет символьное имя длиной до восьми символов.
|
|---|
Здесь следует отметить одну тонкость: все секции в исполняемом файле
хранятся в компактном виде - ради экономии места промежутки между ними
искусственно уменьшены. Но после загрузки их в память
в качестве исполняемого модуля, их адреса выравниваются системой по границам страниц.
По этой причине многие значения в заголовках имеют пометку VA (virtual address),
означающий, что их реальное значение действительно только после выравнивания.
Поэтому для определения реального смещения таких значения в самом файле
из адреса следует вычитать значение #delta, вычисляемое по следующей формуле:
delta = section->VirtualAddress - section->PointerToRawData
Где #section - это секция, в пределах которой находится какой-либо VirtualAddress.
Для ее нахождения можно написать функцию FindSectionByVA,
которая сканирует секции и определяет в какой из них лежит заданный VA.
Разработчикам компиляторов
К ним у меня одна просьба: перестать изобретать велосипед,
забыть всякие "бинарные" форматы и использовать для отладочной
информации обычный текст, анализируя который можно понять его формат.
В качестве основы описания структур, перечисления и переменных можно
использовать Си-подобный синтаксис. Он очень легко поддается анализу,
и извлечению из него информации.
Самое главное достоинство такого подхода в том,
что его информативная избыточность делает его расширяемым снизу вверх.
Если вы хотите, чтобы для вашего компилятора существавал отладчик,
то позаботьтесь о доступности его формата отладки.
Пример плагина
В этом окне представлен пример простого плагина,
который в момент инициализации создает команды,
доступные через диалог управления плагинами.
При вызове одной из команд, на экран выскакивает окно
с соответствующим сообщением.
|