Forcing a DebugBreak()



March 01, 1999
URL:http://www.drdobbs.com/forcing-a-debugbreak/184416530

March 1999/Forcing a DebugBreak()

While developing and debugging, I regularly use Win32’s DebugBreak() and assertions to either break into or launch the registered debugger. Most of the time this works fine and accomplishes what I need. However, if the DebugBreak() call happens to lie within an exception handler, the call may be caught (and ignored) rather than launching the debugger as intended. To solve this problem, I devised a method which unconditionally breaks into the debugger.

About DebugBreak()

The primary purpose of DebugBreak() is to signal the debugger that an event has occurred. Internally, DebugBreak() raises the STATUS_BREAKPOINT (0x80000003) exception. The breakpoint exception is treated as any other: if unhandled, the application is terminated; however, it is typically (and often transparently) handled by the debugger.

Although all Windows platforms support DebugBreak(), under x86 builds it is advantageous to directly issue the breakpoint exception via the INT 3 opcode. The benefit is that the debugger will stop in application code rather than in system-level code within DebugBreak().

Additionally, certain Visual C++ library components internally use DebugBreak() (under x86 builds, MFC and the C runtime library use the INT 3 opcode rather than an actual DebugBreak() call, however the end result is the same), specifically assert() under the C runtime and ASSERT() under MFC. In debug builds, both of these functions eventually end up issuing a breakpoint exception if the assert condition fails.

Just-in-Time Debugging

Just-in-time (or JIT) debugging is an operating system feature that allows an external application the chance to gain control over an application that for some reason or another has generated an unhandled exception. Although the JIT settings typically refer to a debugger, it could be any application. For instance, Dr. Watson is the default JIT “debugger” under NT, but it gathers post-mortem information rather than operating as a true debugger.

Originally, just-in-time debugging was controlled via the “AeDebug” section in win.ini. With the introduction of NT and the subsequent move towards the centralized registry, these settings have migrated to:

HKEY_LOCAL_MACHINE
  \Software
    \Microsoft
      \Windows NT
        \CurrentVersion
          \AeDebug

Under Win95 and Win98 (both of which still support the win.ini settings), the registry setting takes precedence over any win.ini settings. Within the “AeDebug” key (or section) the “Debugger” value defines the application command line that should be executed if JIT is activated.

I have been unable to locate firm documentation on the actual supported parameter substitutions for the “Debugger” command line. However, after disassembly, reverse engineering, and empirical study, I have concluded that two command-line substitutions are required, and in the following order: the process id to be debugged, and an event to be signaled when the debugger has attached. Parameter substitution is performed using traditional printf()-style format specifiers. For example, Microsoft Visual C++ uses the following command line (see Figure 1):

C:\DevStudio\SharedIDE\BIN\msdev.exe -p %ld -e %ld

Basically, the JIT process is as follows:

1) An application causes an exception.

2) If the application does not handle the exception, the operating system steps in and traps the exception, suspending the application in the process.

3) The application error dialog is displayed (see Figure 2 and Figure 3). If a JIT debugger is registered, the user is offered the choice to debug the offending application. If not, the only option is to terminate the application. The “AeDebug” key also supports an “Auto” setting, that (if set to “1”) causes the registered JIT debugger to execute without user intervention via the Application Error dialog box.

4) Presuming the application is JIT-debugged, the operating system constructs the appropriate command line using the “Debugger” value from the “AeDebug” key and executes it.

5) The debugger attaches to the offending application via DebugActiveProcess() and signals the event specified in the command line, at which point the debugger has full control over the application.

Exception Handling

As previously mentioned, DebugBreak() results in a breakpoint exception which can be treated as any other exception using either operating system or language constructs. Both structured exception handling (__try/__except) and C++ exception handling (try/catch) allow an application to trap and optionally handle an exception. Unfortunately, these exception-handling mechanisms also trap (and optionally handle) debug breakpoint exceptions which may then interfere with the intended result of launching the debugger. Since failed assertions are generally used to signal exceptional conditions, it is often tedious to clutter standard exception handling clauses to special-case such breakpoint exceptions. Further, catch-all clauses (such as catch(...)) will transparently swallow breakpoint exceptions, never allowing just-in-time debugging a chance to execute.

