HOWTO: Avoid Crashes When Debugging System-wide Hooks

Last reviewed: January 27, 1998
Article ID: Q179905
The information in this article applies to:
  • Microsoft Win32 Software Development Kit (SDK) on the following platforms: - Microsoft Windows NT - Microsoft Windows 95

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

  1. 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.

  2. Certain hooks will still have problems with this method (most notably the journal hooks).

  3. 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.

  4. 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.

  5. 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:

   ARTICLE-ID: Q125677
   TITLE     : HOWTO: Share Data Between Different Mappings of a DLL

   ARTICLE-ID: Q100292
   TITLE     : PRB: Data Section Names Limited to Eight Characters

   ARTICLE-ID: Q100634
   TITLE     : HOWTO: Specify Shared and Nonshared Data in a DLL

   ARTICLE-ID: Q102428
   TITLE     : HOWTO: Debug a System-Wide Hook
Keywords          : UsrHks
Version           : WINNT:4.0
Platform          : winnt
Issue type        : kbhowto


================================================================================


THE INFORMATION PROVIDED IN THE MICROSOFT KNOWLEDGE BASE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL MICROSOFT CORPORATION OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, EVEN IF MICROSOFT CORPORATION OR ITS SUPPLIERS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES SO THE FOREGOING LIMITATION MAY NOT APPLY.

Last reviewed: January 27, 1998
© 1998 Microsoft Corporation. All rights reserved. Terms of Use.