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;

例如, ccx 是 regcontext 的第二个成员。在 x64 体系结构上,它位于字节偏移量 8,在 x86 体系结构上,它位于字节偏移量 4 处。在这两种体系结构中,它都位于索引 1,以及 cax 位于索引 0。因此,当 RegisterChangePosition[0] = 0 时, RegisterChangeNewData[0] 包含 cax 的新值。如果 RegisterChangePosition[1] = 0,则 RegisterChangeNewData[1] 包含 ccx 的新值,因为绝对索引由 0+0+1=1 计算。如果随后对跟踪文件应用无损压缩,则使用相对索引有助于实现更好的数据压缩,并且还允许将来扩展 REGDUMP 结构,而不会将 RegisterChangesRegisterChangePosition 的大小增加到一个字节以上。注意:文件读取器可以在此结构中使用 cip 寄存器找到指令的地址。

x64dbg 将在跟踪开始时保存所有寄存器,并保存每 512 条指令(此数字可能会在将来的版本中更改,以便在速度和空间之间进行不同的权衡)。保存了所有寄存器的块将具有 RegisterChanges=172(在 64 位平台上)和 216(在 32 位平台上)。这允许随机访问 x64dbg 跟踪文件。如果不保存所有寄存器,x64dbg 可能无法打开其指令序列长度超过实现定义限制的跟踪文件。

MemoryAccessFlags 是一个字节数组,指示内存访问的属性。目前,仅定义位 0,所有其他位都保留并设置为 0。当设置位 0 时,它表示内存未更改(这可能意味着它被读取,或者它被相同的值覆盖),因此 MemoryAccessNewData 将没有用于此内存访问的条目。文件读取器可以使用反汇编程序来确定内存访问的真实类型。

MemoryAccessAddress 是指示内存访问地址的指针数组。

MemoryAccessOldData 是一个指针大小的整数数组,用于存储旧的内存内容。

MemoryAccessNewData 是一个指针大小的整数数组,用于存储新的内存内容。