Although exception handling is nothing new, it is becoming more and more prevalent in application code. Because of this and without careful consideration, DebugBreak() calls can often be transparently handled by such exception handling clauses, thus defeating the overriding purpose of DebugBreak(). For a number of reasons, it is not always practical or efficient to special-case breakpoint exceptions, therefore I came up with the solution presented below.

Forcing a DebugBreak()

What I needed was a method to guarantee that just-in-time debugging was activated for a DebugBreak() call. Although the actual process is relatively straightforward, a truly platform-independent implementation is clouded by the system differences between Win95/Win98 and NT.

Basically, there are two required steps to ensure that JIT is successfully activated during a call to DebugBreak(): if the current process is not running under the control of a debugger, launch the registered JIT debugger to attach and debug the process, then issue the DebugBreak() call. The debugger will then trap the call; execution will stop at that point, thus allowing further interactive debugging of the application.

In creating this solution, the first problem that I encountered was in determining if the currently executing process was running under the control of a debugger. Under NT and Win98, IsDebuggerPresent() returns TRUE if the current process is being debugged, FALSE otherwise. Alas, Win95 does not support IsDebuggerPresent(), and in fact it is not even included in the standard import libraries or as a Win95 stub export. Because of this, the function must be dynamically linked. Fortunately, Matt Pietrek in his excellent dissection of operating system internals [1] located an undocumented field in the process database that indicates whether or not the process is being debugged.

Using this information, my code first attempts to dynamically link to IsDebuggerPresent(). If that fails, then the undocumented hacks are performed on the process database to determine if the application is being debugged.

Following that, I attempt to locate the registered system JIT debugger. As previously detailed, I first search the registry and then the win.ini file to locate the debugger command line.

After successfully locating the registered debugger, it is a relatively simple task to create the signaling event and the required command line, and then launch the debugger. Of particular note, the created event handle that is passed to the debugger must be specified as inheritable, both in the CreateEvent() and CreateProcess() calls. After the appropriate process id and event handle substitutions are made, the resultant command line is passed to CreateProcess() to actually launch the debugger.

Once launched, it is necessary to wait for the debugger to assume control of the process before issuing the DebugBreak() call. This is done by waiting on the event handle that was initially passed to the debugger on the command line. All that remains once the event is signaled is to issue a breakpoint exception, at which point the process is suspended and the debugger gains control.

Source Code

fdbgbrk.cpp (Listing 1) contains the main source code for ForceDebugBreak(), which implements the techniques described in this article. ForceDebugBreak() will launch the debugger and cause a break, but only if the application is not currently being debugged. fdbgbrk.cpp also contains LocateAeDebugger(), which demonstrates how to locate the registered JIT debugger.

isdbgex.cpp (Listing 2) implements a function called IsDebuggerPresentEx(), which ForceDebugBreak() uses to determine whether or not the process is executing under the context of a debugger. In that same file, IsDebuggerPresent95() uses undocumented process fields and flags to determine whether a Win95 process is currently being debugged. I recommend Chapter 3 of Matt Pietrek’s book [1] for details on accessing these undocumented structures.

Finally, main.cpp (Listing 3) is a small application that tests ForceDebugBreak(). The program displays a message box and the user can press OK to call ForceDebugBreak(), or Cancel to attempt a DebugBreak() (see Figure 4). All of this takes place within a C++ try/catch clause. You can compile this test program under Visual C++ with this command line:

cl -GX -Zi fdbgbrk.cpp isdbgex.cpp main.cpp /Fefdbgbrk.exe

The source assumes that the application is running under Win95, Win98, or NT 4.0 or later. Further operating system version checks would be required if this cannot be guaranteed.

