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

774 lines
30 KiB
C++

#include "TraceRecord.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 "stringformat.h"
#include "value.h"
#define MAX_INSTRUCTIONS_TRACED_FULL_REG_DUMP 512
TraceRecordManager TraceRecord;
TraceRecordManager::TraceRecordManager()
{
ModuleNames.emplace_back("");
}
TraceRecordManager::~TraceRecordManager()
{
clear();
}
void TraceRecordManager::clear()
{
EXCLUSIVE_ACQUIRE(LockTraceRecord);
for(auto i = TraceRecord.begin(); i != TraceRecord.end(); ++i)
efree(i->second.rawPtr, "TraceRecordManager");
TraceRecord.clear();
ModuleNames.clear();
ModuleNames.emplace_back("");
}
bool TraceRecordManager::setTraceRecordType(duint pageAddress, TraceRecordType type)
{
EXCLUSIVE_ACQUIRE(LockTraceRecord);
pageAddress &= ~((duint)4096 - 1);
auto pageInfo = TraceRecord.find(ModHashFromAddr(pageAddress));
if(pageInfo == TraceRecord.end())
{
if(type != TraceRecordType::TraceRecordNone)
{
TraceRecordPage newPage;
char modName[MAX_MODULE_SIZE];
switch(type)
{
case TraceRecordBitExec:
newPage.rawPtr = emalloc(4096 / 8, "TraceRecordManager");
memset(newPage.rawPtr, 0, 4096 / 8);
break;
case TraceRecordByteWithExecTypeAndCounter:
newPage.rawPtr = emalloc(4096, "TraceRecordManager");
memset(newPage.rawPtr, 0, 4096);
break;
case TraceRecordWordWithExecTypeAndCounter:
newPage.rawPtr = emalloc(4096 * 2, "TraceRecordManager");
memset(newPage.rawPtr, 0, 4096 * 2);
break;
default:
return false;
}
newPage.dataType = type;
if(ModNameFromAddr(pageAddress, modName, true))
{
newPage.rva = pageAddress - ModBaseFromAddr(pageAddress);
newPage.moduleIndex = getModuleIndex(std::string(modName));
}
else
newPage.moduleIndex = ~0;
auto inserted = TraceRecord.insert(std::make_pair(ModHashFromAddr(pageAddress), newPage));
if(inserted.second == false) // we failed to insert new page into the map
{
efree(newPage.rawPtr);
return false;
}
return true;
}
else
return true;
}
else
{
if(type == TraceRecordType::TraceRecordNone)
{
if(pageInfo != TraceRecord.end())
{
efree(pageInfo->second.rawPtr, "TraceRecordManager");
TraceRecord.erase(pageInfo);
}
return true;
}
else
return pageInfo->second.dataType == type; //Can't covert between data types
}
}
TraceRecordManager::TraceRecordType TraceRecordManager::getTraceRecordType(duint pageAddress)
{
SHARED_ACQUIRE(LockTraceRecord);
pageAddress &= ~((duint)4096 - 1);
auto pageInfo = TraceRecord.find(ModHashFromAddr(pageAddress));
if(pageInfo == TraceRecord.end())
return TraceRecordNone;
else
return pageInfo->second.dataType;
}
void TraceRecordManager::TraceExecute(duint address, duint size)
{
SHARED_ACQUIRE(LockTraceRecord);
if(size == 0)
return;
duint base = address & ~((duint)4096 - 1);
auto pageInfoIterator = TraceRecord.find(ModHashFromAddr(base));
if(pageInfoIterator == TraceRecord.end())
return;
TraceRecordPage pageInfo;
pageInfo = pageInfoIterator->second;
duint offset = address - base;
bool isMixed;
if((offset + size) > 4096) // execution crossed page boundary, splitting into 2 sub calls. Noting that byte type may be mislabelled.
{
SHARED_RELEASE();
TraceExecute(address, 4096 - offset);
TraceExecute(base + 4096, size + offset - 4096);
return;
}
isMixed = false;
switch(pageInfo.dataType)
{
case TraceRecordType::TraceRecordBitExec:
for(unsigned char i = 0; i < size; i++)
*((char*)pageInfo.rawPtr + (i + offset) / 8) |= 1 << ((i + offset) % 8);
break;
case TraceRecordType::TraceRecordByteWithExecTypeAndCounter:
for(unsigned char i = 0; i < size; i++)
{
TraceRecordByteType_2bit currentByteType;
if(isMixed)
currentByteType = TraceRecordByteType_2bit::_InstructionOverlapped;
else if(i == 0)
currentByteType = TraceRecordByteType_2bit::_InstructionHeading;
else if(i == size - 1)
currentByteType = TraceRecordByteType_2bit::_InstructionTailing;
else
currentByteType = TraceRecordByteType_2bit::_InstructionBody;
char* data = (char*)pageInfo.rawPtr + offset + i;
if(*data == 0)
{
*data = (char)currentByteType << 6 | 1;
}
else
{
isMixed |= (*data & 0xC0) >> 6 == currentByteType;
*data = ((char)currentByteType << 6) | ((*data & 0x3F) == 0x3F ? 0x3F : (*data & 0x3F) + 1);
}
}
if(isMixed)
for(unsigned char i = 0; i < size; i++)
*((char*)pageInfo.rawPtr + i + offset) |= 0xC0;
break;
case TraceRecordType::TraceRecordWordWithExecTypeAndCounter:
for(unsigned char i = 0; i < size; i++)
{
TraceRecordByteType_2bit currentByteType;
if(isMixed)
currentByteType = TraceRecordByteType_2bit::_InstructionOverlapped;
else if(i == 0)
currentByteType = TraceRecordByteType_2bit::_InstructionHeading;
else if(i == size - 1)
currentByteType = TraceRecordByteType_2bit::_InstructionTailing;
else
currentByteType = TraceRecordByteType_2bit::_InstructionBody;
short* data = (short*)pageInfo.rawPtr + offset + i;
if(*data == 0)
{
*data = (char)currentByteType << 14 | 1;
}
else
{
isMixed |= (*data & 0xC0) >> 6 == currentByteType;
*data = ((char)currentByteType << 14) | ((*data & 0x3FFF) == 0x3FFF ? 0x3FFF : (*data & 0x3FFF) + 1);
}
}
if(isMixed)
for(unsigned char i = 0; i < size; i++)
*((short*)pageInfo.rawPtr + i + offset) |= 0xC000;
break;
default:
break;
}
}
//See https://www.felixcloutier.com/x86/FXSAVE.html, max 512 bytes
#define memoryContentSize 512
static void HandleZydisOperand(const Zydis & cp, int opindex, DISASM_ARGTYPE* argType, duint* value, unsigned char memoryContent[memoryContentSize], unsigned char* memorySize)
{
*value = cp.ResolveOpValue(opindex, [&cp](ZydisRegister 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 ZYDIS_OPERAND_TYPE_REGISTER:
*argType = arg_normal;
break;
case ZYDIS_OPERAND_TYPE_IMMEDIATE:
*argType = arg_normal;
break;
case ZYDIS_OPERAND_TYPE_POINTER:
*argType = arg_normal;
break;
case ZYDIS_OPERAND_TYPE_MEMORY:
{
*argType = arg_memory;
const auto & mem = op.mem;
if(mem.segment == ArchValue(ZYDIS_REGISTER_FS, ZYDIS_REGISTER_GS))
{
*value += ThreadGetLocalBase(ThreadGetId(hActiveThread));
}
*memorySize = op.size / 8;
if(*memorySize <= memoryContentSize && DbgMemIsValidReadPtr(*value))
{
MemRead(*value, memoryContent, max(op.size / 8, sizeof(duint)));
}
}
break;
default:
__debugbreak();
}
}
void TraceRecordManager::TraceExecuteRecord(const Zydis & newInstruction)
{
if(!isRunTraceEnabled())
return;
unsigned char WriteBuffer[3072];
unsigned char* WriteBufferPtr = WriteBuffer;
//Get current data
REGDUMPWORD newContext;
//DISASM_INSTR newInstruction;
DWORD newThreadId;
const size_t memoryArrayCount = 32;
duint newMemory[memoryArrayCount];
duint newMemoryAddress[memoryArrayCount];
duint oldMemory[memoryArrayCount];
unsigned char newMemoryArrayCount = 0;
DbgGetRegDumpEx(&newContext.registers, sizeof(REGDUMP));
newThreadId = ThreadGetId(hActiveThread);
// Don't try to resolve memory values for invalid/lea/nop instructions
if(newInstruction.Success() && !newInstruction.IsNop() && newInstruction.GetId() != ZYDIS_MNEMONIC_LEA)
{
// This can happen when trace execute instruction is used, we need to fix that or something would go wrong with the GUI.
newContext.registers.regcontext.cip = newInstruction.Address();
DISASM_ARGTYPE argType;
duint value;
unsigned char memoryContent[memoryContentSize];
unsigned char memorySize;
for(int i = 0; i < newInstruction.OpCount(); i++)
{
memset(memoryContent, 0, sizeof(memoryContent));
HandleZydisOperand(newInstruction, i, &argType, &value, memoryContent, &memorySize);
// check for overflow of the memory buffer
if(newMemoryArrayCount * sizeof(duint) + memorySize > memoryArrayCount * sizeof(duint))
continue;
// 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() == ZYDIS_MNEMONIC_PUSH || newInstruction.GetId() == ZYDIS_MNEMONIC_PUSHF || newInstruction.GetId() == ZYDIS_MNEMONIC_PUSHFD
|| newInstruction.GetId() == ZYDIS_MNEMONIC_PUSHFQ || newInstruction.GetId() == ZYDIS_MNEMONIC_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() == ZYDIS_MNEMONIC_POP || newInstruction.GetId() == ZYDIS_MNEMONIC_POPF || newInstruction.GetId() == ZYDIS_MNEMONIC_POPFD
|| newInstruction.GetId() == ZYDIS_MNEMONIC_POPFQ || newInstruction.GetId() == ZYDIS_MNEMONIC_RET)
{
MemRead(newContext.registers.regcontext.csp, &newMemory[newMemoryArrayCount], sizeof(duint));
newMemoryAddress[newMemoryArrayCount] = newContext.registers.regcontext.csp;
newMemoryArrayCount++;
}
//TODO: PUSHAD/POPAD
assert(newMemoryArrayCount < memoryArrayCount);
}
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);
String error = stringformatinline(StringUtils::sprintf("{winerror@%d}", GetLastError()));
dprintf(QT_TRANSLATE_NOOP("DBG", "Run trace has stopped unexpectedly because WriteFile() failed. GetLastError() = %s.\r\n"), error.c_str());
rtEnabled = false;
}
}
else
__debugbreak(); // Buffer overrun?
}
rtPrevInstAvailable = true;
rtRecordedInstructions++;
dbgtracebrowserneedsupdate();
}
unsigned int TraceRecordManager::getHitCount(duint address)
{
SHARED_ACQUIRE(LockTraceRecord);
duint base = address & ~((duint)4096 - 1);
auto pageInfoIterator = TraceRecord.find(ModHashFromAddr(base));
if(pageInfoIterator == TraceRecord.end())
return 0;
else
{
TraceRecordPage pageInfo = pageInfoIterator->second;
duint offset = address - base;
switch(pageInfo.dataType)
{
case TraceRecordType::TraceRecordBitExec:
return ((char*)pageInfo.rawPtr)[offset / 8] & (1 << (offset % 8)) ? 1 : 0;
case TraceRecordType::TraceRecordByteWithExecTypeAndCounter:
return ((char*)pageInfo.rawPtr)[offset] & 0x3F;
case TraceRecordType::TraceRecordWordWithExecTypeAndCounter:
return ((short*)pageInfo.rawPtr)[offset] & 0x3FFF;
default:
return 0;
}
}
}
TraceRecordManager::TraceRecordByteType TraceRecordManager::getByteType(duint address)
{
SHARED_ACQUIRE(LockTraceRecord);
duint base = address & ~((duint)4096 - 1);
auto pageInfoIterator = TraceRecord.find(ModHashFromAddr(base));
if(pageInfoIterator == TraceRecord.end())
return TraceRecordByteType::InstructionHeading;
else
{
TraceRecordPage pageInfo = pageInfoIterator->second;
duint offset = address - base;
switch(pageInfo.dataType)
{
case TraceRecordType::TraceRecordBitExec:
default:
return TraceRecordByteType::InstructionHeading;
case TraceRecordType::TraceRecordByteWithExecTypeAndCounter:
return (TraceRecordByteType)((((char*)pageInfo.rawPtr)[offset] & 0xC0) >> 6);
case TraceRecordType::TraceRecordWordWithExecTypeAndCounter:
return (TraceRecordByteType)((((short*)pageInfo.rawPtr)[offset] & 0xC000) >> 14);
}
}
}
void TraceRecordManager::increaseInstructionCounter()
{
InterlockedIncrement((volatile long*)&instructionCounter);
}
bool TraceRecordManager::enableRunTrace(bool enabled, const char* fileName)
{
if(enabled)
{
if(rtEnabled)
enableRunTrace(false, NULL); //re-enable run trace
if(!DbgIsDebugging())
return false;
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);
Zydis cp;
unsigned char instr[MAX_DISASM_BUFFER];
auto cip = GetContextDataEx(hActiveThread, UE_CIP);
if(MemRead(cip, instr, MAX_DISASM_BUFFER))
{
cp.DisassembleSafe(cip, instr, MAX_DISASM_BUFFER);
TraceExecuteRecord(cp);
}
GuiOpenTraceFile(fileName);
return true;
}
else
{
String error = stringformatinline(StringUtils::sprintf("{winerror@%d}", GetLastError()));
dprintf(QT_TRANSLATE_NOOP("DBG", "Cannot create run trace file. GetLastError() = %s.\r\n"), error.c_str());
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);
const JSON jsonTraceRecords = json_array();
const char* byteToHex = "0123456789ABCDEF";
for(auto i : TraceRecord)
{
JSON jsonObj = json_object();
if(i.second.moduleIndex != ~0)
{
json_object_set_new(jsonObj, "module", json_string(ModuleNames[i.second.moduleIndex].c_str()));
json_object_set_new(jsonObj, "rva", json_hex(i.second.rva));
}
else
{
json_object_set_new(jsonObj, "module", json_string(""));
json_object_set_new(jsonObj, "rva", json_hex(i.first));
}
json_object_set_new(jsonObj, "type", json_hex((duint)i.second.dataType));
auto ptr = (unsigned char*)i.second.rawPtr;
duint size = 0;
switch(i.second.dataType)
{
case TraceRecordType::TraceRecordBitExec:
size = 4096 / 8;
break;
case TraceRecordType::TraceRecordByteWithExecTypeAndCounter:
size = 4096;
break;
case TraceRecordType::TraceRecordWordWithExecTypeAndCounter:
size = 4096 * 2;
break;
default:
__debugbreak(); // We have encountered an error condition.
}
auto hex = StringUtils::ToCompressedHex(ptr, size);
json_object_set_new(jsonObj, "data", json_string(hex.c_str()));
json_array_append_new(jsonTraceRecords, jsonObj);
}
if(json_array_size(jsonTraceRecords))
json_object_set(root, "tracerecord", jsonTraceRecords);
// Notify garbage collector
json_decref(jsonTraceRecords);
}
void TraceRecordManager::loadFromDb(JSON root)
{
EXCLUSIVE_ACQUIRE(LockTraceRecord);
// get the root object
const JSON tracerecord = json_object_get(root, "tracerecord");
// return if nothing found
if(!tracerecord)
return;
size_t i;
JSON value;
json_array_foreach(tracerecord, i, value)
{
TraceRecordPage currentPage;
size_t size;
currentPage.dataType = (TraceRecordType)json_hex_value(json_object_get(value, "type"));
currentPage.rva = (duint)json_hex_value(json_object_get(value, "rva"));
switch(currentPage.dataType)
{
case TraceRecordType::TraceRecordBitExec:
size = 4096 / 8;
break;
case TraceRecordType::TraceRecordByteWithExecTypeAndCounter:
size = 4096;
break;
case TraceRecordType::TraceRecordWordWithExecTypeAndCounter:
size = 4096 * 2;
break;
default:
size = 0;
break;
}
if(size != 0)
{
currentPage.rawPtr = emalloc(size, "TraceRecordManager");
const char* p = json_string_value(json_object_get(value, "data"));
std::vector<unsigned char> data;
if(StringUtils::FromCompressedHex(p, data) && data.size() == size)
{
memcpy(currentPage.rawPtr, data.data(), size);
const char* moduleName = json_string_value(json_object_get(value, "module"));
duint key;
if(*moduleName)
{
currentPage.moduleIndex = getModuleIndex(std::string(moduleName));
key = currentPage.rva + ModHashFromName(moduleName);
}
else
{
currentPage.moduleIndex = ~0;
key = currentPage.rva;
}
TraceRecord.insert(std::make_pair(key, currentPage));
}
else
efree(currentPage.rawPtr, "TraceRecordManager");
}
}
}
unsigned int TraceRecordManager::getModuleIndex(const String & moduleName)
{
auto iterator = std::find(ModuleNames.begin(), ModuleNames.end(), moduleName);
if(iterator != ModuleNames.end())
return (unsigned int)(iterator - ModuleNames.begin());
else
{
ModuleNames.push_back(moduleName);
return (unsigned int)(ModuleNames.size() - 1);
}
}
bool TraceRecordManager::isRunTraceEnabled()
{
return rtEnabled;
}
void _dbg_dbgtraceexecute(duint CIP)
{
if(TraceRecord.getTraceRecordType(CIP) != TraceRecordManager::TraceRecordType::TraceRecordNone)
{
Zydis instruction;
unsigned char data[MAX_DISASM_BUFFER];
if(MemRead(CIP, data, MAX_DISASM_BUFFER))
{
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
{
if(TraceRecord.isRunTraceEnabled())
{
Zydis 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)
{
return TraceRecord.getHitCount(address);
}
TRACERECORDBYTETYPE _dbg_dbggetTraceRecordByteType(duint address)
{
return (TRACERECORDBYTETYPE)TraceRecord.getByteType(address);
}
bool _dbg_dbgsetTraceRecordType(duint pageAddress, TRACERECORDTYPE type)
{
return TraceRecord.setTraceRecordType(pageAddress, (TraceRecordManager::TraceRecordType)type);
}
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();
}