1
0
Fork 0
x64dbg/src/dbg/debugger.cpp

2975 lines
102 KiB
C++

/**
@file debugger.cpp
@brief Implements the debugger class.
*/
#include "debugger.h"
#include "console.h"
#include "memory.h"
#include "threading.h"
#include "command.h"
#include "database.h"
#include "watch.h"
#include "thread.h"
#include "plugin_loader.h"
#include "breakpoint.h"
#include "symbolinfo.h"
#include "variable.h"
#include "x64dbg.h"
#include "exception.h"
#include "module.h"
#include "commandline.h"
#include "stackinfo.h"
#include "stringformat.h"
#include "TraceRecord.h"
#include "historycontext.h"
#include "taskthread.h"
#include "animate.h"
#include "simplescript.h"
#include "zydis_wrapper.h"
#include "cmd-watch-control.h"
#include "filemap.h"
#include "jit.h"
#include "handle.h"
#include "dbghelp_safe.h"
#include "exprfunc.h"
#include "debugger_cookie.h"
#include "debugger_tracing.h"
// Debugging variables
static PROCESS_INFORMATION g_pi = {0, 0, 0, 0};
static char szBaseFileName[MAX_PATH] = "";
static TraceState traceState;
static bool bFileIsDll = false;
static bool bEntryIsInMzHeader = false;
static duint pDebuggedBase = 0;
static duint pCreateProcessBase = 0;
static duint pDebuggedEntry = 0;
static bool bRepeatIn = false;
static duint stepRepeat = 0;
static bool isDetachedByUser = false;
static bool bIsAttached = false;
static bool bSkipExceptions = false;
static duint skipExceptionCount = 0;
static bool bFreezeStack = false;
static std::vector<ExceptionRange> ignoredExceptionRange;
static HANDLE hEvent = 0;
static duint tidToResume = 0;
static HANDLE hMemMapThread = 0;
static bool bStopMemMapThread = false;
static HANDLE hTimeWastedCounterThread = 0;
static bool bStopTimeWastedCounterThread = false;
static HANDLE hDumpRefreshThread = 0;
static bool bStopDumpRefreshThread = false;
static String lastDebugText;
static duint timeWastedDebugging = 0;
static EXCEPTION_DEBUG_INFO lastExceptionInfo = { 0 };
static char szDebuggeeInitializationScript[MAX_PATH] = "";
static WString gInitExe, gInitCmd, gInitDir, gDllLoader;
static CookieQuery cookie;
static duint exceptionDispatchAddr = 0;
static bool bPausedOnException = false;
static HANDLE DebugDLLFileMapping = 0;
char szProgramDir[MAX_PATH] = "";
char szDebuggeePath[MAX_PATH] = "";
char szDllLoaderPath[MAX_PATH] = "";
char szSymbolCachePath[MAX_PATH] = "";
std::vector<std::pair<duint, duint>> RunToUserCodeBreakpoints;
PROCESS_INFORMATION* fdProcessInfo = &g_pi;
HANDLE hActiveThread;
HANDLE hProcessToken;
bool bUndecorateSymbolNames = true;
bool bEnableSourceDebugging = false;
bool bTraceRecordEnabledDuringTrace = true;
bool bSkipInt3Stepping = false;
bool bIgnoreInconsistentBreakpoints = false;
bool bNoForegroundWindow = false;
bool bVerboseExceptionLogging = true;
bool bNoWow64SingleStepWorkaround = false;
bool bTraceBrowserNeedsUpdate = false;
bool bForceLoadSymbols = false;
duint DbgEvents = 0;
duint maxSkipExceptionCount = 0;
HANDLE mProcHandle;
HANDLE mForegroundHandle;
duint mRtrPreviousCSP = 0;
HANDLE hDebugLoopThread = nullptr;
DWORD dwDebugFlags = 0;
static duint dbgcleartracestate()
{
auto steps = traceState.StepCount();
traceState.Clear();
return steps;
}
static void dbgClearRtuBreakpoints()
{
EXCLUSIVE_ACQUIRE(LockRunToUserCode);
for(auto & i : RunToUserCodeBreakpoints)
{
BREAKPOINT bp;
if(!BpGet(i.first, BPMEMORY, nullptr, &bp))
RemoveMemoryBPX(i.first, i.second);
}
RunToUserCodeBreakpoints.clear();
}
bool dbgsettracecondition(const String & expression, duint maxSteps)
{
if(dbgtraceactive())
return false;
if(!traceState.InitTraceCondition(expression, maxSteps))
return false;
if(traceState.InitLogFile())
return true;
dbgcleartracestate();
return false;
}
bool dbgsettracelog(const String & expression, const String & text)
{
if(dbgtraceactive())
return false;
return traceState.InitLogCondition(expression, text);
}
bool dbgsettracecmd(const String & expression, const String & text)
{
if(dbgtraceactive())
return false;
return traceState.InitCmdCondition(expression, text);
}
bool dbgsettraceswitchcondition(const String & expression)
{
if(dbgtraceactive())
return false;
return traceState.InitSwitchCondition(expression);
}
bool dbgtraceactive()
{
return traceState.IsActive();
}
void dbgforcebreaktrace()
{
if(traceState.IsActive())
traceState.SetForceBreakTrace();
}
bool dbgsettracelogfile(const char* fileName)
{
traceState.SetLogFile(fileName);
return true;
}
static DWORD WINAPI memMapThread(void* ptr)
{
while(!bStopMemMapThread)
{
while(!DbgIsDebugging())
{
if(bStopMemMapThread)
break;
Sleep(10);
}
if(bStopMemMapThread)
break;
MemUpdateMapAsync();
ThreadUpdateWaitReasons();
GuiUpdateThreadView();
Sleep(2000);
}
return 0;
}
static bool isUserIdle()
{
LASTINPUTINFO lii;
lii.cbSize = sizeof(LASTINPUTINFO);
GetLastInputInfo(&lii);
return GetTickCount() - lii.dwTime > 1000 * 60; //60 seconds without input is considered idle
}
static DWORD WINAPI timeWastedCounterThread(void* ptr)
{
if(!BridgeSettingGetUint("Engine", "TimeWastedDebugging", &timeWastedDebugging))
timeWastedDebugging = 0;
GuiUpdateTimeWastedCounter();
while(!bStopTimeWastedCounterThread)
{
while(!DbgIsDebugging() || isUserIdle())
{
if(bStopTimeWastedCounterThread)
break;
Sleep(10);
}
if(bStopTimeWastedCounterThread)
break;
timeWastedDebugging++;
GuiUpdateTimeWastedCounter();
Sleep(1000);
}
BridgeSettingSetUint("Engine", "TimeWastedDebugging", timeWastedDebugging);
return 0;
}
static DWORD WINAPI dumpRefreshThread(void* ptr)
{
while(!bStopDumpRefreshThread)
{
while(!DbgIsDebugging())
{
if(bStopDumpRefreshThread)
break;
Sleep(100);
}
if(bStopDumpRefreshThread)
break;
GuiUpdateDumpView();
GuiUpdateWatchView();
if(bTraceBrowserNeedsUpdate)
{
bTraceBrowserNeedsUpdate = false;
GuiUpdateTraceBrowser();
}
Sleep(400);
}
return 0;
}
/**
\brief Called when the debugger pauses.
*/
void cbDebuggerPaused()
{
// Clear tracing conditions
dbgcleartracestate();
dbgClearRtuBreakpoints();
mRtrPreviousCSP = 0;
// Trace record is not handled by this function currently.
// Signal thread switch warning
if(settingboolget("Engine", "HardcoreThreadSwitchWarning"))
{
static DWORD PrevThreadId = 0;
if(PrevThreadId == 0)
PrevThreadId = fdProcessInfo->dwThreadId; // Initialize to Main Thread
DWORD currentThreadId = ThreadGetId(hActiveThread);
if(currentThreadId != PrevThreadId && PrevThreadId != 0)
{
dprintf(QT_TRANSLATE_NOOP("DBG", "Thread switched from %X to %X !\n"), PrevThreadId, currentThreadId);
PrevThreadId = currentThreadId;
}
}
// Watchdog
cbCheckWatchdog(0, nullptr);
}
void dbginit()
{
hTimeWastedCounterThread = CreateThread(nullptr, 0, timeWastedCounterThread, nullptr, 0, nullptr);
hMemMapThread = CreateThread(nullptr, 0, memMapThread, nullptr, 0, nullptr);
hDumpRefreshThread = CreateThread(nullptr, 0, dumpRefreshThread, nullptr, 0, nullptr);
}
void dbgstop()
{
bStopTimeWastedCounterThread = true;
bStopMemMapThread = true;
bStopDumpRefreshThread = true;
HANDLE hThreads[] = { hTimeWastedCounterThread, hMemMapThread, hDumpRefreshThread };
WaitForMultipleThreadsTermination(hThreads, _countof(hThreads), 10000); // Total time out is 10 seconds.
}
duint dbgdebuggedbase()
{
return pDebuggedBase;
}
duint dbggettimewastedcounter()
{
return timeWastedDebugging;
}
bool dbgisrunning()
{
return !waitislocked(WAITID_RUN);
}
bool dbgisdll()
{
return bFileIsDll;
}
void dbgsetattachevent(HANDLE handle)
{
hEvent = handle;
}
void dbgsetresumetid(duint tid)
{
tidToResume = tid;
}
void dbgsetskipexceptions(bool skip)
{
bSkipExceptions = skip;
skipExceptionCount = 0;
}
void dbgsetsteprepeat(bool steppingIn, duint repeat)
{
bRepeatIn = steppingIn;
stepRepeat = repeat;
}
void dbgsetisdetachedbyuser(bool b)
{
isDetachedByUser = b;
}
void dbgsetfreezestack(bool freeze)
{
bFreezeStack = freeze;
}
void dbgclearignoredexceptions()
{
ignoredExceptionRange.clear();
}
void dbgaddignoredexception(ExceptionRange range)
{
ignoredExceptionRange.push_back(range);
}
bool dbgisignoredexception(unsigned int exception)
{
for(unsigned int i = 0; i < ignoredExceptionRange.size(); i++)
{
unsigned int curStart = ignoredExceptionRange.at(i).start;
unsigned int curEnd = ignoredExceptionRange.at(i).end;
if(exception >= curStart && exception <= curEnd)
return true;
}
return false;
}
bool dbgcmdnew(const char* name, CBCOMMAND cbCommand, bool debugonly)
{
if(!cmdnew(name, cbCommand, debugonly))
return false;
GuiAutoCompleteAddCmd(name);
return true;
}
bool dbgcmddel(const char* name)
{
if(!cmddel(name))
return false;
GuiAutoCompleteDelCmd(name);
return true;
}
duint dbggetdbgevents()
{
return InterlockedExchange((volatile long*)&DbgEvents, 0);
}
void dbgtracebrowserneedsupdate()
{
bTraceBrowserNeedsUpdate = true;
}
static std::unordered_map<std::string, std::pair<DWORD, bool>> dllBreakpoints;
bool dbgsetdllbreakpoint(const char* mod, DWORD type, bool singleshoot)
{
EXCLUSIVE_ACQUIRE(LockDllBreakpoints);
return dllBreakpoints.insert({ mod, { type, singleshoot } }).second;
}
bool dbgdeletedllbreakpoint(const char* mod, DWORD type)
{
EXCLUSIVE_ACQUIRE(LockDllBreakpoints);
auto found = dllBreakpoints.find(mod);
if(found == dllBreakpoints.end())
return false;
dllBreakpoints.erase(found);
return true;
}
void dbgsetdebugflags(DWORD flags)
{
dwDebugFlags = flags;
}
bool dbghandledllbreakpoint(const char* mod, bool loadDll)
{
EXCLUSIVE_ACQUIRE(LockDllBreakpoints);
auto shouldBreak = false;
auto found = dllBreakpoints.find(mod);
if(found != dllBreakpoints.end())
{
if(found->second.first == UE_ON_LIB_ALL || found->second.first == (loadDll ? UE_ON_LIB_LOAD : UE_ON_LIB_UNLOAD))
shouldBreak = true;
if(found->second.second)
dllBreakpoints.erase(found);
}
return shouldBreak;
}
static DWORD WINAPI updateCallStackThread(duint ptr)
{
stackupdatecallstack(ptr);
GuiUpdateCallStack();
return 0;
}
void updateCallStackAsync(duint ptr)
{
static TaskThread_<decltype(&updateCallStackThread), duint> updateCallStackTask(&updateCallStackThread);
updateCallStackTask.WakeUp(ptr);
}
DWORD WINAPI updateSEHChainThread()
{
GuiUpdateSEHChain();
stackupdateseh();
GuiUpdateDumpView();
return 0;
}
void updateSEHChainAsync()
{
static TaskThread_<decltype(&updateSEHChainThread)> updateSEHChainTask(&updateSEHChainThread);
updateSEHChainTask.WakeUp();
}
static void DebugUpdateTitle(duint disasm_addr, bool analyzeThreadSwitch)
{
char modname[MAX_MODULE_SIZE] = "";
char modtext[MAX_MODULE_SIZE * 2] = "";
if(!ModNameFromAddr(disasm_addr, modname, true))
*modname = 0;
else
sprintf_s(modtext, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Module: %s - ")), modname);
char threadswitch[256] = "";
DWORD currentThreadId = ThreadGetId(hActiveThread);
if(analyzeThreadSwitch)
{
static DWORD PrevThreadId = 0;
if(PrevThreadId == 0)
PrevThreadId = fdProcessInfo->dwThreadId; // Initialize to Main Thread
if(currentThreadId != PrevThreadId && PrevThreadId != 0)
{
char threadName2[MAX_THREAD_NAME_SIZE] = "";
if(!ThreadGetName(PrevThreadId, threadName2) || threadName2[0] == 0)
sprintf_s(threadName2, "%X", PrevThreadId);
sprintf_s(threadswitch, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", " (switched from %s)")), threadName2);
PrevThreadId = currentThreadId;
}
}
char title[deflen] = "";
char threadName[MAX_THREAD_NAME_SIZE + 1] = "";
if(ThreadGetName(currentThreadId, threadName) && *threadName)
strcat_s(threadName, " ");
char PIDnumber[64], TIDnumber[64];
if(settingboolget("Gui", "PidInHex"))
{
sprintf_s(PIDnumber, "%X", fdProcessInfo->dwProcessId);
sprintf_s(TIDnumber, "%X", currentThreadId);
}
else
{
sprintf_s(PIDnumber, "%u", fdProcessInfo->dwProcessId);
sprintf_s(TIDnumber, "%u", currentThreadId);
}
sprintf_s(title, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "%s - PID: %s - %sThread: %s%s%s")), szBaseFileName, PIDnumber, modtext, threadName, TIDnumber, threadswitch);
GuiUpdateWindowTitle(title);
}
void DebugUpdateGui(duint disasm_addr, bool stack)
{
if(GuiIsUpdateDisabled())
return;
duint cip = GetContextDataEx(hActiveThread, UE_CIP);
//Check if the addresses are in the memory map and force update if they are not
if(!MemIsValidReadPtr(disasm_addr, true) || !MemIsValidReadPtr(cip, true))
MemUpdateMap();
else
MemUpdateMapAsync();
if(MemIsValidReadPtr(disasm_addr))
{
if(bEnableSourceDebugging)
{
char szSourceFile[MAX_STRING_SIZE] = "";
int line = 0;
if(SymGetSourceLine(cip, szSourceFile, &line))
GuiLoadSourceFileEx(szSourceFile, cip);
}
GuiDisasmAt(disasm_addr, cip);
}
duint csp = GetContextDataEx(hActiveThread, UE_CSP);
if(stack)
DebugUpdateStack(csp, csp);
static volatile duint cacheCsp = 0;
if(csp != cacheCsp)
{
#ifdef _WIN64
InterlockedExchange((volatile unsigned long long*)&cacheCsp, csp);
#else
InterlockedExchange((volatile unsigned long*)&cacheCsp, csp);
#endif //_WIN64
updateCallStackAsync(csp);
updateSEHChainAsync();
}
DebugUpdateTitle(disasm_addr, true);
GuiUpdateRegisterView();
GuiUpdateDisassemblyView();
GuiUpdateThreadView();
GuiUpdateSideBar();
}
void GuiSetDebugStateAsync(DBGSTATE state)
{
GuiSetDebugStateFast(state);
static TaskThread_<decltype(&GuiSetDebugState), DBGSTATE> GuiSetDebugStateTask(&GuiSetDebugState, 300);
GuiSetDebugStateTask.WakeUp(state);
}
void DebugUpdateGuiAsync(duint disasm_addr, bool stack)
{
static TaskThread_<decltype(&DebugUpdateGui), duint, bool> DebugUpdateGuiTask(&DebugUpdateGui);
DebugUpdateGuiTask.WakeUp(disasm_addr, stack);
}
void DebugUpdateTitleAsync(duint disasm_addr, bool analyzeThreadSwitch)
{
static TaskThread_<decltype(&DebugUpdateTitle), duint, bool> DebugUpdateTitleTask(&DebugUpdateTitle);
DebugUpdateTitleTask.WakeUp(disasm_addr, analyzeThreadSwitch);
}
void DebugUpdateGuiSetStateAsync(duint disasm_addr, bool stack, DBGSTATE state)
{
// call paused routine to clean up various tracing states.
if(state == paused)
cbDebuggerPaused();
GuiSetDebugStateAsync(state);
DebugUpdateGuiAsync(disasm_addr, stack);
}
void DebugUpdateBreakpointsViewAsync()
{
static TaskThread_<decltype(&GuiUpdateBreakpointsView)> BreakpointsUpdateGuiTask(&GuiUpdateBreakpointsView);
BreakpointsUpdateGuiTask.WakeUp();
}
void DebugUpdateStack(duint dumpAddr, duint csp, bool forceDump)
{
if(GuiIsUpdateDisabled())
return;
if(!forceDump && bFreezeStack)
dumpAddr = 0;
GuiStackDumpAt(dumpAddr, csp);
GuiUpdateArgumentWidget();
}
static void printSoftBpInfo(const BREAKPOINT & bp)
{
auto bptype = "INT3";
int titantype = bp.titantype;
if((titantype & UE_BREAKPOINT_TYPE_UD2) == UE_BREAKPOINT_TYPE_UD2)
bptype = "UD2";
else if((titantype & UE_BREAKPOINT_TYPE_LONG_INT3) == UE_BREAKPOINT_TYPE_LONG_INT3)
bptype = "LONG INT3";
auto symbolicname = SymGetSymbolicName(bp.addr);
if(symbolicname.length())
{
if(*bp.name)
dprintf(QT_TRANSLATE_NOOP("DBG", "%s breakpoint \"%s\" at %s (%p)!\n"), bptype, bp.name, symbolicname.c_str(), bp.addr);
else
dprintf(QT_TRANSLATE_NOOP("DBG", "%s breakpoint at %s (%p)!\n"), bptype, symbolicname.c_str(), bp.addr);
}
else
{
if(*bp.name)
dprintf(QT_TRANSLATE_NOOP("DBG", "%s breakpoint \"%s\" at %p!\n"), bptype, bp.name, bp.addr);
else
dprintf(QT_TRANSLATE_NOOP("DBG", "%s breakpoint at %p!\n"), bptype, bp.addr);
}
}
static void printHwBpInfo(const BREAKPOINT & bp)
{
const char* bpsize = "";
switch(TITANGETSIZE(bp.titantype)) //size
{
case UE_HARDWARE_SIZE_1:
bpsize = "byte, ";
break;
case UE_HARDWARE_SIZE_2:
bpsize = "word, ";
break;
case UE_HARDWARE_SIZE_4:
bpsize = "dword, ";
break;
#ifdef _WIN64
case UE_HARDWARE_SIZE_8:
bpsize = "qword, ";
break;
#endif //_WIN64
}
char* bptype;
switch(TITANGETTYPE(bp.titantype)) //type
{
case UE_HARDWARE_EXECUTE:
bptype = _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "execute")));
bpsize = "";
break;
case UE_HARDWARE_READWRITE:
bptype = _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "read/write")));
break;
case UE_HARDWARE_WRITE:
bptype = _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "write")));
break;
default:
bptype = _strdup(" ");
}
auto symbolicname = SymGetSymbolicName(bp.addr);
if(symbolicname.length())
{
if(*bp.name)
dprintf(QT_TRANSLATE_NOOP("DBG", "Hardware breakpoint (%s%s) \"%s\" at %s (%p)!\n"), bpsize, bptype, bp.name, symbolicname.c_str(), bp.addr);
else
dprintf(QT_TRANSLATE_NOOP("DBG", "Hardware breakpoint (%s%s) at %s (%p)!\n"), bpsize, bptype, symbolicname.c_str(), bp.addr);
}
else
{
if(*bp.name)
dprintf(QT_TRANSLATE_NOOP("DBG", "Hardware breakpoint (%s%s) \"%s\" at %p!\n"), bpsize, bptype, bp.name, bp.addr);
else
dprintf(QT_TRANSLATE_NOOP("DBG", "Hardware breakpoint (%s%s) at %p!\n"), bpsize, bptype, bp.addr);
}
free(bptype);
}
static void printMemBpInfo(const BREAKPOINT & bp, const void* ExceptionAddress)
{
char* bptype;
switch(bp.titantype)
{
case UE_MEMORY_READ:
bptype = _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", " (read)")));
break;
case UE_MEMORY_WRITE:
bptype = _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", " (write)")));
break;
case UE_MEMORY_EXECUTE:
bptype = _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", " (execute)")));
break;
case UE_MEMORY:
bptype = _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", " (read/write/execute)")));
break;
default:
bptype = _strdup("");
}
auto symbolicname = SymGetSymbolicName(bp.addr);
if(symbolicname.length())
{
if(*bp.name)
dprintf(QT_TRANSLATE_NOOP("DBG", "Memory breakpoint%s \"%s\" at %s (%p, %p)!\n"), bptype, bp.name, symbolicname.c_str(), bp.addr, ExceptionAddress);
else
dprintf(QT_TRANSLATE_NOOP("DBG", "Memory breakpoint%s at %s (%p, %p)!\n"), bptype, symbolicname.c_str(), bp.addr, ExceptionAddress);
}
else
{
if(*bp.name)
dprintf(QT_TRANSLATE_NOOP("DBG", "Memory breakpoint%s \"%s\" at %p (%p)!\n"), bptype, bp.name, bp.addr, ExceptionAddress);
else
dprintf(QT_TRANSLATE_NOOP("DBG", "Memory breakpoint%s at %p (%p)!\n"), bptype, bp.addr, ExceptionAddress);
}
free(bptype);
}
static void printDllBpInfo(const BREAKPOINT & bp)
{
char* bptype;
switch(bp.titantype)
{
case UE_ON_LIB_LOAD:
bptype = _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "DLL Load")));
break;
case UE_ON_LIB_UNLOAD:
bptype = _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "DLL Unload")));
break;
case UE_ON_LIB_ALL:
bptype = _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "DLL Load and unload")));
break;
default:
bptype = _strdup("");
}
if(*bp.name)
dprintf(QT_TRANSLATE_NOOP("DBG", "DLL Breakpoint %s (%s): Module %s\n"), bp.name, bptype, bp.mod);
else
dprintf(QT_TRANSLATE_NOOP("DBG", "DLL Breakpoint (%s): Module %s\n"), bptype, bp.mod);
free(bptype);
}
static void printExceptionBpInfo(const BREAKPOINT & bp, duint CIP)
{
if(*bp.name != 0)
dprintf(QT_TRANSLATE_NOOP("DBG", "Exception Breakpoint %s (%p) at %p!\n"), bp.name, bp.addr, CIP);
else
dprintf(QT_TRANSLATE_NOOP("DBG", "Exception Breakpoint %s (%p) at %p!\n"), ExceptionCodeToName((unsigned int)bp.addr).c_str(), bp.addr, CIP);
}
static bool getConditionValue(const char* expression)
{
auto word = *(uint16*)expression;
if(word == '0') // short circuit for condition "0\0"
return false;
if(word == '1') //short circuit for condition "1\0"
return true;
duint value;
if(valfromstring(expression, &value))
return value != 0;
return true;
}
void cbPauseBreakpoint()
{
dputs(QT_TRANSLATE_NOOP("DBG", "paused!"));
hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
auto CIP = GetContextDataEx(hActiveThread, UE_CIP);
DeleteBPX(CIP);
DebugUpdateGuiSetStateAsync(CIP, true);
_dbg_animatestop(); // Stop animating when paused
// Trace record
_dbg_dbgtraceexecute(CIP);
//lock
lock(WAITID_RUN);
// Plugin callback
PLUG_CB_PAUSEDEBUG pauseInfo = { nullptr };
plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
dbgsetforeground();
dbgsetskipexceptions(false);
wait(WAITID_RUN);
}
static void handleBreakCondition(const BREAKPOINT & bp, const void* ExceptionAddress, duint CIP, bool doBreak)
{
if(doBreak)
{
if(bp.singleshoot)
BpDelete(bp.addr, bp.type);
if(!bp.silent)
{
switch(bp.type)
{
case BPNORMAL:
printSoftBpInfo(bp);
break;
case BPHARDWARE:
printHwBpInfo(bp);
break;
case BPMEMORY:
printMemBpInfo(bp, ExceptionAddress);
break;
case BPDLL:
printDllBpInfo(bp);
break;
case BPEXCEPTION:
printExceptionBpInfo(bp, CIP);
break;
default:
break;
}
}
DebugUpdateGuiSetStateAsync(CIP, true);
// Plugin callback
PLUG_CB_PAUSEDEBUG pauseInfo = { nullptr };
plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
_dbg_animatestop(); // Stop animating when a breakpoint is hit
}
}
static void cbGenericBreakpoint(BP_TYPE bptype, void* ExceptionAddress = nullptr)
{
hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
auto CIP = GetContextDataEx(hActiveThread, UE_CIP);
//handle process cookie retrieval
if(bptype == BPNORMAL && cookie.HandleBreakpoint(CIP))
return;
BREAKPOINT* bpPtr = nullptr;
//NOTE: this locking is very tricky, make sure you understand it before modifying anything
EXCLUSIVE_ACQUIRE(LockBreakpoints);
duint breakpointExceptionAddress = 0;
switch(bptype)
{
case BPNORMAL:
bpPtr = BpInfoFromAddr(BPNORMAL, CIP);
breakpointExceptionAddress = CIP;
break;
case BPHARDWARE:
bpPtr = BpInfoFromAddr(BPHARDWARE, duint(ExceptionAddress));
breakpointExceptionAddress = duint(ExceptionAddress);
break;
case BPMEMORY:
bpPtr = BpInfoFromAddr(BPMEMORY, MemFindBaseAddr(duint(ExceptionAddress), nullptr, true));
breakpointExceptionAddress = duint(ExceptionAddress);
break;
case BPDLL:
bpPtr = BpInfoFromAddr(BPDLL, BpGetDLLBpAddr(reinterpret_cast<const char*>(ExceptionAddress)));
breakpointExceptionAddress = 0; //makes no sense
break;
case BPEXCEPTION:
bpPtr = BpInfoFromAddr(BPEXCEPTION, ((EXCEPTION_DEBUG_INFO*)ExceptionAddress)->ExceptionRecord.ExceptionCode);
breakpointExceptionAddress = (duint)((EXCEPTION_DEBUG_INFO*)ExceptionAddress)->ExceptionRecord.ExceptionAddress;
break;
default:
break;
}
varset("$breakpointexceptionaddress", breakpointExceptionAddress, true);
if(!(bpPtr && bpPtr->enabled)) //invalid / disabled breakpoint hit (most likely a bug)
{
if(bptype != BPDLL || !BpUpdateDllPath(reinterpret_cast<const char*>(ExceptionAddress), &bpPtr))
{
// release the breakpoint lock to prevent deadlocks during the wait
EXCLUSIVE_RELEASE();
dputs(QT_TRANSLATE_NOOP("DBG", "Breakpoint reached not in list!"));
DebugUpdateGuiSetStateAsync(GetContextDataEx(hActiveThread, UE_CIP), true);
//lock
lock(WAITID_RUN);
// Plugin callback
PLUG_CB_PAUSEDEBUG pauseInfo = { nullptr };
plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
dbgsetforeground();
dbgsetskipexceptions(false);
wait(WAITID_RUN);
return;
}
}
// increment hit count
InterlockedIncrement((volatile long*)&bpPtr->hitcount);
// copy the breakpoint structure and release the breakpoint lock to prevent deadlocks during the wait
auto bp = *bpPtr;
EXCLUSIVE_RELEASE();
if(bptype != BPDLL && bptype != BPEXCEPTION)
bp.addr += ModBaseFromAddr(CIP);
bp.active = true; //a breakpoint that has been hit is active
varset("$breakpointcounter", bp.hitcount, true); //save the breakpoint counter as a variable
//get condition values
bool breakCondition;
bool logCondition;
bool commandCondition;
if(*bp.breakCondition)
breakCondition = getConditionValue(bp.breakCondition);
else
breakCondition = true; //break if no condition is set
if(bp.fastResume && !breakCondition) // fast resume: ignore GUI/Script/Plugin/Other if the debugger would not break
return;
if(*bp.logCondition)
logCondition = getConditionValue(bp.logCondition);
else
logCondition = true; //log if no condition is set
if(*bp.commandCondition)
commandCondition = getConditionValue(bp.commandCondition);
else
commandCondition = breakCondition; //if no condition is set, execute the command when the debugger would break
lock(WAITID_RUN);
handleBreakCondition(bp, ExceptionAddress, CIP, breakCondition);
PLUG_CB_BREAKPOINT bpInfo;
BRIDGEBP bridgebp;
memset(&bridgebp, 0, sizeof(bridgebp));
bpInfo.breakpoint = &bridgebp;
BpToBridge(&bp, &bridgebp);
plugincbcall(CB_BREAKPOINT, &bpInfo);
// Trace record
_dbg_dbgtraceexecute(CIP);
// Watchdog
cbCheckWatchdog(0, nullptr);
// Update breakpoint view
DebugUpdateBreakpointsViewAsync();
if(*bp.logText && logCondition) //log
{
dprintf_untranslated("%s\n", stringformatinline(bp.logText).c_str());
}
if(*bp.commandText && commandCondition) //command
{
//TODO: commands like run/step etc will fuck up your shit
varset("$breakpointcondition", breakCondition ? 1 : 0, false);
varset("$breakpointlogcondition", logCondition ? 1 : 0, true);
cmddirectexec(bp.commandText);
duint script_breakcondition;
if(varget("$breakpointcondition", &script_breakcondition, nullptr, nullptr))
{
if(script_breakcondition != 0)
{
handleBreakCondition(bp, ExceptionAddress, CIP, !breakCondition);
breakCondition = true;
}
else
breakCondition = false;
}
}
if(breakCondition) //break the debugger
{
dbgsetforeground();
dbgsetskipexceptions(false);
}
else //resume immediately
unlock(WAITID_RUN);
//wait until the user resumes
wait(WAITID_RUN);
}
void cbUserBreakpoint()
{
cbGenericBreakpoint(BPNORMAL);
}
void cbHardwareBreakpoint(void* ExceptionAddress)
{
cbGenericBreakpoint(BPHARDWARE, ExceptionAddress);
}
void cbMemoryBreakpoint(void* ExceptionAddress)
{
cbGenericBreakpoint(BPMEMORY, ExceptionAddress);
}
void cbRunToUserCodeBreakpoint(void* ExceptionAddress)
{
hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
auto CIP = GetContextDataEx(hActiveThread, UE_CIP);
auto symbolicname = SymGetSymbolicName(CIP);
dprintf(QT_TRANSLATE_NOOP("DBG", "User code reached at %s (%p)!"), symbolicname.c_str(), CIP);
// lock
lock(WAITID_RUN);
// Trace record
_dbg_dbgtraceexecute(CIP);
// Update GUI
DebugUpdateGuiSetStateAsync(GetContextDataEx(hActiveThread, UE_CIP), true);
// Plugin callback
PLUG_CB_PAUSEDEBUG pauseInfo = { nullptr };
plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
dbgsetforeground();
dbgsetskipexceptions(false);
wait(WAITID_RUN);
}
static BOOL CALLBACK SymRegisterCallbackProc64(HANDLE, ULONG ActionCode, ULONG64 CallbackData, ULONG64)
{
PIMAGEHLP_CBA_EVENT evt;
switch(ActionCode)
{
case CBA_EVENT:
{
evt = (PIMAGEHLP_CBA_EVENT)CallbackData;
auto strText = StringUtils::Utf16ToUtf8((const wchar_t*)evt->desc);
const char* text = strText.c_str();
if(strstr(text, "Successfully received a response from the server."))
break;
if(strstr(text, "Waiting for the server to respond to a request."))
break;
int len = (int)strlen(text);
bool suspress = false;
for(int i = 0; i < len; i++)
if(text[i] == 0x08)
{
suspress = true;
break;
}
int percent = 0;
static bool zerobar = false;
if(zerobar)
{
zerobar = false;
GuiSymbolSetProgress(0);
}
if(strstr(text, " bytes - "))
{
Memory<char*> newtext(len + 1, "SymRegisterCallbackProc64:newtext");
strcpy_s(newtext(), len + 1, text);
strstr(newtext(), " bytes - ")[8] = 0;
GuiSymbolLogAdd(newtext());
suspress = true;
}
else if(strstr(text, " copied "))
{
GuiSymbolSetProgress(100);
GuiSymbolLogAdd(" downloaded!\n");
suspress = true;
zerobar = true;
}
else if(sscanf_s(text, "%*s %d percent", &percent) == 1 || sscanf_s(text, "%d percent", &percent) == 1)
{
GuiSymbolSetProgress(percent);
suspress = true;
}
if(!suspress)
GuiSymbolLogAdd(text);
}
break;
case CBA_DEBUG_INFO:
{
GuiSymbolLogAdd((const char*)CallbackData);
}
break;
default:
{
return FALSE;
}
}
return TRUE;
}
bool cbSetModuleBreakpoints(const BREAKPOINT* bp)
{
if(!bp->enabled)
return true;
switch(bp->type)
{
case BPNORMAL:
{
unsigned short oldbytes;
if(MemRead(bp->addr, &oldbytes, sizeof(oldbytes)))
{
if(oldbytes != bp->oldbytes && !bIgnoreInconsistentBreakpoints)
{
dprintf(QT_TRANSLATE_NOOP("DBG", "Breakpoint %p has been disabled because the bytes don't match! Expected: %02X %02X, Found: %02X %02X\n"),
bp->addr,
((unsigned char*)&bp->oldbytes)[0], ((unsigned char*)&bp->oldbytes)[1],
((unsigned char*)&oldbytes)[0], ((unsigned char*)&oldbytes)[1]);
BpEnable(bp->addr, BPNORMAL, false);
}
else if(!SetBPX(bp->addr, bp->titantype, (void*)cbUserBreakpoint))
dprintf(QT_TRANSLATE_NOOP("DBG", "Could not set breakpoint %p! (SetBPX)\n"), bp->addr);
}
else
dprintf(QT_TRANSLATE_NOOP("DBG", "MemRead failed on breakpoint address %p!\n"), bp->addr);
}
break;
case BPMEMORY:
{
duint size = 0;
MemFindBaseAddr(bp->addr, &size);
if(!SetMemoryBPXEx(bp->addr, size, bp->titantype, !bp->singleshoot, (void*)cbMemoryBreakpoint))
dprintf(QT_TRANSLATE_NOOP("DBG", "Could not set memory breakpoint %p! (SetMemoryBPXEx)\n"), bp->addr);
}
break;
case BPHARDWARE:
{
DWORD drx = 0;
if(!GetUnusedHardwareBreakPointRegister(&drx))
{
dputs(QT_TRANSLATE_NOOP("DBG", "You can only set 4 hardware breakpoints"));
return false;
}
int titantype = bp->titantype;
TITANSETDRX(titantype, drx);
BpSetTitanType(bp->addr, BPHARDWARE, titantype);
if(!SetHardwareBreakPoint(bp->addr, drx, TITANGETTYPE(bp->titantype), TITANGETSIZE(bp->titantype), (void*)cbHardwareBreakpoint))
dprintf(QT_TRANSLATE_NOOP("DBG", "Could not set hardware breakpoint %p! (SetHardwareBreakPoint)\n"), bp->addr);
else
dprintf(QT_TRANSLATE_NOOP("DBG", "Set hardware breakpoint on %p!\n"), bp->addr);
}
break;
default:
break;
}
return true;
}
bool cbSetDLLBreakpoints(const BREAKPOINT* bp)
{
if(!bp->enabled)
return true;
if(bp->type != BPDLL)
return true;
dbgsetdllbreakpoint(bp->mod, bp->titantype, bp->singleshoot);
return true;
}
EXCEPTION_DEBUG_INFO & getLastExceptionInfo()
{
return lastExceptionInfo;
}
static bool cbRemoveModuleBreakpoints(const BREAKPOINT* bp)
{
if(!bp->enabled)
return true;
switch(bp->type)
{
case BPNORMAL:
if(!DeleteBPX(bp->addr))
dprintf(QT_TRANSLATE_NOOP("DBG", "Could not delete breakpoint %p! (DeleteBPX)\n"), bp->addr);
break;
case BPMEMORY:
if(!RemoveMemoryBPX(bp->addr, 0))
dprintf(QT_TRANSLATE_NOOP("DBG", "Could not delete memory breakpoint %p! (RemoveMemoryBPX)\n"), bp->addr);
break;
case BPHARDWARE:
if(TITANDRXVALID(bp->titantype) && !DeleteHardwareBreakPoint(TITANGETDRX(bp->titantype)))
dprintf(QT_TRANSLATE_NOOP("DBG", "Could not delete hardware breakpoint %p! (DeleteHardwareBreakPoint)\n"), bp->addr);
break;
default:
break;
}
return true;
}
void DebugRemoveBreakpoints()
{
BpEnumAll(cbRemoveModuleBreakpoints);
}
void DebugSetBreakpoints()
{
BpEnumAll(cbSetModuleBreakpoints);
}
void cbStep()
{
hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
duint CIP = GetContextDataEx(hActiveThread, UE_CIP);
if(!stepRepeat || !--stepRepeat)
{
DebugUpdateGuiSetStateAsync(CIP, true);
// Trace record
_dbg_dbgtraceexecute(CIP);
// Plugin interaction
PLUG_CB_STEPPED stepInfo;
stepInfo.reserved = 0;
//lock
lock(WAITID_RUN);
// Plugin callback
PLUG_CB_PAUSEDEBUG pauseInfo = { nullptr };
plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
dbgsetforeground();
dbgsetskipexceptions(false);
plugincbcall(CB_STEPPED, &stepInfo);
wait(WAITID_RUN);
}
else
{
if(bTraceRecordEnabledDuringTrace)
_dbg_dbgtraceexecute(CIP);
(bRepeatIn ? StepIntoWow64 : StepOverWrapper)((void*)cbStep);
}
}
static void cbRtrFinalStep(bool checkRepeat = false)
{
if(!checkRepeat || !stepRepeat || !--stepRepeat)
{
dbgcleartracestate();
hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
duint CIP = GetContextDataEx(hActiveThread, UE_CIP);
// Trace record
_dbg_dbgtraceexecute(CIP);
DebugUpdateGuiSetStateAsync(CIP, true);
//lock
lock(WAITID_RUN);
// Plugin callback
PLUG_CB_PAUSEDEBUG pauseInfo = { nullptr };
plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
dbgsetforeground();
dbgsetskipexceptions(false);
wait(WAITID_RUN);
}
else
StepOverWrapper((void*)cbRtrStep);
}
void cbRtrStep()
{
hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
unsigned char ch = 0x90;
duint cip = GetContextDataEx(hActiveThread, UE_CIP);
duint csp = GetContextDataEx(hActiveThread, UE_CSP);
MemRead(cip, &ch, 1);
if(bTraceRecordEnabledDuringTrace)
_dbg_dbgtraceexecute(cip);
if(mRtrPreviousCSP <= csp) //"Run until return" should break only if RSP is bigger than or equal to current value
{
if(ch == 0xC3 || ch == 0xC2) //retn instruction
cbRtrFinalStep(true);
else if(ch == 0x26 || ch == 0x36 || ch == 0x2e || ch == 0x3e || (ch >= 0x64 && ch <= 0x67) || ch == 0xf2 || ch == 0xf3 //instruction prefixes
#ifdef _WIN64
|| (ch >= 0x40 && ch <= 0x4f)
#endif //_WIN64
)
{
Zydis cp;
unsigned char data[MAX_DISASM_BUFFER];
memset(data, 0, sizeof(data));
MemRead(cip, data, MAX_DISASM_BUFFER);
if(cp.Disassemble(cip, data) && cp.IsRet())
cbRtrFinalStep(true);
else
StepOverWrapper((void*)cbRtrStep);
}
else
{
StepOverWrapper((void*)cbRtrStep);
}
}
else
StepOverWrapper((void*)cbRtrStep);
}
static void cbTraceUniversalConditionalStep(duint cip, bool bStepInto, void(*callback)(), bool forceBreakTrace)
{
PLUG_CB_TRACEEXECUTE info;
info.cip = cip;
auto breakCondition = (info.stop = traceState.BreakTrace() || forceBreakTrace);
if(traceState.IsExtended()) //only set when needed
varset("$tracecounter", traceState.StepCount(), true);
plugincbcall(CB_TRACEEXECUTE, &info);
breakCondition = info.stop;
auto logCondition = traceState.EvaluateLog(true);
auto cmdCondition = traceState.EvaluateCmd(breakCondition);
auto switchCondition = traceState.EvaluateSwitch(false);
if(logCondition) //log
{
traceState.LogWrite(stringformatinline(traceState.LogText()));
}
if(cmdCondition) //command
{
//TODO: commands like run/step etc will fuck up your shit
varset("$tracecondition", breakCondition ? 1 : 0, false);
varset("$tracelogcondition", logCondition ? 1 : 0, true);
varset("$traceswitchcondition", switchCondition ? 1 : 0, false);
cmddirectexec(traceState.CmdText().c_str());
duint script_breakcondition;
if(varget("$tracecondition", &script_breakcondition, nullptr, nullptr))
breakCondition = script_breakcondition != 0;
if(varget("$traceswitchcondition", &script_breakcondition, nullptr, nullptr))
switchCondition = script_breakcondition != 0;
}
if(breakCondition || traceState.ForceBreakTrace()) //break the debugger
{
auto steps = dbgcleartracestate();
varset("$tracecounter", steps, true);
#ifdef _WIN64
dprintf(QT_TRANSLATE_NOOP("DBG", "Trace finished after %llu steps!\n"), steps);
#else //x86
dprintf(QT_TRANSLATE_NOOP("DBG", "Trace finished after %u steps!\n"), steps);
#endif //_WIN64
cbRtrFinalStep();
}
else //continue tracing
{
if(bTraceRecordEnabledDuringTrace)
_dbg_dbgtraceexecute(cip);
if(switchCondition) //switch (invert) the step type once
bStepInto = !bStepInto;
(bStepInto ? StepIntoWow64 : StepOverWrapper)((void*)callback);
}
}
static void cbTraceXConditionalStep(bool bStepInto, void (*callback)())
{
hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
cbTraceUniversalConditionalStep(GetContextDataEx(hActiveThread, UE_CIP), bStepInto, callback, false);
}
static void cbTraceXXTraceRecordStep(bool bStepInto, bool bInto, void(*callback)())
{
hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
auto cip = GetContextDataEx(hActiveThread, UE_CIP);
auto forceBreakTrace = TraceRecord.getTraceRecordType(cip) != TraceRecordManager::TraceRecordNone && (TraceRecord.getHitCount(cip) == 0) ^ bInto;
cbTraceUniversalConditionalStep(cip, bStepInto, callback, forceBreakTrace);
}
void cbTraceOverConditionalStep()
{
cbTraceXConditionalStep(false, cbTraceOverConditionalStep);
}
void cbTraceIntoConditionalStep()
{
cbTraceXConditionalStep(true, cbTraceIntoConditionalStep);
}
void cbTraceIntoBeyondTraceRecordStep()
{
cbTraceXXTraceRecordStep(true, false, cbTraceIntoBeyondTraceRecordStep);
}
void cbTraceOverBeyondTraceRecordStep()
{
cbTraceXXTraceRecordStep(false, false, cbTraceOverBeyondTraceRecordStep);
}
void cbTraceIntoIntoTraceRecordStep()
{
cbTraceXXTraceRecordStep(true, true, cbTraceIntoIntoTraceRecordStep);
}
void cbTraceOverIntoTraceRecordStep()
{
cbTraceXXTraceRecordStep(false, true, cbTraceOverIntoTraceRecordStep);
}
static void cbCreateProcess(CREATE_PROCESS_DEBUG_INFO* CreateProcessInfo)
{
fdProcessInfo->hProcess = CreateProcessInfo->hProcess;
fdProcessInfo->hThread = CreateProcessInfo->hThread;
varset("$hp", (duint)fdProcessInfo->hProcess, true);
void* base = CreateProcessInfo->lpBaseOfImage;
char DebugFileName[deflen] = "";
if(!GetFileNameFromHandle(CreateProcessInfo->hFile, DebugFileName) && !GetFileNameFromProcessHandle(CreateProcessInfo->hProcess, DebugFileName))
strcpy_s(DebugFileName, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "??? (GetFileNameFromHandle failed)")));
dprintf(QT_TRANSLATE_NOOP("DBG", "Process Started: %p %s\n"), base, DebugFileName);
char* cmdline = nullptr;
if(dbggetcmdline(&cmdline, nullptr, fdProcessInfo->hProcess))
{
// Parse the command line from the debuggee
int argc = 0;
wchar_t** argv = CommandLineToArgvW(StringUtils::Utf8ToUtf16(cmdline).c_str(), &argc);
// Print the command line to the log
dprintf_untranslated(" %s\n", cmdline);
for(int i = 0; i < argc; i++)
dprintf_untranslated(" argv[%i]: %s\n", i, StringUtils::Utf16ToUtf8(argv[i]).c_str());
LocalFree(argv);
efree(cmdline);
}
//update memory map
MemUpdateMap();
GuiUpdateMemoryView();
GuiDumpAt(MemFindBaseAddr(GetContextDataEx(CreateProcessInfo->hThread, UE_CIP), 0) + PAGE_SIZE); //dump somewhere
ModLoad((duint)base, 1, DebugFileName);
char modname[256] = "";
if(ModNameFromAddr((duint)base, modname, true))
BpEnumAll(cbSetModuleBreakpoints, modname, duint(base));
BpEnumAll(cbSetDLLBreakpoints);
BpEnumAll(cbSetModuleBreakpoints, "");
DebugUpdateBreakpointsViewAsync();
pCreateProcessBase = (duint)CreateProcessInfo->lpBaseOfImage;
pDebuggedBase = pCreateProcessBase; //debugged base = executable
DbCheckHash(ModContentHashFromAddr(pDebuggedBase)); //Check hash mismatch
if(!bFileIsDll && !bIsAttached) //Set entry breakpoint
{
char command[deflen] = "";
if(settingboolget("Events", "TlsCallbacks"))
{
SHARED_ACQUIRE(LockModules);
auto modInfo = ModInfoFromAddr(duint(base));
int invalidCount = 0;
for(size_t i = 0; i < modInfo->tlsCallbacks.size(); i++)
{
auto callbackVA = modInfo->tlsCallbacks.at(i);
if(MemIsValidReadPtr(callbackVA))
{
String breakpointname = StringUtils::sprintf(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "TLS Callback %d")), i + 1);
sprintf_s(command, "bp %p,\"%s\",ss", callbackVA, breakpointname.c_str());
cmddirectexec(command);
}
else
invalidCount++;
}
if(invalidCount)
dprintf(QT_TRANSLATE_NOOP("DBG", "%d invalid TLS callback addresses...\n"), invalidCount);
}
if(settingboolget("Events", "EntryBreakpoint") && !bEntryIsInMzHeader)
{
sprintf_s(command, "bp %p,\"%s\",ss", pDebuggedBase + pDebuggedEntry, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "entry breakpoint")));
cmddirectexec(command);
}
bTraceRecordEnabledDuringTrace = settingboolget("Engine", "TraceRecordEnabledDuringTrace");
}
else if(bFileIsDll && strstr(DebugFileName, "DLLLoader" ArchValue("32", "64"))) //DLL Loader
gDllLoader = StringUtils::Utf8ToUtf16(DebugFileName);
DebugUpdateBreakpointsViewAsync();
//call plugin callback
PLUG_CB_CREATEPROCESS callbackInfo;
callbackInfo.CreateProcessInfo = CreateProcessInfo;
IMAGEHLP_MODULE64 modInfoUtf8;
memset(&modInfoUtf8, 0, sizeof(modInfoUtf8));
modInfoUtf8.SizeOfStruct = sizeof(modInfoUtf8);
modInfoUtf8.BaseOfImage = (DWORD64)base;
modInfoUtf8.ImageSize = 0;
modInfoUtf8.TimeDateStamp = 0;
modInfoUtf8.CheckSum = 0;
modInfoUtf8.NumSyms = 1;
modInfoUtf8.SymType = SymDia;
strncpy_s(modInfoUtf8.ModuleName, DebugFileName, _TRUNCATE);
strncpy_s(modInfoUtf8.ImageName, DebugFileName, _TRUNCATE);
strncpy_s(modInfoUtf8.LoadedImageName, "", _TRUNCATE);
strncpy_s(modInfoUtf8.LoadedPdbName, "", _TRUNCATE);
modInfoUtf8.CVSig = 0;
strncpy_s(modInfoUtf8.CVData, "", _TRUNCATE);
modInfoUtf8.PdbSig = 0;
modInfoUtf8.PdbAge = 0;
modInfoUtf8.PdbUnmatched = FALSE;
modInfoUtf8.DbgUnmatched = FALSE;
modInfoUtf8.LineNumbers = TRUE;
modInfoUtf8.GlobalSymbols = 0;
modInfoUtf8.TypeInfo = TRUE;
modInfoUtf8.SourceIndexed = TRUE;
modInfoUtf8.Publics = TRUE;
callbackInfo.modInfo = &modInfoUtf8;
callbackInfo.DebugFileName = DebugFileName;
callbackInfo.fdProcessInfo = fdProcessInfo;
plugincbcall(CB_CREATEPROCESS, &callbackInfo);
//update thread list
CREATE_THREAD_DEBUG_INFO threadInfo;
threadInfo.hThread = CreateProcessInfo->hThread;
threadInfo.lpStartAddress = CreateProcessInfo->lpStartAddress;
threadInfo.lpThreadLocalBase = CreateProcessInfo->lpThreadLocalBase;
ThreadCreate(&threadInfo);
}
static void cbExitProcess(EXIT_PROCESS_DEBUG_INFO* ExitProcess)
{
dprintf(QT_TRANSLATE_NOOP("DBG", "Process stopped with exit code 0x%X\n"), ExitProcess->dwExitCode);
PLUG_CB_EXITPROCESS callbackInfo;
callbackInfo.ExitProcess = ExitProcess;
plugincbcall(CB_EXITPROCESS, &callbackInfo);
_dbg_animatestop(); // Stop animating
//history
dbgcleartracestate();
dbgClearRtuBreakpoints();
HistoryClear();
}
static void cbCreateThread(CREATE_THREAD_DEBUG_INFO* CreateThread)
{
ThreadCreate(CreateThread); //update thread list
DWORD dwThreadId = ((DEBUG_EVENT*)GetDebugData())->dwThreadId;
hActiveThread = ThreadGetHandle(dwThreadId);
PLUG_CB_CREATETHREAD callbackInfo;
callbackInfo.CreateThread = CreateThread;
callbackInfo.dwThreadId = dwThreadId;
plugincbcall(CB_CREATETHREAD, &callbackInfo);
auto entry = duint(CreateThread->lpStartAddress);
auto symbolic = SymGetSymbolicName(entry);
if(!symbolic.length())
symbolic = StringUtils::sprintf("%p", entry);
dprintf(QT_TRANSLATE_NOOP("DBG", "Thread %X created, Entry: %s\n"), dwThreadId, symbolic.c_str());
if(settingboolget("Events", "ThreadEntry"))
{
String command;
command = StringUtils::sprintf("bp %p,\"%s %X\",ss", entry, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Thread Entry")), dwThreadId);
cmddirectexec(command.c_str());
}
if(settingboolget("Events", "ThreadStart"))
{
HistoryClear();
//update memory map
MemUpdateMap();
//update GUI
DebugUpdateGuiSetStateAsync(GetContextDataEx(hActiveThread, UE_CIP), true);
//lock
lock(WAITID_RUN);
// Plugin callback
PLUG_CB_PAUSEDEBUG pauseInfo = { nullptr };
plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
dbgsetforeground();
dbgsetskipexceptions(false);
wait(WAITID_RUN);
}
else
{
//insert the thread stack as a dummy page to prevent cache misses (issue #1475)
NT_TIB tib;
if(ThreadGetTib(ThreadGetLocalBase(dwThreadId), &tib))
{
MEMPAGE page;
auto limit = duint(tib.StackLimit);
auto base = duint(tib.StackBase);
sprintf_s(page.info, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Thread %X Stack")), dwThreadId);
page.mbi.BaseAddress = page.mbi.AllocationBase = tib.StackLimit;
page.mbi.Protect = page.mbi.AllocationProtect = PAGE_READWRITE;
page.mbi.RegionSize = base - limit;
page.mbi.State = MEM_COMMIT;
page.mbi.Type = MEM_PRIVATE;
EXCLUSIVE_ACQUIRE(LockMemoryPages);
memoryPages.insert({ Range(limit, base - 1), page });
}
}
}
static void cbExitThread(EXIT_THREAD_DEBUG_INFO* ExitThread)
{
// Not called when the main (last) thread exits. Instead
// EXIT_PROCESS_DEBUG_EVENT is signalled.
// Switch to the main thread (because the thread is terminated).
hActiveThread = ThreadGetHandle(fdProcessInfo->dwThreadId);
if(!hActiveThread)
{
std::vector<THREADINFO> threads;
ThreadGetList(threads);
if(threads.size())
hActiveThread = threads[0].Handle;
else
dputs(QT_TRANSLATE_NOOP("DBG", "No threads left to switch to (bug?)"));
}
DWORD dwThreadId = ((DEBUG_EVENT*)GetDebugData())->dwThreadId;
PLUG_CB_EXITTHREAD callbackInfo;
callbackInfo.ExitThread = ExitThread;
callbackInfo.dwThreadId = dwThreadId;
plugincbcall(CB_EXITTHREAD, &callbackInfo);
HistoryClear();
ThreadExit(dwThreadId);
dprintf(QT_TRANSLATE_NOOP("DBG", "Thread %X exit\n"), dwThreadId);
if(settingboolget("Events", "ThreadEnd"))
{
//update GUI
DebugUpdateGuiSetStateAsync(GetContextDataEx(hActiveThread, UE_CIP), true);
//lock
lock(WAITID_RUN);
// Plugin callback
PLUG_CB_PAUSEDEBUG pauseInfo = { nullptr };
plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
dbgsetforeground();
dbgsetskipexceptions(false);
wait(WAITID_RUN);
}
}
static DWORD WINAPI cbInitializationScriptThread(void*)
{
Memory<char*> script(MAX_SETTING_SIZE + 1);
if(BridgeSettingGet("Engine", "InitializeScript", script())) // Global script file
{
if(scriptLoadSync(script()))
{
if(scriptRunSync(0, true))
scriptunload();
}
else
dputs(QT_TRANSLATE_NOOP("DBG", "Error: Cannot load global initialization script."));
}
if(szDebuggeeInitializationScript[0] != 0)
{
if(scriptLoadSync(szDebuggeeInitializationScript))
{
if(scriptRunSync(0, true))
scriptunload();
}
else
dputs(QT_TRANSLATE_NOOP("DBG", "Error: Cannot load debuggee initialization script."));
}
return 0;
}
static void cbSystemBreakpoint(void* ExceptionData) // TODO: System breakpoint event shouldn't be dropped
{
hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
//Get on top of things
SetForegroundWindow(GuiGetWindowHandle());
// Update GUI (this should be the first triggered event)
duint cip = GetContextDataEx(hActiveThread, UE_CIP);
GuiDumpAt(MemFindBaseAddr(cip, 0, true)); //dump somewhere
DebugUpdateGuiSetStateAsync(cip, true, running);
MemInitRemoteProcessCookie(cookie.cookie);
GuiUpdateAllViews();
//log message
if(bIsAttached)
dputs(QT_TRANSLATE_NOOP("DBG", "Attach breakpoint reached!"));
else
dputs(QT_TRANSLATE_NOOP("DBG", "System breakpoint reached!"));
dbgsetskipexceptions(false); //we are not skipping first-chance exceptions
//plugin callbacks
PLUG_CB_SYSTEMBREAKPOINT callbackInfo;
callbackInfo.reserved = 0;
plugincbcall(CB_SYSTEMBREAKPOINT, &callbackInfo);
lock(WAITID_RUN); // Allow the user to run a script file now
bool systemBreakpoint = settingboolget("Events", "SystemBreakpoint");
if(!systemBreakpoint && bEntryIsInMzHeader)
{
dputs(QT_TRANSLATE_NOOP("DBG", "It has been detected that the debuggee entry point is in the MZ header of the executable. This will cause strange behavior, so the system breakpoint has been enabled regardless of your setting. Be careful!"));
systemBreakpoint = true;
}
if(bIsAttached ? settingboolget("Events", "AttachBreakpoint") : systemBreakpoint)
{
//lock
GuiSetDebugStateAsync(paused);
// Plugin callback
PLUG_CB_PAUSEDEBUG pauseInfo = { nullptr };
plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
dbgsetforeground();
CloseHandle(CreateThread(NULL, 0, cbInitializationScriptThread, NULL, 0, NULL));
}
else
{
CloseHandle(CreateThread(NULL, 0, cbInitializationScriptThread, NULL, 0, NULL));
unlock(WAITID_RUN);
}
wait(WAITID_RUN);
}
static void cbLoadDll(LOAD_DLL_DEBUG_INFO* LoadDll)
{
hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
void* base = LoadDll->lpBaseOfDll;
char DLLDebugFileName[deflen] = "";
if(!GetFileNameFromHandle(LoadDll->hFile, DLLDebugFileName) && !GetFileNameFromModuleHandle(fdProcessInfo->hProcess, HMODULE(base), DLLDebugFileName))
strcpy_s(DLLDebugFileName, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "??? (GetFileNameFromHandle failed)")));
ModLoad((duint)base, 1, DLLDebugFileName);
// Update memory map
MemUpdateMapAsync();
char modname[MAX_MODULE_SIZE] = "";
if(ModNameFromAddr(duint(base), modname, true))
BpEnumAll(cbSetModuleBreakpoints, modname, duint(base));
DebugUpdateBreakpointsViewAsync();
bool bAlreadySetEntry = false;
char command[MAX_PATH * 2] = "";
bool bIsDebuggingThis = false;
if(bFileIsDll && !_stricmp(DLLDebugFileName, szDebuggeePath) && !bIsAttached) //Set entry breakpoint
{
CloseHandle(DebugDLLFileMapping);
DebugDLLFileMapping = 0;
bIsDebuggingThis = true;
pDebuggedBase = (duint)base;
DbCheckHash(ModContentHashFromAddr(pDebuggedBase)); //Check hash mismatch
if(settingboolget("Events", "EntryBreakpoint"))
{
bAlreadySetEntry = true;
sprintf_s(command, "bp %p,\"%s\",ss", pDebuggedBase + pDebuggedEntry, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "entry breakpoint")));
cmddirectexec(command);
}
}
DebugUpdateBreakpointsViewAsync();
if(settingboolget("Events", "TlsCallbacks"))
{
SHARED_ACQUIRE(LockModules);
auto modInfo = ModInfoFromAddr(duint(base));
int invalidCount = 0;
for(size_t i = 0; i < modInfo->tlsCallbacks.size(); i++)
{
auto callbackVA = modInfo->tlsCallbacks.at(i);
if(MemIsValidReadPtr(callbackVA))
{
if(bIsDebuggingThis)
sprintf_s(command, "bp %p,\"%s %u\",ss", callbackVA, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "TLS Callback")), i + 1);
else
sprintf_s(command, "bp %p,\"%s %u (%s)\",ss", callbackVA, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "TLS Callback")), i + 1, modname);
cmddirectexec(command);
}
else
invalidCount++;
}
if(invalidCount)
dprintf(QT_TRANSLATE_NOOP("DBG", "%d invalid TLS callback addresses...\n"), invalidCount);
}
auto breakOnDll = dbghandledllbreakpoint(modname, true);
if((breakOnDll || settingboolget("Events", "DllEntry")) && !bAlreadySetEntry)
{
auto entry = ModEntryFromAddr(duint(base));
if(entry)
{
sprintf_s(command, "bp %p,\"DllMain (%s)\",ss", entry, modname);
cmddirectexec(command);
}
}
if(ModNameFromAddr(duint(base), modname, true) && scmp(modname, "ntdll.dll"))
{
if(settingboolget("Misc", "QueryProcessCookie"))
cookie.HandleNtdllLoad(bIsAttached);
if(settingboolget("Misc", "TransparentExceptionStepping"))
exceptionDispatchAddr = DbgValFromString("ntdll:KiUserExceptionDispatcher");
//set debug flags
if(dwDebugFlags != 0)
{
SHARED_ACQUIRE(LockModules);
auto info = ModInfoFromAddr(duint(base));
if(info->symbols->isOpen())
{
dprintf(QT_TRANSLATE_NOOP("DBG", "Waiting until ntdll.dll symbols are loaded...\n"));
info->symbols->waitUntilLoaded();
SymbolInfo LdrpDebugFlags;
if(info->symbols->findSymbolByName("LdrpDebugFlags", LdrpDebugFlags, true))
{
if(MemWrite(info->base + LdrpDebugFlags.rva, &dwDebugFlags, sizeof(dwDebugFlags)))
dprintf(QT_TRANSLATE_NOOP("DBG", "Set LdrpDebugFlags to 0x%08X successfully!\n"), dwDebugFlags);
else
dprintf(QT_TRANSLATE_NOOP("DBG", "Failed to write to LdrpDebugFlags\n"));
}
else
{
dprintf(QT_TRANSLATE_NOOP("DBG", "Symbol 'LdrpDebugFlags' not found!\n"));
}
}
else
{
dprintf(QT_TRANSLATE_NOOP("DBG", "Failed to find LdrpDebugFlags (you need to load symbols for ntdll.dll)\n"));
}
}
}
dprintf(QT_TRANSLATE_NOOP("DBG", "DLL Loaded: %p %s\n"), base, DLLDebugFileName);
//plugin callback
PLUG_CB_LOADDLL callbackInfo;
callbackInfo.LoadDll = LoadDll;
IMAGEHLP_MODULE64 modInfoUtf8;
memset(&modInfoUtf8, 0, sizeof(modInfoUtf8));
modInfoUtf8.SizeOfStruct = sizeof(modInfoUtf8);
modInfoUtf8.BaseOfImage = (DWORD64)base;
modInfoUtf8.ImageSize = 0;
modInfoUtf8.TimeDateStamp = 0;
modInfoUtf8.CheckSum = 0;
modInfoUtf8.NumSyms = 0;
modInfoUtf8.SymType = SymDia;
strncpy_s(modInfoUtf8.ModuleName, DLLDebugFileName, _TRUNCATE);
strncpy_s(modInfoUtf8.ImageName, DLLDebugFileName, _TRUNCATE);
strncpy_s(modInfoUtf8.LoadedImageName, "", _TRUNCATE);
strncpy_s(modInfoUtf8.LoadedPdbName, "", _TRUNCATE);
modInfoUtf8.CVSig = 0;
strncpy_s(modInfoUtf8.CVData, "", _TRUNCATE);
modInfoUtf8.PdbSig = 0;
modInfoUtf8.PdbAge = 0;
modInfoUtf8.PdbUnmatched = FALSE;
modInfoUtf8.DbgUnmatched = FALSE;
modInfoUtf8.LineNumbers = 0;
modInfoUtf8.GlobalSymbols = TRUE;
modInfoUtf8.TypeInfo = TRUE;
modInfoUtf8.SourceIndexed = TRUE;
modInfoUtf8.Publics = TRUE;
callbackInfo.modInfo = &modInfoUtf8;
callbackInfo.modname = modname;
plugincbcall(CB_LOADDLL, &callbackInfo);
if(breakOnDll)
{
cbGenericBreakpoint(BPDLL, DLLDebugFileName);
}
else if(settingboolget("Events", "DllLoad"))
{
//update GUI
DebugUpdateGuiSetStateAsync(GetContextDataEx(hActiveThread, UE_CIP), true);
//lock
lock(WAITID_RUN);
// Plugin callback
PLUG_CB_PAUSEDEBUG pauseInfo = { nullptr };
plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
dbgsetforeground();
dbgsetskipexceptions(false);
wait(WAITID_RUN);
}
}
static void cbUnloadDll(UNLOAD_DLL_DEBUG_INFO* UnloadDll)
{
hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
PLUG_CB_UNLOADDLL callbackInfo;
callbackInfo.UnloadDll = UnloadDll;
plugincbcall(CB_UNLOADDLL, &callbackInfo);
void* base = UnloadDll->lpBaseOfDll;
char modname[256] = "???";
if(ModNameFromAddr((duint)base, modname, true))
BpEnumAll(cbRemoveModuleBreakpoints, modname, duint(base));
DebugUpdateBreakpointsViewAsync();
dprintf(QT_TRANSLATE_NOOP("DBG", "DLL Unloaded: %p %s\n"), base, modname);
if(dbghandledllbreakpoint(modname, false))
{
cbGenericBreakpoint(BPDLL, modname);
}
else if(settingboolget("Events", "DllUnload"))
{
//update GUI
DebugUpdateGuiSetStateAsync(GetContextDataEx(hActiveThread, UE_CIP), true);
//lock
lock(WAITID_RUN);
// Plugin callback
PLUG_CB_PAUSEDEBUG pauseInfo = { nullptr };
plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
dbgsetforeground();
dbgsetskipexceptions(false);
wait(WAITID_RUN);
}
ModUnload((duint)base);
//update memory map
MemUpdateMapAsync();
}
static void cbOutputDebugString(OUTPUT_DEBUG_STRING_INFO* DebugString)
{
hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
PLUG_CB_OUTPUTDEBUGSTRING callbackInfo;
callbackInfo.DebugString = DebugString;
plugincbcall(CB_OUTPUTDEBUGSTRING, &callbackInfo);
if(!DebugString->fUnicode) //ASCII
{
Memory<char*> DebugText(DebugString->nDebugStringLength + 1, "cbOutputDebugString:DebugText");
if(MemRead((duint)DebugString->lpDebugStringData, DebugText(), DebugString->nDebugStringLength))
{
String str = String(DebugText());
if(str != lastDebugText) //fix for every string being printed twice
{
if(str != "\n")
dprintf(QT_TRANSLATE_NOOP("DBG", "DebugString: \"%s\"\n"), StringUtils::Trim(StringUtils::Escape(str, false)).c_str());
lastDebugText = str;
}
else
lastDebugText.clear();
}
}
else
{
//TODO: implement Windows 10 unicode debug string
}
if(settingboolget("Events", "DebugStrings"))
{
//update GUI
DebugUpdateGuiSetStateAsync(GetContextDataEx(hActiveThread, UE_CIP), true);
//lock
lock(WAITID_RUN);
// Plugin callback
PLUG_CB_PAUSEDEBUG pauseInfo = { nullptr };
plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
dbgsetforeground();
dbgsetskipexceptions(false);
wait(WAITID_RUN);
}
}
static bool dbgdetachDisableAllBreakpoints(const BREAKPOINT* bp)
{
if(bp->enabled)
{
if(bp->type == BPNORMAL)
DeleteBPX(bp->addr);
else if(bp->type == BPMEMORY)
RemoveMemoryBPX(bp->addr, 0);
else if(bp->type == BPHARDWARE && TITANDRXVALID(bp->titantype))
DeleteHardwareBreakPoint(TITANGETDRX(bp->titantype));
}
return true;
}
static void cbException(EXCEPTION_DEBUG_INFO* ExceptionData)
{
hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
PLUG_CB_EXCEPTION callbackInfo;
callbackInfo.Exception = ExceptionData;
unsigned int ExceptionCode = ExceptionData->ExceptionRecord.ExceptionCode;
GuiSetLastException(ExceptionCode);
lastExceptionInfo = *ExceptionData;
duint addr = (duint)ExceptionData->ExceptionRecord.ExceptionAddress;
{
BREAKPOINT bp;
if(BpGet(ExceptionCode, BPEXCEPTION, nullptr, &bp) && bp.enabled && ((bp.titantype == 1 && ExceptionData->dwFirstChance) || (bp.titantype == 2 && !ExceptionData->dwFirstChance) || bp.titantype == 3))
{
bPausedOnException = true;
cbGenericBreakpoint(BPEXCEPTION, ExceptionData);
bPausedOnException = false;
return;
}
}
if(ExceptionData->ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT)
{
if(isDetachedByUser)
{
PLUG_CB_DETACH detachInfo;
detachInfo.fdProcessInfo = fdProcessInfo;
plugincbcall(CB_DETACH, &detachInfo);
BpEnumAll(dbgdetachDisableAllBreakpoints); // Disable all software breakpoints before detaching.
if(!DetachDebuggerEx(fdProcessInfo->dwProcessId))
dputs(QT_TRANSLATE_NOOP("DBG", "DetachDebuggerEx failed..."));
else
dputs(QT_TRANSLATE_NOOP("DBG", "Detached!"));
isDetachedByUser = false;
_dbg_animatestop(); // Stop animating
return;
}
}
else if(ExceptionData->ExceptionRecord.ExceptionCode == MS_VC_EXCEPTION) //SetThreadName exception
{
THREADNAME_INFO nameInfo; //has no valid local pointers
memcpy(&nameInfo, ExceptionData->ExceptionRecord.ExceptionInformation, sizeof(THREADNAME_INFO));
if(nameInfo.dwThreadID == -1) //current thread
nameInfo.dwThreadID = ((DEBUG_EVENT*)GetDebugData())->dwThreadId;
if(nameInfo.dwType == 0x1000 && nameInfo.dwFlags == 0 && ThreadIsValid(nameInfo.dwThreadID)) //passed basic checks
{
Memory<char*> ThreadName(MAX_THREAD_NAME_SIZE, "cbException:ThreadName");
if(MemRead((duint)nameInfo.szName, ThreadName(), MAX_THREAD_NAME_SIZE - 1))
{
String ThreadNameEscaped = StringUtils::Escape(ThreadName());
dprintf(QT_TRANSLATE_NOOP("DBG", "SetThreadName(%X, \"%s\")\n"), nameInfo.dwThreadID, ThreadNameEscaped.c_str());
ThreadSetName(nameInfo.dwThreadID, ThreadNameEscaped.c_str());
}
}
}
if(bVerboseExceptionLogging)
DbgCmdExecDirect("exinfo"); //show extended exception information
auto exceptionName = ExceptionCodeToName(ExceptionCode);
if(!exceptionName.size()) //if no exception was found, try the error codes (RPC_S_*)
exceptionName = ErrorCodeToName(ExceptionCode);
if(ExceptionData->dwFirstChance) //first chance exception
{
if(exceptionName.size())
dprintf(QT_TRANSLATE_NOOP("DBG", "First chance exception on %p (%.8X, %s)!\n"), addr, ExceptionCode, exceptionName.c_str());
else
dprintf(QT_TRANSLATE_NOOP("DBG", "First chance exception on %p (%.8X)!\n"), addr, ExceptionCode);
SetNextDbgContinueStatus(DBG_EXCEPTION_NOT_HANDLED);
if((bSkipExceptions || dbgisignoredexception(ExceptionCode)) && (!maxSkipExceptionCount || ++skipExceptionCount < maxSkipExceptionCount))
return;
}
else //lock the exception
{
if(exceptionName.size())
dprintf(QT_TRANSLATE_NOOP("DBG", "Last chance exception on %p (%.8X, %s)!\n"), addr, ExceptionCode, exceptionName.c_str());
else
dprintf(QT_TRANSLATE_NOOP("DBG", "Last chance exception on %p (%.8X)!\n"), addr, ExceptionCode);
SetNextDbgContinueStatus(DBG_CONTINUE);
}
DebugUpdateGuiSetStateAsync(GetContextDataEx(hActiveThread, UE_CIP), true);
//lock
lock(WAITID_RUN);
bPausedOnException = true;
// Plugin callback
PLUG_CB_PAUSEDEBUG pauseInfo = { nullptr };
plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
dbgsetforeground();
dbgsetskipexceptions(false);
plugincbcall(CB_EXCEPTION, &callbackInfo);
wait(WAITID_RUN);
bPausedOnException = false;
}
static void cbDebugEvent(DEBUG_EVENT* DebugEvent)
{
InterlockedIncrement((volatile long*)&DbgEvents);
PLUG_CB_DEBUGEVENT debugEventInfo;
debugEventInfo.DebugEvent = DebugEvent;
plugincbcall(CB_DEBUGEVENT, &debugEventInfo);
}
static void cbAttachDebugger()
{
if(hEvent) //Signal the AeDebug event
{
SetEvent(hEvent);
CloseHandle(hEvent);
hEvent = 0;
}
if(tidToResume) //Resume a thread
{
cmddirectexec(StringUtils::sprintf("resumethread %p", tidToResume).c_str());
tidToResume = 0;
}
varset("$pid", fdProcessInfo->dwProcessId, true);
}
void cbDetach()
{
if(!isDetachedByUser)
return;
PLUG_CB_DETACH detachInfo;
detachInfo.fdProcessInfo = fdProcessInfo;
plugincbcall(CB_DETACH, &detachInfo);
BpEnumAll(dbgdetachDisableAllBreakpoints); // Disable all software breakpoints before detaching.
if(!DetachDebuggerEx(fdProcessInfo->dwProcessId))
dputs(QT_TRANSLATE_NOOP("DBG", "DetachDebuggerEx failed..."));
else
dputs(QT_TRANSLATE_NOOP("DBG", "Detached!"));
return;
}
cmdline_qoutes_placement_t getqoutesplacement(const char* cmdline)
{
cmdline_qoutes_placement_t quotesPos;
quotesPos.firstPos = quotesPos.secondPos = 0;
auto len = strlen(cmdline);
char quoteSymb = cmdline[0];
if(quoteSymb == '"' || quoteSymb == '\'')
{
for(size_t i = 1; i < len; i++)
{
if(cmdline[i] == quoteSymb)
{
quotesPos.posEnum = i == len - 1 ? QOUTES_AT_BEGIN_AND_END : QOUTES_AROUND_EXE;
quotesPos.secondPos = i;
break;
}
}
if(!quotesPos.secondPos)
quotesPos.posEnum = NO_CLOSE_QUOTE_FOUND;
}
else
{
quotesPos.posEnum = NO_QOUTES;
//try to locate first quote
for(size_t i = 1; i < len; i++)
if(cmdline[i] == '"' || cmdline[i] == '\'')
quotesPos.secondPos = i;
}
return quotesPos;
}
BOOL ismainwindow(HWND handle)
{
// using only OWNER condition allows getting titles of hidden "main windows"
return !GetWindow(handle, GW_OWNER) && IsWindowVisible(handle);
}
BOOL CALLBACK chkWindowPidCallback(HWND hWnd, LPARAM lParam)
{
DWORD procId = (DWORD)lParam;
DWORD hwndPid = 0;
GetWindowThreadProcessId(hWnd, &hwndPid);
if(hwndPid == procId)
{
if(!mForegroundHandle) // get the foreground if no owner visible
mForegroundHandle = hWnd;
if(ismainwindow(hWnd))
{
mProcHandle = hWnd;
return FALSE;
}
}
return TRUE;
}
bool dbggetwintext(std::vector<std::string>* winTextList, const DWORD dwProcessId)
{
mProcHandle = NULL;
mForegroundHandle = NULL;
EnumWindows(chkWindowPidCallback, dwProcessId);
if(!mProcHandle && !mForegroundHandle)
return false;
wchar_t limitedbuffer[256];
limitedbuffer[255] = 0;
if(mProcHandle) // get info from the "main window" (GW_OWNER + visible)
{
if(!GetWindowTextW((HWND)mProcHandle, limitedbuffer, 256))
GetClassNameW((HWND)mProcHandle, limitedbuffer, 256); // go for the class name if none of the above
}
else if(mForegroundHandle) // get info from the foreground window
{
if(!GetWindowTextW((HWND)mForegroundHandle, limitedbuffer, 256))
GetClassNameW((HWND)mForegroundHandle, limitedbuffer, 256); // go for the class name if none of the above
}
if(limitedbuffer[255] != 0) //Window title too long. Add "..." to the end of buffer.
{
if(limitedbuffer[252] < 0xDC00 || limitedbuffer[252] > 0xDFFF) //protect the last surrogate of UTF-16 surrogate pair
limitedbuffer[252] = L'.';
limitedbuffer[253] = L'.';
limitedbuffer[254] = L'.';
limitedbuffer[255] = 0;
}
auto UTF8WindowTitle = StringUtils::Utf16ToUtf8(limitedbuffer);
winTextList->push_back(UTF8WindowTitle);
return true;
}
bool dbglistprocesses(std::vector<PROCESSENTRY32>* infoList, std::vector<std::string>* commandList, std::vector<std::string>* winTextList)
{
infoList->clear();
Handle hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if(!hProcessSnap)
return false;
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
if(!Process32First(hProcessSnap, &pe32))
return false;
do
{
if(pe32.th32ProcessID == GetCurrentProcessId())
continue;
if(pe32.th32ProcessID == 0 || pe32.th32ProcessID == 4) // System process and Idle process have special PID.
continue;
Handle hProcess = TitanOpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pe32.th32ProcessID);
if(!hProcess)
continue;
BOOL wow64 = false, mewow64 = false;
if(!IsWow64Process(hProcess, &wow64) || !IsWow64Process(GetCurrentProcess(), &mewow64))
continue;
if((mewow64 && !wow64) || (!mewow64 && wow64))
continue;
char szExePath[MAX_PATH] = "";
if(GetFileNameFromProcessHandle(hProcess, szExePath))
strcpy_s(pe32.szExeFile, szExePath);
infoList->push_back(pe32);
//
char* cmdline;
if(!dbggetwintext(winTextList, pe32.th32ProcessID))
winTextList->push_back("");
if(!dbggetcmdline(&cmdline, NULL, hProcess))
commandList->push_back("ARG_GET_ERROR");
else
{
cmdline_qoutes_placement_t posEnum = getqoutesplacement(cmdline);
char* cmdLineExe = strstr(cmdline, pe32.szExeFile);
size_t cmdLineExeSize = cmdLineExe ? strlen(pe32.szExeFile) : 0;
if(!cmdLineExe)
{
char* exeName = strrchr(pe32.szExeFile, '\\') ? strrchr(pe32.szExeFile, '\\') + 1 :
strrchr(pe32.szExeFile, '/') ? strrchr(pe32.szExeFile, '/') + 1 : pe32.szExeFile;
size_t exeNameLen = strlen(exeName);
char* peNameInCmd = strstr(cmdline, exeName);
//check for exe name is used in path to exe
for(char* exeNameInCmdTmp = peNameInCmd; exeNameInCmdTmp;)
{
exeNameInCmdTmp = strstr(exeNameInCmdTmp + exeNameLen, exeName);
if(!exeNameInCmdTmp)
break;
char* nextSlash = strchr(exeNameInCmdTmp, '\\') ? strchr(exeNameInCmdTmp, '\\') :
strchr(exeNameInCmdTmp, '/') ? strchr(exeNameInCmdTmp, '/') : NULL;
if(nextSlash && posEnum.posEnum == NO_QOUTES) //if there NO_QOUTES, then the path to PE in cmdline can't contain spaces
{
if(strchr(exeNameInCmdTmp, ' ') < nextSlash) //slash is in arguments
{
peNameInCmd = exeNameInCmdTmp;
break;
}
else
continue;
}
else if(nextSlash && posEnum.posEnum == QOUTES_AROUND_EXE)
{
if((cmdline + posEnum.secondPos) < nextSlash) //slash is in arguments
{
peNameInCmd = exeNameInCmdTmp;
break;
}
else
continue;
}
else
{
peNameInCmd = exeNameInCmdTmp;
break;
}
}
if(peNameInCmd)
cmdLineExeSize = (size_t)(((LPBYTE)peNameInCmd - (LPBYTE)cmdline) + exeNameLen);
else
{
//try to locate basic name, without extension
Memory<char*> basicName(strlen(exeName) + 1, "dbglistprocesses:basicName");
strncpy_s(basicName(), sizeof(char) * strlen(exeName) + 1, exeName, _TRUNCATE);
char* dotInName = strrchr(basicName(), '.');
dotInName[0] = '\0';
size_t basicNameLen = strlen(basicName());
peNameInCmd = strstr(cmdline, basicName());
//check for basic name is used in path to exe
for(char* basicNameInCmdTmp = peNameInCmd; basicNameInCmdTmp;)
{
basicNameInCmdTmp = strstr(basicNameInCmdTmp + basicNameLen, basicName());
if(!basicNameInCmdTmp)
break;
char* nextSlash = strchr(basicNameInCmdTmp, '\\') ? strchr(basicNameInCmdTmp, '\\') :
strchr(basicNameInCmdTmp, '/') ? strchr(basicNameInCmdTmp, '/') : NULL;
if(nextSlash && posEnum.posEnum == NO_QOUTES) //if there NO_QOUTES, then the path to PE in cmdline can't contain spaces
{
if(strchr(basicNameInCmdTmp, ' ') < nextSlash) //slash is in arguments
{
peNameInCmd = basicNameInCmdTmp;
break;
}
else
continue;
}
else if(nextSlash && posEnum.posEnum == QOUTES_AROUND_EXE)
{
if((cmdline + posEnum.secondPos) < nextSlash) //slash is in arguments
{
peNameInCmd = basicNameInCmdTmp;
break;
}
else
continue;
}
else
{
peNameInCmd = basicNameInCmdTmp;
break;
}
}
if(peNameInCmd)
cmdLineExeSize = (size_t)(((LPBYTE)peNameInCmd - (LPBYTE)cmdline) + basicNameLen);
}
}
switch(posEnum.posEnum)
{
case NO_CLOSE_QUOTE_FOUND:
commandList->push_back(cmdline + cmdLineExeSize + 1);
break;
case NO_QOUTES:
if(!posEnum.secondPos)
commandList->push_back(cmdline + cmdLineExeSize);
else
commandList->push_back(cmdline + (cmdLineExeSize > posEnum.secondPos + 1 ? cmdLineExeSize : posEnum.secondPos + 1));
break;
case QOUTES_AROUND_EXE:
commandList->push_back(cmdline + cmdLineExeSize + 2);
break;
case QOUTES_AT_BEGIN_AND_END:
cmdline[strlen(cmdline) - 1] = '\0';
commandList->push_back(cmdline + cmdLineExeSize + 1);
break;
}
if(!commandList->empty())
commandList->back() = StringUtils::Trim(commandList->back());
efree(cmdline);
}
}
while(Process32Next(hProcessSnap, &pe32));
return true;
}
static bool getcommandlineaddr(duint* addr, cmdline_error_t* cmd_line_error, HANDLE hProcess = NULL)
{
duint pprocess_parameters;
cmd_line_error->addr = (duint)GetPEBLocation(hProcess ? hProcess : fdProcessInfo->hProcess);
if(cmd_line_error->addr == 0)
{
cmd_line_error->type = CMDL_ERR_GET_PEB;
return false;
}
if(hProcess)
{
duint NumberOfBytesRead;
if(!MemoryReadSafe(hProcess, (LPVOID)((cmd_line_error->addr) + offsetof(PEB, ProcessParameters)),
&pprocess_parameters, sizeof(duint), &NumberOfBytesRead))
{
cmd_line_error->type = CMDL_ERR_READ_PROCPARM_PTR;
return false;
}
*addr = (pprocess_parameters) + offsetof(RTL_USER_PROCESS_PARAMETERS, CommandLine);
}
else
{
//cast-trick to calculate the address of the remote peb field ProcessParameters
cmd_line_error->addr = (duint) & (((PPEB)cmd_line_error->addr)->ProcessParameters);
if(!MemRead(cmd_line_error->addr, &pprocess_parameters, sizeof(pprocess_parameters)))
{
cmd_line_error->type = CMDL_ERR_READ_PEBBASE;
return false;
}
*addr = (duint) & (((RTL_USER_PROCESS_PARAMETERS*)pprocess_parameters)->CommandLine);
}
return true;
}
static bool patchcmdline(duint getcommandline, duint new_command_line, cmdline_error_t* cmd_line_error)
{
duint command_line_stored = 0;
unsigned char data[100];
cmd_line_error->addr = getcommandline;
if(!MemRead(cmd_line_error->addr, & data, sizeof(data)))
{
cmd_line_error->type = CMDL_ERR_READ_GETCOMMANDLINEBASE;
return false;
}
#ifdef _WIN64
/*
00007FFC5B91E3C8 | 48 8B 05 19 1D 0E 00 | mov rax,qword ptr ds:[7FFC5BA000E8]
00007FFC5B91E3CF | C3 | ret |
This is a relative offset then to get the symbol: next instruction of getmodulehandle (+7 bytes) + offset to symbol
(the last 4 bytes of the instruction)
*/
if(data[0] != 0x48 || data[1] != 0x8B || data[2] != 0x05 || data[7] != 0xC3)
{
cmd_line_error->type = CMDL_ERR_CHECK_GETCOMMANDLINESTORED;
return false;
}
DWORD offset = * ((DWORD*) & data[3]);
command_line_stored = getcommandline + 7 + offset;
#else //x86
/*
750FE9CA | A1 CC DB 1A 75 | mov eax,dword ptr ds:[751ADBCC] |
750FE9CF | C3 | ret |
*/
if(data[0] != 0xA1 || data[5] != 0xC3)
{
cmd_line_error->type = CMDL_ERR_CHECK_GETCOMMANDLINESTORED;
return false;
}
command_line_stored = * ((duint*) & data[1]);
#endif
//update the pointer in the debuggee
if(!MemWrite(command_line_stored, &new_command_line, sizeof(new_command_line)))
{
cmd_line_error->addr = command_line_stored;
cmd_line_error->type = CMDL_ERR_WRITE_GETCOMMANDLINESTORED;
return false;
}
return true;
}
static bool fixgetcommandlinesbase(duint new_command_line_unicode, duint new_command_line_ascii, cmdline_error_t* cmd_line_error)
{
duint getcommandline;
if(!valfromstring("kernelBase:GetCommandLineA", &getcommandline))
{
if(!valfromstring("kernel32:GetCommandLineA", &getcommandline))
{
cmd_line_error->type = CMDL_ERR_GET_GETCOMMANDLINE;
return false;
}
}
if(!patchcmdline(getcommandline, new_command_line_ascii, cmd_line_error))
return false;
if(!valfromstring("kernelbase:GetCommandLineW", &getcommandline))
{
if(!valfromstring("kernel32:GetCommandLineW", &getcommandline))
{
cmd_line_error->type = CMDL_ERR_GET_GETCOMMANDLINE;
return false;
}
}
if(!patchcmdline(getcommandline, new_command_line_unicode, cmd_line_error))
return false;
return true;
}
static std::vector<char> Utf16ToAnsi(const wchar_t* wstr)
{
std::vector<char> buffer;
auto requiredSize = WideCharToMultiByte(CP_ACP, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
if(requiredSize > 0)
{
buffer.resize(requiredSize);
WideCharToMultiByte(CP_ACP, 0, wstr, -1, &buffer[0], requiredSize, nullptr, nullptr);
}
return buffer;
}
bool dbgsetcmdline(const char* cmd_line, cmdline_error_t* cmd_line_error)
{
// Make sure cmd_line_error is a valid pointer
cmdline_error_t cmd_line_error_aux;
if(cmd_line_error == NULL)
cmd_line_error = &cmd_line_error_aux;
// Get the command line address
if(!getcommandlineaddr(&cmd_line_error->addr, cmd_line_error))
return false;
auto command_line_addr = cmd_line_error->addr;
// Convert the string to UTF-16
auto command_linewstr = StringUtils::Utf8ToUtf16(cmd_line);
if(command_linewstr.length() >= 32766) //32766 is maximum character count for a null-terminated UNICODE_STRING
command_linewstr.resize(32766);
// Convert the UTF-16 string to ANSI
auto command_linestr = Utf16ToAnsi(command_linewstr.c_str());
// Fill the UNICODE_STRING to be set in the debuggee
UNICODE_STRING new_command_line;
new_command_line.Length = USHORT(command_linewstr.length() * sizeof(WCHAR)); //max value: 32766 * 2 = 65532
new_command_line.MaximumLength = new_command_line.Length + sizeof(WCHAR); //max value: 65532 + 2 = 65534
new_command_line.Buffer = PWSTR(command_linewstr.c_str()); //allow cast from const because the UNICODE_STRING will not be used locally
// Allocate remote memory for both the UNICODE_STRING.Buffer and the (null terminated) ANSI buffer
duint mem = MemAllocRemote(0, new_command_line.MaximumLength + command_linestr.size());
if(!mem)
{
cmd_line_error->type = CMDL_ERR_ALLOC_UNICODEANSI_COMMANDLINE;
return false;
}
// Write the UNICODE_STRING.Buffer to the debuggee (UNICODE_STRING.Length is used because the remote memory is zeroed)
if(!MemWrite(mem, new_command_line.Buffer, new_command_line.Length))
{
cmd_line_error->addr = mem;
cmd_line_error->type = CMDL_ERR_WRITE_UNICODE_COMMANDLINE;
return false;
}
// Write the (null-terminated) ANSI buffer to the debuggee
if(!MemWrite(mem + new_command_line.MaximumLength, command_linestr.data(), command_linestr.size()))
{
cmd_line_error->addr = mem + new_command_line.MaximumLength;
cmd_line_error->type = CMDL_ERR_WRITE_ANSI_COMMANDLINE;
return false;
}
// Change the pointers to the command line
if(!fixgetcommandlinesbase(mem, mem + new_command_line.MaximumLength, cmd_line_error))
return false;
// Put the remote buffer address in the UNICODE_STRING and write it to the PEB
new_command_line.Buffer = PWSTR(mem);
if(!MemWrite(command_line_addr, &new_command_line, sizeof(new_command_line)))
{
cmd_line_error->addr = command_line_addr;
cmd_line_error->type = CMDL_ERR_WRITE_PEBUNICODE_COMMANDLINE;
return false;
}
// Copy command line
copyCommandLine(cmd_line);
return true;
}
bool dbggetcmdline(char** cmd_line, cmdline_error_t* cmd_line_error, HANDLE hProcess /* = NULL */)
{
UNICODE_STRING CommandLine;
Memory<wchar_t*> wstr_cmd;
cmdline_error_t cmd_line_error_aux;
if(!cmd_line_error)
cmd_line_error = &cmd_line_error_aux;
if(!getcommandlineaddr(&cmd_line_error->addr, cmd_line_error, hProcess))
return false;
if(hProcess)
{
duint NumberOfBytesRead;
if(!MemoryReadSafe(hProcess, (LPVOID)cmd_line_error->addr, &CommandLine, sizeof(UNICODE_STRING), &NumberOfBytesRead))
{
cmd_line_error->type = CMDL_ERR_READ_GETCOMMANDLINEBASE;
return false;
}
wstr_cmd.realloc(CommandLine.Length + sizeof(wchar_t));
cmd_line_error->addr = (duint)CommandLine.Buffer;
if(!MemoryReadSafe(hProcess, (LPVOID)cmd_line_error->addr, wstr_cmd(), CommandLine.Length, &NumberOfBytesRead))
{
cmd_line_error->type = CMDL_ERR_GET_GETCOMMANDLINE;
return false;
}
}
else
{
if(!MemRead(cmd_line_error->addr, &CommandLine, sizeof(CommandLine)))
{
cmd_line_error->type = CMDL_ERR_READ_PROCPARM_PTR;
return false;
}
wstr_cmd.realloc(CommandLine.Length + sizeof(wchar_t));
cmd_line_error->addr = (duint)CommandLine.Buffer;
if(!MemRead(cmd_line_error->addr, wstr_cmd(), CommandLine.Length))
{
cmd_line_error->type = CMDL_ERR_READ_PROCPARM_CMDLINE;
return false;
}
}
SIZE_T wstr_cmd_size = wcslen(wstr_cmd()) + 1;
SIZE_T cmd_line_size = wstr_cmd_size * 2;
*cmd_line = (char*)emalloc(cmd_line_size, "dbggetcmdline:cmd_line");
if(cmd_line_size <= 2)
{
*cmd_line[0] = '\0';
return true;
}
//Convert TO UTF-8
if(!WideCharToMultiByte(CP_UTF8, 0, wstr_cmd(), (int)wstr_cmd_size, *cmd_line, (int)cmd_line_size, NULL, NULL))
{
efree(*cmd_line);
*cmd_line = nullptr;
cmd_line_error->type = CMDL_ERR_CONVERTUNICODE;
return false;
}
return true;
}
static DWORD WINAPI scriptThread(void* data)
{
CBPLUGINSCRIPT cbScript = (CBPLUGINSCRIPT)data;
cbScript();
return 0;
}
void dbgstartscriptthread(CBPLUGINSCRIPT cbScript)
{
CloseHandle(CreateThread(0, 0, scriptThread, (LPVOID)cbScript, 0, 0));
}
static void* InitDLLDebugW(const wchar_t* szFileName, const wchar_t* szCommandLine, const wchar_t* szCurrentFolder)
{
WString loaderFilename = StringUtils::sprintf(L"\\DLLLoader" ArchValue(L"32", L"64") L"_%04X.exe", GetTickCount() & 0xFFFF);
WString debuggeeLoaderPath = szFileName;
{
auto backslashIdx = debuggeeLoaderPath.rfind('\\');
if(backslashIdx != WString::npos)
debuggeeLoaderPath.resize(backslashIdx);
}
debuggeeLoaderPath += loaderFilename;
WString loaderPath = StringUtils::Utf8ToUtf16(szDllLoaderPath);
if(!CopyFileW(loaderPath.c_str(), debuggeeLoaderPath.c_str(), FALSE))
{
debuggeeLoaderPath = StringUtils::Utf8ToUtf16(szProgramDir);
debuggeeLoaderPath += loaderFilename;
if(!CopyFileW(loaderPath.c_str(), debuggeeLoaderPath.c_str(), FALSE))
{
dprintf(QT_TRANSLATE_NOOP("DBG", "Error debugging DLL (failed to copy loader)\n"));
return nullptr;
}
}
PPROCESS_INFORMATION ReturnValue = (PPROCESS_INFORMATION)InitDebugW(debuggeeLoaderPath.c_str(), szCommandLine, szCurrentFolder);
WString mappingName = StringUtils::sprintf(L"Local\\szLibraryName%X", ReturnValue->dwProcessId);
const auto mappingSize = 512;
DebugDLLFileMapping = CreateFileMappingW(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, mappingSize * sizeof(wchar_t), mappingName.c_str());
if(DebugDLLFileMapping)
{
wchar_t* szLibraryPathMapping = (wchar_t*)MapViewOfFile(DebugDLLFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, mappingSize * sizeof(wchar_t));
if(szLibraryPathMapping)
{
wcscpy_s(szLibraryPathMapping, mappingSize, szFileName);
UnmapViewOfFile(szLibraryPathMapping);
}
}
return ReturnValue;
}
static void debugLoopFunction(void* lpParameter, bool attach)
{
//initialize variables
bIsAttached = attach;
dbgsetskipexceptions(false);
bFreezeStack = false;
//prepare attach/createprocess
DWORD pid;
INIT_STRUCT* init;
if(attach)
{
gInitExe = StringUtils::Utf8ToUtf16(szDebuggeePath);
pid = DWORD(lpParameter);
static PROCESS_INFORMATION pi_attached;
memset(&pi_attached, 0, sizeof(pi_attached));
fdProcessInfo = &pi_attached;
}
else
{
init = (INIT_STRUCT*)lpParameter;
gInitExe = StringUtils::Utf8ToUtf16(init->exe);
strcpy_s(szDebuggeePath, init->exe);
}
pDebuggedEntry = GetPE32DataW(gInitExe.c_str(), 0, UE_OEP);
bEntryIsInMzHeader = pDebuggedEntry == 0 || pDebuggedEntry == 1;
bFileIsDll = IsFileDLLW(StringUtils::Utf8ToUtf16(szDebuggeePath).c_str(), 0);
if(bFileIsDll && !FileExists(szDllLoaderPath))
{
dprintf(QT_TRANSLATE_NOOP("DBG", "Error debugging DLL (loaddll.exe not found)\n"));
return;
}
DbSetPath(nullptr, szDebuggeePath);
if(!attach)
{
// Load command line if it exists in DB
DbLoad(DbLoadSaveType::CommandLine);
if(!isCmdLineEmpty())
{
char* commandLineArguments = NULL;
commandLineArguments = getCommandLineArgs();
if(commandLineArguments && *commandLineArguments)
init->commandline = commandLineArguments;
}
gInitCmd = StringUtils::Utf8ToUtf16(init->commandline);
gInitDir = StringUtils::Utf8ToUtf16(init->currentfolder);
//start the process
if(bFileIsDll)
fdProcessInfo = (PROCESS_INFORMATION*)InitDLLDebugW(gInitExe.c_str(), gInitCmd.c_str(), gInitDir.c_str());
else
fdProcessInfo = (PROCESS_INFORMATION*)InitDebugW(gInitExe.c_str(), gInitCmd.c_str(), gInitDir.c_str());
if(!fdProcessInfo)
{
auto lastError = GetLastError();
auto isElevated = BridgeIsProcessElevated();
String error = stringformatinline(StringUtils::sprintf("{winerror@%d}", lastError));
if(lastError == ERROR_ELEVATION_REQUIRED && !isElevated)
{
auto msg = StringUtils::Utf8ToUtf16(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "The executable you are trying to debug requires elevation. Restart as admin?")));
auto title = StringUtils::Utf8ToUtf16(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Elevation")));
auto answer = MessageBoxW(GuiGetWindowHandle(), msg.c_str(), title.c_str(), MB_ICONQUESTION | MB_YESNO);
wchar_t wszProgramPath[MAX_PATH] = L"";
if(answer == IDYES && dbgrestartadmin())
{
fdProcessInfo = &g_pi;
GuiCloseApplication();
return;
}
}
else if(isElevated)
{
//This is most likely an application with uiAccess="true"
//https://github.com/x64dbg/x64dbg/issues/1501
//https://blogs.techsmith.com/inside-techsmith/devcorner-debug-uiaccess
error += ", uiAccess=\"true\"";
}
fdProcessInfo = &g_pi;
dprintf(QT_TRANSLATE_NOOP("DBG", "Error starting process (CreateProcess, %s)!\n"), error.c_str());
return;
}
//check for WOW64
BOOL wow64 = false, mewow64 = false;
if(!IsWow64Process(fdProcessInfo->hProcess, &wow64) || !IsWow64Process(GetCurrentProcess(), &mewow64))
{
dputs(QT_TRANSLATE_NOOP("DBG", "IsWow64Process failed!"));
StopDebug();
return;
}
if((mewow64 && !wow64) || (!mewow64 && wow64))
{
#ifdef _WIN64
dputs(QT_TRANSLATE_NOOP("DBG", "Use x32dbg to debug this process!"));
#else
dputs(QT_TRANSLATE_NOOP("DBG", "Use x64dbg to debug this process!"));
#endif // _WIN64
return;
}
//set script variables
varset("$pid", fdProcessInfo->dwProcessId, true);
if(!OpenProcessToken(fdProcessInfo->hProcess, TOKEN_ALL_ACCESS, &hProcessToken))
hProcessToken = 0;
}
else //attach
{
gInitCmd.clear();
gInitDir.clear();
}
//set custom handlers
SetCustomHandler(UE_CH_CREATEPROCESS, (void*)cbCreateProcess);
SetCustomHandler(UE_CH_EXITPROCESS, (void*)cbExitProcess);
SetCustomHandler(UE_CH_CREATETHREAD, (void*)cbCreateThread);
SetCustomHandler(UE_CH_EXITTHREAD, (void*)cbExitThread);
SetCustomHandler(UE_CH_SYSTEMBREAKPOINT, (void*)cbSystemBreakpoint);
SetCustomHandler(UE_CH_LOADDLL, (void*)cbLoadDll);
SetCustomHandler(UE_CH_UNLOADDLL, (void*)cbUnloadDll);
SetCustomHandler(UE_CH_OUTPUTDEBUGSTRING, (void*)cbOutputDebugString);
SetCustomHandler(UE_CH_UNHANDLEDEXCEPTION, (void*)cbException);
SetCustomHandler(UE_CH_DEBUGEVENT, (void*)cbDebugEvent);
//inform GUI we started without problems
GuiSetDebugState(initialized);
GuiFocusView(GUI_DISASSEMBLY);
GuiAddRecentFile(szDebuggeePath);
//set GUI title
strcpy_s(szBaseFileName, szDebuggeePath);
int len = (int)strlen(szBaseFileName);
while(szBaseFileName[len] != '\\' && len)
len--;
if(len)
strcpy_s(szBaseFileName, szBaseFileName + len + 1);
GuiUpdateWindowTitle(szBaseFileName);
//call plugin callback
PLUG_CB_INITDEBUG initInfo;
initInfo.szFileName = szDebuggeePath;
plugincbcall(CB_INITDEBUG, &initInfo);
//call plugin callback (attach)
if(attach)
{
PLUG_CB_ATTACH attachInfo;
attachInfo.dwProcessId = (DWORD)pid;
plugincbcall(CB_ATTACH, &attachInfo);
}
// Init program database
DbLoad(DbLoadSaveType::DebugData);
//run debug loop (returns when process debugging is stopped)
if(attach)
{
if(AttachDebugger(pid, true, fdProcessInfo, (void*)cbAttachDebugger) == false)
{
String error = stringformatinline(StringUtils::sprintf("{winerror@%d}", GetLastError()));
dprintf(QT_TRANSLATE_NOOP("DBG", "Attach to process failed! GetLastError() = %s\n"), error.c_str());
}
}
else
{
//close the process and thread handles we got back from CreateProcess, to prevent duplicating the ones we will receive in cbCreateProcess
CloseHandle(fdProcessInfo->hProcess);
CloseHandle(fdProcessInfo->hThread);
fdProcessInfo->hProcess = fdProcessInfo->hThread = nullptr;
DebugLoop();
}
//fixes data loss when attach failed (https://github.com/x64dbg/x64dbg/issues/1899)
DbClose();
//call plugin callback
PLUG_CB_STOPDEBUG stopInfo;
stopInfo.reserved = 0;
plugincbcall(CB_STOPDEBUG, &stopInfo);
//message the user/do final stuff
RemoveAllBreakPoints(UE_OPTION_REMOVEALL); //remove all breakpoints
{
EXCLUSIVE_ACQUIRE(LockDllBreakpoints);
dllBreakpoints.clear(); //RemoveAllBreakPoints doesn't remove librarian breakpoints
}
//cleanup
dbgcleartracestate();
dbgClearRtuBreakpoints();
ModClear();
ThreadClear();
WatchClear();
TraceRecord.clear();
_dbg_dbgenableRunTrace(false, nullptr); //Stop run trace
GuiSetDebugState(stopped);
GuiUpdateAllViews();
dputs(QT_TRANSLATE_NOOP("DBG", "Debugging stopped!"));
fdProcessInfo->hProcess = fdProcessInfo->hThread = nullptr;
varset("$hp", (duint)0, true);
varset("$pid", (duint)0, true);
if(hProcessToken)
{
CloseHandle(hProcessToken);
hProcessToken = 0;
}
if(DebugDLLFileMapping)
{
CloseHandle(DebugDLLFileMapping);
DebugDLLFileMapping = 0;
}
pDebuggedEntry = 0;
pDebuggedBase = 0;
pCreateProcessBase = 0;
isDetachedByUser = false;
hActiveThread = nullptr;
if(!gDllLoader.empty()) //Delete the DLL loader (#1496)
{
DeleteFileW(gDllLoader.c_str());
gDllLoader.clear();
}
}
void dbgsetdebuggeeinitscript(const char* fileName)
{
if(fileName)
strcpy_s(szDebuggeeInitializationScript, fileName);
else
szDebuggeeInitializationScript[0] = 0;
}
const char* dbggetdebuggeeinitscript()
{
return szDebuggeeInitializationScript;
}
void dbgsetforeground()
{
if(!bNoForegroundWindow)
SetForegroundWindow(GuiGetWindowHandle());
}
DWORD WINAPI threadDebugLoop(void* lpParameter)
{
debugLoopFunction(lpParameter, false);
return 0;
}
DWORD WINAPI threadAttachLoop(void* lpParameter)
{
debugLoopFunction(lpParameter, true);
return 0;
}
bool dbgrestartadmin()
{
wchar_t wszProgramPath[MAX_PATH] = L"";
if(GetModuleFileNameW(GetModuleHandleW(nullptr), wszProgramPath, _countof(wszProgramPath)))
{
std::wstring file = wszProgramPath;
auto last = wcsrchr(wszProgramPath, L'\\');
if(last)
*last = L'\0';
//TODO: possibly escape characters in gInitCmd
std::wstring params = L"\"" + gInitExe + L"\" \"" + gInitCmd + L"\" \"" + gInitDir + L"\"";
auto result = ShellExecuteW(NULL, L"runas", file.c_str(), params.c_str(), wszProgramPath, SW_SHOWDEFAULT);
return int(result) > 32 && GetLastError() == ERROR_SUCCESS;
}
return false;
}
void StepIntoWow64(LPVOID traceCallBack)
{
#ifndef _WIN64
//NOTE: this workaround has the potential of detecting x64dbg while tracing, disable it if that happens
if(!bNoWow64SingleStepWorkaround)
{
unsigned char data[7];
auto cip = GetContextDataEx(hActiveThread, UE_CIP);
if(MemRead(cip, data, sizeof(data)) && data[0] == 0xEA && data[5] == 0x33 && data[6] == 0x00) //ljmp 33,XXXXXXXX
{
auto csp = GetContextDataEx(hActiveThread, UE_CSP);
duint ret;
if(MemRead(csp, &ret, sizeof(ret)))
{
SetBPX(ret, UE_SINGLESHOOT, traceCallBack);
return;
}
}
}
#endif //_WIN64
if(bPausedOnException && exceptionDispatchAddr && !IsBPXEnabled(exceptionDispatchAddr))
{
SetBPX(exceptionDispatchAddr, UE_SINGLESHOOT, traceCallBack);
}
else
{
StepInto(traceCallBack);
}
}
void StepOverWrapper(LPVOID traceCallBack)
{
if(bPausedOnException && exceptionDispatchAddr && !IsBPXEnabled(exceptionDispatchAddr))
{
SetBPX(exceptionDispatchAddr, UE_SINGLESHOOT, traceCallBack);
}
else
{
StepOver(traceCallBack);
}
}
bool dbgisdepenabled()
{
auto depEnabled = false;
#ifndef _WIN64
typedef BOOL(WINAPI * GETPROCESSDEPPOLICY)(
_In_ HANDLE /*hProcess*/,
_Out_ LPDWORD /*lpFlags*/,
_Out_ PBOOL /*lpPermanent*/
);
static auto GPDP = GETPROCESSDEPPOLICY(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetProcessDEPPolicy"));
if(GPDP)
{
//If you use fdProcessInfo->hProcess GetProcessDEPPolicy will put garbage in bPermanent.
auto hProcess = TitanOpenProcess(PROCESS_QUERY_INFORMATION, false, fdProcessInfo->dwProcessId);
DWORD lpFlags;
BOOL bPermanent;
if(GPDP(hProcess, &lpFlags, &bPermanent))
depEnabled = lpFlags != 0;
CloseHandle(hProcess);
}
#else
depEnabled = true;
#endif //_WIN64
return depEnabled;
}