The code was developed with Visual C++ v5.0, compiles cleanly with STRICT defined and at warning level 4, and was tested on NT 4.0 (service packs 3 and 4), Win95, and Win98. It has not been tested on non-Intel builds or with other compilers.

Reference

[1] Pietrek, Matt. Windows 95 System Programming Secrets. Foster City, CA: IDG Books Worldwide, Inc., 1995.

Bret S. Pehrson started professionally programming educational software back in the days of Windows 3.0. Since then, he has worked on Multimedia authoring packages and 3-D rendering applications. He currently works as a professional consultant writing Windows CE applications.

Get Source Code

March 1999/Forcing a DebugBreak()/Figure 1

Figure 1: Registry description of JIT debugger

March 1999/Forcing a DebugBreak()/Figure 2

Figure 2: Typical exception dialog

March 1999/Forcing a DebugBreak()/Figure 3

Figure 3: Invoking Dr. Watson for an exception

March 1999/Forcing a DebugBreak()/Figure 4

Figure 4: The test application in action

March 1999/Forcing a DebugBreak()/Listing 1

Listing 1: fdbgbrk.cpp — Implementation of ForceDebugBreak()

/* disable unnecessary level-4 warnings */
#pragma warning(disable : 4201)  //nameless struct/union
#pragma warning(disable : 4214)  //bit field types other than int
#pragma warning(disable : 4514)  //unreferenced inline function

/* include files */
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tchar.h>
#include <regstr.h>
#include "IsDbgEx.h"
#include "FDbgBrk.h"

/* required link libraries */
#pragma comment(lib, "kernel32")
#pragma comment(lib, "user32")
#pragma comment(lib, "advapi32")

/* private macros */
#ifndef CountOf
#define CountOf(array) (sizeof(array)/sizeof((array)[0]))
#endif

/* private type definitions */
typedef BOOL (WINAPI * ISDEBUGGERPRESENTPROC)(VOID);

/* private function prototypes */
BOOL LocateAeDebugger(LPTSTR lpszDest, const UINT cchDest);

//------------------------------------------------------------------
//ForceDebugBreak()
//------------------------------------------------------------------
VOID WINAPI ForceDebugBreak(VOID)
{
    /* TEMPORARY VARIABLES */
    PROCESS_INFORMATION pi = { NULL };
    SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) };
    STARTUPINFO         si = { sizeof(STARTUPINFO) };
    const DWORD         dwProcessId = GetCurrentProcessId();
    HANDLE              hEventDebuggerReady = NULL;
    TCHAR               szDebugger[MAX_PATH+1] = TEXT("");
    TCHAR               szCmdLine[MAX_PATH+1] = TEXT("");
    DWORD               cbData = 0;
    HKEY                hkeyDebugger = NULL;
    LONG                lResult = 0;

    /* continue if not currently being debugged */
    if (!IsDebuggerPresentEx())
    {
        /* determine the installed debugger */
        if (LocateAeDebugger(szDebugger, CountOf(szDebugger)))
        {
            /* create the debugger ready signaling event */
            sa.lpSecurityDescriptor = NULL;
            sa.bInheritHandle = TRUE;  //handle must be inheritable
            hEventDebuggerReady = CreateEvent(&sa, TRUE, FALSE, NULL);
            /* format the command line and execute */
            wsprintf(
                szCmdLine,
                szDebugger,
                (unsigned long)dwProcessId,
                (unsigned long)hEventDebuggerReady);
            if (CreateProcess(
              NULL, 
              szCmdLine, 
              NULL, 
              NULL, 
              TRUE,  //debugger must inherit handles
              0, 
              NULL, 
              NULL, 
              &si, 
              &pi))
            {
                CloseHandle(pi.hProcess);
                CloseHandle(pi.hThread);
                /* wait for debugger */
                WaitForSingleObject(hEventDebuggerReady, INFINITE);
            }
            /* clean up */
            CloseHandle(hEventDebuggerReady);
            hEventDebuggerReady = NULL;
        }
    }
    /* break */
    #ifndef _M_IX86
    DebugBreak();
    #else //_M_IX86
    __asm { int 3 }
    #endif //_M_IX86
    /* done */
    return;
}

