diff --git a/src/bridge/bridgemain.cpp b/src/bridge/bridgemain.cpp index 15a1a83d..9ffc3825 100644 --- a/src/bridge/bridgemain.cpp +++ b/src/bridge/bridgemain.cpp @@ -1648,6 +1648,18 @@ BRIDGE_IMPEXP void GuiReferenceAddCommand(const char* title, const char* command _gui_sendmessage(GUI_REF_ADDCOMMAND, (void*)title, (void*)command); } +BRIDGE_IMPEXP void GuiUpdateTraceBrowser() +{ + CHECK_GUI_UPDATE_DISABLED + _gui_sendmessage(GUI_UPDATE_TRACE_BROWSER, nullptr, nullptr); +} + +BRIDGE_IMPEXP void GuiOpenTraceFile(const char* fileName) +{ + CHECK_GUI_UPDATE_DISABLED + _gui_sendmessage(GUI_OPEN_TRACE_FILE, (void*)fileName, nullptr); +} + BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { hInst = hinstDLL; diff --git a/src/bridge/bridgemain.h b/src/bridge/bridgemain.h index 7cc8c5e9..da654334 100644 --- a/src/bridge/bridgemain.h +++ b/src/bridge/bridgemain.h @@ -1130,7 +1130,9 @@ typedef enum GUI_REF_SEARCH_GETROWCOUNT, // param1=unused, param2=unused GUI_REF_SEARCH_GETCELLCONTENT, // param1=int row, param2=int col GUI_MENU_REMOVE, // param1=int hEntryMenu, param2=unused - GUI_REF_ADDCOMMAND // param1=const char* title, param2=const char* command + GUI_REF_ADDCOMMAND, // param1=const char* title, param2=const char* command + GUI_OPEN_TRACE_FILE, // param1=const char* file name,param2=unused + GUI_UPDATE_TRACE_BROWSER // param1=unused, param2=unused } GUIMSG; //GUI Typedefs @@ -1305,6 +1307,8 @@ BRIDGE_IMPEXP void GuiUpdateTypeWidget(); BRIDGE_IMPEXP void GuiCloseApplication(); BRIDGE_IMPEXP void GuiFlushLog(); BRIDGE_IMPEXP void GuiReferenceAddCommand(const char* title, const char* command); +BRIDGE_IMPEXP void GuiUpdateTraceBrowser(); +BRIDGE_IMPEXP void GuiOpenTraceFile(const char* fileName); #ifdef __cplusplus } diff --git a/src/dbg/TraceRecord.cpp b/src/dbg/TraceRecord.cpp index f2608b56..dece494c 100644 --- a/src/dbg/TraceRecord.cpp +++ b/src/dbg/TraceRecord.cpp @@ -1,9 +1,15 @@ #include "TraceRecord.h" -#include "zydis_wrapper.h" +#include "capstone_wrapper.h" #include "module.h" #include "memory.h" #include "threading.h" +#include "thread.h" +#include "disasm_helper.h" +#include "disasm_fast.h" #include "plugin_loader.h" +#include "value.h" + +#define MAX_INSTRUCTIONS_TRACED_FULL_REG_DUMP 512 TraceRecordManager TraceRecord; @@ -193,6 +199,230 @@ void TraceRecordManager::TraceExecute(duint address, duint size) } } + +static void HandleCapstoneOperand(const Capstone & cp, int opindex, DISASM_ARGTYPE* argType, duint* value, unsigned char* memoryContent, unsigned char* memorySize) +{ + *value = cp.ResolveOpValue(opindex, [&cp](x86_reg reg) + { + auto regName = cp.RegName(reg); + return regName ? getregister(nullptr, regName) : 0; //TODO: temporary needs enums + caching + }); + const auto & op = cp[opindex]; + switch(op.type) + { + case X86_OP_REG: + *argType = arg_normal; + break; + + case X86_OP_IMM: + *argType = arg_normal; + break; + + case X86_OP_MEM: + { + *argType = arg_memory; + const x86_op_mem & mem = op.mem; +#ifdef _WIN64 + if(mem.segment == X86_REG_GS) +#else //x86 + if(mem.segment == X86_REG_FS) +#endif + { + *value += ThreadGetLocalBase(ThreadGetId(hActiveThread)); + } + *memorySize = op.size; + if(DbgMemIsValidReadPtr(*value)) + { + MemRead(*value, memoryContent, max(op.size, sizeof(duint))); + } + } + break; + + default: + __debugbreak(); + } +} + +void TraceRecordManager::TraceExecuteRecord(const Capstone & newInstruction) +{ + if(!isRunTraceEnabled()) + return; + unsigned char WriteBuffer[3072]; + unsigned char* WriteBufferPtr = WriteBuffer; + //Get current data + REGDUMPWORD newContext; + //DISASM_INSTR newInstruction; + DWORD newThreadId; + duint newMemory[32]; + duint newMemoryAddress[32]; + duint oldMemory[32]; + unsigned char newMemoryArrayCount = 0; + DbgGetRegDump(&newContext.registers); + newThreadId = ThreadGetId(hActiveThread); + // Don't try to resolve memory values for lea and nop instructions + if(!(newInstruction.IsNop() || newInstruction.GetId() == X86_INS_LEA)) + { + DISASM_ARGTYPE argType; + duint value; + unsigned char memoryContent[128]; + unsigned char memorySize; + for(int i = 0; i < newInstruction.OpCount(); i++) + { + memset(memoryContent, 0, sizeof(memoryContent)); + HandleCapstoneOperand(newInstruction, i, &argType, &value, memoryContent, &memorySize); + // TODO: Implicit memory access by push and pop instructions + // TODO: Support memory value of ??? for invalid memory access + if(argType == arg_memory) + { + if(memorySize <= sizeof(duint)) + { + memcpy(&newMemory[newMemoryArrayCount], memoryContent, sizeof(duint)); + newMemoryAddress[newMemoryArrayCount] = value; + newMemoryArrayCount++; + } + else + for(unsigned char index = 0; index < memorySize / sizeof(duint) + ((memorySize % sizeof(duint)) > 0 ? 1 : 0); index++) + { + memcpy(&newMemory[newMemoryArrayCount], memoryContent + sizeof(duint) * index, sizeof(duint)); + newMemoryAddress[newMemoryArrayCount] = value + sizeof(duint) * index; + newMemoryArrayCount++; + } + } + } + if(newInstruction.GetId() == X86_INS_PUSH || newInstruction.GetId() == X86_INS_PUSHF || newInstruction.GetId() == X86_INS_PUSHFD + || newInstruction.GetId() == X86_INS_PUSHFQ || newInstruction.GetId() == X86_INS_CALL //TODO: far call accesses 2 stack entries + ) + { + MemRead(newContext.registers.regcontext.csp - sizeof(duint), &newMemory[newMemoryArrayCount], sizeof(duint)); + newMemoryAddress[newMemoryArrayCount] = newContext.registers.regcontext.csp - sizeof(duint); + newMemoryArrayCount++; + } + else if(newInstruction.GetId() == X86_INS_POP || newInstruction.GetId() == X86_INS_POPF || newInstruction.GetId() == X86_INS_POPFD + || newInstruction.GetId() == X86_INS_POPFQ || newInstruction.GetId() == X86_INS_RET) + { + MemRead(newContext.registers.regcontext.csp, &newMemory[newMemoryArrayCount], sizeof(duint)); + newMemoryAddress[newMemoryArrayCount] = newContext.registers.regcontext.csp; + newMemoryArrayCount++; + } + //TODO: PUSHAD/POPAD + assert(newMemoryArrayCount < 32); + } + if(rtPrevInstAvailable) + { + for(unsigned char i = 0; i < rtOldMemoryArrayCount; i++) + { + MemRead(rtOldMemoryAddress[i], oldMemory + i, sizeof(duint)); + } + //Delta compress registers + //Data layout is Structure of Arrays to gather the same type of data in continuous memory to improve RLE compression performance. + //1byte:block type,1byte:reg changed count,1byte:memory accessed count,1byte:flags,4byte/none:threadid,string:opcode,1byte[]:position,ptrbyte[]:regvalue,1byte[]:flags,ptrbyte[]:address,ptrbyte[]:oldmem,ptrbyte[]:newmem + + //Always record state of LAST INSTRUCTION! (NOT current instruction) + unsigned char changed = 0; + for(unsigned char i = 0; i < _countof(rtOldContext.regword); i++) + { + //rtRecordedInstructions - 1 hack: always record full registers dump at first instruction (recorded at 2nd instruction execution time) + //prints ASCII table in run trace at first instruction :) + if(rtOldContext.regword[i] != newContext.regword[i] || rtOldContextChanged[i] || ((rtRecordedInstructions - 1) % MAX_INSTRUCTIONS_TRACED_FULL_REG_DUMP == 0)) + changed++; + } + unsigned char blockFlags = 0; + if(newThreadId != rtOldThreadId || ((rtRecordedInstructions - 1) % MAX_INSTRUCTIONS_TRACED_FULL_REG_DUMP == 0)) + blockFlags = 0x80; + blockFlags |= rtOldOpcodeSize; + + WriteBufferPtr[0] = 0; //1byte: block type + WriteBufferPtr[1] = changed; //1byte: registers changed + WriteBufferPtr[2] = rtOldMemoryArrayCount; //1byte: memory accesses count + WriteBufferPtr[3] = blockFlags; //1byte: flags and opcode size + WriteBufferPtr += 4; + if(newThreadId != rtOldThreadId || rtNeedThreadId || ((rtRecordedInstructions - 1) % MAX_INSTRUCTIONS_TRACED_FULL_REG_DUMP == 0)) + { + memcpy(WriteBufferPtr, &rtOldThreadId, sizeof(rtOldThreadId)); + WriteBufferPtr += sizeof(rtOldThreadId); + rtNeedThreadId = (newThreadId != rtOldThreadId); + } + memcpy(WriteBufferPtr, rtOldOpcode, rtOldOpcodeSize); + WriteBufferPtr += rtOldOpcodeSize; + int lastChangedPosition = -1; //-1 + for(int i = 0; i < _countof(rtOldContext.regword); i++) //1byte: position + { + if(rtOldContext.regword[i] != newContext.regword[i] || rtOldContextChanged[i] || ((rtRecordedInstructions - 1) % MAX_INSTRUCTIONS_TRACED_FULL_REG_DUMP == 0)) + { + WriteBufferPtr[0] = (unsigned char)(i - lastChangedPosition - 1); + WriteBufferPtr++; + lastChangedPosition = i; + } + } + for(unsigned char i = 0; i < _countof(rtOldContext.regword); i++) //ptrbyte: newvalue + { + if(rtOldContext.regword[i] != newContext.regword[i] || rtOldContextChanged[i] || ((rtRecordedInstructions - 1) % MAX_INSTRUCTIONS_TRACED_FULL_REG_DUMP == 0)) + { + memcpy(WriteBufferPtr, &rtOldContext.regword[i], sizeof(duint)); + WriteBufferPtr += sizeof(duint); + } + rtOldContextChanged[i] = (rtOldContext.regword[i] != newContext.regword[i]); + } + for(unsigned char i = 0; i < rtOldMemoryArrayCount; i++) //1byte: flags + { + unsigned char memoryOperandFlags = 0; + if(rtOldMemory[i] == oldMemory[i]) //bit 0: memory is unchanged, no new memory is saved + memoryOperandFlags |= 1; + //proposed flags: is memory valid, is memory zero + WriteBufferPtr[0] = memoryOperandFlags; + WriteBufferPtr += 1; + } + for(unsigned char i = 0; i < rtOldMemoryArrayCount; i++) //ptrbyte: address + { + memcpy(WriteBufferPtr, &rtOldMemoryAddress[i], sizeof(duint)); + WriteBufferPtr += sizeof(duint); + } + for(unsigned char i = 0; i < rtOldMemoryArrayCount; i++) //ptrbyte: old content + { + memcpy(WriteBufferPtr, &rtOldMemory[i], sizeof(duint)); + WriteBufferPtr += sizeof(duint); + } + for(unsigned char i = 0; i < rtOldMemoryArrayCount; i++) //ptrbyte: new content + { + if(rtOldMemory[i] != oldMemory[i]) + { + memcpy(WriteBufferPtr, &oldMemory[i], sizeof(duint)); + WriteBufferPtr += sizeof(duint); + } + } + } + //Switch context buffers + rtOldThreadId = newThreadId; + rtOldContext = newContext; + rtOldMemoryArrayCount = newMemoryArrayCount; + memcpy(rtOldMemory, newMemory, sizeof(newMemory)); + memcpy(rtOldMemoryAddress, newMemoryAddress, sizeof(newMemoryAddress)); + memset(rtOldOpcode, 0, 16); + rtOldOpcodeSize = newInstruction.Size() & 0x0F; + MemRead(newContext.registers.regcontext.cip, rtOldOpcode, rtOldOpcodeSize); + //Write to file + if(rtPrevInstAvailable) + { + if(WriteBufferPtr - WriteBuffer <= sizeof(WriteBuffer)) + { + DWORD written; + WriteFile(rtFile, WriteBuffer, WriteBufferPtr - WriteBuffer, &written, NULL); + if(written < DWORD(WriteBufferPtr - WriteBuffer)) //Disk full? + { + CloseHandle(rtFile); + dprintf(QT_TRANSLATE_NOOP("DBG", "Run trace has stopped unexpectedly because WriteFile() failed. GetLastError()= %X .\r\n"), GetLastError()); + rtEnabled = false; + } + } + else + __debugbreak(); // Buffer overrun? + } + rtPrevInstAvailable = true; + rtRecordedInstructions++; + + dbgtracebrowserneedsupdate(); +} + unsigned int TraceRecordManager::getHitCount(duint address) { SHARED_ACQUIRE(LockTraceRecord); @@ -247,6 +477,101 @@ void TraceRecordManager::increaseInstructionCounter() InterlockedIncrement((volatile long*)&instructionCounter); } +bool TraceRecordManager::enableRunTrace(bool enabled, const char* fileName) +{ + if(!DbgIsDebugging()) + return false; + if(enabled) + { + if(rtEnabled) + enableRunTrace(false, NULL); //re-enable run trace + rtFile = CreateFileW(StringUtils::Utf8ToUtf16(fileName).c_str(), FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if(rtFile != INVALID_HANDLE_VALUE) + { + LARGE_INTEGER size; + if(GetFileSizeEx(rtFile, &size)) + { + if(size.QuadPart != 0) + { + SetFilePointer(rtFile, 0, 0, FILE_END); + } + else //file is empty, write some file header + { + //TRAC, SIZE, JSON header + json_t* root = json_object(); + json_object_set_new(root, "ver", json_integer(1)); + json_object_set_new(root, "arch", json_string(ArchValue("x86", "x64"))); + json_object_set_new(root, "hashAlgorithm", json_string("murmurhash")); + json_object_set_new(root, "hash", json_hex(dbgfunctionsget()->DbGetHash())); + json_object_set_new(root, "compression", json_string("")); + char path[MAX_PATH]; + ModPathFromAddr(dbgdebuggedbase(), path, MAX_PATH); + json_object_set_new(root, "path", json_string(path)); + char* headerinfo; + headerinfo = json_dumps(root, JSON_COMPACT); + size_t headerinfosize = strlen(headerinfo); + LARGE_INTEGER header; + DWORD written; + header.LowPart = MAKEFOURCC('T', 'R', 'A', 'C'); + header.HighPart = headerinfosize; + WriteFile(rtFile, &header, 8, &written, nullptr); + if(written < 8) //read-only? + { + CloseHandle(rtFile); + json_free(headerinfo); + json_decref(root); + dputs(QT_TRANSLATE_NOOP("DBG", "Run trace failed to start because file header cannot be written.")); + return false; + } + WriteFile(rtFile, headerinfo, headerinfosize, &written, nullptr); + json_free(headerinfo); + json_decref(root); + if(written < headerinfosize) //disk-full? + { + CloseHandle(rtFile); + dputs(QT_TRANSLATE_NOOP("DBG", "Run trace failed to start because file header cannot be written.")); + return false; + } + } + } + rtPrevInstAvailable = false; + rtEnabled = true; + rtRecordedInstructions = 0; + rtNeedThreadId = true; + for(size_t i = 0; i < _countof(rtOldContextChanged); i++) + rtOldContextChanged[i] = true; + dprintf(QT_TRANSLATE_NOOP("DBG", "Run trace started. File: %s\r\n"), fileName); + REGDUMP cip; + Capstone cp; + unsigned char instr[MAX_DISASM_BUFFER]; + DbgGetRegDump(&cip); + if(MemRead(cip.regcontext.cip, instr, MAX_DISASM_BUFFER)) + { + cp.DisassembleSafe(cip.regcontext.cip, instr, MAX_DISASM_BUFFER); + TraceExecuteRecord(cp); + } + GuiOpenTraceFile(fileName); + return true; + } + else + { + dprintf(QT_TRANSLATE_NOOP("DBG", "Cannot create run trace file. GetLastError()= %X .\r\n"), GetLastError()); + return false; + } + } + else + { + if(rtEnabled) + { + CloseHandle(rtFile); + rtPrevInstAvailable = false; + rtEnabled = false; + dputs(QT_TRANSLATE_NOOP("DBG", "Run trace stopped.")); + } + return true; + } +} + void TraceRecordManager::saveToDb(JSON root) { EXCLUSIVE_ACQUIRE(LockTraceRecord); @@ -366,25 +691,45 @@ unsigned int TraceRecordManager::getModuleIndex(const String & moduleName) } } +bool TraceRecordManager::isRunTraceEnabled() +{ + return rtEnabled; +} + void _dbg_dbgtraceexecute(duint CIP) { if(TraceRecord.getTraceRecordType(CIP) != TraceRecordManager::TraceRecordType::TraceRecordNone) { - unsigned char buffer[MAX_DISASM_BUFFER]; - if(MemRead(CIP, buffer, MAX_DISASM_BUFFER)) + Capstone instruction; + unsigned char data[MAX_DISASM_BUFFER]; + if(MemRead(CIP, data, MAX_DISASM_BUFFER)) { - TraceRecord.increaseInstructionCounter(); - Zydis instruction; - instruction.Disassemble(CIP, buffer, MAX_DISASM_BUFFER); - TraceRecord.TraceExecute(CIP, instruction.Size()); - } - else - { - // if we reaches here, then the executable had executed an invalid address. Don't trace it. + instruction.DisassembleSafe(CIP, data, MAX_DISASM_BUFFER); + if(TraceRecord.isRunTraceEnabled()) + { + TraceRecord.TraceExecute(CIP, instruction.Size()); + TraceRecord.TraceExecuteRecord(instruction); + } + else + { + TraceRecord.TraceExecute(CIP, instruction.Size()); + } } } else - TraceRecord.increaseInstructionCounter(); + { + if(TraceRecord.isRunTraceEnabled()) + { + Capstone instruction; + unsigned char data[MAX_DISASM_BUFFER]; + if(MemRead(CIP, data, MAX_DISASM_BUFFER)) + { + instruction.DisassembleSafe(CIP, data, MAX_DISASM_BUFFER); + TraceRecord.TraceExecuteRecord(instruction); + } + } + } + TraceRecord.increaseInstructionCounter(); } unsigned int _dbg_dbggetTraceRecordHitCount(duint address) @@ -406,3 +751,14 @@ TRACERECORDTYPE _dbg_dbggetTraceRecordType(duint pageAddress) { return (TRACERECORDTYPE)TraceRecord.getTraceRecordType(pageAddress); } + +// When disabled, file name is not relevant and can be NULL +bool _dbg_dbgenableRunTrace(bool enabled, const char* fileName) +{ + return TraceRecord.enableRunTrace(enabled, fileName); +} + +bool _dbg_dbgisRunTraceEnabled() +{ + return TraceRecord.isRunTraceEnabled(); +} \ No newline at end of file diff --git a/src/dbg/TraceRecord.h b/src/dbg/TraceRecord.h index e3131989..3abc4f3c 100644 --- a/src/dbg/TraceRecord.h +++ b/src/dbg/TraceRecord.h @@ -2,8 +2,11 @@ #define TRACERECORD_H #include "_global.h" #include "_dbgfunctions.h" +#include "debugger.h" #include "jansson/jansson_x64dbg.h" +class Capstone; + class TraceRecordManager { public: @@ -52,11 +55,15 @@ public: void TraceExecute(duint address, duint size); //void TraceAccess(duint address, unsigned char size, TraceRecordByteType accessType); + void TraceExecuteRecord(const Capstone & newInstruction); unsigned int getHitCount(duint address); TraceRecordByteType getByteType(duint address); void increaseInstructionCounter(); + bool isRunTraceEnabled(); + bool enableRunTrace(bool enabled, const char* fileName); + void saveToDb(JSON root); void loadFromDb(JSON root); private: @@ -76,11 +83,34 @@ private: unsigned int moduleIndex; }; + typedef union _REGDUMPWORD + { + REGDUMP registers; + // 172 qwords on x64, 216 dwords on x86. Almost no space left for AVX512 + // strip off 128 bytes of lastError.name member. + duint regword[(sizeof(REGDUMP) - 128) / sizeof(duint)]; + } REGDUMPWORD; + //Key := page base, value := trace record raw data std::unordered_map TraceRecord; std::vector ModuleNames; unsigned int getModuleIndex(const String & moduleName); unsigned int instructionCounter; + + bool rtEnabled; + bool rtPrevInstAvailable; + HANDLE rtFile; + + REGDUMPWORD rtOldContext; + bool rtOldContextChanged[(sizeof(REGDUMP) - 128) / sizeof(duint)]; + DWORD rtOldThreadId; + bool rtNeedThreadId; + duint rtOldMemory[32]; + duint rtOldMemoryAddress[32]; + char rtOldOpcode[16]; + unsigned int rtRecordedInstructions; + unsigned char rtOldOpcodeSize; + unsigned char rtOldMemoryArrayCount; }; extern TraceRecordManager TraceRecord; @@ -91,5 +121,7 @@ unsigned int _dbg_dbggetTraceRecordHitCount(duint address); TRACERECORDBYTETYPE _dbg_dbggetTraceRecordByteType(duint address); bool _dbg_dbgsetTraceRecordType(duint pageAddress, TRACERECORDTYPE type); TRACERECORDTYPE _dbg_dbggetTraceRecordType(duint pageAddress); +bool _dbg_dbgenableRunTrace(bool enabled, const char* fileName); +bool _dbg_dbgisRunTraceEnabled(); #endif // TRACERECORD_H diff --git a/src/dbg/_dbgfunctions.cpp b/src/dbg/_dbgfunctions.cpp index a72c0d86..4b40df8b 100644 --- a/src/dbg/_dbgfunctions.cpp +++ b/src/dbg/_dbgfunctions.cpp @@ -29,6 +29,7 @@ #include "thread.h" #include "comment.h" #include "exception.h" +#include "database.h" static DBGFUNCTIONS _dbgfunctions; @@ -439,4 +440,5 @@ void dbgfunctionsinit() _dbgfunctions.ModRelocationsFromAddr = _modrelocationsfromaddr; _dbgfunctions.ModRelocationAtAddr = (MODRELOCATIONATADDR)ModRelocationAtAddr; _dbgfunctions.ModRelocationsInRange = _modrelocationsinrange; + _dbgfunctions.DbGetHash = DbGetHash; } diff --git a/src/dbg/_dbgfunctions.h b/src/dbg/_dbgfunctions.h index dda3ea58..e698d053 100644 --- a/src/dbg/_dbgfunctions.h +++ b/src/dbg/_dbgfunctions.h @@ -195,6 +195,7 @@ typedef duint(*MEMBPSIZE)(duint addr); typedef bool(*MODRELOCATIONSFROMADDR)(duint addr, ListOf(DBGRELOCATIONINFO) relocations); typedef bool(*MODRELOCATIONATADDR)(duint addr, DBGRELOCATIONINFO* relocation); typedef bool(*MODRELOCATIONSINRANGE)(duint addr, duint size, ListOf(DBGRELOCATIONINFO) relocations); +typedef duint(*DBGETHASH)(); //The list of all the DbgFunctions() return value. //WARNING: This list is append only. Do not insert things in the middle or plugins would break. @@ -268,6 +269,7 @@ typedef struct DBGFUNCTIONS_ MODRELOCATIONSFROMADDR ModRelocationsFromAddr; MODRELOCATIONATADDR ModRelocationAtAddr; MODRELOCATIONSINRANGE ModRelocationsInRange; + DBGETHASH DbGetHash; } DBGFUNCTIONS; #ifdef BUILD_DBG diff --git a/src/dbg/_exports.cpp b/src/dbg/_exports.cpp index f6c80966..0a000748 100644 --- a/src/dbg/_exports.cpp +++ b/src/dbg/_exports.cpp @@ -658,6 +658,7 @@ extern "C" DLL_EXPORT bool _dbg_getregdump(REGDUMP* regdump) Getx87ControlWordFields(& (regdump->x87ControlWordFields), regdump->regcontext.x87fpu.ControlWord); Getx87StatusWordFields(& (regdump->x87StatusWordFields), regdump->regcontext.x87fpu.StatusWord); LASTERROR lastError; + memset(&lastError.name, 0, sizeof(lastError.name)); lastError.code = ThreadGetLastError(ThreadGetId(hActiveThread)); strncpy_s(lastError.name, ErrorCodeToName(lastError.code).c_str(), _TRUNCATE); regdump->lastError = lastError; diff --git a/src/dbg/_scriptapi_module.cpp b/src/dbg/_scriptapi_module.cpp index c78cc15a..3cb30a13 100644 --- a/src/dbg/_scriptapi_module.cpp +++ b/src/dbg/_scriptapi_module.cpp @@ -130,7 +130,7 @@ SCRIPT_EXPORT bool Script::Module::GetMainModuleInfo(ModuleInfo* info) SCRIPT_EXPORT duint Script::Module::GetMainModuleBase() { - return dbggetdebuggedbase(); + return dbgdebuggedbase(); } SCRIPT_EXPORT duint Script::Module::GetMainModuleSize() diff --git a/src/dbg/commands/cmd-tracing.cpp b/src/dbg/commands/cmd-tracing.cpp index 4797ab40..b9b092b0 100644 --- a/src/dbg/commands/cmd-tracing.cpp +++ b/src/dbg/commands/cmd-tracing.cpp @@ -6,6 +6,8 @@ #include "console.h" #include "cmd-debug-control.h" #include "value.h" +#include "variable.h" +#include "TraceRecord.h" extern std::vector> RunToUserCodeBreakpoints; @@ -168,3 +170,15 @@ bool cbDebugTraceSetLogFile(int argc, char* argv[]) auto fileName = argc > 1 ? argv[1] : ""; return dbgsettracelogfile(fileName); } + +bool cbDebugStartRunTrace(int argc, char* argv[]) +{ + if(IsArgumentsLessThan(argc, 2)) + return false; + return _dbg_dbgenableRunTrace(true, argv[1]); +} + +bool cbDebugStopRunTrace(int argc, char* argv[]) +{ + return _dbg_dbgenableRunTrace(false, nullptr); +} \ No newline at end of file diff --git a/src/dbg/commands/cmd-tracing.h b/src/dbg/commands/cmd-tracing.h index af483bf4..b9aa3402 100644 --- a/src/dbg/commands/cmd-tracing.h +++ b/src/dbg/commands/cmd-tracing.h @@ -13,4 +13,6 @@ bool cbDebugRunToUserCode(int argc, char* argv[]); bool cbDebugTraceSetLog(int argc, char* argv[]); bool cbDebugTraceSetCommand(int argc, char* argv[]); bool cbDebugTraceSetSwitchCondition(int argc, char* argv[]); -bool cbDebugTraceSetLogFile(int argc, char* argv[]); \ No newline at end of file +bool cbDebugTraceSetLogFile(int argc, char* argv[]); +bool cbDebugStartRunTrace(int argc, char* argv[]); +bool cbDebugStopRunTrace(int argc, char* argv[]); \ No newline at end of file diff --git a/src/dbg/database.cpp b/src/dbg/database.cpp index eaa6cf66..8327b2c6 100644 --- a/src/dbg/database.cpp +++ b/src/dbg/database.cpp @@ -407,4 +407,9 @@ bool DbCheckHash(duint currentHash) dbhash = currentHash; return true; } +} + +duint DbGetHash() +{ + return dbhash; } \ No newline at end of file diff --git a/src/dbg/database.h b/src/dbg/database.h index 7a4c3b60..33261954 100644 --- a/src/dbg/database.h +++ b/src/dbg/database.h @@ -16,5 +16,6 @@ void DbClose(); void DbClear(bool terminating = false); void DbSetPath(const char* Directory, const char* ModulePath); bool DbCheckHash(duint currentHash); +duint DbGetHash(); #endif // _DATABASE_H \ No newline at end of file diff --git a/src/dbg/debugger.cpp b/src/dbg/debugger.cpp index a7356b5c..373178f6 100644 --- a/src/dbg/debugger.cpp +++ b/src/dbg/debugger.cpp @@ -86,6 +86,7 @@ bool bIgnoreInconsistentBreakpoints = false; bool bNoForegroundWindow = false; bool bVerboseExceptionLogging = true; bool bNoWow64SingleStepWorkaround = false; +bool bTraceBrowserNeedsUpdate = false; duint DbgEvents = 0; duint maxSkipExceptionCount = 10000; HANDLE mProcHandle; @@ -226,6 +227,11 @@ static DWORD WINAPI dumpRefreshThread(void* ptr) break; GuiUpdateDumpView(); GuiUpdateWatchView(); + if(bTraceBrowserNeedsUpdate) + { + bTraceBrowserNeedsUpdate = false; + GuiUpdateTraceBrowser(); + } Sleep(400); } return 0; @@ -373,6 +379,11 @@ duint dbggetdbgevents() return InterlockedExchange((volatile long*)&DbgEvents, 0); } +void dbgtracebrowserneedsupdate() +{ + bTraceBrowserNeedsUpdate = true; +} + static DWORD WINAPI updateCallStackThread(duint ptr) { stackupdatecallstack(ptr); @@ -2506,11 +2517,6 @@ void dbgstartscriptthread(CBPLUGINSCRIPT cbScript) CloseHandle(CreateThread(0, 0, scriptThread, (LPVOID)cbScript, 0, 0)); } -duint dbggetdebuggedbase() -{ - return pDebuggedBase; -} - static void debugLoopFunction(void* lpParameter, bool attach) { //we are running @@ -2712,6 +2718,7 @@ static void debugLoopFunction(void* lpParameter, bool attach) ThreadClear(); WatchClear(); TraceRecord.clear(); + _dbg_dbgenableRunTrace(false, nullptr); //Stop run trace GuiSetDebugState(stopped); GuiUpdateAllViews(); dputs(QT_TRANSLATE_NOOP("DBG", "Debugging stopped!")); diff --git a/src/dbg/debugger.h b/src/dbg/debugger.h index 58803187..390add30 100644 --- a/src/dbg/debugger.h +++ b/src/dbg/debugger.h @@ -66,7 +66,6 @@ bool dbgsetcmdline(const char* cmd_line, cmdline_error_t* cmd_line_error); bool dbggetcmdline(char** cmd_line, cmdline_error_t* cmd_line_error, HANDLE hProcess = NULL); cmdline_qoutes_placement_t getqoutesplacement(const char* cmdline); void dbgstartscriptthread(CBPLUGINSCRIPT cbScript); -duint dbggetdebuggedbase(); duint dbggetdbgevents(); bool dbgsettracecondition(const String & expression, duint maxCount); bool dbgsettracelog(const String & expression, const String & text); @@ -79,6 +78,7 @@ void dbgsetdebuggeeinitscript(const char* fileName); const char* dbggetdebuggeeinitscript(); void dbgsetforeground(); bool dbggetwintext(std::vector* winTextList, const DWORD dwProcessId); +void dbgtracebrowserneedsupdate(); void cbStep(); void cbRtrStep(); diff --git a/src/dbg/expressionfunctions.cpp b/src/dbg/expressionfunctions.cpp index f5e85d63..404fcea4 100644 --- a/src/dbg/expressionfunctions.cpp +++ b/src/dbg/expressionfunctions.cpp @@ -63,7 +63,7 @@ void ExpressionFunctions::Init() RegisterEasy("mod.entry", ModEntryFromAddr); RegisterEasy("mod.system,mod.issystem", modsystem); RegisterEasy("mod.user,mod.isuser", moduser); - RegisterEasy("mod.main,mod.mainbase", dbggetdebuggedbase); + RegisterEasy("mod.main,mod.mainbase", dbgdebuggedbase); RegisterEasy("mod.rva", modrva); RegisterEasy("mod.offset,mod.fileoffset", valvatofileoffset); @@ -104,6 +104,7 @@ void ExpressionFunctions::Init() //Trace record RegisterEasy("tr.enabled", trenabled); RegisterEasy("tr.hitcount,tr.count", trhitcount); + RegisterEasy("tr.runtraceenabled", trisruntraceenabled); //Byte/Word/Dword/Qword/Pointer RegisterEasy("ReadByte,Byte,byte", readbyte); diff --git a/src/dbg/exprfunc.cpp b/src/dbg/exprfunc.cpp index 11bfedcb..46e5e0e9 100644 --- a/src/dbg/exprfunc.cpp +++ b/src/dbg/exprfunc.cpp @@ -9,6 +9,7 @@ #include "disasm_helper.h" #include "function.h" #include "value.h" +#include "TraceRecord.h" #include "exhandlerinfo.h" namespace Exprfunc @@ -261,6 +262,11 @@ namespace Exprfunc return trenabled(addr) ? TraceRecord.getHitCount(addr) : 0; } + duint trisruntraceenabled() + { + return _dbg_dbgisRunTraceEnabled() ? 1 : 0; + } + duint gettickcount() { #ifdef _WIN64 diff --git a/src/dbg/exprfunc.h b/src/dbg/exprfunc.h index 04616305..f2cc85e1 100644 --- a/src/dbg/exprfunc.h +++ b/src/dbg/exprfunc.h @@ -47,6 +47,7 @@ namespace Exprfunc duint trenabled(duint addr); duint trhitcount(duint addr); + duint trisruntraceenabled(); duint gettickcount(); duint readbyte(duint addr); diff --git a/src/dbg/jansson/jansson_x64dbg.h b/src/dbg/jansson/jansson_x64dbg.h index 5e952a3f..952ccdca 100644 --- a/src/dbg/jansson/jansson_x64dbg.h +++ b/src/dbg/jansson/jansson_x64dbg.h @@ -22,4 +22,4 @@ unsigned json_int_t json_hex_value(const json_t* hex) return 0; sscanf_s(hexvalue, "0x%llX", &ret); return ret; -} \ No newline at end of file +} diff --git a/src/dbg/x64dbg.cpp b/src/dbg/x64dbg.cpp index 064ae44e..6fdc5c3c 100644 --- a/src/dbg/x64dbg.cpp +++ b/src/dbg/x64dbg.cpp @@ -221,6 +221,8 @@ static void registercommands() dbgcmdnew("TraceSetCommand,SetTraceCommand", cbDebugTraceSetCommand, true); //Set trace command text + condition dbgcmdnew("TraceSetSwitchCondition,SetTraceSwitchCondition", cbDebugTraceSetSwitchCondition, true); //Set trace switch condition dbgcmdnew("TraceSetLogFile,SetTraceLogFile", cbDebugTraceSetLogFile, true); //Set trace log file + dbgcmdnew("StartRunTrace,opentrace", cbDebugStartRunTrace, true); //start run trace (Ollyscript command "opentrace" "opens run trace window") + dbgcmdnew("StopRunTrace,tc", cbDebugStopRunTrace, true); //stop run trace (and Ollyscript command) //thread control dbgcmdnew("createthread,threadcreate,newthread,threadnew", cbDebugCreatethread, true); //create thread diff --git a/src/gui/Src/BasicView/AbstractTableView.cpp b/src/gui/Src/BasicView/AbstractTableView.cpp index 21110971..f4a42b21 100644 --- a/src/gui/Src/BasicView/AbstractTableView.cpp +++ b/src/gui/Src/BasicView/AbstractTableView.cpp @@ -961,11 +961,11 @@ int AbstractTableView::getColumnPosition(int index) } /** - * @brief Substracts the header heigth from the given y. + * @brief Substracts the header height from the given y. * * @param[in] y y coordinate * - * @return y - getHeaderHeigth(). + * @return y - getHeaderHeight(). */ int AbstractTableView::transY(int y) { @@ -1150,7 +1150,7 @@ int AbstractTableView::getHeaderHeight() return 0; } -int AbstractTableView::getTableHeigth() +int AbstractTableView::getTableHeight() { return this->viewport()->height() - getHeaderHeight(); } diff --git a/src/gui/Src/BasicView/AbstractTableView.h b/src/gui/Src/BasicView/AbstractTableView.h index 9cb17646..848f2377 100644 --- a/src/gui/Src/BasicView/AbstractTableView.h +++ b/src/gui/Src/BasicView/AbstractTableView.h @@ -97,7 +97,7 @@ public: void setColumnOrder(int pos, int index); int getColumnOrder(int index); int getHeaderHeight(); - int getTableHeigth(); + int getTableHeight(); int getGuiState(); int getNbrOfLineToPrint(); void setNbrOfLineToPrint(int parNbrOfLineToPrint); diff --git a/src/gui/Src/BasicView/Disassembly.cpp b/src/gui/Src/BasicView/Disassembly.cpp index f87ba4e1..4b6fd3d5 100644 --- a/src/gui/Src/BasicView/Disassembly.cpp +++ b/src/gui/Src/BasicView/Disassembly.cpp @@ -712,7 +712,7 @@ void Disassembly::mouseMoveEvent(QMouseEvent* event) { //qDebug() << "State = MultiRowsSelectionState"; - if((transY(y) >= 0) && (transY(y) <= this->getTableHeigth())) + if((transY(y) >= 0) && (transY(y) <= this->getTableHeight())) { int wI = getIndexOffsetFromY(transY(y)); diff --git a/src/gui/Src/BasicView/StdTable.cpp b/src/gui/Src/BasicView/StdTable.cpp index e1f0a9c9..20f82fbf 100644 --- a/src/gui/Src/BasicView/StdTable.cpp +++ b/src/gui/Src/BasicView/StdTable.cpp @@ -40,7 +40,7 @@ void StdTable::mouseMoveEvent(QMouseEvent* event) { //qDebug() << "State = MultiRowsSelectionState"; - if(y >= 0 && y <= this->getTableHeigth()) + if(y >= 0 && y <= this->getTableHeight()) { int wRowIndex = getTableOffset() + getIndexOffsetFromY(y); @@ -60,7 +60,7 @@ void StdTable::mouseMoveEvent(QMouseEvent* event) { verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepSub); } - else if(y > getTableHeigth()) + else if(y > getTableHeight()) { verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepAdd); } diff --git a/src/gui/Src/Bridge/Bridge.cpp b/src/gui/Src/Bridge/Bridge.cpp index 7a22cbed..52c25902 100644 --- a/src/gui/Src/Bridge/Bridge.cpp +++ b/src/gui/Src/Bridge/Bridge.cpp @@ -823,6 +823,19 @@ void* Bridge::processMessage(GUIMSG type, void* param1, void* param2) emit referenceAddCommand(QString::fromUtf8((const char*)param1), QString::fromUtf8((const char*)param2)); } break; + + case GUI_OPEN_TRACE_FILE: + { + if(param1 == nullptr) + return nullptr; + emit openTraceFile(QString::fromUtf8((const char*)param1)); + } + break; + + case GUI_UPDATE_TRACE_BROWSER: + emit updateTraceBrowser(); + break; + } return nullptr; diff --git a/src/gui/Src/Bridge/Bridge.h b/src/gui/Src/Bridge/Bridge.h index a780055b..b391a6c6 100644 --- a/src/gui/Src/Bridge/Bridge.h +++ b/src/gui/Src/Bridge/Bridge.h @@ -154,6 +154,8 @@ signals: void closeApplication(); void flushLog(); void getDumpAttention(); + void openTraceFile(const QString & fileName); + void updateTraceBrowser(); private: CRITICAL_SECTION csBridge; diff --git a/src/gui/Src/Gui/BreakpointMenu.cpp b/src/gui/Src/Gui/BreakpointMenu.cpp index b48250aa..898ca8cb 100644 --- a/src/gui/Src/Gui/BreakpointMenu.cpp +++ b/src/gui/Src/Gui/BreakpointMenu.cpp @@ -27,7 +27,10 @@ void BreakpointMenu::build(MenuBuilder* builder) builder->addMenu(makeMenu(DIcon("breakpoint.png"), tr("Breakpoint")), [ = ](QMenu * menu) { - BPXTYPE bpType = DbgGetBpxTypeAt(mGetSelection()); + auto selection = mGetSelection(); + if(selection == 0) + return false; + BPXTYPE bpType = DbgGetBpxTypeAt(selection); if((bpType & bp_normal) == bp_normal) menu->addAction(editSoftwareBreakpointAction); diff --git a/src/gui/Src/Gui/BrowseDialog.cpp b/src/gui/Src/Gui/BrowseDialog.cpp index 10a095ea..4d320d52 100644 --- a/src/gui/Src/Gui/BrowseDialog.cpp +++ b/src/gui/Src/Gui/BrowseDialog.cpp @@ -1,5 +1,6 @@ #include "BrowseDialog.h" #include "ui_BrowseDialog.h" +#include "MiscUtil.h" #include #include @@ -28,9 +29,8 @@ void BrowseDialog::on_browse_clicked() file = QFileDialog::getSaveFileName(this, ui->label->text(), ui->lineEdit->text(), mFilter); else file = QFileDialog::getOpenFileName(this, ui->label->text(), ui->lineEdit->text(), mFilter); - file = QDir::toNativeSeparators(file); if(file.size() != 0) - ui->lineEdit->setText(file); + ui->lineEdit->setText(QDir::toNativeSeparators(file)); } void BrowseDialog::on_ok_clicked() diff --git a/src/gui/Src/Gui/CPUDisassembly.cpp b/src/gui/Src/Gui/CPUDisassembly.cpp index 0b0fc98f..c9ec45b8 100644 --- a/src/gui/Src/Gui/CPUDisassembly.cpp +++ b/src/gui/Src/Gui/CPUDisassembly.cpp @@ -4,6 +4,7 @@ #include #include #include "CPUDisassembly.h" +#include "main.h" #include "CPUSideBar.h" #include "CPUWidget.h" #include "EncodeMap.h" @@ -26,6 +27,7 @@ #include "SnowmanView.h" #include "MemoryPage.h" #include "BreakpointMenu.h" +#include "BrowseDialog.h" CPUDisassembly::CPUDisassembly(CPUWidget* parent) : Disassembly(parent) { @@ -382,6 +384,7 @@ void CPUDisassembly::setupRightClickContextMenu() QAction* traceRecordEnableBit = makeAction(DIcon("bit.png"), tr("Bit"), SLOT(ActionTraceRecordBitSlot())); QAction* traceRecordEnableByte = makeAction(DIcon("byte.png"), tr("Byte"), SLOT(ActionTraceRecordByteSlot())); QAction* traceRecordEnableWord = makeAction(DIcon("word.png"), tr("Word"), SLOT(ActionTraceRecordWordSlot())); + QAction* traceRecordToggleRunTrace = makeShortcutAction(tr("Start Run Trace"), SLOT(ActionTraceRecordToggleRunTraceSlot()), "ActionToggleRunTrace"); mMenuBuilder->addMenu(makeMenu(DIcon("trace.png"), tr("Trace record")), [ = ](QMenu * menu) { if(DbgFunctions()->GetTraceRecordType(rvaToVa(getInitialSelection())) == TRACERECORDTYPE::TraceRecordNone) @@ -392,6 +395,12 @@ void CPUDisassembly::setupRightClickContextMenu() } else menu->addAction(traceRecordDisable); + menu->addSeparator(); + if(DbgValFromString("tr.runtraceenabled()") == 1) + traceRecordToggleRunTrace->setText(tr("Stop Run Trace")); + else + traceRecordToggleRunTrace->setText(tr("Start Run Trace")); + menu->addAction(traceRecordToggleRunTrace); return true; }); @@ -1452,11 +1461,14 @@ void CPUDisassembly::pushSelectionInto(bool copyBytes, QTextStream & stream, QTe duint cur_addr = rvaToVa(inst.rva); QString address = getAddrText(cur_addr, 0, addressLen > sizeof(duint) * 2 + 1); QString bytes; - for(int j = 0; j < inst.dump.size(); j++) + if(copyBytes) { - if(j) - bytes += " "; - bytes += ToByteString((unsigned char)(inst.dump.at(j))); + for(int j = 0; j < inst.dump.size(); j++) + { + if(j) + bytes += " "; + bytes += ToByteString((unsigned char)(inst.dump.at(j))); + } } QString disassembly; QString htmlDisassembly; @@ -1996,3 +2008,32 @@ void CPUDisassembly::downloadCurrentSymbolsSlot() if(DbgGetModuleAt(rvaToVa(getInitialSelection()), module)) DbgCmdExec(QString("symdownload \"%0\"").arg(module).toUtf8().constData()); } + +void CPUDisassembly::ActionTraceRecordToggleRunTraceSlot() +{ + if(!DbgIsDebugging()) + return; + if(DbgValFromString("tr.runtraceenabled()") == 1) + DbgCmdExec("StopRunTrace"); + else + { + QString defaultFileName; + char moduleName[MAX_MODULE_SIZE]; + QDateTime currentTime = QDateTime::currentDateTime(); + duint defaultModule = DbgValFromString("mod.main()"); + if(DbgFunctions()->ModNameFromAddr(defaultModule, moduleName, false)) + { + defaultFileName = QString::fromUtf8(moduleName); + } + defaultFileName += "-" + QLocale(QString(currentLocale)).toString(currentTime.date()) + " " + currentTime.time().toString("hh-mm-ss") + ArchValue(".trace32", ".trace64"); + BrowseDialog browse(this, tr("Select stored file"), tr("Store run trace to the following file"), + tr("Run trace files (*.%1);;All files (*.*)").arg(ArchValue("trace32", "trace64")), QCoreApplication::applicationDirPath() + QDir::separator() + "db" + QDir::separator() + defaultFileName, true); + if(browse.exec() == QDialog::Accepted) + { + if(browse.path.contains(QChar('"')) || browse.path.contains(QChar('\''))) + SimpleErrorBox(this, tr("Error"), tr("File name contains invalid character.")); + else + DbgCmdExec(QString("StartRunTrace \"%1\"").arg(browse.path).toUtf8().constData()); + } + } +} diff --git a/src/gui/Src/Gui/CPUDisassembly.h b/src/gui/Src/Gui/CPUDisassembly.h index 4be15747..712e05cc 100644 --- a/src/gui/Src/Gui/CPUDisassembly.h +++ b/src/gui/Src/Gui/CPUDisassembly.h @@ -97,6 +97,7 @@ public slots: void ActionTraceRecordByteSlot(); void ActionTraceRecordWordSlot(); void ActionTraceRecordDisableSlot(); + void ActionTraceRecordToggleRunTraceSlot(); void displayWarningSlot(QString title, QString text); void labelHelpSlot(); void analyzeSingleFunctionSlot(); diff --git a/src/gui/Src/Gui/MainWindow.cpp b/src/gui/Src/Gui/MainWindow.cpp index da46eea2..380ead7e 100644 --- a/src/gui/Src/Gui/MainWindow.cpp +++ b/src/gui/Src/Gui/MainWindow.cpp @@ -52,6 +52,7 @@ #include "MRUList.h" #include "AboutDialog.h" #include "UpdateChecker.h" +#include "Tracer/TraceBrowser.h" QString MainWindow::windowTitle = ""; @@ -204,6 +205,12 @@ MainWindow::MainWindow(QWidget* parent) mGraphView->setWindowTitle(tr("Graph")); mGraphView->setWindowIcon(DIcon("graph.png")); + // Trace view + mTraceBrowser = new TraceBrowser(this); + mTraceBrowser->setWindowTitle(tr("Trace")); + mTraceBrowser->setWindowIcon(DIcon("trace.png")); + connect(mTraceBrowser, SIGNAL(displayReferencesWidget()), this, SLOT(displayReferencesWidget())); + // Create the tab widget and enable detaching and hiding mTabWidget = new MHTabWidget(this, true, true); @@ -223,6 +230,7 @@ MainWindow::MainWindow(QWidget* parent) mWidgetList.push_back(WidgetInfo(mThreadView, "ThreadsTab")); mWidgetList.push_back(WidgetInfo(mSnowmanView, "SnowmanTab")); mWidgetList.push_back(WidgetInfo(mHandlesView, "HandlesTab")); + mWidgetList.push_back(WidgetInfo(mTraceBrowser, "TraceTab")); // If LoadSaveTabOrder disabled, load tabs in default order if(!ConfigBool("Gui", "LoadSaveTabOrder")) @@ -297,6 +305,7 @@ MainWindow::MainWindow(QWidget* parent) connect(ui->actionFunctions, SIGNAL(triggered()), this, SLOT(displayFunctions())); connect(ui->actionCallStack, SIGNAL(triggered()), this, SLOT(displayCallstack())); connect(ui->actionSEHChain, SIGNAL(triggered()), this, SLOT(displaySEHChain())); + connect(ui->actionTrace, SIGNAL(triggered()), this, SLOT(displayRunTrace())); connect(ui->actionDonate, SIGNAL(triggered()), this, SLOT(donate())); connect(ui->actionReportBug, SIGNAL(triggered()), this, SLOT(reportBug())); connect(ui->actionBlog, SIGNAL(triggered()), this, SLOT(blog())); @@ -1432,6 +1441,11 @@ void MainWindow::displaySEHChain() showQWidgetTab(mSEHChainView); } +void MainWindow::displayRunTrace() +{ + showQWidgetTab(mTraceBrowser); +} + void MainWindow::donate() { QMessageBox msg(QMessageBox::Information, tr("Donate"), tr("All the money will go to x64dbg development.")); diff --git a/src/gui/Src/Gui/MainWindow.h b/src/gui/Src/Gui/MainWindow.h index ef0adfee..3a37654d 100644 --- a/src/gui/Src/Gui/MainWindow.h +++ b/src/gui/Src/Gui/MainWindow.h @@ -33,6 +33,7 @@ class DisassemblerGraphView; class SimpleTraceDialog; class MRUList; class UpdateChecker; +class TraceBrowser; namespace Ui { @@ -88,6 +89,7 @@ public slots: void displaySnowmanWidget(); void displayVariables(); void displayGraphWidget(); + void displayRunTrace(); void displayPreviousTab(); void displayNextTab(); void hideTab(); @@ -176,6 +178,7 @@ private: HandlesView* mHandlesView; NotesManager* mNotesManager; DisassemblerGraphView* mGraphView; + TraceBrowser* mTraceBrowser; SimpleTraceDialog* mSimpleTraceDialog; UpdateChecker* mUpdateChecker; DebugStatusLabel* mStatusLabel; diff --git a/src/gui/Src/Gui/MainWindow.ui b/src/gui/Src/Gui/MainWindow.ui index 1949547b..893e6522 100644 --- a/src/gui/Src/Gui/MainWindow.ui +++ b/src/gui/Src/Gui/MainWindow.ui @@ -23,7 +23,7 @@ 0 0 868 - 21 + 23 @@ -67,15 +67,16 @@ + + + + - - - @@ -1141,6 +1142,15 @@ Plugins + + + + :/icons/images/trace.png:/icons/images/trace.png + + + Trace + + diff --git a/src/gui/Src/Tracer/TraceBrowser.cpp b/src/gui/Src/Tracer/TraceBrowser.cpp new file mode 100644 index 00000000..25042c6a --- /dev/null +++ b/src/gui/Src/Tracer/TraceBrowser.cpp @@ -0,0 +1,1287 @@ +#include "TraceBrowser.h" +#include "TraceFileReader.h" +#include "TraceFileSearch.h" +#include "RichTextPainter.h" +#include "main.h" +#include "BrowseDialog.h" +#include "QBeaEngine.h" +#include "GotoDialog.h" +#include "LineEditDialog.h" +#include "WordEditDialog.h" +#include "CachedFontMetrics.h" +#include "BreakpointMenu.h" +#include "MRUList.h" +#include + +TraceBrowser::TraceBrowser(QWidget* parent) : AbstractTableView(parent) +{ + mTraceFile = nullptr; + addColumnAt(getCharWidth() * 2 * 8 + 8, "", false); //index + addColumnAt(getCharWidth() * 2 * sizeof(dsint) + 8, "", false); //address + addColumnAt(getCharWidth() * 2 * 12 + 8, "", false); //bytes + addColumnAt(getCharWidth() * 40, "", false); //disassembly + addColumnAt(1000, "", false); //comments + + setShowHeader(false); //hide header + + mSelection.firstSelectedIndex = 0; + mSelection.fromIndex = 0; + mSelection.toIndex = 0; + setRowCount(0); + mRvaDisplayBase = 0; + mRvaDisplayEnabled = false; + + int maxModuleSize = (int)ConfigUint("Disassembler", "MaxModuleSize"); + mDisasm = new QBeaEngine(maxModuleSize); + mHighlightingMode = false; + mPermanentHighlightingMode = false; + + mMRUList = new MRUList(this, "Recent Trace Files"); + connect(mMRUList, SIGNAL(openFile(QString)), this, SLOT(openSlot(QString))); + mMRUList->load(); + + setupRightClickContextMenu(); + + Initialize(); + + connect(Bridge::getBridge(), SIGNAL(updateTraceBrowser()), this, SLOT(updateSlot())); + connect(Bridge::getBridge(), SIGNAL(openTraceFile(const QString &)), this, SLOT(openSlot(const QString &))); +} + +TraceBrowser::~TraceBrowser() +{ + delete mDisasm; +} + +QString TraceBrowser::getAddrText(dsint cur_addr, char label[MAX_LABEL_SIZE], bool getLabel) +{ + QString addrText = ""; + if(mRvaDisplayEnabled) //RVA display + { + dsint rva = cur_addr - mRvaDisplayBase; + if(rva == 0) + { +#ifdef _WIN64 + addrText = "$ ==> "; +#else + addrText = "$ ==> "; +#endif //_WIN64 + } + else if(rva > 0) + { +#ifdef _WIN64 + addrText = "$+" + QString("%1").arg(rva, -15, 16, QChar(' ')).toUpper(); +#else + addrText = "$+" + QString("%1").arg(rva, -7, 16, QChar(' ')).toUpper(); +#endif //_WIN64 + } + else if(rva < 0) + { +#ifdef _WIN64 + addrText = "$-" + QString("%1").arg(-rva, -15, 16, QChar(' ')).toUpper(); +#else + addrText = "$-" + QString("%1").arg(-rva, -7, 16, QChar(' ')).toUpper(); +#endif //_WIN64 + } + } + addrText += ToPtrString(cur_addr); + char label_[MAX_LABEL_SIZE] = ""; + if(getLabel && DbgGetLabelAt(cur_addr, SEG_DEFAULT, label_)) //has label + { + char module[MAX_MODULE_SIZE] = ""; + if(DbgGetModuleAt(cur_addr, module) && !QString(label_).startsWith("JMP.&")) + addrText += " <" + QString(module) + "." + QString(label_) + ">"; + else + addrText += " <" + QString(label_) + ">"; + } + else + *label_ = 0; + if(label) + strcpy_s(label, MAX_LABEL_SIZE, label_); + return addrText; +} + +QString TraceBrowser::paintContent(QPainter* painter, dsint rowBase, int rowOffset, int col, int x, int y, int w, int h) +{ + if(!mTraceFile || mTraceFile->Progress() != 100) + { + return ""; + } + if(mTraceFile->isError()) + { + GuiAddLogMessage(tr("An error occured when reading trace file.\r\n").toUtf8().constData()); + mTraceFile->Close(); + delete mTraceFile; + mTraceFile = nullptr; + setRowCount(0); + return ""; + } + if(mHighlightingMode) + { + QPen pen(mInstructionHighlightColor); + pen.setWidth(2); + painter->setPen(pen); + QRect rect = viewport()->rect(); + rect.adjust(1, 1, -1, -1); + painter->drawRect(rect); + } + + int index = rowBase + rowOffset; + duint cur_addr; + cur_addr = mTraceFile->Registers(index).regcontext.cip; + bool wIsSelected = (index >= mSelection.fromIndex && index <= mSelection.toIndex); + if(wIsSelected) + { + painter->fillRect(QRect(x, y, w, h), QBrush(selectionColor)); + } + if(index >= mTraceFile->Length()) + return ""; + switch(col) + { + case 0: //index + { + return getIndexText(index); + } + + case 1: //address + { + QString addrText; + char label[MAX_LABEL_SIZE] = ""; + if(!DbgIsDebugging()) + { + addrText = ToPtrString(cur_addr); + goto NotDebuggingLabel; + } + else + addrText = getAddrText(cur_addr, label, true); + BPXTYPE bpxtype = DbgGetBpxTypeAt(cur_addr); + bool isbookmark = DbgGetBookmarkAt(cur_addr); + //todo: cip + { + if(!isbookmark) //no bookmark + { + if(*label) //label + { + if(bpxtype == bp_none) //label only : fill label background + { + painter->setPen(mLabelColor); //red -> address + label text + painter->fillRect(QRect(x, y, w, h), QBrush(mLabelBackgroundColor)); //fill label background + } + else //label + breakpoint + { + if(bpxtype & bp_normal) //label + normal breakpoint + { + painter->setPen(mBreakpointColor); + painter->fillRect(QRect(x, y, w, h), QBrush(mBreakpointBackgroundColor)); //fill red + } + else if(bpxtype & bp_hardware) //label + hardware breakpoint only + { + painter->setPen(mHardwareBreakpointColor); + painter->fillRect(QRect(x, y, w, h), QBrush(mHardwareBreakpointBackgroundColor)); //fill ? + } + else //other cases -> do as normal + { + painter->setPen(mLabelColor); //red -> address + label text + painter->fillRect(QRect(x, y, w, h), QBrush(mLabelBackgroundColor)); //fill label background + } + } + } + else //no label + { + if(bpxtype == bp_none) //no label, no breakpoint + { +NotDebuggingLabel: + QColor background; + if(wIsSelected) + { + background = mSelectedAddressBackgroundColor; + painter->setPen(mSelectedAddressColor); //black address (DisassemblySelectedAddressColor) + } + else + { + background = mAddressBackgroundColor; + painter->setPen(mAddressColor); //DisassemblyAddressColor + } + if(background.alpha()) + painter->fillRect(QRect(x, y, w, h), QBrush(background)); //fill background + } + else //breakpoint only + { + if(bpxtype & bp_normal) //normal breakpoint + { + painter->setPen(mBreakpointColor); + painter->fillRect(QRect(x, y, w, h), QBrush(mBreakpointBackgroundColor)); //fill red + } + else if(bpxtype & bp_hardware) //hardware breakpoint only + { + painter->setPen(mHardwareBreakpointColor); + painter->fillRect(QRect(x, y, w, h), QBrush(mHardwareBreakpointBackgroundColor)); //fill red + } + else //other cases (memory breakpoint in disassembly) -> do as normal + { + QColor background; + if(wIsSelected) + { + background = mSelectedAddressBackgroundColor; + painter->setPen(mSelectedAddressColor); //black address (DisassemblySelectedAddressColor) + } + else + { + background = mAddressBackgroundColor; + painter->setPen(mAddressColor); + } + if(background.alpha()) + painter->fillRect(QRect(x, y, w, h), QBrush(background)); //fill background + } + } + } + } + else //bookmark + { + if(*label) //label + bookmark + { + if(bpxtype == bp_none) //label + bookmark + { + painter->setPen(mLabelColor); //red -> address + label text + painter->fillRect(QRect(x, y, w, h), QBrush(mBookmarkBackgroundColor)); //fill label background + } + else //label + breakpoint + bookmark + { + QColor color = mBookmarkBackgroundColor; + if(!color.alpha()) //we don't want transparent text + color = mAddressColor; + painter->setPen(color); + if(bpxtype & bp_normal) //label + bookmark + normal breakpoint + { + painter->fillRect(QRect(x, y, w, h), QBrush(mBreakpointBackgroundColor)); //fill red + } + else if(bpxtype & bp_hardware) //label + bookmark + hardware breakpoint only + { + painter->fillRect(QRect(x, y, w, h), QBrush(mHardwareBreakpointBackgroundColor)); //fill ? + } + } + } + else //bookmark, no label + { + if(bpxtype == bp_none) //bookmark only + { + painter->setPen(mBookmarkColor); //black address + painter->fillRect(QRect(x, y, w, h), QBrush(mBookmarkBackgroundColor)); //fill bookmark color + } + else //bookmark + breakpoint + { + QColor color = mBookmarkBackgroundColor; + if(!color.alpha()) //we don't want transparent text + color = mAddressColor; + painter->setPen(color); + if(bpxtype & bp_normal) //bookmark + normal breakpoint + { + painter->fillRect(QRect(x, y, w, h), QBrush(mBreakpointBackgroundColor)); //fill red + } + else if(bpxtype & bp_hardware) //bookmark + hardware breakpoint only + { + painter->fillRect(QRect(x, y, w, h), QBrush(mHardwareBreakpointBackgroundColor)); //fill red + } + else //other cases (bookmark + memory breakpoint in disassembly) -> do as normal + { + painter->setPen(mBookmarkColor); //black address + painter->fillRect(QRect(x, y, w, h), QBrush(mBookmarkBackgroundColor)); //fill bookmark color + } + } + } + } + } + painter->drawText(QRect(x + 4, y, w - 4, h), Qt::AlignVCenter | Qt::AlignLeft, addrText); + } + return ""; + + case 2: //opcode + { + RichTextPainter::List richBytes; + RichTextPainter::CustomRichText_t curByte; + RichTextPainter::CustomRichText_t space; + unsigned char opcodes[16]; + int opcodeSize = 0; + mTraceFile->OpCode(index, opcodes, &opcodeSize); + space.text = " "; + space.flags = RichTextPainter::FlagNone; + space.highlightWidth = 1; + space.highlightConnectPrev = true; + curByte.flags = RichTextPainter::FlagAll; + curByte.highlightWidth = 1; + space.highlight = false; + curByte.highlight = false; + + for(int i = 0; i < opcodeSize; i++) + { + if(i) + richBytes.push_back(space); + + curByte.text = ToByteString(opcodes[i]); + curByte.textColor = mBytesColor; + curByte.textBackground = mBytesBackgroundColor; + richBytes.push_back(curByte); + } + + RichTextPainter::paintRichText(painter, x, y, getColumnWidth(col), getRowHeight(), 4, richBytes, mFontMetrics); + return ""; + } + + case 3: //disassembly + { + RichTextPainter::List richText; + unsigned char opcodes[16]; + int opcodeSize = 0; + mTraceFile->OpCode(index, opcodes, &opcodeSize); + + Instruction_t inst = mDisasm->DisassembleAt(opcodes, opcodeSize, 0, mTraceFile->Registers(index).regcontext.cip, false); + + if(mHighlightToken.text.length()) + CapstoneTokenizer::TokenToRichText(inst.tokens, richText, &mHighlightToken); + else + CapstoneTokenizer::TokenToRichText(inst.tokens, richText, 0); + RichTextPainter::paintRichText(painter, x + 0, y, getColumnWidth(col) - 0, getRowHeight(), 4, richText, mFontMetrics); + return ""; + } + + case 4: //comments + { + if(DbgIsDebugging()) + { + //TODO: draw arguments + QString comment; + bool autoComment = false; + char label[MAX_LABEL_SIZE] = ""; + if(GetCommentFormat(cur_addr, comment, &autoComment)) + { + QColor backgroundColor; + if(autoComment) + { + painter->setPen(mAutoCommentColor); + backgroundColor = mAutoCommentBackgroundColor; + } + else //user comment + { + painter->setPen(mCommentColor); + backgroundColor = mCommentBackgroundColor; + } + + int width = mFontMetrics->width(comment); + if(width > w) + width = w; + if(width) + painter->fillRect(QRect(x, y, width, h), QBrush(backgroundColor)); //fill comment color + painter->drawText(QRect(x, y, width, h), Qt::AlignVCenter | Qt::AlignLeft, comment); + } + else if(DbgGetLabelAt(cur_addr, SEG_DEFAULT, label)) // label but no comment + { + QString labelText(label); + QColor backgroundColor; + painter->setPen(mLabelColor); + backgroundColor = mLabelBackgroundColor; + + int width = mFontMetrics->width(labelText); + if(width > w) + width = w; + if(width) + painter->fillRect(QRect(x, y, width, h), QBrush(backgroundColor)); //fill comment color + painter->drawText(QRect(x, y, width, h), Qt::AlignVCenter | Qt::AlignLeft, labelText); + } + } + return ""; + } + default: + return ""; + } +} + +void TraceBrowser::prepareData() +{ + auto viewables = getViewableRowsCount(); + int lines = 0; + if(mTraceFile != nullptr) + { + if(mTraceFile->Progress() == 100) + { + if(mTraceFile->Length() < getTableOffset() + viewables) + lines = mTraceFile->Length() - getTableOffset(); + else + lines = viewables; + } + } + setNbrOfLineToPrint(lines); +} + +void TraceBrowser::setupRightClickContextMenu() +{ + mMenuBuilder = new MenuBuilder(this); + QAction* toggleRunTrace = makeShortcutAction(DIcon("trace.png"), tr("Start Run Trace"), SLOT(toggleRunTraceSlot()), "ActionToggleRunTrace"); + mMenuBuilder->addAction(toggleRunTrace, [toggleRunTrace](QMenu*) + { + if(!DbgIsDebugging()) + return false; + if(DbgValFromString("tr.runtraceenabled()") == 1) + toggleRunTrace->setText(tr("Stop Run Trace")); + else + toggleRunTrace->setText(tr("Start Run Trace")); + return true; + }); + auto mTraceFileIsNull = [this](QMenu*) + { + return mTraceFile == nullptr; + }; + + mMenuBuilder->addAction(makeAction(DIcon("folder-horizontal-open.png"), tr("Open"), SLOT(openFileSlot())), mTraceFileIsNull); + mMenuBuilder->addMenu(makeMenu(DIcon("recentfiles.png"), tr("Recent Files")), [this](QMenu * menu) + { + if(mTraceFile == nullptr) + { + mMRUList->appendMenu(menu); + return true; + } + else + return false; + }); + mMenuBuilder->addAction(makeAction(DIcon("fatal-error.png"), tr("Close"), SLOT(closeFileSlot())), [this](QMenu*) + { + return mTraceFile != nullptr; + }); + mMenuBuilder->addSeparator(); + auto isValid = [this](QMenu*) + { + return mTraceFile != nullptr && mTraceFile->Progress() == 100 && mTraceFile->Length() > 0; + }; + auto isDebugging = [this](QMenu*) + { + return mTraceFile != nullptr && mTraceFile->Progress() == 100 && mTraceFile->Length() > 0 && DbgIsDebugging(); + }; + + MenuBuilder* copyMenu = new MenuBuilder(this, isValid); + copyMenu->addAction(makeShortcutAction(DIcon("copy_selection.png"), tr("&Selection"), SLOT(copySelectionSlot()), "ActionCopy")); + copyMenu->addAction(makeAction(DIcon("copy_selection.png"), tr("Selection to &File"), SLOT(copySelectionToFileSlot()))); + copyMenu->addAction(makeAction(DIcon("copy_selection_no_bytes.png"), tr("Selection (&No Bytes)"), SLOT(copySelectionNoBytesSlot()))); + copyMenu->addAction(makeAction(DIcon("copy_selection_no_bytes.png"), tr("Selection to File (No Bytes)"), SLOT(copySelectionToFileNoBytesSlot()))); + copyMenu->addAction(makeShortcutAction(DIcon("copy_address.png"), tr("Address"), SLOT(copyCipSlot()), "ActionCopyAddress")); + copyMenu->addAction(makeShortcutAction(DIcon("copy_address.png"), tr("&RVA"), SLOT(copyRvaSlot()), "ActionCopyRva"), isDebugging); + copyMenu->addAction(makeAction(DIcon("fileoffset.png"), tr("&File Offset"), SLOT(copyFileOffsetSlot())), isDebugging); + copyMenu->addAction(makeAction(DIcon("copy_disassembly.png"), tr("Disassembly"), SLOT(copyDisassemblySlot()))); + copyMenu->addAction(makeAction(DIcon("copy_address.png"), tr("Index"), SLOT(copyIndexSlot()))); + + mMenuBuilder->addMenu(makeMenu(DIcon("copy.png"), tr("&Copy")), copyMenu); + mMenuBuilder->addAction(makeShortcutAction(DIcon(ArchValue("processor32.png", "processor64.png")), tr("Follow in Disassembly"), SLOT(followDisassemblySlot()), "ActionFollowDisasm"), isValid); + + mBreakpointMenu = new BreakpointMenu(this, getActionHelperFuncs(), [this, isValid]() + { + if(isValid(nullptr)) + return mTraceFile->Registers(getInitialSelection()).regcontext.cip; + else + return (duint)0; + }); + mBreakpointMenu->build(mMenuBuilder); + mMenuBuilder->addAction(makeShortcutAction(DIcon("comment.png"), tr("&Comment"), SLOT(setCommentSlot()), "ActionSetComment"), isDebugging); + mMenuBuilder->addAction(makeShortcutAction(DIcon("highlight.png"), tr("&Highlighting mode"), SLOT(enableHighlightingModeSlot()), "ActionHighlightingMode"), isValid); + MenuBuilder* gotoMenu = new MenuBuilder(this, isValid); + gotoMenu->addAction(makeShortcutAction(DIcon("goto.png"), tr("Expression"), SLOT(gotoSlot()), "ActionGotoExpression"), isValid); + gotoMenu->addAction(makeShortcutAction(DIcon("previous.png"), tr("Previous"), SLOT(gotoPreviousSlot()), "ActionGotoPrevious"), [this](QMenu*) + { + return mHistory.historyHasPrev(); + }); + gotoMenu->addAction(makeShortcutAction(DIcon("next.png"), tr("Next"), SLOT(gotoNextSlot()), "ActionGotoNext"), [this](QMenu*) + { + return mHistory.historyHasNext(); + }); + mMenuBuilder->addMenu(makeMenu(DIcon("goto.png"), tr("Go to")), gotoMenu); + + MenuBuilder* searchMenu = new MenuBuilder(this, isValid); + searchMenu->addAction(makeAction(tr("Constant"), SLOT(searchConstantSlot()))); + searchMenu->addAction(makeAction(tr("Memory Reference"), SLOT(searchMemRefSlot()))); + mMenuBuilder->addMenu(makeMenu(DIcon("search.png"), tr("&Search")), searchMenu); + + // The following code adds a menu to view the information about currently selected instruction. When info box is completed, remove me. + MenuBuilder* infoMenu = new MenuBuilder(this, [this, isValid](QMenu * menu) + { + duint MemoryAddress[MAX_MEMORY_OPERANDS]; + duint MemoryOldContent[MAX_MEMORY_OPERANDS]; + duint MemoryNewContent[MAX_MEMORY_OPERANDS]; + bool MemoryIsValid[MAX_MEMORY_OPERANDS]; + int MemoryOperandsCount; + unsigned long long index; + + if(!isValid(nullptr)) + return false; + index = getInitialSelection(); + MemoryOperandsCount = mTraceFile->MemoryAccessCount(index); + if(MemoryOperandsCount > 0) + { + mTraceFile->MemoryAccessInfo(index, MemoryAddress, MemoryOldContent, MemoryNewContent, MemoryIsValid); + bool RvaDisplayEnabled = mRvaDisplayEnabled; + char nolabel[MAX_LABEL_SIZE]; + mRvaDisplayEnabled = false; + for(int i = 0; i < MemoryOperandsCount; i++) + { + menu->addAction(QString("%1: %2 -> %3").arg(getAddrText(MemoryAddress[i], nolabel, false)).arg(ToPtrString(MemoryOldContent[i])).arg(ToPtrString(MemoryNewContent[i]))); + } + mRvaDisplayEnabled = RvaDisplayEnabled; + menu->addSeparator(); + } +#define addReg(str, reg) if(index + 1 < mTraceFile->Length()){menu->addAction(QString(str ":%1 -> %2").arg(ToPtrString(mTraceFile->Registers(index).regcontext.##reg)) \ + .arg(ToPtrString(mTraceFile->Registers(index + 1).regcontext.##reg))); }else{ menu->addAction(QString(str ":%1").arg(ToPtrString(mTraceFile->Registers(index).regcontext.##reg))); } + addReg(ArchValue("EAX", "RAX"), cax) + addReg(ArchValue("EBX", "RBX"), cbx) + addReg(ArchValue("ECX", "RCX"), ccx) + addReg(ArchValue("EDX", "RDX"), cdx) + addReg(ArchValue("ESP", "RSP"), csp) + addReg(ArchValue("EBP", "RBP"), cbp) + addReg(ArchValue("ESI", "RSI"), csi) + addReg(ArchValue("EDI", "RDI"), cdi) +#ifdef _WIN64 + addReg("R8", r8) + addReg("R9", r9) + addReg("R10", r10) + addReg("R11", r11) + addReg("R12", r12) + addReg("R13", r13) + addReg("R14", r14) + addReg("R15", r15) +#endif //_WIN64 + addReg(ArchValue("EIP", "RIP"), cip) + addReg(ArchValue("EFLAGS", "RFLAGS"), eflags) + return true; + }); + mMenuBuilder->addMenu(makeMenu(tr("Information")), infoMenu); +} + +void TraceBrowser::contextMenuEvent(QContextMenuEvent* event) +{ + QMenu menu(this); + mMenuBuilder->build(&menu); + menu.exec(event->globalPos()); +} + +void TraceBrowser::mousePressEvent(QMouseEvent* event) +{ + duint index = getIndexOffsetFromY(transY(event->y())) + getTableOffset(); + if(getGuiState() == AbstractTableView::NoState && mTraceFile != nullptr && mTraceFile->Progress() == 100) + { + switch(event->button()) + { + case Qt::LeftButton: + if(index < getRowCount()) + { + if(mHighlightingMode || mPermanentHighlightingMode) + { + if(getColumnIndexFromX(event->x()) == 3) //click in instruction column + { + Instruction_t inst; + unsigned char opcode[16]; + int opcodeSize; + mTraceFile->OpCode(index, opcode, &opcodeSize); + inst = mDisasm->DisassembleAt(opcode, opcodeSize, mTraceFile->Registers(index).regcontext.cip, 0); + CapstoneTokenizer::SingleToken token; + if(CapstoneTokenizer::TokenFromX(inst.tokens, token, event->x() - getColumnPosition(3), mFontMetrics)) + { + if(CapstoneTokenizer::IsHighlightableToken(token)) + { + if(!CapstoneTokenizer::TokenEquals(&token, &mHighlightToken) || event->button() == Qt::RightButton) + mHighlightToken = token; + else + mHighlightToken = CapstoneTokenizer::SingleToken(); + } + else if(!mPermanentHighlightingMode) + { + mHighlightToken = CapstoneTokenizer::SingleToken(); + } + } + else if(!mPermanentHighlightingMode) + { + mHighlightToken = CapstoneTokenizer::SingleToken(); + } + } + else if(!mPermanentHighlightingMode) + { + mHighlightToken = CapstoneTokenizer::SingleToken(); + } + if(mHighlightingMode) //disable highlighting mode after clicked + { + mHighlightingMode = false; + reloadData(); + } + } + if(event->modifiers() & Qt::ShiftModifier) + expandSelectionUpTo(index); + else + setSingleSelection(index); + mHistory.addVaToHistory(index); + updateViewport(); + return; + } + break; + case Qt::MiddleButton: + copyCipSlot(); + MessageBeep(MB_OK); + break; + case Qt::BackButton: + gotoPreviousSlot(); + break; + case Qt::ForwardButton: + gotoNextSlot(); + break; + } + } + AbstractTableView::mousePressEvent(event); +} + +void TraceBrowser::mouseDoubleClickEvent(QMouseEvent* event) +{ + if(event->button() == Qt::LeftButton && mTraceFile != nullptr && mTraceFile->Progress() == 100) + { + switch(getColumnIndexFromX(event->x())) + { + case 0://Index: ??? + break; + case 1://Address: set RVA + if(mRvaDisplayEnabled && mTraceFile->Registers(getInitialSelection()).regcontext.cip == mRvaDisplayBase) + mRvaDisplayEnabled = false; + else + { + mRvaDisplayEnabled = true; + mRvaDisplayBase = mTraceFile->Registers(getInitialSelection()).regcontext.cip; + } + reloadData(); + break; + case 2: //Opcode: Breakpoint + mBreakpointMenu->toggleInt3BPActionSlot(); + break; + case 3: //Instructions: ??? + break; + case 4: //Comment + setCommentSlot(); + break; + } + } + AbstractTableView::mouseDoubleClickEvent(event); +} + +void TraceBrowser::mouseMoveEvent(QMouseEvent* event) +{ + dsint index = getIndexOffsetFromY(transY(event->y())) + getTableOffset(); + if((event->buttons() & Qt::LeftButton) != 0 && getGuiState() == AbstractTableView::NoState && mTraceFile != nullptr && mTraceFile->Progress() == 100) + { + if(index < getRowCount()) + { + setSingleSelection(getInitialSelection()); + expandSelectionUpTo(index); + } + if(transY(event->y()) > this->height()) + { + verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepAdd); + } + else if(transY(event->y()) < 0) + { + verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepSub); + } + updateViewport(); + } + AbstractTableView::mouseMoveEvent(event); +} + +void TraceBrowser::keyPressEvent(QKeyEvent* event) +{ + int key = event->key(); + int curindex = getInitialSelection(); + int visibleindex = curindex; + if((key == Qt::Key_Up || key == Qt::Key_Down) && mTraceFile && mTraceFile->Progress() == 100) + { + if(key == Qt::Key_Up) + { + if(event->modifiers() == Qt::ShiftModifier) + { + if(curindex == getSelectionStart()) + { + if(getSelectionEnd() > 0) + { + visibleindex = getSelectionEnd() - 1; + expandSelectionUpTo(visibleindex); + } + } + else + { + if(getSelectionStart() > 0) + { + visibleindex = getSelectionStart() - 1; + expandSelectionUpTo(visibleindex); + } + } + } + else + { + if(curindex > 0) + { + visibleindex = curindex - 1; + setSingleSelection(visibleindex); + } + } + } + else + { + if(getSelectionEnd() + 1 < mTraceFile->Length()) + { + if(event->modifiers() == Qt::ShiftModifier) + { + visibleindex = getSelectionEnd() + 1; + expandSelectionUpTo(visibleindex); + } + else + { + visibleindex = getSelectionEnd() + 1; + setSingleSelection(visibleindex); + } + } + } + makeVisible(visibleindex); + mHistory.addVaToHistory(visibleindex); + updateViewport(); + } + else + AbstractTableView::keyPressEvent(event); +} + +void TraceBrowser::tokenizerConfigUpdatedSlot() +{ + mDisasm->UpdateConfig(); + mPermanentHighlightingMode = ConfigBool("Disassembler", "PermanentHighlightingMode"); +} + +void TraceBrowser::expandSelectionUpTo(duint to) +{ + if(to < mSelection.firstSelectedIndex) + { + mSelection.fromIndex = to; + } + else if(to > mSelection.firstSelectedIndex) + { + mSelection.toIndex = to; + } + else if(to == mSelection.firstSelectedIndex) + { + setSingleSelection(to); + } +} + +void TraceBrowser::setSingleSelection(duint index) +{ + mSelection.firstSelectedIndex = index; + mSelection.fromIndex = index; + mSelection.toIndex = index; +} + +duint TraceBrowser::getInitialSelection() +{ + return mSelection.firstSelectedIndex; +} + +duint TraceBrowser::getSelectionSize() +{ + return mSelection.toIndex - mSelection.fromIndex + 1; +} + +duint TraceBrowser::getSelectionStart() +{ + return mSelection.fromIndex; +} + +duint TraceBrowser::getSelectionEnd() +{ + return mSelection.toIndex; +} + +void TraceBrowser::makeVisible(duint index) +{ + if(index < getTableOffset()) + setTableOffset(index); + else if(index + 2 > getTableOffset() + getViewableRowsCount()) + setTableOffset(index - getViewableRowsCount() + 2); +} + +QString TraceBrowser::getIndexText(duint index) +{ + QString indexString; + indexString = QString::number(index, 16).toUpper(); + if(mTraceFile->Length() < 16) + return indexString; + int digits; + digits = floor(log2(mTraceFile->Length() - 1) / 4) + 1; + digits -= indexString.size(); + while(digits > 0) + { + indexString = '0' + indexString; + digits = digits - 1; + } + return indexString; +} + +void TraceBrowser::updateColors() +{ + AbstractTableView::updateColors(); + //CapstoneTokenizer::UpdateColors(); //Already called in disassembly + mDisasm->UpdateConfig(); + backgroundColor = ConfigColor("DisassemblyBackgroundColor"); + + mInstructionHighlightColor = ConfigColor("InstructionHighlightColor"); + mSelectionColor = ConfigColor("DisassemblySelectionColor"); + mCipBackgroundColor = ConfigColor("DisassemblyCipBackgroundColor"); + mCipColor = ConfigColor("DisassemblyCipColor"); + mBreakpointBackgroundColor = ConfigColor("DisassemblyBreakpointBackgroundColor"); + mBreakpointColor = ConfigColor("DisassemblyBreakpointColor"); + mHardwareBreakpointBackgroundColor = ConfigColor("DisassemblyHardwareBreakpointBackgroundColor"); + mHardwareBreakpointColor = ConfigColor("DisassemblyHardwareBreakpointColor"); + mBookmarkBackgroundColor = ConfigColor("DisassemblyBookmarkBackgroundColor"); + mBookmarkColor = ConfigColor("DisassemblyBookmarkColor"); + mLabelColor = ConfigColor("DisassemblyLabelColor"); + mLabelBackgroundColor = ConfigColor("DisassemblyLabelBackgroundColor"); + mSelectedAddressBackgroundColor = ConfigColor("DisassemblySelectedAddressBackgroundColor"); + mTracedAddressBackgroundColor = ConfigColor("DisassemblyTracedBackgroundColor"); + mSelectedAddressColor = ConfigColor("DisassemblySelectedAddressColor"); + mAddressBackgroundColor = ConfigColor("DisassemblyAddressBackgroundColor"); + mAddressColor = ConfigColor("DisassemblyAddressColor"); + mBytesColor = ConfigColor("DisassemblyBytesColor"); + mBytesBackgroundColor = ConfigColor("DisassemblyBytesBackgroundColor"); + mAutoCommentColor = ConfigColor("DisassemblyAutoCommentColor"); + mAutoCommentBackgroundColor = ConfigColor("DisassemblyAutoCommentBackgroundColor"); + mCommentColor = ConfigColor("DisassemblyCommentColor"); + mCommentBackgroundColor = ConfigColor("DisassemblyCommentBackgroundColor"); +} + +void TraceBrowser::openFileSlot() +{ + BrowseDialog browse(this, tr("Open run trace file"), tr("Open trace file"), tr("Run trace files (*.%1);;All files (*.*)").arg(ArchValue("trace32", "trace64")), QApplication::applicationDirPath(), false); + if(browse.exec() != QDialog::Accepted) + return; + emit openSlot(browse.path); +} + +void TraceBrowser::openSlot(const QString & fileName) +{ + if(mTraceFile != nullptr) + { + mTraceFile->Close(); + delete mTraceFile; + } + mTraceFile = new TraceFileReader(this); + connect(mTraceFile, SIGNAL(parseFinished()), this, SLOT(parseFinishedSlot())); + mFileName = fileName; + mTraceFile->Open(fileName); +} + +void TraceBrowser::toggleRunTraceSlot() +{ + if(!DbgIsDebugging()) + return; + if(DbgValFromString("tr.runtraceenabled()") == 1) + DbgCmdExec("StopRunTrace"); + else + { + QString defaultFileName; + char moduleName[MAX_MODULE_SIZE]; + QDateTime currentTime = QDateTime::currentDateTime(); + duint defaultModule = DbgValFromString("mod.main()"); + if(DbgFunctions()->ModNameFromAddr(defaultModule, moduleName, false)) + { + defaultFileName = QString::fromUtf8(moduleName); + } + defaultFileName += "-" + QLocale(QString(currentLocale)).toString(currentTime.date()) + " " + currentTime.time().toString("hh-mm-ss") + ArchValue(".trace32", ".trace64"); + BrowseDialog browse(this, tr("Select stored file"), tr("Store run trace to the following file"), + tr("Run trace files (*.%1);;All files (*.*)").arg(ArchValue("trace32", "trace64")), QCoreApplication::applicationDirPath() + QDir::separator() + "db" + QDir::separator() + defaultFileName, true); + if(browse.exec() == QDialog::Accepted) + { + if(browse.path.contains(QChar('"')) || browse.path.contains(QChar('\''))) + SimpleErrorBox(this, tr("Error"), tr("File name contains invalid character.")); + else + DbgCmdExec(QString("StartRunTrace \"%1\"").arg(browse.path).toUtf8().constData()); + } + } +} + +void TraceBrowser::closeFileSlot() +{ + mTraceFile->Close(); + delete mTraceFile; + mTraceFile = nullptr; + reloadData(); +} + +void TraceBrowser::parseFinishedSlot() +{ + if(mTraceFile->isError()) + { + SimpleErrorBox(this, tr("Error"), "Error when opening run trace file"); + delete mTraceFile; + mTraceFile = nullptr; + setRowCount(0); + } + else + { + if(mTraceFile->HashValue() && DbgIsDebugging()) + if(DbgFunctions()->DbGetHash() != mTraceFile->HashValue()) + { + SimpleWarningBox(this, tr("Trace file is recorded for another debuggee"), + tr("Checksum is different for current trace file and the debugee. This probably means you have opened a wrong trace file. This trace file is recorded for \"%1\"").arg(mTraceFile->ExePath())); + } + setRowCount(mTraceFile->Length()); + mMRUList->addEntry(mFileName); + mMRUList->save(); + } + reloadData(); +} + +void TraceBrowser::gotoSlot() +{ + GotoDialog gotoDlg(this, false, true); // Problem: Cannot use when not debugging + if(gotoDlg.exec() == QDialog::Accepted) + { + auto val = DbgValFromString(gotoDlg.expressionText.toUtf8().constData()); + if(val > 0 && val < mTraceFile->Length()) + { + setSingleSelection(val); + makeVisible(val); + mHistory.addVaToHistory(val); + updateViewport(); + } + } +} + +void TraceBrowser::gotoNextSlot() +{ + if(mHistory.historyHasNext()) + { + auto index = mHistory.historyNext(); + setSingleSelection(index); + makeVisible(index); + updateViewport(); + } +} + +void TraceBrowser::gotoPreviousSlot() +{ + if(mHistory.historyHasPrev()) + { + auto index = mHistory.historyPrev(); + setSingleSelection(index); + makeVisible(index); + updateViewport(); + } +} + +void TraceBrowser::copyCipSlot() +{ + QString clipboard; + for(auto i = getSelectionStart(); i <= getSelectionEnd(); i++) + { + if(i != getSelectionStart()) + clipboard += "\r\n"; + clipboard += ToPtrString(mTraceFile->Registers(i).regcontext.cip); + } + Bridge::CopyToClipboard(clipboard); +} + +void TraceBrowser::copyIndexSlot() +{ + QString clipboard; + for(auto i = getSelectionStart(); i <= getSelectionEnd(); i++) + { + if(i != getSelectionStart()) + clipboard += "\r\n"; + clipboard += getIndexText(i); + } + Bridge::CopyToClipboard(clipboard); +} + +void TraceBrowser::pushSelectionInto(bool copyBytes, QTextStream & stream, QTextStream* htmlStream) +{ + const int addressLen = getColumnWidth(1) / getCharWidth() - 1; + const int bytesLen = getColumnWidth(2) / getCharWidth() - 1; + const int disassemblyLen = getColumnWidth(3) / getCharWidth() - 1; + if(htmlStream) + *htmlStream << QString("").arg(font().family()).arg(getRowHeight()); + for(unsigned long long i = getSelectionStart(); i <= getSelectionEnd(); i++) + { + if(i != getSelectionStart()) + stream << "\r\n"; + duint cur_addr = mTraceFile->Registers(i).regcontext.cip; + unsigned char opcode[16]; + int opcodeSize; + mTraceFile->OpCode(i, opcode, &opcodeSize); + Instruction_t inst; + inst = mDisasm->DisassembleAt(opcode, opcodeSize, cur_addr, 0); + QString address = getAddrText(cur_addr, 0, addressLen > sizeof(duint) * 2 + 1); + QString bytes; + if(copyBytes) + { + for(int j = 0; j < opcodeSize; j++) + { + if(j) + bytes += " "; + bytes += ToByteString((unsigned char)(opcode[j])); + } + } + QString disassembly; + QString htmlDisassembly; + if(htmlStream) + { + RichTextPainter::List richText; + if(mHighlightToken.text.length()) + CapstoneTokenizer::TokenToRichText(inst.tokens, richText, &mHighlightToken); + else + CapstoneTokenizer::TokenToRichText(inst.tokens, richText, 0); + RichTextPainter::htmlRichText(richText, htmlDisassembly, disassembly); + } + else + { + for(const auto & token : inst.tokens.tokens) + disassembly += token.text; + } + QString fullComment; + QString comment; + bool autocomment; + if(GetCommentFormat(cur_addr, comment, &autocomment)) + fullComment = " " + comment; + stream << address.leftJustified(addressLen, QChar(' '), true); + if(copyBytes) + stream << " | " + bytes.leftJustified(bytesLen, QChar(' '), true); + stream << " | " + disassembly.leftJustified(disassemblyLen, QChar(' '), true) + " |" + fullComment; + if(htmlStream) + { + *htmlStream << QString(""; + } + else + { + char label[MAX_LABEL_SIZE]; + if(DbgGetLabelAt(cur_addr, SEG_DEFAULT, label)) + { + comment = QString::fromUtf8(label); + *htmlStream << QString("" << comment.toHtmlEscaped() << ""; + } + else + *htmlStream << ""; + } + } + } + if(htmlStream) + *htmlStream << "
%1%2").arg(address.toHtmlEscaped(), htmlDisassembly); + if(!comment.isEmpty()) + { + if(autocomment) + { + *htmlStream << QString("" << comment.toHtmlEscaped() << "
"; +} + +void TraceBrowser::copySelectionSlot(bool copyBytes) +{ + QString selectionString = ""; + QString selectionHtmlString = ""; + QTextStream stream(&selectionString); + QTextStream htmlStream(&selectionHtmlString); + pushSelectionInto(copyBytes, stream, &htmlStream); + Bridge::CopyToClipboard(selectionString, selectionHtmlString); +} + +void TraceBrowser::copySelectionToFileSlot(bool copyBytes) +{ + QString fileName = QFileDialog::getSaveFileName(this, tr("Open File"), "", tr("Text Files (*.txt)")); + if(fileName != "") + { + QFile file(fileName); + if(!file.open(QIODevice::WriteOnly)) + { + QMessageBox::critical(this, tr("Error"), tr("Could not open file")); + return; + } + + QTextStream stream(&file); + pushSelectionInto(copyBytes, stream); + file.close(); + } +} + +void TraceBrowser::copySelectionSlot() +{ + copySelectionSlot(true); +} + +void TraceBrowser::copySelectionToFileSlot() +{ + copySelectionToFileSlot(true); +} + +void TraceBrowser::copySelectionNoBytesSlot() +{ + copySelectionSlot(false); +} + +void TraceBrowser::copySelectionToFileNoBytesSlot() +{ + copySelectionToFileSlot(false); +} + +void TraceBrowser::copyDisassemblySlot() +{ + QString clipboardHtml = QString("
").arg(font().family()).arg(getRowHeight()); + QString clipboard = ""; + for(auto i = getSelectionStart(); i <= getSelectionEnd(); i++) + { + if(i != getSelectionStart()) + { + clipboard += "\r\n"; + clipboardHtml += "
"; + } + RichTextPainter::List richText; + unsigned char opcode[16]; + int opcodeSize; + mTraceFile->OpCode(i, opcode, &opcodeSize); + Instruction_t inst = mDisasm->DisassembleAt(opcode, opcodeSize, mTraceFile->Registers(i).regcontext.cip, 0); + CapstoneTokenizer::TokenToRichText(inst.tokens, richText, 0); + RichTextPainter::htmlRichText(richText, clipboardHtml, clipboard); + } + clipboardHtml += QString("
"); + Bridge::CopyToClipboard(clipboard, clipboardHtml); +} + +void TraceBrowser::copyRvaSlot() +{ + QString text; + for(unsigned long long i = getSelectionStart(); i <= getSelectionEnd(); i++) + { + duint cip = mTraceFile->Registers(i).regcontext.cip; + duint base = DbgFunctions()->ModBaseFromAddr(cip); + if(base) + { + if(i != getSelectionStart()) + text += "\r\n"; + text += ToHexString(cip - base); + } + else + { + SimpleWarningBox(this, tr("Error!"), tr("Selection not in a module...")); + return; + } + } + Bridge::CopyToClipboard(text); +} + +void TraceBrowser::copyFileOffsetSlot() +{ + QString text; + for(unsigned long long i = getSelectionStart(); i <= getSelectionEnd(); i++) + { + duint cip = mTraceFile->Registers(i).regcontext.cip; + cip = DbgFunctions()->VaToFileOffset(cip); + if(cip) + { + if(i != getSelectionStart()) + text += "\r\n"; + text += ToHexString(cip); + } + else + { + SimpleErrorBox(this, tr("Error!"), tr("Selection not in a file...")); + return; + } + } + Bridge::CopyToClipboard(text); +} + +void TraceBrowser::setCommentSlot() +{ + if(!DbgIsDebugging()) + return; + duint wVA = mTraceFile->Registers(getInitialSelection()).regcontext.cip; + LineEditDialog mLineEdit(this); + mLineEdit.setTextMaxLength(MAX_COMMENT_SIZE - 2); + QString addr_text = ToPtrString(wVA); + char comment_text[MAX_COMMENT_SIZE] = ""; + if(DbgGetCommentAt((duint)wVA, comment_text)) + { + if(comment_text[0] == '\1') //automatic comment + mLineEdit.setText(QString(comment_text + 1)); + else + mLineEdit.setText(QString(comment_text)); + } + mLineEdit.setWindowTitle(tr("Add comment at ") + addr_text); + if(mLineEdit.exec() != QDialog::Accepted) + return; + QString comment = mLineEdit.editText.replace('\r', "").replace('\n', ""); + if(!DbgSetCommentAt(wVA, comment.toUtf8().constData())) + SimpleErrorBox(this, tr("Error!"), tr("DbgSetCommentAt failed!")); + + static bool easter = isEaster(); + if(easter && comment.toLower() == "oep") + { + QFile file(":/icons/images/egg.wav"); + if(file.open(QIODevice::ReadOnly)) + { + QByteArray egg = file.readAll(); + PlaySoundA(egg.data(), 0, SND_MEMORY | SND_ASYNC | SND_NODEFAULT); + } + } + + GuiUpdateAllViews(); +} + +void TraceBrowser::enableHighlightingModeSlot() +{ + if(mHighlightingMode) + mHighlightingMode = false; + else + mHighlightingMode = true; + reloadData(); +} + +void TraceBrowser::followDisassemblySlot() +{ + DbgCmdExec(QString("dis ").append(ToPtrString(mTraceFile->Registers(getInitialSelection()).regcontext.cip)).toUtf8().constData()); +} + +void TraceBrowser::searchConstantSlot() +{ + WordEditDialog constantDlg(this); + constantDlg.setup(tr("Constant"), 0, sizeof(duint)); + if(constantDlg.exec() == QDialog::Accepted) + { + TraceFileSearchConstantRange(mTraceFile, constantDlg.getVal(), constantDlg.getVal()); + emit displayReferencesWidget(); + } +} + +void TraceBrowser::searchMemRefSlot() +{ + WordEditDialog memRefDlg(this); + memRefDlg.setup(tr("References"), 0, sizeof(duint)); + if(memRefDlg.exec() == QDialog::Accepted) + { + TraceFileSearchMemReference(mTraceFile, memRefDlg.getVal()); + emit displayReferencesWidget(); + } +} + +void TraceBrowser::updateSlot() +{ + if(mTraceFile && mTraceFile->Progress() == 100) // && this->isVisible() + { + mTraceFile->purgeLastPage(); + setRowCount(mTraceFile->Length()); + reloadData(); + } +} diff --git a/src/gui/Src/Tracer/TraceBrowser.h b/src/gui/Src/Tracer/TraceBrowser.h new file mode 100644 index 00000000..a1fc3ca2 --- /dev/null +++ b/src/gui/Src/Tracer/TraceBrowser.h @@ -0,0 +1,135 @@ +#ifndef TRACEBROWSER_H +#define TRACEBROWSER_H + +#include "AbstractTableView.h" +#include "VaHistory.h" +#include "QBeaEngine.h" + +class TraceFileReader; +class BreakpointMenu; +class MRUList; + +class TraceBrowser : public AbstractTableView +{ + Q_OBJECT +public: + explicit TraceBrowser(QWidget* parent = 0); + ~TraceBrowser(); + + QString paintContent(QPainter* painter, dsint rowBase, int rowOffset, int col, int x, int y, int w, int h); + + void prepareData(); + virtual void updateColors(); + + void expandSelectionUpTo(duint to); + void setSingleSelection(duint index); + duint getInitialSelection(); + duint getSelectionSize(); + duint getSelectionStart(); + duint getSelectionEnd(); + +private: + void setupRightClickContextMenu(); + void makeVisible(duint index); + QString getAddrText(dsint cur_addr, char label[MAX_LABEL_SIZE], bool getLabel); + QString getIndexText(duint index); + void pushSelectionInto(bool copyBytes, QTextStream & stream, QTextStream* htmlStream = nullptr); + void copySelectionSlot(bool copyBytes); + void copySelectionToFileSlot(bool copyBytes); + + void contextMenuEvent(QContextMenuEvent* event); + void mousePressEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + void mouseDoubleClickEvent(QMouseEvent* event); + void keyPressEvent(QKeyEvent* event); + + VaHistory mHistory; + MenuBuilder* mMenuBuilder; + bool mRvaDisplayEnabled; + duint mRvaDisplayBase; + + typedef struct _SelectionData_t + { + duint firstSelectedIndex; + duint fromIndex; + duint toIndex; + } SelectionData_t; + + SelectionData_t mSelection; + CapstoneTokenizer::SingleToken mHighlightToken; + bool mHighlightingMode; + bool mPermanentHighlightingMode; + + TraceFileReader* mTraceFile; + QBeaEngine* mDisasm; + BreakpointMenu* mBreakpointMenu; + MRUList* mMRUList; + QString mFileName; + + QColor mBytesColor; + QColor mBytesBackgroundColor; + + QColor mInstructionHighlightColor; + QColor mSelectionColor; + + QColor mCipBackgroundColor; + QColor mCipColor; + + QColor mBreakpointBackgroundColor; + QColor mBreakpointColor; + + QColor mHardwareBreakpointBackgroundColor; + QColor mHardwareBreakpointColor; + + QColor mBookmarkBackgroundColor; + QColor mBookmarkColor; + + QColor mLabelColor; + QColor mLabelBackgroundColor; + + QColor mSelectedAddressBackgroundColor; + QColor mTracedAddressBackgroundColor; + QColor mSelectedAddressColor; + QColor mAddressBackgroundColor; + QColor mAddressColor; + + QColor mAutoCommentColor; + QColor mAutoCommentBackgroundColor; + QColor mCommentColor; + QColor mCommentBackgroundColor; + +signals: + void displayReferencesWidget(); + +public slots: + + void openFileSlot(); + void openSlot(const QString & fileName); + void toggleRunTraceSlot(); + void closeFileSlot(); + void parseFinishedSlot(); + void tokenizerConfigUpdatedSlot(); + + void gotoSlot(); + void gotoPreviousSlot(); + void gotoNextSlot(); + void followDisassemblySlot(); + void enableHighlightingModeSlot(); + void setCommentSlot(); + void copyDisassemblySlot(); + void copyCipSlot(); + void copyIndexSlot(); + void copySelectionSlot(); + void copySelectionNoBytesSlot(); + void copySelectionToFileSlot(); + void copySelectionToFileNoBytesSlot(); + void copyFileOffsetSlot(); + void copyRvaSlot(); + + void searchConstantSlot(); + void searchMemRefSlot(); + + void updateSlot(); //debug +}; + +#endif //TRACEBROWSER_H diff --git a/src/gui/Src/Tracer/TraceFileReader.cpp b/src/gui/Src/Tracer/TraceFileReader.cpp new file mode 100644 index 00000000..8ef46364 --- /dev/null +++ b/src/gui/Src/Tracer/TraceFileReader.cpp @@ -0,0 +1,620 @@ +#include "TraceFileReaderInternal.h" +#include +#include +#include + +TraceFileReader::TraceFileReader(QObject* parent) : QObject(parent) +{ + length = 0; + progress = 0; + error = true; + parser = nullptr; + lastAccessedPage = nullptr; + lastAccessedIndexOffset = 0; + hashValue = 0; + EXEPath.clear(); +} + +bool TraceFileReader::Open(const QString & fileName) +{ + if(parser != NULL && parser->isRunning()) //Trace file parser is busy + { + parser->requestInterruption(); + parser->wait(); + } + error = true; + traceFile.setFileName(fileName); + traceFile.open(QFile::ReadOnly); + if(traceFile.isReadable()) + { + parser = new TraceFileParser(this); + connect(parser, SIGNAL(finished()), this, SLOT(parseFinishedSlot())); + progress.store(0); + traceFile.moveToThread(parser); + parser->start(); + return true; + } + else + { + progress.store(0); + emit parseFinished(); + return false; + } +} + +void TraceFileReader::Close() +{ + if(parser != NULL) + { + parser->requestInterruption(); + parser->wait(); + } + traceFile.close(); + progress.store(0); + length = 0; + fileIndex.clear(); + hashValue = 0; + EXEPath.clear(); + error = false; +} + +void TraceFileReader::parseFinishedSlot() +{ + if(!error) + progress.store(100); + else + progress.store(0); + delete parser; + parser = nullptr; + emit parseFinished(); + + //for(auto i : fileIndex) + //GuiAddLogMessage(QString("%1;%2;%3\r\n").arg(i.first).arg(i.second.first).arg(i.second.second).toUtf8().constData()); +} + +bool TraceFileReader::isError() +{ + return error; +} + +int TraceFileReader::Progress() +{ + return progress.load(); +} + +unsigned long long TraceFileReader::Length() +{ + return length; +} + +duint TraceFileReader::HashValue() +{ + return hashValue; +} + +QString TraceFileReader::ExePath() +{ + return EXEPath; +} + +REGDUMP TraceFileReader::Registers(unsigned long long index) +{ + unsigned long long base; + TraceFilePage* page = getPage(index, &base); + if(page == nullptr) + { + REGDUMP registers; + memset(®isters, 0, sizeof(registers)); + return registers; + } + else + return page->Registers(index - base); +} + +void TraceFileReader::OpCode(unsigned long long index, unsigned char* buffer, int* opcodeSize) +{ + unsigned long long base; + TraceFilePage* page = getPage(index, &base); + if(page == nullptr) + { + memset(buffer, 0, 16); + return; + } + else + page->OpCode(index - base, buffer, opcodeSize); +} + +DWORD TraceFileReader::ThreadId(unsigned long long index) +{ + unsigned long long base; + TraceFilePage* page = getPage(index, &base); + if(page == nullptr) + return 0; + else + return page->ThreadId(index - base); +} + +int TraceFileReader::MemoryAccessCount(unsigned long long index) +{ + unsigned long long base; + TraceFilePage* page = getPage(index, &base); + if(page == nullptr) + return 0; + else + return page->MemoryAccessCount(index - base); +} + +void TraceFileReader::MemoryAccessInfo(unsigned long long index, duint* address, duint* oldMemory, duint* newMemory, bool* isValid) +{ + unsigned long long base; + TraceFilePage* page = getPage(index, &base); + if(page == nullptr) + return; + else + return page->MemoryAccessInfo(index - base, address, oldMemory, newMemory, isValid); +} + +TraceFilePage* TraceFileReader::getPage(unsigned long long index, unsigned long long* base) +{ + if(lastAccessedPage) + { + if(index >= lastAccessedIndexOffset && index < lastAccessedIndexOffset + lastAccessedPage->Length()) + { + *base = lastAccessedIndexOffset; + return lastAccessedPage; + } + } + const auto cache = pages.find(Range(index, index)); + if(cache != pages.cend()) + { + if(cache->first.first >= index && cache->first.second <= index) + { + if(lastAccessedPage) + GetSystemTimes(nullptr, nullptr, &lastAccessedPage->lastAccessed); + lastAccessedPage = &cache->second; + lastAccessedIndexOffset = cache->first.first; + GetSystemTimes(nullptr, nullptr, &lastAccessedPage->lastAccessed); + *base = lastAccessedIndexOffset; + return lastAccessedPage; + } + } + else if(index >= Length()) //Out of bound + return nullptr; + //page in + if(pages.size() >= 2048) //TODO: trim resident pages based on system memory usage, instead of a hard limit. + { + FILETIME pageOutTime = pages.begin()->second.lastAccessed; + Range pageOutIndex = pages.begin()->first; + for(auto i : pages) + { + if(pageOutTime.dwHighDateTime < i.second.lastAccessed.dwHighDateTime || (pageOutTime.dwHighDateTime == i.second.lastAccessed.dwHighDateTime && pageOutTime.dwLowDateTime < i.second.lastAccessed.dwLowDateTime)) + { + pageOutTime = i.second.lastAccessed; + pageOutIndex = i.first; + } + } + pages.erase(pageOutIndex); + } + //binary search fileIndex to get file offset, push a TraceFilePage into cache and return it. + size_t start = 0; + size_t end = fileIndex.size() - 1; + size_t middle = (start + end) / 2; + std::pair* fileOffset; + while(true) + { + if(start == end || start == end - 1) + { + if(fileIndex[end].first <= index) + fileOffset = &fileIndex[end]; + else + fileOffset = &fileIndex[start]; + break; + } + if(fileIndex[middle].first > index) + end = middle; + else if(fileIndex[middle].first == index) + { + fileOffset = &fileIndex[middle]; + break; + } + else + start = middle; + middle = (start + end) / 2; + } + + if(fileOffset->second.second + fileOffset->first >= index && fileOffset->first <= index) + { + pages.insert(std::make_pair(Range(fileOffset->first, fileOffset->first + fileOffset->second.second - 1), TraceFilePage(this, fileOffset->second.first, fileOffset->second.second))); + const auto newPage = pages.find(Range(index, index)); + if(newPage != pages.cend()) + { + if(lastAccessedPage) + GetSystemTimes(nullptr, nullptr, &lastAccessedPage->lastAccessed); + lastAccessedPage = &newPage->second; + lastAccessedIndexOffset = newPage->first.first; + GetSystemTimes(nullptr, nullptr, &lastAccessedPage->lastAccessed); + *base = lastAccessedIndexOffset; + return lastAccessedPage; + } + else + { + GuiAddLogMessage("PAGEFAULT2\r\n"); //debug + return nullptr; //??? + } + } + else + { + GuiAddLogMessage("PAGEFAULT1\r\n"); //debug + return nullptr; //??? + } +} + +//Parser + +static bool checkKey(const QJsonObject & root, const QString & key, const QString & value) +{ + const auto obj = root.find(key); + if(obj == root.constEnd()) + throw std::wstring(L"Unspecified"); + QJsonValue val = obj.value(); + if(val.isString()) + if(val.toString() == value) + return true; + return false; +} + +void TraceFileParser::readFileHeader(TraceFileReader* that) +{ + LARGE_INTEGER header; + if(that->traceFile.read((char*)&header, 8) != 8) + throw std::wstring(L"Unspecified"); + if(header.LowPart != MAKEFOURCC('T', 'R', 'A', 'C')) + throw std::wstring(L"File type mismatch"); + if(header.HighPart > 16384) + throw std::wstring(L"Header info is too big"); + QByteArray jsonData = that->traceFile.read(header.HighPart); + if(jsonData.size() != header.HighPart) + throw std::wstring(L"Unspecified"); + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData); + if(jsonDoc.isNull()) + throw std::wstring(L"Unspecified"); + const QJsonObject jsonRoot = jsonDoc.object(); + + const auto ver = jsonRoot.find("ver"); + if(ver == jsonRoot.constEnd()) + throw std::wstring(L"Unspecified"); + QJsonValue verVal = ver.value(); + if(verVal.toInt(0) != 1) + throw std::wstring(L"Version not supported"); + checkKey(jsonRoot, "arch", ArchValue("x86", "x64")); + checkKey(jsonRoot, "compression", ""); + const auto hashAlgorithmObj = jsonRoot.find("hashAlgorithm"); + if(hashAlgorithmObj != jsonRoot.constEnd()) + { + QJsonValue hashAlgorithmVal = hashAlgorithmObj.value(); + if(hashAlgorithmVal.toString() == "murmurhash") + { + const auto hashObj = jsonRoot.find("hash"); + if(hashObj != jsonRoot.constEnd()) + { + QJsonValue hashVal = hashObj.value(); + QString a = hashVal.toString(); + if(a.startsWith("0x")) + { + a = a.mid(2); +#ifdef _WIN64 + that->hashValue = a.toLongLong(nullptr, 16); +#else //x86 + that->hashValue = a.toLong(nullptr, 16); +#endif //_WIN64 + } + } + } + } + const auto pathObj = jsonRoot.find("path"); + if(pathObj != jsonRoot.constEnd()) + { + QJsonValue pathVal = pathObj.value(); + that->EXEPath = pathVal.toString(); + } +} + +static bool readBlock(QFile & traceFile) +{ + if(!traceFile.isReadable()) + throw std::wstring(L"File is not readable"); + unsigned char blockType; + unsigned char changedCountFlags[3]; //reg changed count, mem accessed count, flags + if(traceFile.read((char*)&blockType, 1) != 1) + throw std::wstring(L"Read block type failed"); + if(blockType == 0) + { + + if(traceFile.read((char*)&changedCountFlags, 3) != 3) + throw std::wstring(L"Read flags failed"); + //skipping: thread id, registers + if(traceFile.seek(traceFile.pos() + ((changedCountFlags[2] & 0x80) ? 4 : 0) + (changedCountFlags[2] & 0x0F) + changedCountFlags[0] * (1 + sizeof(duint))) == false) + throw std::wstring(L"Unspecified"); + QByteArray memflags; + memflags = traceFile.read(changedCountFlags[1]); + if(memflags.length() < changedCountFlags[1]) + throw std::wstring(L"Read memory flags failed"); + unsigned int skipOffset = 0; + for(unsigned char i = 0; i < changedCountFlags[1]; i++) + skipOffset += ((memflags[i] & 1) == 1) ? 2 : 3; + if(traceFile.seek(traceFile.pos() + skipOffset * sizeof(duint)) == false) + throw std::wstring(L"Unspecified"); + //Gathered information, build index + if(changedCountFlags[0] == (sizeof(REGDUMP) - 128) / sizeof(duint)) + return true; + else + return false; + } + else + throw std::wstring(L"Unsupported block type"); + return false; +} + +void TraceFileParser::run() +{ + TraceFileReader* that = dynamic_cast(parent()); + unsigned long long index = 0; + unsigned long long lastIndex = 0; + if(that == NULL) + { + return; //Error + } + try + { + if(that->traceFile.size() == 0) + throw std::wstring(L"File is empty"); + //Process file header + readFileHeader(that); + //Update progress + that->progress.store(that->traceFile.pos() * 100 / that->traceFile.size()); + //Process file content + while(!that->traceFile.atEnd()) + { + quint64 blockStart = that->traceFile.pos(); + bool isPageBoundary = readBlock(that->traceFile); + if(isPageBoundary) + { + if(lastIndex != 0) + that->fileIndex.back().second.second = index - (lastIndex - 1); + that->fileIndex.push_back(std::make_pair(index, TraceFileReader::Range(blockStart, 0))); + lastIndex = index + 1; + //Update progress + that->progress.store(that->traceFile.pos() * 100 / that->traceFile.size()); + if(this->isInterruptionRequested() && !that->traceFile.atEnd()) //Cancel loading + throw std::wstring(L"Canceled"); + } + index++; + } + if(index > 0) + that->fileIndex.back().second.second = index - (lastIndex - 1); + that->error = false; + that->length = index; + } + catch(const std::wstring & errReason) + { + //MessageBox(0, errReason.c_str(), L"debug", MB_ICONERROR); + that->error = true; + } + catch(std::bad_alloc &) + { + that->error = true; + } + + that->traceFile.moveToThread(that->thread()); +} + +void TraceFileReader::purgeLastPage() +{ + unsigned long long index = 0; + unsigned long long lastIndex = 0; + bool isBlockExist = false; + if(length > 0) + { + index = fileIndex.back().first; + const auto lastpage = pages.find(Range(index, index)); + if(lastpage != pages.cend()) + { + //Purge last accessed page + if(index == lastAccessedIndexOffset) + lastAccessedPage = nullptr; + //Remove last page from page cache + pages.erase(lastpage); + } + //Seek start of last page + traceFile.seek(fileIndex.back().second.first); + //Remove last page from file index cache + fileIndex.pop_back(); + } + try + { + while(!traceFile.atEnd()) + { + quint64 blockStart = traceFile.pos(); + bool isPageBoundary = readBlock(traceFile); + if(isPageBoundary) + { + if(lastIndex != 0) + fileIndex.back().second.second = index - (lastIndex - 1); + fileIndex.push_back(std::make_pair(index, TraceFileReader::Range(blockStart, 0))); + lastIndex = index + 1; + isBlockExist = true; + } + index++; + } + if(isBlockExist) + fileIndex.back().second.second = index - (lastIndex - 1); + error = false; + length = index; + } + catch(std::wstring & errReason) + { + error = true; + } +} + +//TraceFilePage +TraceFilePage::TraceFilePage(TraceFileReader* parent, unsigned long long fileOffset, unsigned long long maxLength) +{ + DWORD lastThreadId = 0; + union + { + REGDUMP registers; + duint regwords[(sizeof(REGDUMP) - 128) / sizeof(duint)]; + }; + unsigned char changed[_countof(regwords)]; + duint regContent[_countof(regwords)]; + duint memAddress[MAX_MEMORY_OPERANDS]; + duint memOldContent[MAX_MEMORY_OPERANDS]; + duint memNewContent[MAX_MEMORY_OPERANDS]; + size_t memOperandOffset = 0; + mParent = parent; + length = 0; + GetSystemTimes(nullptr, nullptr, &lastAccessed); //system user time, no GetTickCount64() for XP compatibility. + memset(®isters, 0, sizeof(registers)); + try + { + if(mParent->traceFile.seek(fileOffset) == false) + throw std::exception(); + //Process file content + while(!mParent->traceFile.atEnd() && length < maxLength) + { + if(!mParent->traceFile.isReadable()) + throw std::exception(); + unsigned char blockType; + unsigned char changedCountFlags[3]; //reg changed count, mem accessed count, flags + mParent->traceFile.read((char*)&blockType, 1); + if(blockType == 0) + { + if(mParent->traceFile.read((char*)&changedCountFlags, 3) != 3) + throw std::exception(); + if(changedCountFlags[2] & 0x80) //Thread Id + mParent->traceFile.read((char*)&lastThreadId, 4); + threadId.push_back(lastThreadId); + if((changedCountFlags[2] & 0x0F) > 0) //Opcode + { + QByteArray opcode = mParent->traceFile.read(changedCountFlags[2] & 0x0F); + if(opcode.isEmpty()) + throw std::exception(); + opcodeOffset.push_back(opcodes.size()); + opcodeSize.push_back(opcode.size()); + opcodes.append(opcode); + } + else + throw std::exception(); + if(changedCountFlags[0] > 0) //registers + { + int lastPosition = -1; + if(changedCountFlags[0] > _countof(regwords)) //Bad count? + throw std::exception(); + if(mParent->traceFile.read((char*)changed, changedCountFlags[0]) != changedCountFlags[0]) + throw std::exception(); + if(mParent->traceFile.read((char*)regContent, changedCountFlags[0] * sizeof(duint)) != changedCountFlags[0] * sizeof(duint)) + { + throw std::exception(); + } + for(int i = 0; i < changedCountFlags[0]; i++) + { + lastPosition = lastPosition + changed[i] + 1; + if(lastPosition < _countof(regwords) && lastPosition >= 0) + regwords[lastPosition] = regContent[i]; + else //out of bounds? + { + throw std::exception(); + } + } + mRegisters.push_back(registers); + } + if(changedCountFlags[1] > 0) //memory + { + QByteArray memflags; + if(changedCountFlags[1] > _countof(memAddress)) //too many memory operands? + throw std::exception(); + memflags = mParent->traceFile.read(changedCountFlags[1]); + if(memflags.length() < changedCountFlags[1]) + throw std::exception(); + memoryOperandOffset.push_back(memOperandOffset); + memOperandOffset += changedCountFlags[1]; + if(mParent->traceFile.read((char*)memAddress, sizeof(duint) * changedCountFlags[1]) != sizeof(duint) * changedCountFlags[1]) + throw std::exception(); + if(mParent->traceFile.read((char*)memOldContent, sizeof(duint) * changedCountFlags[1]) != sizeof(duint) * changedCountFlags[1]) + throw std::exception(); + for(unsigned char i = 0; i < changedCountFlags[1]; i++) + { + if((memflags[i] & 1) == 0) + { + if(mParent->traceFile.read((char*)&memNewContent[i], sizeof(duint)) != sizeof(duint)) + throw std::exception(); + } + else + memNewContent[i] = memOldContent[i]; + } + for(unsigned char i = 0; i < changedCountFlags[1]; i++) + { + memoryFlags.push_back(memflags[i]); + memoryAddress.push_back(memAddress[i]); + oldMemory.push_back(memOldContent[i]); + newMemory.push_back(memNewContent[i]); + } + } + else + memoryOperandOffset.push_back(memOperandOffset); + length++; + } + else + throw std::exception(); + } + + } + catch(const std::exception &) + { + mParent->error = true; + } +} + +unsigned long long TraceFilePage::Length() const +{ + return length; +} + +REGDUMP TraceFilePage::Registers(unsigned long long index) const +{ + return mRegisters.at(index); +} + +void TraceFilePage::OpCode(unsigned long long index, unsigned char* buffer, int* opcodeSize) const +{ + *opcodeSize = this->opcodeSize.at(index); + memcpy(buffer, opcodes.constData() + opcodeOffset.at(index), *opcodeSize); +} + +DWORD TraceFilePage::ThreadId(unsigned long long index) const +{ + return threadId.at(index); +} + +int TraceFilePage::MemoryAccessCount(unsigned long long index) const +{ + size_t a = memoryOperandOffset.at(index); + if(index == length - 1) + return memoryAddress.size() - a; + else + return memoryOperandOffset.at(index + 1) - a; +} + +void TraceFilePage::MemoryAccessInfo(unsigned long long index, duint* address, duint* oldMemory, duint* newMemory, bool* isValid) const +{ + auto count = MemoryAccessCount(index); + auto base = memoryOperandOffset.at(index); + for(size_t i = 0; i < count; i++) + { + address[i] = memoryAddress.at(base + i); + oldMemory[i] = this->oldMemory.at(base + i); + newMemory[i] = this->newMemory.at(base + i); + isValid[i] = true; // proposed flag + } +} diff --git a/src/gui/Src/Tracer/TraceFileReader.h b/src/gui/Src/Tracer/TraceFileReader.h new file mode 100644 index 00000000..aa46ff02 --- /dev/null +++ b/src/gui/Src/Tracer/TraceFileReader.h @@ -0,0 +1,68 @@ +#ifndef TRACEFILEREADER_H +#define TRACEFILEREADER_H + +#include "Bridge.h" +#include +#include + +class TraceFileParser; +class TraceFilePage; + +#define MAX_MEMORY_OPERANDS 32 + +class TraceFileReader : public QObject +{ + Q_OBJECT +public: + TraceFileReader(QObject* parent = NULL); + bool Open(const QString & fileName); + void Close(); + bool isError(); + int Progress(); + + unsigned long long Length(); + + REGDUMP Registers(unsigned long long index); + void OpCode(unsigned long long index, unsigned char* buffer, int* opcodeSize); + DWORD ThreadId(unsigned long long index); + int MemoryAccessCount(unsigned long long index); + void MemoryAccessInfo(unsigned long long index, duint* address, duint* oldMemory, duint* newMemory, bool* isValid); + duint HashValue(); + QString ExePath(); + + void purgeLastPage(); + +signals: + void parseFinished(); + +public slots: + void parseFinishedSlot(); + +private: + typedef std::pair Range; + struct RangeCompare //from addrinfo.h + { + bool operator()(const Range & a, const Range & b) const //a before b? + { + return a.second < b.first; + } + }; + + QFile traceFile; + unsigned long long length; + duint hashValue; + QString EXEPath; + std::vector> fileIndex; //index; + std::atomic progress; + bool error; + TraceFilePage* lastAccessedPage; + unsigned long long lastAccessedIndexOffset; + friend class TraceFileParser; + friend class TraceFilePage; + + TraceFileParser* parser; + std::map pages; + TraceFilePage* getPage(unsigned long long index, unsigned long long* base); +}; + +#endif //TRACEFILEREADER_H diff --git a/src/gui/Src/Tracer/TraceFileReaderInternal.h b/src/gui/Src/Tracer/TraceFileReaderInternal.h new file mode 100644 index 00000000..03ec45b9 --- /dev/null +++ b/src/gui/Src/Tracer/TraceFileReaderInternal.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include "TraceFileReader.h" + +class TraceFileParser : public QThread +{ + Q_OBJECT + friend class TraceFileReader; + TraceFileParser(TraceFileReader* parent) : QThread(parent) {} + static void readFileHeader(TraceFileReader* that); + void run(); +}; + +class TraceFilePage +{ +public: + TraceFilePage(TraceFileReader* parent, unsigned long long fileOffset, unsigned long long maxLength); + unsigned long long Length() const; + REGDUMP Registers(unsigned long long index) const; + void OpCode(unsigned long long index, unsigned char* buffer, int* opcodeSize) const; + DWORD ThreadId(unsigned long long index) const; + int MemoryAccessCount(unsigned long long index) const; + void MemoryAccessInfo(unsigned long long index, duint* address, duint* oldMemory, duint* newMemory, bool* isValid) const; + + FILETIME lastAccessed; //system user time + +private: + friend class TraceFileReader; + TraceFileReader* mParent; + std::vector mRegisters; + QByteArray opcodes; + std::vector opcodeOffset; + std::vector opcodeSize; + std::vector memoryOperandOffset; + std::vector memoryFlags; + std::vector memoryAddress; + std::vector oldMemory; + std::vector newMemory; + std::vector threadId; + unsigned long long length; +}; diff --git a/src/gui/Src/Tracer/TraceFileSearch.cpp b/src/gui/Src/Tracer/TraceFileSearch.cpp new file mode 100644 index 00000000..f261d50c --- /dev/null +++ b/src/gui/Src/Tracer/TraceFileSearch.cpp @@ -0,0 +1,135 @@ +#include "TraceFileReader.h" +#include "TraceFileSearch.h" +#include "capstone_wrapper.h" + +static bool inRange(duint value, duint start, duint end) +{ + return value >= start && value <= end; +} + +static QString getIndexText(TraceFileReader* file, duint index) +{ + QString indexString; + indexString = QString::number(index, 16).toUpper(); + if(file->Length() < 16) + return indexString; + int digits; + digits = floor(log2(file->Length() - 1) / 4) + 1; + digits -= indexString.size(); + while(digits > 0) + { + indexString = '0' + indexString; + digits = digits - 1; + } + return indexString; +} + +int TraceFileSearchConstantRange(TraceFileReader* file, duint start, duint end) +{ + int count = 0; + Capstone cp; + QString title; + if(start == end) + title = QCoreApplication::translate("TraceFileSearch", "Constant: %1").arg(ToPtrString(start)); + else + title = QCoreApplication::translate("TraceFileSearch", "Range: %1-%2").arg(ToPtrString(start)).arg(ToPtrString(end)); + GuiReferenceInitialize(title.toUtf8().constData()); + GuiReferenceAddColumn(sizeof(duint) * 2, QCoreApplication::translate("TraceFileSearch", "Address").toUtf8().constData()); + GuiReferenceAddColumn(sizeof(duint) * 2, QCoreApplication::translate("TraceFileSearch", "Index").toUtf8().constData()); + GuiReferenceAddColumn(100, QCoreApplication::translate("TraceFileSearch", "Disassembly").toUtf8().constData()); + GuiReferenceSetRowCount(0); + + for(unsigned long long index = 0; index < file->Length(); index++) + { + bool found = false; + //Registers +#define FINDREG(fieldName) found |= inRange(file->Registers(index).regcontext.##fieldName, start, end) + FINDREG(cax); + FINDREG(cbx); + FINDREG(ccx); + FINDREG(cdx); + FINDREG(csp); + FINDREG(cbp); + FINDREG(csi); + FINDREG(cdi); + FINDREG(cip); + //Memory + duint memAddr[MAX_MEMORY_OPERANDS]; + duint memOldContent[MAX_MEMORY_OPERANDS]; + duint memNewContent[MAX_MEMORY_OPERANDS]; + bool isValid[MAX_MEMORY_OPERANDS]; + int memAccessCount = file->MemoryAccessCount(index); + if(memAccessCount > 0) + { + file->MemoryAccessInfo(index, memAddr, memOldContent, memNewContent, isValid); + for(size_t i = 0; i < memAccessCount; i++) + { + found |= inRange(memAddr[i], start, end); + found |= inRange(memOldContent[i], start, end); + found |= inRange(memNewContent[i], start, end); + } + } + //Constants: TO DO + //Populate reference view + if(found) + { + GuiReferenceSetRowCount(count + 1); + GuiReferenceSetCellContent(count, 0, ToPtrString(file->Registers(index).regcontext.cip).toUtf8().constData()); + GuiReferenceSetCellContent(count, 1, getIndexText(file, index).toUtf8().constData()); + unsigned char opcode[16]; + int opcodeSize = 0; + file->OpCode(index, opcode, &opcodeSize); + cp.Disassemble(file->Registers(index).regcontext.cip, opcode, opcodeSize); + GuiReferenceSetCellContent(count, 2, cp.InstructionText(true).c_str()); + //GuiReferenceSetCurrentTaskProgress; GuiReferenceSetProgress + count++; + } + } + return count; +} + +int TraceFileSearchMemReference(TraceFileReader* file, duint address) +{ + int count = 0; + Capstone cp; + GuiReferenceInitialize(QCoreApplication::translate("TraceFileSearch", "Reference").toUtf8().constData()); + GuiReferenceAddColumn(sizeof(duint) * 2, QCoreApplication::translate("TraceFileSearch", "Address").toUtf8().constData()); + GuiReferenceAddColumn(sizeof(duint) * 2, QCoreApplication::translate("TraceFileSearch", "Index").toUtf8().constData()); + GuiReferenceAddColumn(100, QCoreApplication::translate("TraceFileSearch", "Disassembly").toUtf8().constData()); + GuiReferenceSetRowCount(0); + + for(unsigned long long index = 0; index < file->Length(); index++) + { + bool found = false; + //Memory + duint memAddr[MAX_MEMORY_OPERANDS]; + duint memOldContent[MAX_MEMORY_OPERANDS]; + duint memNewContent[MAX_MEMORY_OPERANDS]; + bool isValid[MAX_MEMORY_OPERANDS]; + int memAccessCount = file->MemoryAccessCount(index); + if(memAccessCount > 0) + { + file->MemoryAccessInfo(index, memAddr, memOldContent, memNewContent, isValid); + for(size_t i = 0; i < memAccessCount; i++) + { + found |= inRange(memAddr[i], address, address + sizeof(duint) - 1); + } + //Constants: TO DO + //Populate reference view + if(found) + { + GuiReferenceSetRowCount(count + 1); + GuiReferenceSetCellContent(count, 0, ToPtrString(file->Registers(index).regcontext.cip).toUtf8().constData()); + GuiReferenceSetCellContent(count, 1, getIndexText(file, index).toUtf8().constData()); + unsigned char opcode[16]; + int opcodeSize = 0; + file->OpCode(index, opcode, &opcodeSize); + cp.Disassemble(file->Registers(index).regcontext.cip, opcode, opcodeSize); + GuiReferenceSetCellContent(count, 2, cp.InstructionText(true).c_str()); + //GuiReferenceSetCurrentTaskProgress; GuiReferenceSetProgress + count++; + } + } + } + return count; +} diff --git a/src/gui/Src/Tracer/TraceFileSearch.h b/src/gui/Src/Tracer/TraceFileSearch.h new file mode 100644 index 00000000..9d72dadb --- /dev/null +++ b/src/gui/Src/Tracer/TraceFileSearch.h @@ -0,0 +1,8 @@ +#ifndef TRACEFILESEARCH_H +#define TRACEFILESEARCH_H +#include "Bridge.h" +class TraceFileReader; + +int TraceFileSearchConstantRange(TraceFileReader* file, duint start, duint end); +int TraceFileSearchMemReference(TraceFileReader* file, duint address); +#endif //TRACEFILESEARCH_H diff --git a/src/gui/Src/Utils/Configuration.cpp b/src/gui/Src/Utils/Configuration.cpp index 89f1cf83..3c8e5aff 100644 --- a/src/gui/Src/Utils/Configuration.cpp +++ b/src/gui/Src/Utils/Configuration.cpp @@ -350,6 +350,7 @@ Configuration::Configuration() : QObject(), noMoreMsgbox(false) tabOrderUint.insert("ThreadsTab", curTab++); tabOrderUint.insert("SnowmanTab", curTab++); tabOrderUint.insert("HandlesTab", curTab++); + tabOrderUint.insert("TraceTab", curTab++); defaultUints.insert("TabOrder", tabOrderUint); //font settings @@ -598,6 +599,7 @@ Configuration::Configuration() : QObject(), noMoreMsgbox(false) defaultShortcuts.insert("ActionWatchDwordQword", Shortcut({tr("Actions"), tr("Watch DWORD/QWORD")})); defaultShortcuts.insert("ActionDataCopy", Shortcut({tr("Actions"), tr("Data Copy")})); defaultShortcuts.insert("ActionCopyFileOffset", Shortcut({tr("Actions"), tr("Copy File Offset")})); + defaultShortcuts.insert("ActionToggleRunTrace", Shortcut({tr("Actions"), tr("Start or Stop Run Trace")})); Shortcuts = defaultShortcuts; diff --git a/src/gui/x64dbg.pro b/src/gui/x64dbg.pro index 70546cea..222fcc19 100644 --- a/src/gui/x64dbg.pro +++ b/src/gui/x64dbg.pro @@ -184,7 +184,10 @@ SOURCES += \ Src/Gui/MessagesBreakpoints.cpp \ Src/Gui/AboutDialog.cpp \ Src/Gui/BreakpointMenu.cpp \ - Src/Gui/ComboBoxDialog.cpp + Src/Gui/ComboBoxDialog.cpp \ + Src/Tracer/TraceBrowser.cpp \ + Src/Tracer/TraceFileReader.cpp \ + Src/Tracer/TraceFileSearch.cpp HEADERS += \ @@ -302,7 +305,11 @@ HEADERS += \ Src/Gui/AboutDialog.h \ Src/Gui/BreakpointMenu.h \ Src/Gui/ComboBoxDialog.h \ - Src/Utils/VaHistory.h + Src/Utils/VaHistory.h \ + Src/Tracer/TraceBrowser.h \ + Src/Tracer/TraceFileReader.h \ + Src/Tracer/TraceFileReaderInternal.h \ + Src/Tracer/TraceFileSearch.h FORMS += \ diff --git a/src/x64dbg_translations.pro b/src/x64dbg_translations.pro index 1081697a..383e7d8c 100644 --- a/src/x64dbg_translations.pro +++ b/src/x64dbg_translations.pro @@ -219,7 +219,10 @@ SOURCES += \ gui/Src/Gui/LocalVarsView.cpp \ gui/Src/Gui/MessagesBreakpoints.cpp \ gui/Src/Gui/AboutDialog.cpp \ - gui/Src/Gui/BreakpointMenu.cpp + gui/Src/Gui/BreakpointMenu.cpp \ + gui/Src/Tracer/TraceBrowser.cpp \ + gui/Src/Tracer/TraceFileReader.cpp \ + gui/Src/Tracer/TraceFileSearch.cpp HEADERS += \ gui/Src/Exports.h \ @@ -449,7 +452,11 @@ HEADERS += \ gui/Src/Gui/LocalVarsView.h \ gui/Src/Gui/MessagesBreakpoints.h \ gui/Src/Gui/AboutDialog.h \ - gui/Src/Gui/BreakpointMenu.h + gui/Src/Gui/BreakpointMenu.h \ + gui/Src/Tracer/TraceBrowser.h \ + gui/Src/Tracer/TraceFileReader.h \ + gui/Src/Tracer/TraceFileReaderInternal.h \ + gui/Src/Tracer/TraceFileSearch.h FORMS += \ gui/Src/Gui/AppearanceDialog.ui \