调试器在异常 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
将设置为在我设置的断点后一段时间执行的地址。
问题是,如果我不将 WaitForDebugEvent
与 INFINITE
一起使用,那么当调试器捕获到异常时,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
参数对命中断点时调试对象等待的时间没有影响。当遇到断点时,被调试者立即停止,并通知你的调试器。这在文档中有明确说明:
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()
时,调试对象才会被唤醒。
我用 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
将设置为在我设置的断点后一段时间执行的地址。
问题是,如果我不将 WaitForDebugEvent
与 INFINITE
一起使用,那么当调试器捕获到异常时,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
参数对命中断点时调试对象等待的时间没有影响。当遇到断点时,被调试者立即停止,并通知你的调试器。这在文档中有明确说明:
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()
时,调试对象才会被唤醒。