From ba2a3aa7c8cbc51f1b21f498592a6680dd6002aa Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 15 Jul 2026 20:11:37 +0200 Subject: [PATCH] Complete orphaned breakpoint step-over reset when the stepped thread exits If a thread is terminated (e.g. by ExitProcess) while it is in the middle of a breakpoint step-over - its breakpoint temporarily removed and a restore pending on its next single-step - the EXIT_THREAD handler cleared ThreadBeingProcessed but left the reset state (ResetBPX/ResetHwBPX/ResetMemBPX) armed and the breakpoint removed. The next thread's event then consumed that orphaned reset and was misinterpreted as a step-over reset, producing a stray single-step that was passed to the debuggee unhandled and crashed it. This was most visible with hardware breakpoints under multi-thread contention. Complete the pending restore when the thread-being-processed exits. --- .../TitanEngine.Debugger.DebugLoop.cpp | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp b/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp index 4641709..942eb02 100644 --- a/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp +++ b/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp @@ -166,6 +166,36 @@ __declspec(dllexport) void TITCALL DebugLoop() SuspendedThreads.clear(); ThreadBeingProcessed = 0; + + // The thread was in the middle of a breakpoint step-over (its + // breakpoint was temporarily removed and a restore was pending on its + // next single-step) but it is exiting before that single-step arrives. + // Complete the restore now: otherwise the breakpoint is left removed + // and the next thread's event would consume this dead thread's orphaned + // reset state and be misinterpreted as a step-over reset, producing a + // stray single-step that is passed to (and crashes) the debuggee. + if(ResetBPX) + { + EnableBPX(ResetBPXAddressTo); + ResetBPXAddressTo = 0; + ResetBPX = false; + } + if(ResetHwBPX) + { + SetHardwareBreakPoint(DebugRegisterX.DrxBreakAddress, DebugRegisterXId, DebugRegisterX.DrxBreakPointType, DebugRegisterX.DrxBreakPointSize, (LPVOID)DebugRegisterX.DrxCallBack); + ResetHwBPX = false; + } + if(ResetMemBPX) + { + ResetMemBpxCallback(); + if(ResetMemBpxExtraCallback != nullptr) + { + ResetMemBpxExtraCallback(); + ResetMemBpxExtraCallback = nullptr; + } + ResetMemBPX = false; + } + PushfBPX = false; } } }