Clean up TTD API

This commit is contained in:
Duncan Ogilvie 2026-09-07 02:42:15 +02:00
parent 5ce48fef05
commit 21ef77f31f
10 changed files with 265 additions and 11 deletions

View File

@ -5,6 +5,7 @@
#include "Global.Mapping.h"
#include "Global.Engine.Hash.h"
#include "Global.Debugger.h"
#include <mutex>
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<std::mutex> 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<std::mutex> lock(pauseMutex);
return pauseCallback && !pauseBreakInExpected && MaximumPolicy == UE_PAUSE_POLICY_AGGRESSIVE &&
GetTickCount64() - pauseRequestTime >= 2000;
}
void EngineExpectPauseBreakIn(ULONG_PTR StartAddress)
{
std::lock_guard<std::mutex> lock(pauseMutex);
pauseBreakInExpected = true;
pauseBreakInStart = StartAddress;
pauseBreakInThreadId = 0;
}
void EngineCancelPauseBreakIn()
{
std::lock_guard<std::mutex> lock(pauseMutex);
pauseBreakInExpected = false;
pauseBreakInStart = 0;
pauseBreakInThreadId = 0;
}
void EngineObservePauseThread(DWORD ThreadId, ULONG_PTR StartAddress)
{
std::lock_guard<std::mutex> lock(pauseMutex);
if(pauseBreakInExpected && pauseBreakInStart && StartAddress == pauseBreakInStart)
pauseBreakInThreadId = ThreadId;
}
bool EngineIsPauseBreakInEvent(DWORD ThreadId)
{
std::lock_guard<std::mutex> lock(pauseMutex);
return pauseBreakInExpected && pauseBreakInThreadId && pauseBreakInThreadId == ThreadId;
}
void EngineCompletePause()
{
LPVOID callback = nullptr;
{
std::lock_guard<std::mutex> lock(pauseMutex);
callback = pauseCallback;
pauseCallback = nullptr;
pauseRequestTime = 0;
pauseBreakInStart = 0;
pauseBreakInThreadId = 0;
pauseBreakInExpected = false;
}
if(callback)
((void(TITCALL*)())callback)();
}
void EngineCancelPause()
{
std::lock_guard<std::mutex> lock(pauseMutex);
pauseCallback = nullptr;
pauseRequestTime = 0;
pauseBreakInStart = 0;
pauseBreakInThreadId = 0;
pauseBreakInExpected = false;
}
// Global.Engine.functions:
void EngineInit()
{

View File

@ -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);

View File

@ -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.

View File

@ -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;

View File

@ -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();

View File

@ -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)

View File

@ -2,6 +2,8 @@
#include "definitions.h"
#include "Global.Handle.h"
#include "Global.Engine.h"
#include "Global.Debugger.h"
#include <TlHelp32.h>
// 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)

View File

@ -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

View File

@ -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);

View File

@ -34,6 +34,7 @@
#include <stdint.h>
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