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.
This commit is contained in:
Duncan Ogilvie 2026-07-15 20:11:37 +02:00
parent e80b959150
commit ba2a3aa7c8
1 changed files with 30 additions and 0 deletions

View File

@ -166,6 +166,36 @@ __declspec(dllexport) void TITCALL DebugLoop()
SuspendedThreads.clear(); SuspendedThreads.clear();
ThreadBeingProcessed = 0; 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;
} }
} }
} }