Отладка приложений

         

Использование 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

Все подключаемые функции

В любой точке программы можно изменять режимы отчетов, вызывая функцию SetDeadiockDetectionOptions. Эта функция принимает тот же набор флажков (объединенных операцией OR), что и функция OpenDeadiockDetection. Чтобы получить текущий режим отчетов, нужно вызвать функцию GetDeadlockDetectionOptions. Во время выполнения программы можно изменять текущий режим сколько угодно раз. Если нужно приостановить или возобновить регистрацию, вызывайте функцию ResumeDeadlockDetection ИЛИ SuspendDeadlockDetection.

Наряду с исходным кодом 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 Мбайт за одну или две минуты, если создавал пару потоков.



Содержание раздела