From 21ef77f31fd42d17f785802c36b2ca4ca44c43d7 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Mon, 7 Sep 2026 02:42:15 +0200 Subject: [PATCH] Clean up TTD API --- TitanEngine/Global.Engine.cpp | 87 ++++++++++++++ TitanEngine/Global.Engine.h | 9 ++ TitanEngine/TitanEngine.Debugger.Control.cpp | 21 ++++ TitanEngine/TitanEngine.Debugger.Data.cpp | 7 +- .../TitanEngine.Debugger.DebugLoop.cpp | 13 ++ TitanEngine/TitanEngine.Engine.cpp | 4 + TitanEngine/TitanEngine.Process.cpp | 112 +++++++++++++++++- TitanEngine/TitanEngine.def | 6 +- TitanEngine/definitions.h | 6 +- TitanEngine/stdafx.h | 11 ++ 10 files changed, 265 insertions(+), 11 deletions(-) diff --git a/TitanEngine/Global.Engine.cpp b/TitanEngine/Global.Engine.cpp index 3a6a088..e13fd57 100644 --- a/TitanEngine/Global.Engine.cpp +++ b/TitanEngine/Global.Engine.cpp @@ -5,6 +5,7 @@ #include "Global.Mapping.h" #include "Global.Engine.Hash.h" #include "Global.Debugger.h" +#include bool engineCheckForwarders = true; bool engineAlowModuleLoading = false; @@ -20,6 +21,7 @@ bool engineSafeAttach = false; bool engineMembpAlt = false; bool engineDisableAslr = false; bool engineSafeStep = true; +bool engineWow64SingleStepWorkaround = true; char engineFoundDLLName[512] = {0}; char engineFoundAPIName[512] = {0}; @@ -30,6 +32,91 @@ LPVOID engineDependencyFiles; LPVOID engineDependencyFilesCWP; void* EngineStartUnpackingCallBack; +namespace +{ +std::mutex pauseMutex; +LPVOID pauseCallback = nullptr; +ULONGLONG pauseRequestTime = 0; +ULONG_PTR pauseBreakInStart = 0; +DWORD pauseBreakInThreadId = 0; +bool pauseBreakInExpected = false; +} + +bool EngineBeginPause(LPVOID Callback) +{ + std::lock_guard lock(pauseMutex); + if(pauseCallback) + return false; + pauseCallback = Callback; + pauseRequestTime = GetTickCount64(); + pauseBreakInStart = 0; + pauseBreakInThreadId = 0; + pauseBreakInExpected = false; + return true; +} + +bool EnginePauseShouldEscalate(TitanPausePolicy MaximumPolicy) +{ + std::lock_guard lock(pauseMutex); + return pauseCallback && !pauseBreakInExpected && MaximumPolicy == UE_PAUSE_POLICY_AGGRESSIVE && + GetTickCount64() - pauseRequestTime >= 2000; +} + +void EngineExpectPauseBreakIn(ULONG_PTR StartAddress) +{ + std::lock_guard lock(pauseMutex); + pauseBreakInExpected = true; + pauseBreakInStart = StartAddress; + pauseBreakInThreadId = 0; +} + +void EngineCancelPauseBreakIn() +{ + std::lock_guard lock(pauseMutex); + pauseBreakInExpected = false; + pauseBreakInStart = 0; + pauseBreakInThreadId = 0; +} + +void EngineObservePauseThread(DWORD ThreadId, ULONG_PTR StartAddress) +{ + std::lock_guard lock(pauseMutex); + if(pauseBreakInExpected && pauseBreakInStart && StartAddress == pauseBreakInStart) + pauseBreakInThreadId = ThreadId; +} + +bool EngineIsPauseBreakInEvent(DWORD ThreadId) +{ + std::lock_guard lock(pauseMutex); + return pauseBreakInExpected && pauseBreakInThreadId && pauseBreakInThreadId == ThreadId; +} + +void EngineCompletePause() +{ + LPVOID callback = nullptr; + { + std::lock_guard lock(pauseMutex); + callback = pauseCallback; + pauseCallback = nullptr; + pauseRequestTime = 0; + pauseBreakInStart = 0; + pauseBreakInThreadId = 0; + pauseBreakInExpected = false; + } + if(callback) + ((void(TITCALL*)())callback)(); +} + +void EngineCancelPause() +{ + std::lock_guard lock(pauseMutex); + pauseCallback = nullptr; + pauseRequestTime = 0; + pauseBreakInStart = 0; + pauseBreakInThreadId = 0; + pauseBreakInExpected = false; +} + // Global.Engine.functions: void EngineInit() { diff --git a/TitanEngine/Global.Engine.h b/TitanEngine/Global.Engine.h index dffe3cd..09f5386 100644 --- a/TitanEngine/Global.Engine.h +++ b/TitanEngine/Global.Engine.h @@ -23,10 +23,19 @@ extern bool engineSafeAttach; extern bool engineMembpAlt; extern bool engineDisableAslr; extern bool engineSafeStep; +extern bool engineWow64SingleStepWorkaround; //Global.Engine.Functions void EngineInit(); bool EngineIsThereFreeHardwareBreakSlot(LPDWORD FreeRegister); +bool EngineBeginPause(LPVOID Callback); +bool EnginePauseShouldEscalate(TitanPausePolicy MaximumPolicy); +void EngineExpectPauseBreakIn(ULONG_PTR StartAddress); +void EngineCancelPauseBreakIn(); +void EngineObservePauseThread(DWORD ThreadId, ULONG_PTR StartAddress); +bool EngineIsPauseBreakInEvent(DWORD ThreadId); +void EngineCompletePause(); +void EngineCancelPause(); bool EngineFileExists(char* szFileName); void EngineCreatePathForFile(char* szFileName); void EngineCreatePathForFileW(wchar_t* szFileName); diff --git a/TitanEngine/TitanEngine.Debugger.Control.cpp b/TitanEngine/TitanEngine.Debugger.Control.cpp index c448b31..3a197e3 100644 --- a/TitanEngine/TitanEngine.Debugger.Control.cpp +++ b/TitanEngine/TitanEngine.Debugger.Control.cpp @@ -38,6 +38,27 @@ __declspec(dllexport) void TITCALL ForceClose() __declspec(dllexport) void TITCALL StepInto(LPVOID StepCallBack) { +#ifndef _WIN64 + // A WOW64 transition through `ljmp 33h` cannot be completed reliably with + // the x86 trap flag. Treat the return address already on the x86 stack as + // an engine-owned one-shot step target instead. The frontend only requests + // StepInto and does not need to know which execution mechanism is used. + if(engineWow64SingleStepWorkaround) + { + unsigned char data[7] = {}; + auto cip = GetContextData(UE_CIP); + if(MemoryReadSafe(dbgProcessInformation.hProcess, (void*)cip, data, sizeof(data), nullptr) && + data[0] == 0xEA && data[5] == 0x33 && data[6] == 0x00) + { + ULONG_PTR returnAddress = 0; + auto csp = GetContextData(UE_CSP); + if(MemoryReadSafe(dbgProcessInformation.hProcess, (void*)csp, &returnAddress, + sizeof(returnAddress), nullptr) && + SetBPX(returnAddress, UE_SINGLESHOOT, StepCallBack)) + return; + } + } +#endif EnterCriticalSection(&engineStepActiveCr); // 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. diff --git a/TitanEngine/TitanEngine.Debugger.Data.cpp b/TitanEngine/TitanEngine.Debugger.Data.cpp index 709a9da..08bb9c3 100644 --- a/TitanEngine/TitanEngine.Debugger.Data.cpp +++ b/TitanEngine/TitanEngine.Debugger.Data.cpp @@ -175,7 +175,8 @@ __declspec(dllexport) bool TITCALL GetSessionInfo(TITAN_SESSION_INFO* SessionInf UE_SESSION_CAP_CONTEXT_READ | UE_SESSION_CAP_FORWARD_EXECUTION | UE_SESSION_CAP_MEMORY_WRITE | UE_SESSION_CAP_CONTEXT_WRITE | UE_SESSION_CAP_PROCESS_CONTROL | UE_SESSION_CAP_THREAD_CONTROL | - UE_SESSION_CAP_NATIVE_HANDLES | UE_SESSION_CAP_EXCEPTION_CONTINUE; + UE_SESSION_CAP_NATIVE_HANDLES | UE_SESSION_CAP_EXCEPTION_CONTINUE | + UE_SESSION_CAP_PAUSE_EXECUTION; #ifdef _WIN64 SessionInfo->machineType = IMAGE_FILE_MACHINE_AMD64; #else @@ -210,13 +211,13 @@ __declspec(dllexport) bool TITCALL ReplaySetPosition(const TITAN_REPLAY_POSITION return false; } -__declspec(dllexport) bool TITCALL ReplayRun(bool Reverse) +__declspec(dllexport) bool TITCALL ReplayRunBack() { SetLastError(ERROR_NOT_SUPPORTED); return false; } -__declspec(dllexport) bool TITCALL ReplayStep(bool Reverse, bool StepOver, TITANCBSTEP StepCallBack) +__declspec(dllexport) bool TITCALL ReplayStepBack(TITANCBSTEP StepCallBack) { SetLastError(ERROR_NOT_SUPPORTED); return false; diff --git a/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp b/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp index 18b2b45..b98e205 100644 --- a/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp +++ b/TitanEngine/TitanEngine.Debugger.DebugLoop.cpp @@ -69,6 +69,7 @@ static void engineStep() __declspec(dllexport) void TITCALL DebugLoop() { + EngineCancelPause(); bool FirstBPX = true; bool ResetBPX = false; bool PushfBPX = false; @@ -375,6 +376,8 @@ __declspec(dllexport) void TITCALL DebugLoop() NewThreadData.ThreadStartAddress = (void*)DBGEvent.u.CreateThread.lpStartAddress; NewThreadData.ThreadLocalBase = (void*)DBGEvent.u.CreateThread.lpThreadLocalBase; hListThread.push_back(NewThreadData); + EngineObservePauseThread(DBGEvent.dwThreadId, + (ULONG_PTR)DBGEvent.u.CreateThread.lpStartAddress); //Set hardware breakpoints to all threads HANDLE hThread = NewThreadData.hThread; @@ -588,6 +591,15 @@ __declspec(dllexport) void TITCALL DebugLoop() { DBGCode = DBG_EXCEPTION_NOT_HANDLED; //let the debuggee handle exceptions per default + if((DBGEvent.u.Exception.ExceptionRecord.ExceptionCode == STATUS_BREAKPOINT || + DBGEvent.u.Exception.ExceptionRecord.ExceptionCode == STATUS_WX86_BREAKPOINT) && + EngineIsPauseBreakInEvent(DBGEvent.dwThreadId)) + { + DBGCode = DBG_CONTINUE; + EngineCompletePause(); + break; + } + if(DBGCustomHandler->chEverythingElse != NULL) { myCustomHandler = (fCustomHandler)((LPVOID)DBGCustomHandler->chEverythingElse); @@ -1490,6 +1502,7 @@ continue_dbg_event: { RtlMoveMemory(&TerminateDBGEvent, &DBGEvent, sizeof(DEBUG_EVENT)); } + EngineCancelPause(); ForceClose(); engineFileIsBeingDebugged = false; DebuggerReset(); diff --git a/TitanEngine/TitanEngine.Engine.cpp b/TitanEngine/TitanEngine.Engine.cpp index c3f497f..121db33 100644 --- a/TitanEngine/TitanEngine.Engine.cpp +++ b/TitanEngine/TitanEngine.Engine.cpp @@ -55,6 +55,10 @@ __declspec(dllexport) void TITCALL SetEngineVariable(DWORD VariableId, bool Vari { engineSafeStep = VariableSet; } + else if(VariableId == UE_ENGINE_WOW64_SINGLE_STEP_WORKAROUND) + { + engineWow64SingleStepWorkaround = VariableSet; + } } __declspec(dllexport) bool TITCALL EngineCreateMissingDependencies(char* szFileName, char* szOutputFolder, bool LogCreatedFiles) diff --git a/TitanEngine/TitanEngine.Process.cpp b/TitanEngine/TitanEngine.Process.cpp index 29094d7..795e869 100644 --- a/TitanEngine/TitanEngine.Process.cpp +++ b/TitanEngine/TitanEngine.Process.cpp @@ -2,6 +2,8 @@ #include "definitions.h" #include "Global.Handle.h" #include "Global.Engine.h" +#include "Global.Debugger.h" +#include // TitanEngine.Process.functions: __declspec(dllexport) long TITCALL GetActiveProcessId(char* szImageName) @@ -178,9 +180,115 @@ __declspec(dllexport) bool TITCALL TitanTerminateProcess(HANDLE hProcess, DWORD return !!TerminateProcess(hProcess, exitCode); } -__declspec(dllexport) bool TITCALL TitanDebugBreakProcess(HANDLE hProcess) +static ULONG_PTR pauseBreakInStartAddress() { - return !!DebugBreakProcess(hProcess); + auto localNtdll = GetModuleHandleW(L"ntdll.dll"); + auto localStart = localNtdll ? GetProcAddress(localNtdll, "DbgUiRemoteBreakin") : nullptr; + if(!localStart || !dbgProcessInformation.dwProcessId) + return 0; + + auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, + dbgProcessInformation.dwProcessId); + if(snapshot == INVALID_HANDLE_VALUE) + return 0; + MODULEENTRY32W module = {}; + module.dwSize = sizeof(module); + ULONG_PTR result = 0; + if(Module32FirstW(snapshot, &module)) + { + do + { + if(!_wcsicmp(module.szModule, L"ntdll.dll")) + { + result = (ULONG_PTR)module.modBaseAddr + + (ULONG_PTR)localStart - (ULONG_PTR)localNtdll; + break; + } + } + while(Module32NextW(snapshot, &module)); + } + CloseHandle(snapshot); + return result; +} + +static bool requestPauseBreakIn() +{ + auto startAddress = pauseBreakInStartAddress(); + if(!startAddress) + { + SetLastError(ERROR_PROC_NOT_FOUND); + return false; + } + EngineExpectPauseBreakIn(startAddress); + if(DebugBreakProcess(dbgProcessInformation.hProcess)) + return true; + EngineCancelPauseBreakIn(); + return false; +} + +__declspec(dllexport) bool TITCALL RequestPause(TitanPausePolicy MaximumPolicy, TITANCBPAUSE PauseCallback) +{ + if(MaximumPolicy < UE_PAUSE_POLICY_NONINVASIVE || MaximumPolicy > UE_PAUSE_POLICY_AGGRESSIVE || + !PauseCallback || !engineFileIsBeingDebugged || !dbgProcessInformation.hProcess) + { + SetLastError(ERROR_INVALID_PARAMETER); + return false; + } + + if(!EngineBeginPause((LPVOID)PauseCallback)) + { + if(EnginePauseShouldEscalate(MaximumPolicy)) + requestPauseBreakIn(); // The original, less intrusive request remains pending on failure. + return true; + } + + if(MaximumPolicy == UE_PAUSE_POLICY_NONINVASIVE) + { + EngineCancelPause(); + SetLastError(ERROR_NOT_SUPPORTED); + return false; + } + + auto threadId = DebugAttachedToProcess || !DBGEvent.dwThreadId + ? dbgProcessInformation.dwThreadId + : DBGEvent.dwThreadId; + auto thread = EngineOpenThread(THREAD_GETSETSUSPEND, false, threadId); + if(!thread) + { + if(MaximumPolicy == UE_PAUSE_POLICY_AGGRESSIVE && requestPauseBreakIn()) + return true; + EngineCancelPause(); + return false; + } + + auto previousSuspendCount = SuspendThread(thread); + if(previousSuspendCount != 0) + { + if(previousSuspendCount != (DWORD)-1) + ResumeThread(thread); + EngineCloseHandle(thread); + if(MaximumPolicy == UE_PAUSE_POLICY_AGGRESSIVE && requestPauseBreakIn()) + return true; + EngineCancelPause(); + SetLastError(ERROR_BUSY); + return false; + } + + auto cip = GetContextDataEx(thread, UE_CIP); + auto armed = cip && SetBPX(cip, UE_SINGLESHOOT, (LPVOID)&EngineCompletePause); + if(armed) + PostThreadMessageW(threadId, WM_NULL, 0, 0); + auto resumed = ResumeThread(thread) != (DWORD)-1; + EngineCloseHandle(thread); + if(armed && resumed) + return true; + if(armed) + DeleteBPX(cip); + + if(MaximumPolicy == UE_PAUSE_POLICY_AGGRESSIVE && requestPauseBreakIn()) + return true; + EngineCancelPause(); + return false; } __declspec(dllexport) HANDLE TITCALL TitanCreateRemoteThread(HANDLE hProcess, LPTHREAD_START_ROUTINE start, LPVOID argument, DWORD creationFlags, LPDWORD threadId) diff --git a/TitanEngine/TitanEngine.def b/TitanEngine/TitanEngine.def index 13a514e..5420e45 100644 --- a/TitanEngine/TitanEngine.def +++ b/TitanEngine/TitanEngine.def @@ -92,8 +92,8 @@ GetSessionInfo ReplayGetPosition ReplayGetExtent ReplaySetPosition -ReplayRun -ReplayStep +ReplayRunBack +ReplayStepBack InitNativeDebug InitNativeDebugW InitDebugEx @@ -406,7 +406,7 @@ TitanGetModulePathW TitanCloseHandle ProcessIsWow64 TitanTerminateProcess -TitanDebugBreakProcess +RequestPause TitanCreateRemoteThread TitanSuspendThread TitanResumeThread diff --git a/TitanEngine/definitions.h b/TitanEngine/definitions.h index 939efba..d3675b9 100644 --- a/TitanEngine/definitions.h +++ b/TitanEngine/definitions.h @@ -171,8 +171,8 @@ __declspec(dllexport) bool TITCALL GetSessionInfo(TITAN_SESSION_INFO* SessionInf __declspec(dllexport) bool TITCALL ReplayGetPosition(TITAN_REPLAY_POSITION* Position); __declspec(dllexport) bool TITCALL ReplayGetExtent(TITAN_REPLAY_POSITION* First, TITAN_REPLAY_POSITION* Last); __declspec(dllexport) bool TITCALL ReplaySetPosition(const TITAN_REPLAY_POSITION* Position); -__declspec(dllexport) bool TITCALL ReplayRun(bool Reverse); -__declspec(dllexport) bool TITCALL ReplayStep(bool Reverse, bool StepOver, TITANCBSTEP StepCallBack); +__declspec(dllexport) bool TITCALL ReplayRunBack(); +__declspec(dllexport) bool TITCALL ReplayStepBack(TITANCBSTEP StepCallBack); __declspec(dllexport) void* TITCALL InitNativeDebug(char* szFileName, char* szCommandLine, char* szCurrentFolder); __declspec(dllexport) void* TITCALL InitNativeDebugW(wchar_t* szFileName, wchar_t* szCommandLine, wchar_t* szCurrentFolder); __declspec(dllexport) void* TITCALL InitDebugEx(char* szFileName, char* szCommandLine, char* szCurrentFolder, LPVOID EntryCallBack); @@ -372,7 +372,7 @@ __declspec(dllexport) bool TITCALL TitanGetModulePathW(HANDLE hProcess, ULONG_PT __declspec(dllexport) bool TITCALL TitanCloseHandle(HANDLE hEngineHandle); __declspec(dllexport) bool TITCALL ProcessIsWow64(HANDLE hProcess, PBOOL isWow64); __declspec(dllexport) bool TITCALL TitanTerminateProcess(HANDLE hProcess, DWORD exitCode); -__declspec(dllexport) bool TITCALL TitanDebugBreakProcess(HANDLE hProcess); +__declspec(dllexport) bool TITCALL RequestPause(TitanPausePolicy MaximumPolicy, TITANCBPAUSE PauseCallback); __declspec(dllexport) HANDLE TITCALL TitanCreateRemoteThread(HANDLE hProcess, LPTHREAD_START_ROUTINE start, LPVOID argument, DWORD creationFlags, LPDWORD threadId); __declspec(dllexport) DWORD TITCALL TitanSuspendThread(HANDLE hThread); __declspec(dllexport) DWORD TITCALL TitanResumeThread(HANDLE hThread); diff --git a/TitanEngine/stdafx.h b/TitanEngine/stdafx.h index f2ee3c3..4697e51 100644 --- a/TitanEngine/stdafx.h +++ b/TitanEngine/stdafx.h @@ -34,6 +34,7 @@ #include typedef void(*TITANCBSTEP)(); +typedef void(*TITANCBPAUSE)(); #ifndef _Out_writes_opt_ #define _Out_writes_opt_(x) @@ -155,6 +156,8 @@ enum TitanSessionCapability : uint64_t UE_SESSION_CAP_NATIVE_HANDLES = 1ull << 12, UE_SESSION_CAP_EXCEPTION_CONTINUE = 1ull << 13, UE_SESSION_CAP_TIMELINE_STATE = 1ull << 14, + UE_SESSION_CAP_PAUSE_EXECUTION = 1ull << 16, + UE_SESSION_CAP_NAVIGABLE_PROCESS_EXIT = 1ull << 17, }; typedef struct @@ -559,6 +562,14 @@ typedef struct HOOK_ENTRY #define UE_ENGINE_MEMBP_ALT 11 #define UE_ENGINE_DISABLE_ASLR 12 #define UE_ENGINE_SAFE_STEP 13 +#define UE_ENGINE_WOW64_SINGLE_STEP_WORKAROUND 14 + +enum TitanPausePolicy +{ + UE_PAUSE_POLICY_NONINVASIVE = 0, + UE_PAUSE_POLICY_STANDARD = 1, + UE_PAUSE_POLICY_AGGRESSIVE = 2, +}; #define UE_OPTION_REMOVEALL 1 #define UE_OPTION_DISABLEALL 2