From ccac889f27622255dfb199b2f534d69e2e1e9050 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Sat, 18 Jul 2026 12:18:18 +0200 Subject: [PATCH] Defer deleted breakpoints until step finishes --- TitanEngine/TitanEngine.Debugger.DebugLoop.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp b/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp index 762c58a..9f92bd0 100644 --- a/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp +++ b/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp @@ -8,6 +8,7 @@ #include "Global.Librarian.h" #include "Global.TLS.h" #include +#include #include #define UE_MODULEx86 0x2000; @@ -83,6 +84,7 @@ __declspec(dllexport) void TITCALL DebugLoop() DWORD ThreadBeingProcessed = 0; std::unordered_map SuspendedThreads; + std::unordered_set DeferredExceptionThreads; bool IsDbgReplyLaterSupported = false; // Check if DBG_REPLY_LATER is supported based on Windows version (Windows 10, version 1507 or above) @@ -131,10 +133,12 @@ __declspec(dllexport) void TITCALL DebugLoop() } else { - // Regular timeout, wait again - // After 2 consecutive timeouts, clear recently deleted breakpoints + // Do not expire deleted breakpoints while a breakpoint step-over is + // still in flight. The stepped instruction can block indefinitely and + // breakpoint events from the suspended threads remain pending until its + // single-step event arrives. consecutiveTimeouts++; - if(consecutiveTimeouts >= 2) + if(consecutiveTimeouts >= 2 && ThreadBeingProcessed == 0 && SuspendedThreads.empty() && DeferredExceptionThreads.empty()) recentlyDeletedBpx.clear(); continue; } @@ -142,6 +146,8 @@ __declspec(dllexport) void TITCALL DebugLoop() // Event received, reset timeout counter consecutiveTimeouts = 0; + const uint64_t eventThreadKey = (uint64_t(DBGEvent.dwProcessId) << 32) | DBGEvent.dwThreadId; + const bool completingDeferredException = DeferredExceptionThreads.count(eventThreadKey) != 0; if(IsDbgReplyLaterSupported) { @@ -150,7 +156,9 @@ __declspec(dllexport) void TITCALL DebugLoop() // Check if there is a thread processing a single step if(ThreadBeingProcessed != 0 && DBGEvent.dwThreadId != ThreadBeingProcessed) { - // Reply to the dbg event later + // Reply to the event later and retain its ownership state until + // the same thread's event is processed normally. + DeferredExceptionThreads.insert(eventThreadKey); DBGCode = DBG_REPLY_LATER; goto continue_dbg_event; @@ -1445,6 +1453,8 @@ continue_dbg_event: { break; } + if(DBGCode != DBG_REPLY_LATER && completingDeferredException) + DeferredExceptionThreads.erase(eventThreadKey); if(engineProcessIsNowDetached) { DebugActiveProcessStop(dbgProcessInformation.dwProcessId);