x64dbg 跟踪文件格式规范

x64dbg 跟踪文件是一个二进制文件,其中包含有关程序执行的所有信息。每个跟踪文件由 3 部分组成:Magic 字, JSON 标头二进制跟踪快。x64dbg 跟踪文件是小端字节序。

Magic 字

每个跟踪文件将以 4 个字节开头,“TRAC”(以 ASCII 编码)。

二进制跟踪块

二进制跟踪数据紧接在标头之后,没有任何填充,可能不会与 4 字节边界对齐。它被定义为一系列块。目前,仅定义了块类型 0。

每个块都以 1 字节的类型编号启动。此类型编号必须为 0,这意味着它是描述跟踪指令的块。

如果类型编号为 0,则该块将包含以下数据:

struct { uint8_t BlockType; //BlockType is 0, indicating it describes an instruction execution. uint8_t RegisterChanges; uint8_t MemoryAccesses; uint8_t BlockFlagsAndOpcodeSize; //Bitfield DWORD ThreadId; uint8_t Opcode[]; uint8_t RegisterChangePosition[]; duint RegisterChangeNewData[]; uint8_t MemoryAccessFlags[]; duint MemoryAccessAddress[]; duint MemoryAccessOldData[]; duint MemoryAccessNewData[]; };

RegisterChanges 是一个无符号字节,用于计算数组RegisterChangePositionRegisterChangeNewData 中元素的数目。

MemoryAccesses 是一个无符号字节,用于计算数组 MemoryAccessFlags 中元素的数量。

BlockFlagsAndOpcodeSize 是一个位域。最高有效位是 ThreadId 位。当该位设置时,ThreadId 字段可用,并表示执行该指令的线程 ID。当该位清除时,执行该指令的线程 id 与最后一条指令相同,因此它不存储在文件中。至少 4 个有效位指定 Opcode 字段的长度,以字节数表示。其他位保留并设置为 0。Opcode 字段包含当前指令的操作码。

RegisterChangePosition 是无符号字节的数组。每个元素指示结构中的指针大小的整数 REGDUMP 执行当前指令后更新的,作为对上一个位置的偏移。绝对索引的计算方式是将上一个元素+1 的绝对索引(如果是第一个元素,则为 0)并添加此相对索引。RegisterChangeNewData 是一个指针大小的整数数组,其中包含在执行指令之前记录的寄存器的新值。REGDUMP 结构如下。

typedef struct { REGISTERCONTEXT regcontext; FLAGS flags; X87FPUREGISTER x87FPURegisters[8]; unsigned long long mmx[8]; MXCSRFIELDS MxCsrFields; X87STATUSWORDFIELDS x87StatusWordFields; X87CONTROLWORDFIELDS x87ControlWordFields; LASTERROR lastError; //LASTSTATUS lastStatus; //This field is not supported and not included in trace file. } REGDUMP;

For example, ccx is the second member of regcontext. On x64 architecture, it is at byte offset 8 and on x86 architecture it is at byte offset 4. On both architecture, it is at index 1 and cax is at index 0. Therefore, when RegisterChangePosition[0] = 0, RegisterChangeNewData[0] contains the new value of cax. If RegisterChangePosition[1] = 0, RegisterChangeNewData[1] contains the new value of ccx, since the absolute index is computed by 0+0+1=1. The use of relative indexing helps achieve better data compression if a lossless compression is then applied to trace file, and also allow future expansion of REGDUMP structure without increasing size of RegisterChanges and RegisterChangePosition beyond a byte. Note: the file reader can locate the address of the instruction using cip register in this structure.

x64dbg will save all registers at the start of trace, and every 512 instructions(this number might be changed in future versions to have different tradeoff between speed and space). A block with all registers saved will have RegisterChanges=172 on 64-bit platform and 216 on 32-bit platform. This allows x64dbg trace file to be randomly accessed. x64dbg might be unable to open a trace file that has a sequence of instruction longer than an implementation-defined limit without all registers saved.

MemoryAccessFlags is an array of bytes that indicates properties of memory access. Currently, only bit 0 is defined and all other bits are reserved and set to 0. When bit 0 is set, it indicates the memory is not changed(This could mean it is read, or it is overwritten with identical value), so MemoryAccessNewData will not have an entry for this memory access. The file reader may use a disassembler to determine the true type of memory access.

MemoryAccessAddress is an array of pointers that indicates the address of memory access.

MemoryAccessOldData is an array of pointer-sized integers that stores the old content of memory.

MemoryAccessNewData is an array of pointer-sized integers that stores the new content of memory.