HOWTO: Avoid Crashes When Debugging System-wide Hooks
ID: Q179905
|
The information in this article applies to:
-
Microsoft Win32 Software Development Kit (SDK)
SUMMARY
Debugging a system-wide hook can be very difficult because the debugger is
also being hooked. It is not uncommon to lock up the system, thus requiring
a reboot. This article shows you how to avoid locking up the system.
Basically, the hook procedure must check to see if the process being hooked
is the debugger. If it is, the hook procedure bypasses the debugger code.
MORE INFORMATION
First, your hook procedure needs to check to see if the current process is
the debugger. If it is not, proceed to your code. If it is, simply call the
CallNextHookEx function. Because this is a system-wide hook, the procedure
for determining if you are in the debugger should be kept to a minimum, or
else it could slow the process down tremendously.
Sample Code
Use the following sample code to debug a keyboard hook:
#ifdef _DEBUG
#define PATHSIZE 256
BOOL IsCurProcDebugger();
#endif
// Shared DATA
#pragma data_seg("HOOKVARS")
// Other variables should be in here (for example, hhookKeyboard).
// Add this variable to the shared data segment.
#ifdef _DEBUG
DWORD dwDebuggerProcId = 0;
#endif
#pragma data_seg()
// Filter function for the WH_KEYBOARD.
LRESULT CALLBACK KeyboardFunc (int nCode, WPARAM wParam, LPARAM lParam )
{
if ( nCode >= 0 )
{
// Add this if-block to your code.
//*** You should put breakpoints ONLY inside this if-block ***
#ifdef _DEBUG
if ( !IsCurProcDebugger() ) // If you are not in the debugger...
{
#endif
// Your hook code goes here.
#ifdef _DEBUG
}
#endif
}
return( CallNextHookEx(hhookKeyboard, nCode, wParam, lParam));
}
#ifdef _DEBUG
// *** DO NOT PUT ANY BREAKPOINTS IN THIS CODE!!!
BOOL IsCurProcDebugger()
{
// In Visual C++, declare these three variables after the
// following if-block to speed things up.
char szPathName[PATHSIZE];
char *szFileName = szPathName;
char *tcp;
// Do this first for speed.
// If there is only one debugger present
// and you have already found it.
if ( dwDebuggerProcId )
{
return ( GetCurrentProcessId() == dwDebuggerProcId );
}
// If only one debugger is running, then the rest of this code
// should be entered only until the debugger is first hooked.
// After that, the preceding code should catch it every time.
GetModuleFileName( NULL, szPathName, PATHSIZE );
// Only check the file name, not the full path.
// A co-worker's path may be different.
for ( tcp = szPathName; *tcp; tcp++ )
{
if ( *tcp == '/' || *tcp == '\\' )
szFileName = tcp + 1;
}
// Use "MSDEV.EXE" for the Visual C++ debugger, or
// else use YOUR debugger's name.
if ( !strcmp( _strupr(szFileName), "MSDEV.EXE") )
{
// It's the debugger!
dwDebuggerProcId = GetCurrentProcessId();
return TRUE;
}
return FALSE;
}
#endif // #ifdef _DEBUG
Important Caveats
- The usual rules apply to the global variables (especially the
DebuggerProcId and the hhookKeyboard). They need to be in a shared data
segment or a memory mapped file.
- Certain hooks will still have problems with this method (most notably
the journal hooks).
- Certain types of hooks will occasionally exhibit strange behavior. For
example, I tested this with a keyboard hook. Without this solution,
pressing the F10 key to step to the next line of code or the F5 key to
continue would be impossible. With this solution, it works. However, if
you press F5 and release too slowly, a problem may occur. The "keydown"
event on F5 causes the debugger to "continue." By the time you release
the key, you are out of the debugger and the hook captures the F5's
"keyup" event. If you press F5 and release it quickly enough, the
"keyup" event occurs before the debugger continues the process.
- This code has not been tested with more than one debugger running. With
minor modifications, it should theoretically work. Also, you have to add
extra code if the debuggers have different names.
- Because of pre-emptive multi-tasking, shared data may be overwritten. If
this is a problem for you, synchronization objects (such as mutual
exclusions and semaphores) can help, but they should be used with
caution because synchronization objects put the hooked thread to sleep.
You may also need to watch out for deadlocks. Critical sections should
not be used because they are valid only within a single process space.
REFERENCES
For additional information, please see the following articles in the
Microsoft Knowledge Base:
Q125677
HOWTO: Share Data Between Different Mappings of a DLL
Q100292
PRB: Data Section Names Limited to Eight Characters
Q100634
HOWTO: Specify Shared and Nonshared Data in a DLL
Q102428
HOWTO: Debug a System-Wide Hook
Additional query words:
Keywords : kbcode kbHook kbNTOS kbGrpUser kbWinOS
Version : WINDOWS:
Platform : WINDOWS
Issue type : kbhowto