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

DeadlockDetection и проблемы высокоуровневого проектирования


Необходимо было найти способ реализации DeadlockDetection, который позволил бы выполнить все предыдущие требования. Во-первых, необходимо было определить, какие нужны функции для организации такого управления программой, который дал бы возможность сообщать пользователю полную трассу блокировки. В табл. 12.1 перечислены (и сгруппированы по типам) все функции, которые, по моему мнению, были нужны для организации управления в программе DeadlockDetection.

Обдумывая, как собрать информацию, необходимую для удовлетворения первых четырех требований, я понял, что надо перехватывать (или подключаться по адресу1) функции, указанные в табл 12.1, для того чтобы регистрировать (записывать) получение и освобождение объектов синхронизации. Подключение функций через адрес — нетривиальная задача (реализация соответствующего кода рассмотрена в разделе "Подключение импортированных функций" чуть ниже). Такое подключение накладывает на DeadlockDetection следующее ограничение: ее код должен постоянно храниться в DLL, потому что такие подключения применяются только к адресному пространству, в котором он создан. Это ограничение означает, что пользователь должен загрузить DLL с программой DeadlockDetection в ее адресное пространство. Это требование не слишком жестко по сравнению с получаемыми от него преимуществами. Как и DLL, утилита будет легко объединяться с программой пользователя (см. условие, указанное в п. 7 списка требований предыдущего раздела).

Сбор информации, удовлетворяющей требованиям 1—4, является прямым следствием выбора особого подхода "с подключением функций в процессе выполнения программы" (in-process function hooking approach). Такой подход означает, что каждая многопоточная функция и функция синхронизации будет вызываться прямо в процессе выполнения DeadlockDetection вместе со всей необходимой информацией.

Таблица 12.1. Функции, мониторинг которых осуществляет DeadlockDetection



                 Тип              Функция

Функции, относящиеся к потокам

CreateThread                                                      ExitThread                                                             SuspendThread                                                         ResumeThread                                                 TerminateThread

Функции критической секции

Initial izeCr it icalSection

InitializeCriticalSectionAndSpinCount                              

DeleteCr it icalSection                                                       

EnterCr it icalSection                                                      

LeaveCr it icalSection                                

SetCriticalSectionSpinCount                      

TryEnterCriticalSection

Функции мьютекса

CreateMutexA                                                       CreateMutexW                                                       OpenMutexA                                                       OpenMutexW                                                      ReleaseMutex


Функции семафора

CreateSemaphoreA                                  CreateSemaphoreW                                             OpenSemaphoreA                                               OpenSemaphoreW                                           ReleaseSemaphore

Функции событий

CreateEventA                                                                 CreateEventW                                                             OpenEventA                                                                  OpenEventW                                                                        PulseEvent                                                                      ResetEvent                                                                       SetEvent

Функции блокировки

WaitForSingleObject                                   WaitForSingleObjectEx                             WaitForMultipleObjects                       WaitForMultipleObjectsEx                   MsgWaitForMultipleObjects    MsgWaitForMultipleObjectsEx                                 SignalOb j ectAndWait

Специальные функции

CloseHandle                                                                ExitProcess                                                         GetProcAddress




Требование минимального вмешательства DeadlockDetection в программу пользователя (п. 5 списка требований) довольно трудно удовлетворить. Рассчитывая на то, что разработчик должен знать, какие типы объектов синхронизации используются в его программе, я сгруппировал типы объектов так, чтобы можно было указывать лишь те функции, которые требуется подключить. Например, если проверяются только блокировки на мьютексах, то можно обрабатывать только мьютекс-функции.

Работая с DeadlockDetection, можно указать "на лету", какой набор функций объектов синхронизации надо наблюдать. Вы можете также включать и выключать DeadlockDetection столько раз, сколько необходимо. Можно даже назначить для приложения клавишу быстрого вызова или специальный пункт меню, который подключает всю DeadlockDetection-систему целиком. Реализация этих узких возможностей удовлетворяет требованиям п. 5 и помогает реализовать требование п. 7.

Что касается требования п. 6 — сделать обработку вывода настолько расширяемой, насколько возможно, то пользователю предоставляется возможность самостоятельно оформлять вывод. Организуя раздельное хранение кода основных подключений и кода обработки вывода, удалось достичь большей степени повторного использования кода, потому что единственную изменяемую часть кода — выводную — намного легче разрабатывать, чем его ядро. Я называю выводящие части кода расширениями утилиты DeadlockDetection (или еще короче — DeadDetExi). DeadDetExt это просто DLL, которая содержит несколько экспортируемых функций. DeadlockDetection отыскивает и вызывает эти функции, когда в них возникает необходимость.

Настало время объяснить, как можно использовать DeadlockDetection. Понимая рассмотренные требования и возможности применения этой утилиты, легче разобраться и в ее реализации.



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