Использование DeadlockDetection
Первым шагом в использовании утилиты DeadlockDetection является размещение файла ее инициализации DEADLOCKDETECTION.DLL и соответствующего файла расширения DeadDetExt DLL в том же каталоге, в котором находится сама программа. Файл инициализации — это простой INI-файл, который, как минимум, должен указывать имя файла расширения (в нашем случае DeadDetExt), чтобы загружать его. В следующем примере показан файл инициализации DEADLOCKDETECTION.INI, который загружает файл TEXTFILEDDEXT.DLL
[Initialization]
Единственное обязательное значение — имя файла DeadDetExt,
который будет обрабатывать вывод
ExtDll = "TextFileDDExt.dll"
Если StartlnDllMain равен 1, DeadlockDetection будет
инициализирована в своей функции DllMain, чтобы регистрация
могла начинаться как можно раньше.
StartlnDllMain = О
Если StartlnDllMain равно 1, InitialOpts указывает
начальные режимы для DeadlockDetection. Это значение
является комбинацией DDOPT_*-флажков.
InitialOpts = 0
Как можно видеть по некоторым установкам, инициализация DeadlockDetection возможна только в том случае, если в ней уже вызвана функция LoadLibrary. Хорошей идеей профилактической (упреждающей) отладки было создание "закулисной" инициализации приложения, которая вызывает LoadLibrary для DLL с указанным именем, если приложение обнаруживает специальный ключ реестра или переменную окружения. Этот альтернативный подход к инициализации приложения означал бы, что условная компиляция не нужна, а пользователю предоставлены средства прямого получения DLL-файлов в свое адресное пространство. Конечно, все это предполагает, что все DLL, которые загружаются таким способом, достаточно "разумны", чтобы совершенно самостоятельно инициализироваться в своих DiiMain-секциях и не требовать вызова никаких других экспортируемых функций в DLL.
Если вместо использования INI-файла ваш код устанавливает параметры инициализации, то нужно включить в приложение файл заголовков DEADLOCKDETECTION.H и компоновать его с библиотечным файлом DEADLOCKDETECTION.LIB.
Для того чтобы инициализировать DeadlockDetection самостоятельно, надо лишь вызвать в подходящий момент функцию openDeadiockDetection, которая принимает единственный параметр — режимы начальных отчетов (initial reporting options). Все DDOPT_*-флажки перечислены в табл. 12.2. Чтобы получить возможность записывать (регистрировать) всю ключевую информацию об объектах синхронизации, функцию OpenDeadiockDetection необходимо вызвать перед тем, как приложение начнет создание потоков.
Таблица 12.2. Режимы отчетов DeadlockDetection
Флажок
|
Пределы регистрации
|
DDOPT_THREADS |
Функции, относящиеся к потокам |
DDOPT CRITSEC |
Функции критической секции |
DDOPT_MUTEX |
Функции мьютекса |
DDOPT SEMAPHORE |
Функции семафора |
DDOPT_EVENT |
Функции событий |
DDOPT ALL |
Все подключаемые функции |
Наряду с исходным кодом DeadlockDetection, сопровождающий компакт-диск содержит и DLL-файл расширения DeadDetExt (TEXTFILEDDEXT.DLL). Это относительно простое расширение записывает всю информацию в текстовый файл. Когда DeadlockDetection выполняется с TEXTFILEDDEXT.DLL, расширение создает текстовый файл в том же каталоге, где находится выполняемая программа. Текстовый файл использует имя выполняемого файла с расширением .DD. Например, если выполняется SIMPTEST.EXE, то в результате будет создан файл SIMPTEST.DD. Листинг 12-1 показывает пример вывода из TEXTFILEDDEXT.DLL.
Листинг 12-1. Вывод DeadlockDetection с использованием TEXTFILEDDEXT.DLL
TID Ret Addr C/R Ret Value Function & Params
0x000000F? [Ox004011AC] (R) 0x00000000 InitializeCriticalSection
0x00403110
0x000000F7 [0x00401106] (R) 0x00000290 CreateEventA 0x00000000, 1, 0,
0x004030F0 [The event name]
0x000000F? [Ox004011E9] (R) 0x00000294 CreateThread 0x00000000,
0x00000000, 0x00401000,
0x00000000, 0x00000000,
0x0012FF68
0x000000F7 [0x0040120C] (R) 0x00000298 CreateThread 0x00000000,
0x00000000, 0x004010BC,
0x00000000, 0x00000000,
0x0012FF68
0x000000FV [0x00401223] (C) EnterCriticalSection 0x00403110
0x000000F7 [0x00401223] (R) 0x00000000 EnterCriticalSection 0x00403110 0x000000F? [0x00401238] (C) WaitForSingleObject 0x00000290,
INFINITE
0x000000FF [Oxl020B973] (C) EnterCriticalSection 0xl025CE90
0x000000FF [Oxl020B973] (R) 0x00000000 EnterCriticalSection 0xl025CE90
0x00000l0C [Ox004010F3] (R) 0x000002A4 OpenEventA 0x001F0003, 0,
0x004030BC
[The event name]
Заметьте, что информация о функции и параметрах (в столбце Function & Params) в листинге 12-1 не поместилась в одной строке и поэтому перенесена на следующие строки (в этом же столбце). Информация выводится на экран в следующем порядке:
1. Идентификатор (ID) выполняющегося потока.
2. Адрес возврата, указывающий, какая из ваших функций вызвала функцию синхронизации. Используя утилиту CrashFinder, рассмотренную в главе 8, можно просмотреть адреса возвратов и узнать, как вы входили в ситуации блокировки.
3. Индикатор вызова/возврата (Call/Return — C/R), помогающий идентифицировать действия, которые происходят до и после конкретной функции.
4. Возвращаемое значение функции, если программа возвращает функции отчетов.
5. Имя функции синхронизации.
6. Список параметров функции синхронизации. В квадратных скобках указаны данные, поясняющие некоторые значения. Я ограничился здесь показом строчных значений, но можно также добавлять такие данные, как индивидуальные флажки.
Если запускаемое приложение блокируется, то для того чтобы увидеть последний вызванный элемент синхронизации, аннулируйте процесс и просмотрите файл вывода.TEXTFILEDDEXT.DLL сохраняет последнее файловое обновление, сбрасывая буферы файлов каждый раз, когда вызываются функции WaitFor*, EnterCriticalSection И TryEnterCriticalSection.
Предостережение
Если включить полную регистрацию (всех функций), то можно довольно долго генерировать чрезвычайно большой файл. Используя Visual С++-приложение MTGDI, я генерировал текстовый файл объемом в 11 Мбайт за одну или две минуты, если создавал пару потоков.