调试器在异常 C++ 时不挂起被调试者

Debugger not suspending debugee upon exception C++

我用 C++ 做了一个非常简单的调试器,它似乎工作正常,除非我调用 WaitForDebugEvent 除了 INFINITE 之外的 dwMilliseconds 的任何值它不会挂起调试器立即。

我有一个if语句检查异常地址是否匹配我设置的断点地址if(ExceptionDebugInfo.ExceptionRecord.ExceptionAddress == lpBreakpoint)

递减eip(wow64cntxt.Eip --;),用原指令(WriteProcessMemory)替换断点(int3)字节,刷新指令缓存(FlushInstructionCache), 然后设置 eip 指向我用原始指令 (Wow64SetThreadContext) 替换的断点。

然后return进入主调试循环(break)并继续调试(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;
}

如果我在 WaitForDebugEvent 中使用 INFINITE 以外的任何其他内容,则 eip 将设置为在我设置的断点后一段时间执行的地址。

问题是,如果我不将 WaitForDebugEventINFINITE 一起使用,那么当调试器捕获到异常时,eip 已经越过了断点。即使我WaitForDebugEvent等待0毫秒,立即return,被调试者仍然跑过断点。

这会导致访问冲突,我猜是因为我用断点替换的指令的另一半变成了一个新的操作码,它修改了不允许的内存。

这是我的主要调试循环:

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

    ProcessDebugEvent(&dbgEvent);

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

如有任何信息、见解、提示、解释等,我们将不胜感激。谢谢。

dwMilliseconds参数告诉WaitForDebugEvent()等待调试事件到达的时间:

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

您需要检查 WaitForDebugEvent() 的 return 值以确保您确实有需要处理的真实调试事件:

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.

例如:

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

也就是说,dwMilliseconds 参数对命中断点时调试对象等待的时间没有影响。当遇到断点时,被调试者立即停止,并通知你的调试器。这在文档中有明确说明:

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.

所以很可能,ProcessDebugEvent() 根本没有正确处理断点,然后只有当您在完成处理后调用 ContinueDebugEvent() 时,调试对象才会被唤醒。