//------------------------------------------------------------------
//LocateAeDebugger()
//    Attempt to locate the registed AeDebugger: first search
//    the registry, then search win.ini.
//------------------------------------------------------------------
BOOL LocateAeDebugger(LPTSTR lpszDest, const UINT cchDest)
{
    /* TEMPORARY VARIABLES */
    DWORD cbData = 0;
    HKEY  hkeyDebugger = NULL;
    LONG  lResult = ERROR_GEN_FAILURE;
    BOOL  fSuccess = FALSE;

    /* determine the installed debugger */
    lResult = RegOpenKeyEx(
        HKEY_LOCAL_MACHINE, 
        REGSTR_PATH_AEDEBUG, 
        0, 
        KEY_READ, 
        &hkeyDebugger);
    if (lResult == ERROR_SUCCESS)
    {
        cbData = cchDest * sizeof(TCHAR);
        lResult = RegQueryValueEx(
            hkeyDebugger, 
            REGSTR_VAL_AEDEBUG_DEBUGGER, 
            NULL, 
            NULL, 
            (LPBYTE)lpszDest, 
            &cbData);
        RegCloseKey(hkeyDebugger);
        hkeyDebugger = NULL;
        fSuccess = lResult == ERROR_SUCCESS;
    }
    /* search win.ini if not found */
    if (lResult != ERROR_SUCCESS)
    {
        fSuccess = GetProfileString(
            TEXT("AeDebug"), 
            TEXT("Debugger"), 
            TEXT(""), 
            lpszDest, 
            cchDest) > 0;
    }
    /* done */
    return fSuccess;
}

/* End of file */
March 1999/Forcing a DebugBreak()/Listing 2

Listing 2: isdbgex.cpp — Implementation of IsDebuggerPresentEx()

// IsDbgEx.cpp
//    Windows platform independent implementation of
//    IsDebuggerPresent.

/* disable unnecessary level-4 warnings */
#pragma warning(disable : 4201)  //nameless struct/union
#pragma warning(disable : 4214)  //bit field types other than int
#pragma warning(disable : 4514)  //unreferenced inline function

/* include files */
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "IsDbgEx.h"

/* required link libraries */
#pragma comment(lib, "kernel32")

/* private type definitions */
typedef BOOL (WINAPI * ISDEBUGGERPRESENTPROC)(VOID);

/* private function prototypes */
BOOL IsDebuggerPresent95(VOID);

//------------------------------------------------------------------
//IsDebuggerPresentEx()
//------------------------------------------------------------------
BOOL IsDebuggerPresentEx(VOID)
{
    /* CONSTANTS */
    static const ISDEBUGGERPRESENTPROC fnIsDebuggerPresent = 
        (ISDEBUGGERPRESENTPROC)GetProcAddress(
            GetModuleHandle(TEXT("KERNEL32")), 
            TEXT("IsDebuggerPresent"));

    /* TEMPORARY VARIABLES */
    BOOL fDebuggerPresent = FALSE;

    /* use documented API if available */
    if (fnIsDebuggerPresent != NULL)
    {
        fDebuggerPresent = fnIsDebuggerPresent();
    }
    /* otherwise use undocumented Win95 technique */
    else
    {
        fDebuggerPresent = IsDebuggerPresent95();
    }
    /* done */
    return fDebuggerPresent;
}

