Keep per-thread stepping state

Closes #21
Closes #20
This commit is contained in:
Duncan Ogilvie 2026-07-15 16:02:34 +02:00
parent ec7a8b9352
commit e80b959150
4 changed files with 41 additions and 18 deletions

View File

@ -29,9 +29,7 @@ DWORD ProcessExitCode = 0;
HANDLE DBGFileHandle; HANDLE DBGFileHandle;
std::vector<ULONG_PTR> tlsCallBackList; std::vector<ULONG_PTR> tlsCallBackList;
std::vector<PROCESS_ITEM_DATA> hListProcess; std::vector<PROCESS_ITEM_DATA> hListProcess;
DWORD engineStepCount = 0; std::unordered_map<DWORD, EngineStepThreadState> engineStepThreads;
LPVOID engineStepCallBack = NULL;
bool engineStepActive = false;
bool engineProcessIsNowDetached = false; bool engineProcessIsNowDetached = false;
DWORD DBGCode = DBG_CONTINUE; DWORD DBGCode = DBG_CONTINUE;
bool engineFileIsBeingDebugged = false; bool engineFileIsBeingDebugged = false;
@ -90,6 +88,7 @@ void DebuggerReset()
std::vector<BreakPointDetail>().swap(BreakPointBuffer); std::vector<BreakPointDetail>().swap(BreakPointBuffer);
std::unordered_map<ULONG_PTR, MemoryBreakpointPageDetail>().swap(MemoryBreakpointPages); std::unordered_map<ULONG_PTR, MemoryBreakpointPageDetail>().swap(MemoryBreakpointPages);
recentlyDeletedBpx.clear(); recentlyDeletedBpx.clear();
engineStepThreads.clear();
} }
void ClearProcessList() void ClearProcessList()

View File

@ -2,6 +2,7 @@
#define _GLOBAL_DEBUGGER_H #define _GLOBAL_DEBUGGER_H
#include <vector> #include <vector>
#include <unordered_map>
#include <Windows.h> #include <Windows.h>
extern HARDWARE_DATA DebugRegister[4]; extern HARDWARE_DATA DebugRegister[4];
@ -29,9 +30,18 @@ extern DWORD ProcessExitCode;
extern HANDLE DBGFileHandle; extern HANDLE DBGFileHandle;
extern std::vector<ULONG_PTR> tlsCallBackList; extern std::vector<ULONG_PTR> tlsCallBackList;
extern std::vector<PROCESS_ITEM_DATA> hListProcess; extern std::vector<PROCESS_ITEM_DATA> hListProcess;
extern DWORD engineStepCount; // Per-thread pending single-step state. A single-step (user step or the internal
extern LPVOID engineStepCallBack; // step-over of a breakpoint) is always armed on a specific thread by setting that
extern bool engineStepActive; // thread's trap flag, but the resulting STATUS_SINGLE_STEP event can be delivered
// interleaved with other threads' events. Keying the step state by thread id makes
// each thread consume only its own step, and allows several threads to be stepped
// independently (matching GleeBug).
struct EngineStepThreadState
{
LPVOID callback; // step callback to fire when the step completes
DWORD count; // remaining repeats (0 = fire the callback on the next step)
};
extern std::unordered_map<DWORD, EngineStepThreadState> engineStepThreads;
extern bool engineProcessIsNowDetached; extern bool engineProcessIsNowDetached;
extern DWORD DBGCode; extern DWORD DBGCode;
extern bool engineFileIsBeingDebugged; extern bool engineFileIsBeingDebugged;

View File

