I had to figure out how to implement DeadlockDetection given the preceding requirements. I first needed to determine what functions I needed to monitor so that I could report the complete deadlock trace. Table 12-1 lists all the functions, grouped by type, I decided I needed to monitor to implement DeadlockDetection.
After pondering the problem of how to collect the information I needed to satisfy the first four requirements, I realized that I was going to have to intercept (or hook) the functions in Table 12-1 to record the acquisition and release of synchronization objects. Hooking functions isn't a trivial task, and I'll explain how I implemented the code in the section "Hooking Imported Functions" later in this chapter. The one constraint that hooking imported functions imposed on DeadlockDetection is that the code for DeadlockDetection must reside in a DLL because the hooks apply only to the address space in which they're created. This constraint means that the user must load the DeadlockDetection DLL into her address space, a requirement that isn't too harsh given the benefits. As a DLL, the utility would integrate easily with a user program, a condition specified in requirement 7 in the list in the preceding section.
Gathering the information to satisfy requirements 1 through 4 follows as a direct consequence of choosing the in-process function hooking approach. This approach means that each of the multithreading and synchronization functions will be calling directly into the DeadlockDetection code with all the information I need.
Table 12-1 Functions That DeadlockDetection Monitors
Type | Function |
---|---|
Thread-related functions | CreateThread ExitThread SuspendThread ResumeThread TerminateThread |
Critical-section functions | InitializeCriticalSection InitializeCriticalSectionAndSpinCount DeleteCriticalSection EnterCriticalSection LeaveCriticalSection SetCriticalSectionSpinCount TryEnterCriticalSection |
Mutex functions | CreateMutexA CreateMutexW OpenMutexA OpenMutexW ReleaseMutex |
Semaphore functions | CreateSemaphoreA CreateSemaphoreW OpenSemaphoreA OpenSemaphoreW ReleaseSemaphore |
Event functions | CreateEventA CreateEventW OpenEventA OpenEventW PulseEvent ResetEvent SetEvent |
Blocking functions | WaitForSingleObject WaitForSingleObjectEx WaitForMultipleObjects WaitForMultipleObjectsEx MsgWaitForMultipleObjects MsgWaitForMultipleObjectsEx SignalObjectAndWait |
Special functions | CloseHandle ExitProcess GetProcAddress |
Making DeadlockDetection as lightweight as possible (requirement 5) was a tough condition to satisfy. I tried to code efficiently, but efficient code went only so far toward fulfilling the goal I set out to achieve. Figuring that you would know best what types of synchronization objects you're using in your program, I grouped the object types so that you can specify just those functions you want to hook. For example, if you're concerned only about deadlock problems on mutexes, you can process only mutex functions.
To make DeadlockDetection even more useful, you can specify, on the fly, which sets of synchronization object functions you want to watch. You can also turn DeadlockDetection on and off as many times as needed. You might even want to give your application an accelerator key or a special menu option that toggles the entire DeadlockDetection system. Allowing this narrow scope meets requirement 5 and helps with requirement 7.
The only requirement left is 6: making the output processing as extensible as possible. I wanted to give you the ability to slice and dice the output, rather than force you to make do with some arbitrary, hard-coded format. By keeping the main hooking and processing separate from the output code, I can achieve greater code reuse because the only part being changed, the output side, is much easier to develop than the core side. I named the output portions DeadlockDetection extensions, or DeadDetExt for short. A DeadDetExt is simply a DLL that has several exported functions that DeadlockDetection looks for and calls when appropriate.
Now it's time to explain how to use DeadlockDetection. If you understand the requirements I set out and understand how to use this utility, you'll find it easier to see how I implemented it.