//------------------------------------------------------------------
//IsDebuggerPresent95()
//    Returns TRUE if process is being debugged, FALSE otherwise.
//    Note that this should only be used under Windows 95.  
//    Windows NT and Windows 98 should use the documented 
//    IsDebuggerPresent() API.
//    See chapter 3 of 'Windows 95 System Programming Secrets' by 
//    Matt Pietrek for details on the obfuscator and the structure 
//    of the process database.
//------------------------------------------------------------------
BOOL IsDebuggerPresent95(VOID)
{
    #ifdef _M_IX86
    /* CONSTANTS */
    static const DWORD FLAG_DEBUGGERPRESENT = 0x00000001;
    static const UINT  cbProcessDatabase = 190;
    static const UINT  dwOffsetFlags = 8;
    /* TEMPORARY VARIABLES */
    const DWORD  dwThreadId = GetCurrentThreadId();
    const DWORD  dwProcessId = GetCurrentProcessId();
    LPVOID       pProcessDatabase = NULL;
    DWORD        obfuscator = 0;
    DWORD        dwFlags = 0;
    BOOL         fIsDebuggerPresent = FALSE;

    /* retrieve the obfuscator */
    __asm
    {
        mov ax, fs
        mov es, ax
        mov eax, 18h
        mov eax, es:[eax]
        sub eax, 10h
        xor eax, [dwThreadId]
        mov [obfuscator], eax
    }
    /* locate the process database and validate */
    pProcessDatabase = (VOID *)(dwProcessId ^ obfuscator);
    if (!IsBadReadPtr(pProcessDatabase, cbProcessDatabase))
    {
        /* get process flags and determine if debugger present */
        dwFlags = ((DWORD *)pProcessDatabase)[dwOffsetFlags];
        fIsDebuggerPresent = dwFlags & FLAG_DEBUGGERPRESENT;
    }
    /* done */
    return fIsDebuggerPresent;
    #else //!_M_IX86
    SetLastError(ERROR_NOT_IMPLEMENTED);
    return FALSE;
    #endif //!_M_IX86
}

/* End of file */
March 1999/Forcing a DebugBreak()/Listing 3

Listing 3: main.cpp — Test application

//==================================================================
// Main.cpp
//    Force debug breakpoint test application
//    Bret S. Pehrson for Windows Developer's Journal (12.23.98)
//==================================================================

/* disable unnecessary level-4 warnings */
#pragma warning(disable : 4201)  //nameless struct/union
#pragma warning(disable : 4214)  //bit field types other than int
#pragma warning(disable : 4514)  //unreferenced inline function

/* include files */
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tchar.h>
#include "FDbgBrk.h"

/* required link libraries */
#pragma comment(lib, "kernel32")
#pragma comment(lib, "user32")
#pragma comment(lib, "advapi32")

//------------------------------------------------------------------
//WinMain()
//------------------------------------------------------------------
int WINAPI WinMain(
    HINSTANCE /*hInstance*/, 
    HINSTANCE /*hPrevInstance*/, 
    LPSTR /*lpCmdLine*/, 
    int /*nCmdShow*/)
{
    /* TEMPORARY VARIABLES */
    int nResult = 0;

    /* attempt force debug break */
    try
    {
        nResult = MessageBox(
            NULL, 
            TEXT("Press OK to force a debug break or "
                 "Cancel for DebugBreak()"), 
            TEXT("Force Debug Break"), 
            MB_ICONQUESTION|MB_OKCANCEL);
        if (nResult == IDOK)
        {
            ForceDebugBreak();
        }
        else
        {
            DebugBreak();
        }
    }
    catch (...)
    {
    }
    /* done */
    return 0;
}

/* End of file */
March 1999/Forcing a DebugBreak()

Forcing a DebugBreak()

Bret S. Pehrson


While developing and debugging, I regularly use Win32’s DebugBreak() and assertions to either break into or launch the registered debugger. Most of the time this works fine and accomplishes what I need. However, if the DebugBreak() call happens to lie within an exception handler, the call may be caught (and ignored) rather than launching the debugger as intended. To solve this problem, I devised a method which unconditionally breaks into the debugger.

About DebugBreak()

The primary purpose of DebugBreak() is to signal the debugger that an event has occurred. Internally, DebugBreak() raises the STATUS_BREAKPOINT (0x80000003) exception. The breakpoint exception is treated as any other: if unhandled, the application is terminated; however, it is typically (and often transparently) handled by the debugger.

