Search code examples
c++debuggingwinapibreakpoints

Debugger not suspending debugee upon exception C++


I made a very simple debugger in C++ and it seems to work alright except if I call WaitForDebugEvent with any value for dwMilliseconds besides INFINITE it doesn't suspend the debugee immediately.

I have an if statement that checks whether the exception address matches the address of the breakpoint I set if(ExceptionDebugInfo.ExceptionRecord.ExceptionAddress == lpBreakpoint).

Decrements eip(wow64cntxt.Eip --;), replaces the breakpoint(int3) byte with the original instruction(WriteProcessMemory), flushes the instruction cache(FlushInstructionCache), and then sets eip to point to the breakpoint I replaced with the original instruction(Wow64SetThreadContext).

Then it returns to the main debug loop(break) and continues debugging(ContinueDebugEvent).

case EXCEPTION_BREAKPOINT:
{
    WOW64_CONTEXT wow64cntxt = {0};

    wow64cntxt.ContextFlags = WOW64_CONTEXT_ALL;

    if(!Wow64GetThreadContext(hThread, &wow64cntxt))
    {
        printf("Error getting thread context: %lu\n", GetLastError());
    }

    //lpFunction is the address of a mov instruction I set a breakpoint on
    if(excDbgInfo.ExceptionRecord.ExceptionAddress == lpBreakpoint)
    {
        printf("EIP-Before: 0x%X\n", wow64cntxt.Eip);

        //Decrement eip value to point back to the opcode I wrote over with int3
        wow64cntxt.Eip --;

        printf("EIP-After: 0x%X\n", wow64cntxt.Eip);

        //original opcode I replaced with int3(0xCC)
        instr = 0x89;

        //replace the breakpoint with the original instruction
        if(!WriteProcessMemory(hProcess, lpBreakpoint, &instr, sizeof(CHAR), NULL))
        {
            printf("Error reversing breakpoint: %lu\n", GetLastError());
        }

        //Flush the instruction cache
        FlushInstructionCache(hProcess, lpBreakpoint, 1);

        //Set eip to previous instruction
        if(!Wow64SetThreadContext(hThread, &wow64cntxt))
        {
            printf("Error setting thread context: %lu\n", GetLastError());
        }

    }
    system("pause");
    //Return to main debug loop, ContinueDebugEvent...
    break;
}

If I use anything other than INFINITE with WaitForDebugEvent then eip is set to an address that executes some time after the breakpoint I set.

The problem is that if I don't use WaitForDebugEvent with INFINITE then eip has already gone past the breakpoint when the debugger catches the exception. Even if I have WaitForDebugEvent wait for 0 milliseconds, return immediately, the debugee still runs past the breakpoint.

This then results in an access violation, I'm guessing because the other half of the instruction I replaced with the breakpoint becomes a new opcode that modifies memory it isn't allowed to.

Here's my main debug loop:

while(1)
{
    WaitForDebugEvent(&dbgEvent, INFINITE);

    ProcessDebugEvent(&dbgEvent);

    ContinueDebugEvent(dbgEvent.dwProcessId, dbgEvent.dwThreadId, DBG_CONTINUE);
}

Any information, insight, tips, explanations, etc would be greatly appreciated. Thanks.


Solution

  • The dwMilliseconds parameter tells WaitForDebugEvent() how long to wait for a debug event to arrive:

    dwMilliseconds [in]
    The number of milliseconds to wait for a debugging event. If this parameter is zero, the function tests for a debugging event and returns immediately. If the parameter is INFINITE, the function does not return until a debugging event has occurred

    You need to check the return value of WaitForDebugEvent() to make sure you actually have a real debug event that needs processing:

    If the function succeeds, the return value is nonzero.

    If the function fails, the return value is zero. To get extended error information, call GetLastError.

    For example:

    while (1)
    {
        if (WaitForDebugEvent(&dbgEvent, AnyValueHere)) // <--
        {
            ProcessDebugEvent(&dbgEvent);
            ContinueDebugEvent(dbgEvent.dwProcessId, dbgEvent.dwThreadId, DBG_CONTINUE);
        }
        ...
    }
    

    That being said, the dwMilliseconds parameter has no effect on how long the debuggee waits when a breakpoint is hit. When the breakpoint is hit, the debugee stops immediately, and your debugger is notified. This is clearly stated in the documentation:

    Debugging Events

    When the system notifies the debugger of a debugging event, it also suspends all threads in the affected process. The threads do not resume execution until the debugger continues the debugging event by using ContinueDebugEvent.

    So chances are, ProcessDebugEvent() is simply not handling the breakpoint correctly, and then the debuggee is woken up only when you call ContinueDebugEvent() after done processing.