mirror of https://github.com/x64dbg/TitanEngine
DebugLoop: continue past a program's DebugBreak()/int 3 like Visual Studio
A program-generated breakpoint exception (DebugBreak(), __debugbreak() or an
inline `int 3`) that is not in the engine's breakpoint table is currently
answered with DBG_EXCEPTION_NOT_HANDLED in the "breakpoint not in list" branch.
For a real single-byte int 3 the saved RIP already points past the instruction,
so passing it back to the debuggee re-raises it there: the program's own SEH
handler runs (or it becomes a second-chance/unhandled stop) and the debugger
cannot simply continue past the program's DebugBreak() the way Visual Studio
does (VS consumes it with DBG_CONTINUE and resumes at the next instruction).
This went through two iterations:
1. First attempt answered DBG_CONTINUE for every stray breakpoint exception.
That fixed DebugBreak() but regressed RaiseException(STATUS_BREAKPOINT):
the exception was consumed and the debuggee's __except handler no longer
ran.
2. Final version distinguishes a genuine single-byte int 3 from a programmatic
raise by reading the byte at ExceptionRecord.ExceptionAddress:
- 0xCC (a real int 3 instruction the program executed): DBG_CONTINUE, so the
thread resumes at the instruction after the int 3 and the debugger can
continue past the program's own DebugBreak() (no register surgery needed -
the CPU already advanced RIP; the Rip -= BreakPointSize rewind only applies
to breakpoints the engine itself patched in).
- otherwise (e.g. RaiseException(STATUS_BREAKPOINT)): DBG_EXCEPTION_NOT_HANDLED
is kept so the debuggee's SEH still runs.
Every other exception path is untouched (debugger-set breakpoints, the system
breakpoint, single-stepping, hardware/memory breakpoints, AV/div-zero etc.).
Verified on Windows x64 with a 21-case debugger test suite: DebugBreak(),
__debugbreak(), DebugBreak() with an SEH handler and
RaiseException(STATUS_BREAKPOINT)+__except all behave correctly, and the suite
shows no regressions. Related-but-distinct step-over-int3 cleanup was addressed
separately in PR #32 / issue #33.
This commit is contained in:
parent
ccac889f27
commit
7c07601581
|
|
@ -694,8 +694,22 @@ __declspec(dllexport) void TITCALL DebugLoop()
|
|||
}
|
||||
else
|
||||
{
|
||||
//not a recently deleted breakpoint - pass to debuggee
|
||||
DBGCode = DBG_EXCEPTION_NOT_HANDLED;
|
||||
// A program-generated breakpoint exception not raised by a debugger-set
|
||||
// breakpoint. Distinguish a real single-byte `int 3` instruction
|
||||
// (DebugBreak()/__debugbreak(): the byte at ExceptionAddress is 0xCC and
|
||||
// the CPU already advanced RIP past it) from a programmatic exception
|
||||
// such as RaiseException(STATUS_BREAKPOINT) (no 0xCC at that address).
|
||||
//
|
||||
// - real int 3: consume it (DBG_CONTINUE) like Visual Studio, so the
|
||||
// thread resumes at the instruction after the int 3 and execution
|
||||
// continues past the program's own DebugBreak.
|
||||
// - otherwise: pass it to the debuggee (DBG_EXCEPTION_NOT_HANDLED) so
|
||||
// the program's SEH handlers run (e.g. RaiseException+__except).
|
||||
unsigned char opcode = 0;
|
||||
bool isInt3 = MemoryReadSafe(dbgProcessInformation.hProcess,
|
||||
(LPVOID)exceptionAddress, &opcode, 1, 0) &&
|
||||
opcode == 0xCC;
|
||||
DBGCode = isInt3 ? DBG_CONTINUE : DBG_EXCEPTION_NOT_HANDLED;
|
||||
if(DBGCustomHandler->chBreakPoint != NULL)
|
||||
{
|
||||
myCustomHandler = (fCustomHandler)((LPVOID)DBGCustomHandler->chBreakPoint);
|
||||
|
|
|
|||
Loading…
Reference in New Issue