Although all Windows platforms support DebugBreak(), under x86 builds it is advantageous to directly issue the breakpoint exception via the INT 3 opcode. The benefit is that the debugger will stop in application code rather than in system-level code within DebugBreak().

Additionally, certain Visual C++ library components internally use DebugBreak() (under x86 builds, MFC and the C runtime library use the INT 3 opcode rather than an actual DebugBreak() call, however the end result is the same), specifically assert() under the C runtime and ASSERT() under MFC. In debug builds, both of these functions eventually end up issuing a breakpoint exception if the assert condition fails.

Just-in-Time Debugging

Just-in-time (or JIT) debugging is an operating system feature that allows an external application the chance to gain control over an application that for some reason or another has generated an unhandled exception. Although the JIT settings typically refer to a debugger, it could be any application. For instance, Dr. Watson is the default JIT “debugger” under NT, but it gathers post-mortem information rather than operating as a true debugger.

Originally, just-in-time debugging was controlled via the “AeDebug” section in win.ini. With the introduction of NT and the subsequent move towards the centralized registry, these settings have migrated to:

HKEY_LOCAL_MACHINE
  \Software
    \Microsoft
      \Windows NT
        \CurrentVersion
          \AeDebug

Under Win95 and Win98 (both of which still support the win.ini settings), the registry setting takes precedence over any win.ini settings. Within the “AeDebug” key (or section) the “Debugger” value defines the application command line that should be executed if JIT is activated.

I have been unable to locate firm documentation on the actual supported parameter substitutions for the “Debugger” command line. However, after disassembly, reverse engineering, and empirical study, I have concluded that two command-line substitutions are required, and in the following order: the process id to be debugged, and an event to be signaled when the debugger has attached. Parameter substitution is performed using traditional printf()-style format specifiers. For example, Microsoft Visual C++ uses the following command line (see Figure 1):

C:\DevStudio\SharedIDE\BIN\msdev.exe -p %ld -e %ld

Basically, the JIT process is as follows:

1) An application causes an exception.

2) If the application does not handle the exception, the operating system steps in and traps the exception, suspending the application in the process.

3) The application error dialog is displayed (see Figure 2 and Figure 3). If a JIT debugger is registered, the user is offered the choice to debug the offending application. If not, the only option is to terminate the application. The “AeDebug” key also supports an “Auto” setting, that (if set to “1”) causes the registered JIT debugger to execute without user intervention via the Application Error dialog box.

4) Presuming the application is JIT-debugged, the operating system constructs the appropriate command line using the “Debugger” value from the “AeDebug” key and executes it.

5) The debugger attaches to the offending application via DebugActiveProcess() and signals the event specified in the command line, at which point the debugger has full control over the application.

Exception Handling

As previously mentioned, DebugBreak() results in a breakpoint exception which can be treated as any other exception using either operating system or language constructs. Both structured exception handling (__try/__except) and C++ exception handling (try/catch) allow an application to trap and optionally handle an exception. Unfortunately, these exception-handling mechanisms also trap (and optionally handle) debug breakpoint exceptions which may then interfere with the intended result of launching the debugger. Since failed assertions are generally used to signal exceptional conditions, it is often tedious to clutter standard exception handling clauses to special-case such breakpoint exceptions. Further, catch-all clauses (such as catch(...)) will transparently swallow breakpoint exceptions, never allowing just-in-time debugging a chance to execute.

Although exception handling is nothing new, it is becoming more and more prevalent in application code. Because of this and without careful consideration, DebugBreak() calls can often be transparently handled by such exception handling clauses, thus defeating the overriding purpose of DebugBreak(). For a number of reasons, it is not always practical or efficient to special-case breakpoint exceptions, therefore I came up with the solution presented below.

Forcing a DebugBreak()