@ -38,7 +38,9 @@ __declspec(dllexport) void TITCALL ForceClose()
__declspec(dllexport) void TITCALL StepInto(LPVOID StepCallBack) __declspec(dllexport) void TITCALL StepInto(LPVOID StepCallBack)
{ {
EnterCriticalSection(&engineStepActiveCr); EnterCriticalSection(&engineStepActiveCr);
if(!engineStepActive) // Arm a single-step for the current event thread only. State is per-thread, so a
// step already pending on another thread does not block this one.
if(engineStepThreads.find(DBGEvent.dwThreadId) == engineStepThreads.end())
{ {
ULONG_PTR ueCurrentPosition = GetContextData(UE_CIP); ULONG_PTR ueCurrentPosition = GetContextData(UE_CIP);
unsigned char instr[16]; unsigned char instr[16];
@ -60,9 +62,7 @@ __declspec(dllexport) void TITCALL StepInto(LPVOID StepCallBack)
myDBGContext.EFlags |= UE_TRAP_FLAG; myDBGContext.EFlags |= UE_TRAP_FLAG;
SetThreadContext(hActiveThread, &myDBGContext); SetThreadContext(hActiveThread, &myDBGContext);
EngineCloseHandle(hActiveThread); EngineCloseHandle(hActiveThread);
engineStepActive = true; engineStepThreads[DBGEvent.dwThreadId] = { StepCallBack, 0 };
engineStepCallBack = StepCallBack;
engineStepCount = 0;
} }
} }
LeaveCriticalSection(&engineStepActiveCr); LeaveCriticalSection(&engineStepActiveCr);
@ -93,7 +93,11 @@ __declspec(dllexport) void TITCALL StepOut(LPVOID StepOut, bool StepFinal)
__declspec(dllexport) void TITCALL SingleStep(DWORD StepCount, LPVOID StepCallBack) __declspec(dllexport) void TITCALL SingleStep(DWORD StepCount, LPVOID StepCallBack)
{ {
StepInto(StepCallBack); StepInto(StepCallBack);
engineStepCount = StepCount - 1; //We already stepped once EnterCriticalSection(&engineStepActiveCr);
auto it = engineStepThreads.find(DBGEvent.dwThreadId);
if(it != engineStepThreads.end())
it->second.count = StepCount - 1; //We already stepped once
LeaveCriticalSection(&engineStepActiveCr);
} }
__declspec(dllexport) void TITCALL SetNextDbgContinueStatus(DWORD SetDbgCode) __declspec(dllexport) void TITCALL SetNextDbgContinueStatus(DWORD SetDbgCode)

View File

@ -16,22 +16,28 @@
static void engineStep() static void engineStep()
{ {
EnterCriticalSection(&engineStepActiveCr); EnterCriticalSection(&engineStepActiveCr);
if(engineStepActive) // Only deliver the pending single-step to the thread it was armed for. State is
// per-thread, so an automatic breakpoint step-over on a different thread cannot
// consume this thread's step or fire its callback on the wrong thread.
auto it = engineStepThreads.find(DBGEvent.dwThreadId);
if(it != engineStepThreads.end())
{ {
DBGCode = DBG_CONTINUE; DBGCode = DBG_CONTINUE;
if(engineStepCount == 0) if(it->second.count == 0)
{ {
typedef void(TITCALL * fCustomBreakPoint)(void); typedef void(TITCALL * fCustomBreakPoint)(void);
auto cbStep = fCustomBreakPoint(engineStepCallBack); auto cbStep = fCustomBreakPoint(it->second.callback);
engineStepActive = false; engineStepThreads.erase(it);
engineStepCallBack = NULL;
LeaveCriticalSection(&engineStepActiveCr); LeaveCriticalSection(&engineStepActiveCr);
cbStep(); cbStep();
} }
else else
{ {
SingleStep(engineStepCount, engineStepCallBack); auto count = it->second.count;
auto callback = it->second.callback;
engineStepThreads.erase(it); // SingleStep re-arms this thread's entry
LeaveCriticalSection(&engineStepActiveCr); LeaveCriticalSection(&engineStepActiveCr);
SingleStep(count, callback);
} }
} }
else else
@ -1288,7 +1294,11 @@ __declspec(dllexport) void TITCALL DebugLoop()
//general unhandled exception callback //general unhandled exception callback
if(DBGCode == DBG_EXCEPTION_NOT_HANDLED) if(DBGCode == DBG_EXCEPTION_NOT_HANDLED)
{ {
engineStepActive = false; // The current thread's pending single-step (if any) did not complete
// normally; cancel it. Other threads' steps are unaffected.
EnterCriticalSection(&engineStepActiveCr);
engineStepThreads.erase(DBGEvent.dwThreadId);
LeaveCriticalSection(&engineStepActiveCr);
if(DBGCustomHandler->chUnhandledException != NULL) if(DBGCustomHandler->chUnhandledException != NULL)
{ {