Creating a featherweight debugger

What do I mean by “featherweight debugger”? I mean implementing just enough of the debugging framework to get what we need from the debuggee and nothing more.

The problem I was trying to solve was how to get more information from first chance exceptions. We have a great deal of library code that uses catch(…) blocks to prevent any exceptions – even structured ones like access violations – from escaping into the application. This is pretty important when your code is running inside an Excel session that someone may’ve been working on for hours (and which could take minutes just to re-calculate in the event of having to re-open it). Unfortunately, because the catch handlers are built into all the code, if you discover something causing access violations, say, there’s no single place that you can change to get more diagnostic information.

So, with source level changes ruled out, how else could you catch exceptions?

Well, the obvious thing that does this already is the debugger, so I decided to look at the debugging API, and see if it was possible to use that. It turns out that the implementation is pretty simple; essentially the debugger simply waits on an event in a loop, receiving further specific information about the event when it’s fired.

    DEBUG_EVENT DebugEv;                   // debugging event information

    DWORD dwContinueStatus = DBG_CONTINUE; // exception continuation

    while (true)

    {

        WaitForDebugEvent(&DebugEv, INFINITE);

 

        switch (DebugEv.dwDebugEventCode)

        {

            // Do something with event

            // Set dwContinueStatus to tell debuggee what to do

        }

 

        // Resume executing the thread that reported the debugging event.

        ContinueDebugEvent(DebugEv.dwProcessId,

                           DebugEv.dwThreadId,

                           dwContinueStatus);

    }

There are several ‘informational’ events, DLLs being loaded and unloaded etc, and, more interestingly, there’s one called EXCEPTION_ACCESS_VIOLATION. Voila!

When this event is received, we can use the additional information we’re passed, along with some thread context data, to create a minidump. This is an extremely useful mechanism for snapshotting the process in a rich enough way to provide full post-mortem debugging abilities.

Here’s some sample code to create a minidump using the with enough information to be able to (amongst other things):

  • Get stack traces
  • Get memory usage information (using !address -summary)

DWORD CreateMiniDump(DEBUG_EVENT ev)

{

    HANDLE hproc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ev.dwProcessId);

    if (!hproc)

        return ReportError(_T(“Failed to open process”),GetLastError());

 

    EXCEPTION_POINTERS ep;

    ep.ExceptionRecord = &ev.u.Exception.ExceptionRecord;

    HANDLE hthread = OpenThread(THREAD_ALL_ACCESS, FALSE, ev.dwThreadId);

    if (!hthread)

    {

        DWORD dw = GetLastError();

        CloseHandle(hproc);

        return ReportError(_T(“Failed to open thread”),dw);

    }

CONTEXT ctxt;

ZeroMemory(&ctxt, sizeof(ctxt));

    GetThreadContext(hthread, &ctxt);

    ep.ContextRecord = &ctxt;

 

    TCHAR sz[MAX_PATH];

    _stprintf_s(sz, _countof(sz), _T(“c:\\dump_%ld_%ld_%ld.dmp”), ev.dwProcessId, ev.dwThreadId, nDumps++);

    HANDLE hfile = CreateFile(sz, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);

    if (!hfile)

    {

        DWORD dw = GetLastError();

        CloseHandle(hproc);

        return ReportError(_T(“Failed to create output file”),dw);

    }

 

    MINIDUMP_EXCEPTION_INFORMATION info;

    info.ClientPointers = FALSE;

    info.ThreadId = ev.dwThreadId;

    info.ExceptionPointers = &ep;

    if (!MiniDumpWriteDump(hproc, ev.dwProcessId, hfile,

        MiniDumpWithFullMemory,

        &info,

        NULL,

        NULL))

    {

        DWORD dw = GetLastError();

        CloseHandle(hfile);

        CloseHandle(hproc);

        return ReportError(_T(“Failed to write mini dump”),dw);

    }

    CloseHandle(hfile);

    CloseHandle(hproc);

    _tprintf(_T(” Minidump written to %s\n”), sz);

    return ERROR_SUCCESS;

}

Caveat: Example code only, please excuse the hard-coded path!

The debugger loop is then wrapped up as a stand-alone application that can be passed the PID of a process to debug. Assuming we’ve got sufficient permissions, our debugger can attach to that process using DebugActiveProcess(pid).

So, here we have a relatively lightweight way of ‘watching’ another process and grabbing lots of potentially useful diagnostic information in the event that something untoward happens. Of course when using this in the wild you’d also need some means of tidying up the generated files, and potentially some means of logging that the event had occurred, but these are relatively straightforward to implement.

This entry was posted in Debugging, Software Development, WinDbg, Windows. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.
  • Sepultang

    Thanks! This really helped me out creating a proper MiniDump.

    One thing to notice though is to be sure to do a ZeroMemory on “CONTEXT ctxt;” before sending it to GetThreadContext as the ctxt is an _inout param. (Else it will randomly fail.)

  • ian

    Glad you found it useful. I’ll update the code based on your suggestion.

  • Sepultang

    I discovered you should also set the ContextFlags to CONTEXT_ALL in order to get a proper context:

    CONTEXT ctxt;
    ZeroMemory(&ctxt, sizeof(ctxt));
    ctxt.ContextFlags = CONTEXT_ALL;

    Thanks.

  • ian

    OK, right.

    BTW, what OS are you using? I’m surprised I didn’t notice these issues myself, and I’m wondering if it’s because of differences between versions of Windows.

  • Sepultang

    Windows Vista 64bit.

    I had no problem at first, but since the CONTEXT is uninitialized but still used I guess you can just be lucky with the values not being all messed up.
    Also with ContextFlags being null you do get a callstack, but you can’t do a proper run&break in VS with the MiniDump.

blog comments powered by Disqus
  • Follow me on Twitter Follow me on Twitter @voyce

  • Categories

  • Archives