x64dbg 跟踪文件格式规范
x64dbg 跟踪文件是一个二进制文件,其中包含有关程序执行的所有信息。每个跟踪文件由 3 部分组成:Magic 字, JSON 标头 和 二进制跟踪块。x64dbg 跟踪文件是小端字节序。
Magic 字
每个跟踪文件将以 4 个字节开头,“TRAC”(以 ASCII 编码)。
标头
标头位于偏移 4 处的标头之后。它由 4 字节长度字段组成,后跟 JSON blob。JSON blob 可能不是以 null 结尾的,并且可能未与 4 字节边界对齐。
二进制跟踪块
二进制跟踪数据紧接在标头之后,没有任何填充,可能不会与 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
是一个无符号字节,用于计算数组RegisterChangePosition
和 RegisterChangeNewData
中元素的数目。
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
结构,而不会将 RegisterChanges
和 RegisterChangePosition
的大小增加到一个字节以上。注意:文件读取器可以在此结构中使用 cip
寄存器找到指令的地址。
x64dbg 将在跟踪开始时保存所有寄存器,并保存每 512 条指令(此数字可能会在将来的版本中更改,以便在速度和空间之间进行不同的权衡)。保存了所有寄存器的块将具有 RegisterChanges
=172(在 64 位平台上)和 216(在 32 位平台上)。这允许随机访问 x64dbg 跟踪文件。如果不保存所有寄存器,x64dbg 可能无法打开其指令序列长度超过实现定义限制的跟踪文件。
MemoryAccessFlags
是一个字节数组,指示内存访问的属性。目前,仅定义位 0,所有其他位都保留并设置为 0。当设置位 0 时,它表示内存未更改(这可能意味着它被读取,或者它被相同的值覆盖),因此 MemoryAccessNewData
将没有用于此内存访问的条目。文件读取器可以使用反汇编程序来确定内存访问的真实类型。
MemoryAccessAddress
是指示内存访问地址的指针数组。
MemoryAccessOldData
是一个指针大小的整数数组,用于存储旧的内存内容。
MemoryAccessNewData
是一个指针大小的整数数组,用于存储新的内存内容。