What I needed was a method to guarantee that just-in-time debugging was activated for a DebugBreak() call. Although the actual process is relatively straightforward, a truly platform-independent implementation is clouded by the system differences between Win95/Win98 and NT.

Basically, there are two required steps to ensure that JIT is successfully activated during a call to DebugBreak(): if the current process is not running under the control of a debugger, launch the registered JIT debugger to attach and debug the process, then issue the DebugBreak() call. The debugger will then trap the call; execution will stop at that point, thus allowing further interactive debugging of the application.

In creating this solution, the first problem that I encountered was in determining if the currently executing process was running under the control of a debugger. Under NT and Win98, IsDebuggerPresent() returns TRUE if the current process is being debugged, FALSE otherwise. Alas, Win95 does not support IsDebuggerPresent(), and in fact it is not even included in the standard import libraries or as a Win95 stub export. Because of this, the function must be dynamically linked. Fortunately, Matt Pietrek in his excellent dissection of operating system internals [1] located an undocumented field in the process database that indicates whether or not the process is being debugged.

Using this information, my code first attempts to dynamically link to IsDebuggerPresent(). If that fails, then the undocumented hacks are performed on the process database to determine if the application is being debugged.

Following that, I attempt to locate the registered system JIT debugger. As previously detailed, I first search the registry and then the win.ini file to locate the debugger command line.

After successfully locating the registered debugger, it is a relatively simple task to create the signaling event and the required command line, and then launch the debugger. Of particular note, the created event handle that is passed to the debugger must be specified as inheritable, both in the CreateEvent() and CreateProcess() calls. After the appropriate process id and event handle substitutions are made, the resultant command line is passed to CreateProcess() to actually launch the debugger.

Once launched, it is necessary to wait for the debugger to assume control of the process before issuing the DebugBreak() call. This is done by waiting on the event handle that was initially passed to the debugger on the command line. All that remains once the event is signaled is to issue a breakpoint exception, at which point the process is suspended and the debugger gains control.

Source Code

fdbgbrk.cpp (Listing 1) contains the main source code for ForceDebugBreak(), which implements the techniques described in this article. ForceDebugBreak() will launch the debugger and cause a break, but only if the application is not currently being debugged. fdbgbrk.cpp also contains LocateAeDebugger(), which demonstrates how to locate the registered JIT debugger.

isdbgex.cpp (Listing 2) implements a function called IsDebuggerPresentEx(), which ForceDebugBreak() uses to determine whether or not the process is executing under the context of a debugger. In that same file, IsDebuggerPresent95() uses undocumented process fields and flags to determine whether a Win95 process is currently being debugged. I recommend Chapter 3 of Matt Pietrek’s book [1] for details on accessing these undocumented structures.

Finally, main.cpp (Listing 3) is a small application that tests ForceDebugBreak(). The program displays a message box and the user can press OK to call ForceDebugBreak(), or Cancel to attempt a DebugBreak() (see Figure 4). All of this takes place within a C++ try/catch clause. You can compile this test program under Visual C++ with this command line:

cl -GX -Zi fdbgbrk.cpp isdbgex.cpp main.cpp /Fefdbgbrk.exe

The source assumes that the application is running under Win95, Win98, or NT 4.0 or later. Further operating system version checks would be required if this cannot be guaranteed.

The code was developed with Visual C++ v5.0, compiles cleanly with STRICT defined and at warning level 4, and was tested on NT 4.0 (service packs 3 and 4), Win95, and Win98. It has not been tested on non-Intel builds or with other compilers.

Reference

[1] Pietrek, Matt. Windows 95 System Programming Secrets. Foster City, CA: IDG Books Worldwide, Inc., 1995.

Bret S. Pehrson started professionally programming educational software back in the days of Windows 3.0. Since then, he has worked on Multimedia authoring packages and 3-D rendering applications. He currently works as a professional consultant writing Windows CE applications.

Get Source Code

March 1999/Forcing a DebugBreak()/Figure 1

Figure 1: Registry description of JIT debugger

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.