XRay 飞行数据记录器跟踪格式¶
- 版本:
1 (截至 2017-07-20)
简介¶
在飞行数据记录器模式下收集 XRay 跟踪时,应用程序的每个线程都会声明缓冲区以填充跟踪数据,这些数据在某个时间点最终确定并刷新。
探查器的目标是最小化开销,刷新后的数据直接对应于缓冲区。
本文档描述了跟踪文件的格式。
概述¶
每个跟踪文件对应于特定线程中的一系列事件。
文件有一个头部,后面跟着一系列区分的记录类型。
字节字段的字节序与生成跟踪文件的平台的字节序匹配。
头部部分¶
跟踪文件以 32 字节的头部开始。
字段 |
大小(字节) |
描述 |
---|---|---|
版本 |
|
预期版本化的读取器。本文档描述了版本 == 1 时的格式 |
类型 |
|
一个枚举,编码跟踪的类型。飞行数据记录器模式跟踪的类型 == 1 |
位字段 |
|
保存未对齐到字节的参数。下面将进一步描述。 |
周期频率 |
|
用于测量事件持续时间的 CPU 振荡器的频率(赫兹)。 |
缓冲区大小 |
|
跟踪数据部分(在头部之后)的大小(字节)。 |
保留 |
|
保留以备将来使用。 |
文件头部的位字段参数由以下字段组成。
字段 |
大小(位) |
描述 |
---|---|---|
恒定 TSC |
|
平台用于记录事件之间滴答次数的时间戳计数器是否以恒定频率滴答,即使 CPU 频率发生变化。0 == 不恒定。1 == 恒定。 |
不停 TSC |
|
无论 CPU 是否处于低功耗状态,tsc 是否继续计数。0 == 停止。1 == 不停。 |
保留 |
|
无意义。 |
数据部分¶
跟踪中的头部之后是数据部分,其大小与头部中的 buffer_size 字段匹配。
数据部分是不同类型元素的流。
序列中有一些数据类别。
函数 记录
:函数记录包含进入和退出函数执行的时间。每个函数记录都有 8 个字节。元数据 记录
:元数据记录用于多种目的。主要用于捕获对每个函数记录成本过高的信息,但这些信息是将细粒度时间安排置于上下文中所必需的。它们也用作用户定义的事件数据有效负载的标记。每个元数据记录都有 16 个字节。事件 数据
:自由格式数据可能与二进制文件跟踪的事件相关联,并对处理程序函数定义的数据进行编码。事件数据总是在一个标记记录之前,该标记记录指示其大小。函数 参数
:某些函数的参数包含在跟踪中。这些参数要么是指针地址,要么是独立于其在高级语言中的类型读取和记录的基元。对于跟踪器来说,它们都是数字。具有附加参数的函数记录将在函数入口记录中指示其存在。我们仅支持记录从参数零开始的连续函数参数序列,对于成员函数调用,这将是“this”指针。例如,我们不支持记录第一个和第三个参数。
内存格式的读取器必须维护一个状态机。该格式不尝试为对齐进行填充,并且不可搜索。
函数记录¶
函数记录具有 8 字节的布局。此布局编码信息以重建已检测函数及其持续时间的调用栈。
字段 |
大小(位) |
描述 |
---|---|---|
判别器 |
|
指示读取器是否应读取函数或元数据记录。对于函数记录,设置为 |
操作 |
|
指定函数是正在进入、退出,还是由优化生成的非标准进入或退出。 |
函数 ID |
|
函数的数字 ID。通过 xray 检测映射解析为名称。检测映射由 xray 在编译时构建到目标文件中,并将函数 ID 与地址配对。它用于修补和作为查找二进制文件符号以获取名称的查找。 |
TSC 增量 |
|
自上次记录增量或其他 TSC 重置事件以来,时间戳计数器的滴答次数。 |
在小端机器上,位字段的顺序是从最低有效位到最高有效位。读取器可以读取一个 8 位值并应用掩码0x01
用于判别器。类似地,它们可以读取 32 位并无符号右移0x04
以获取 function_id 字段。
在大端机器上,位字段的顺序是从最高有效位到最低有效位。读取器将读取一个 8 位值并将其无符号右移 7 位以获取判别器。function_id 字段可以通过读取 32 位值并应用掩码0x0FFFFFFF
来获取。
函数操作类型如下所示。
类型 |
编号 |
描述 |
---|---|---|
入口 |
|
典型的函数入口。 |
出口 |
|
典型的函数出口。 |
尾调用出口 |
|
由于尾调用优化导致的函数出口。 |
入口参数 |
|
记录参数的函数入口。 |
Entry_Args 记录不包含参数本身。相反,每个已记录参数的元数据记录都跟随流中的函数记录。
元数据记录¶
在缓冲区中穿插着 16 字节的元数据记录。对于通常检测的二进制文件,它们将比函数记录稀疏,并且它们提供了二进制文件执行状态的更完整视图。
元数据记录布局部分依赖于记录,但它们共享一个通用结构。
对于函数记录描述的相同位字段规则适用于元数据记录的第一个字节。在此字节内,小端机器使用 lsb 到 msb 顺序,大端机器使用 msb 到 lsb 顺序。
字段 |
大小 |
描述 |
---|---|---|
判别器 |
|
指示读取器是否应读取函数或元数据记录。对于元数据记录,设置为 |
记录种类 |
|
元数据记录的类型。 |
数据 |
|
一个数据字段,每个记录类型使用方式不同。 |
以下是枚举记录种类的表格。
编号 |
类型 |
---|---|
0 |
新缓冲区 |
1 |
缓冲区结束 |
2 |
新 CPU ID |
3 |
TSC 溢出 |
4 |
挂钟时间标记 |
5 |
自定义事件标记 |
6 |
函数参数 |
新缓冲区记录¶
每个缓冲区都以头部之后的 NewBuffer 记录开始。它记录跟踪所属线程的线程 ID。
其数据段如下所示。
字段 |
大小(字节) |
描述 |
---|---|---|
线程 ID |
|
缓冲区的线程 ID。 |
保留 |
|
未使用。 |
挂钟时间记录¶
在 NewBuffer 记录之后,每个缓冲区都会记录一个绝对时间作为时间戳计数器增量记录的参考帧。
其数据段如下所示。
字段 |
大小(字节) |
描述 |
---|---|---|
秒 |
|
绝对时间尺度上的秒数。起点未指定,取决于跟踪器配置的实现和平台。 |
微秒 |
|
时间的微秒分量。 |
保留 |
|
未使用。 |
新 CPU ID 记录¶
每个函数入口都会调用一个例程来确定正在执行哪个 CPU。通常,这是使用 readtscp 完成的,它同时读取时间戳计数器。
如果跟踪检测到执行已切换 CPU,或者如果这是第一个检测到的入口点,则跟踪器将输出 NewCpuId 记录。
其数据段如下所示。
字段 |
大小(字节) |
描述 |
---|---|---|
CPU ID |
|
CPU ID。 |
绝对 TSC |
|
时间戳计数器的绝对值。 |
保留 |
|
未使用。 |
TSC 溢出记录¶
由于每个函数记录都使用 32 位值来表示自上次引用以来时间戳计数器的滴答次数,因此该值可能会溢出,特别是对于稀疏检测的二进制文件。
当此增量不适合 32 位表示时,将以 TSCWrap 记录的形式写入参考绝对时间戳计数器记录。
其数据段如下所示。
字段 |
大小(字节) |
描述 |
---|---|---|
绝对 TSC |
|
时间戳计数器值。 |
保留 |
|
未使用。 |
函数参数记录¶
在 Entry_Args 类型函数记录之后,可能存在一个或多个包含已跟踪函数参数值的函数参数记录。
函数参数记录序列的顺序与函数参数的顺序一一对应。
函数参数数据段
字段 |
大小(字节) |
描述 |
---|---|---|
参数 |
|
数字参数(可能是指针地址)。 |
保留 |
|
未使用。 |
自定义事件标记记录¶
XRay 提供了记录自定义事件的功能。这可以用来记录 RPC 的跟踪信息或类似的特定于应用程序的跟踪数据。
自定义事件本身是缓冲区内大小任意且无结构(应用程序定义)的内存段。它们之前是自定义事件标记,用于指示其存在和大小。
自定义事件标记数据段
字段 |
大小(字节) |
描述 |
---|---|---|
event_size |
|
前一个事件的大小。 |
绝对 TSC |
|
事件的时间戳计数器。 |
保留 |
|
未使用。 |
缓冲区结束记录¶
缓冲区结束记录类型表示此缓冲区中没有更多跟踪数据。读取器应跳过在缓冲区开始之前表达的剩余缓冲区大小,并查找另一个头或文件结束符。
格式语法和不变式¶
并非所有元数据记录和函数记录的序列都是有效数据。序列应作为状态机进行解析。有效格式的期望可以用上下文无关文法来表达。
这是尝试使用 EBNF 格式的语句来解释格式。
Format := Header ThreadBuffer* EOF
ThreadBuffer := NewBuffer WallClockTime NewCPUId BodySequence* End
BodySequence := NewCPUId | TSCWrap | Function | CustomEvent
Function := (Function_Entry_Args CallArgument*) | Function_Other_Type
CustomEvent := CustomEventMarker CustomEventUnstructuredMemory
End := EndOfBuffer RemainingBufferSizeToSkip
函数记录顺序¶
有一些说明可能有助于理解对函数记录的期望。
具有 Exit 的函数预计在其跟踪中会有一个对应的 Entry 或 Entry_Args 函数记录在其之前。
Tail_Exit 函数记录记录程序计数器将获取其返回地址的函数的函数 ID。换句话说,如果未使用尾调用优化,则将从调用堆栈中弹出最后的函数。
并非所有标记为需要检测的函数都一定存在于跟踪中。跟踪器使用启发式方法来保留非平凡函数的跟踪。
并非每个入口都必须具有跟踪的 Exit 或 Tail Exit。缓冲区可能会用完空间,或者程序可能会请求跟踪器完成以在检测到的函数退出之前返回缓冲区。