XRay Instrumentation

版本:

1,截至 2016-11-08

简介

XRay 是一个函数调用跟踪系统,它结合了编译器插入的 instrumentation 点和一个运行时库,该库可以动态地启用和禁用 instrumentation。

关于 XRay 的更多高层次信息可以在 XRay 白皮书中找到。

本文档描述了如何在 LLVM 中使用 XRay。

LLVM 中的 XRay

XRay 由三个主要部分组成

  • 编译器插入的 instrumentation 点。

  • 一个用于在运行时启用/禁用跟踪的运行时库。

  • 一套用于分析跟踪的工具。

    注意: 截至 2018 年 7 月 25 日,XRay 仅适用于运行 Linux 的以下架构:x86_64、arm7(无 thumb)、aarch64、powerpc64le、mips、mipsel、mips64、mips64el、NetBSD:x86_64、FreeBSD:x86_64 和 OpenBSD:x86_64。

编译器插入的 instrumentation 点以 nop-sleds 的形式出现在最终生成的二进制文件中,以及一个名为 xray_instr_map 的 ELF section,其中包含指向这些 instrumentation 点的条目。运行时库依赖于能够访问 xray_instr_map 的条目,并在运行时覆盖 instrumentation 点。

使用 XRay

你可以通过几种方式使用 XRay

  • 为你的 C/C++/Objective-C/Objective-C++ 应用程序进行 Instrumentation。

  • 使用正确的函数属性生成 LLVM IR。

本节的其余部分涵盖了这些主要方法,以及稍后介绍如何自定义 XRay 在 XRay-instrumented 二进制文件中的行为。

为你的 C/C++/Objective-C 应用程序进行 Instrumentation

为你的应用程序获取 XRay instrumentation 最简单的方法是在你的 clang 调用中启用 -fxray-instrument 标志。

例如

clang -fxray-instrument ...

默认情况下,指令数至少为 200 条(或包含循环)的函数将获得 XRay instrumentation 点。你可以通过 -fxray-instruction-threshold= 标志来调整该数字

clang -fxray-instrument -fxray-instruction-threshold=1 ...

可以使用 -fxray-ignore-loops 禁用循环检测,仅使用指令阈值。你还可以使用源代码级别的属性专门为二进制文件中的函数进行 instrumentation,以使其始终或从不进行 instrumentation。你可以使用 GCC 风格的属性或 C++11 风格的属性来完成。

[[clang::xray_always_instrument]] void always_instrumented();

[[clang::xray_never_instrument]] void never_instrumented();

void alt_always_instrumented() __attribute__((xray_always_instrument));

void alt_never_instrumented() __attribute__((xray_never_instrument));

在链接二进制文件时,你可以手动链接到 XRay 运行时库,或者使用 clang-fxray-instrument 标志自动链接它。或者,你可以从 compiler-rt 静态链接 XRay 运行时库 – 这些归档文件的名称将为 libclang_rt.xray-{arch},其中 {arch} 是 clang 支持的助记符(x86_64、arm7 等)。

LLVM 函数属性

如果你直接使用 LLVM IR,你可以将 function-instrument 字符串属性添加到你的函数中,以获得与 C/C++/Objective-C 源代码级别属性类似的效果

define i32 @always_instrument() uwtable "function-instrument"="xray-always" {
  ; ...
}

define i32 @never_instrument() uwtable "function-instrument"="xray-never" {
  ; ...
}

你还可以设置 xray-instruction-threshold 属性,并为函数在进行 instrumentation 之前应包含的指令数提供数字字符串值。

define i32 @maybe_instrument() uwtable "xray-instruction-threshold"="2" {
  ; ...
}

特殊情况文件

可以通过使用特殊情况文件而不是将属性添加到原始源文件中来赋予属性。你可以使用它来标记某些函数和类,使其永远不进行 instrumentation、始终进行 instrumentation 或使用来自文件的第一个参数日志进行 instrumentation。文件的格式如下所述

# Comments are supported
[always]
fun:always_instrument
fun:log_arg1=arg1 # Log the first argument for the function

[never]
fun:never_instrument

这些文件可以通过 -fxray-attr-list= 标志提供给 clang。你可能通过该标志的多个实例加载多个文件。

XRay 运行时库

XRay 运行时库是 compiler-rt 项目的一部分,它实现了执行插入的 instrumentation 点的 patching 和 unpatching 的运行时组件。当你使用 clang 链接你的二进制文件和 -fxray-instrument 标志时,它将自动链接 XRay 运行时。

XRay 运行时的默认实现将在 main 启动之前启用 XRay instrumentation,这适用于生命周期较短的应用程序。此实现还会记录所有函数入口和出口事件,这可能会导致生成的跟踪中包含大量记录。

默认情况下,XRay 跟踪的文件名是 xray-log.XXXXXX,其中 XXXXXX 部分是随机生成的。

这些选项可以通过程序运行时期间的 XRAY_OPTIONS 环境变量来控制,我们在下面列出了这些选项及其默认值。

选项

类型

默认值

描述

patch_premain

bool

false

是否在 main 之前 patching instrumentation 点。

xray_mode

const char*

""

main 之前安装和初始化的默认模式。

xray_logfile_base

const char*

xray-log.

XRay 日志文件的文件名基础。

verbosity

int

0

运行时详细级别。

除了环境变量之外,你还可以提供你自己的 const char *__xray_default_options(void) 函数的定义,该函数返回选项字符串。此方法有效地在程序构建时提供默认选项。例如,你可以创建一个额外的源文件(例如 xray-opt.c ),其中包含以下 __xray_default_options 定义

__attribute__((xray_never_instrument))
const char *__xray_default_options() {
  return "patch_premain=true,xray_mode=xray-basic";
}

并将其与你要 instrumentation 的程序链接

clang -fxray-instrument prog.c xray-opt.c ...

即使不设置 XRAY_OPTIONS,instrumentation 后的二进制文件也将默认使用 ‘patch_premain=true,xray_mode=xray-basic’。

请注意,你仍然可以使用运行时期间的 XRAY_OPTIONS 覆盖由 __xray_default_options 指定的选项。

如果你选择不使用 XRay 运行时附带的默认日志记录实现,和/或控制 XRay instrumentation 运行的时间/方式,你可以直接使用 XRay API 来执行此操作。为此,你需要包含 compiler-rt xray 目录中的 xray_log_interface.h。我们下面列出了重要的 API 函数

  • __xray_log_register_mode(...):针对字符串模式标识符注册日志记录实现。该实现是 xray/xray_log_interface.h 中定义的 XRayLogImpl 的实例。

  • __xray_log_select_mode(...):选择要安装的模式,与字符串模式标识符关联。只有使用 __xray_log_register_mode(...) 注册的实现才能使用此函数选择。

  • __xray_log_init_mode(...):此函数允许初始化和重新初始化已安装的日志记录实现。有关详细信息,请参阅 xray/xray_log_interface.h,它是 XRay compiler-rt 安装的一部分。

一旦日志记录实现被初始化,就可以通过 __xray_log_finalize() 函数最终确定实现来“停止”它。最终确定例程与初始化相反。最终确定后,可以通过 __xray_log_flushLog() 函数清除实现的数据。对于支持内存处理的实现,这些实现应注册一个迭代器函数,以通过 __xray_log_set_buffer_iterator(...) 提供对数据的访问,这允许调用 __xray_log_process_buffers(...) 函数的代码处理内存中的数据。

所有这些都在 xray/xray_log_interface.h 头文件中得到更好的解释。

基本模式

XRay 支持基本日志记录模式,该模式将跟踪应用程序的执行,并定期附加到单个日志。可以通过在 XRAY_OPTIONS 环境变量中设置 xray_mode=xray-basic 来安装/启用此模式。与 patch_premain=true 结合使用,这可以允许从开始到结束跟踪应用程序。

与通过 __xray_log_select_mode(...) 安装的所有其他模式一样,可以通过 __xray_log_init_mode(...) 函数配置实现,提供模式字符串和标志选项。基本模式特定的默认值可以在 XRAY_BASIC_OPTIONS 环境变量中提供。

飞行数据记录器模式

XRay 支持一种日志记录模式,该模式允许应用程序仅捕获固定数量的内存事件。飞行数据记录器 (FDR) 模式的工作方式非常类似于飞机的“黑匣子”,它不断将数据记录到固定大小的循环缓冲区队列中的内存中,并以编程方式提供数据,直到缓冲区被最终确定和刷新。要在你的应用程序上使用 FDR 模式,你可以将 xray_mode 变量设置为 XRAY_OPTIONS 环境变量中的 xray-fdr。FDR 模式实现的附加选项可以在 XRAY_FDR_OPTIONS 环境变量中提供。可以通过调用 __xray_log_init_mode("xray-fdr", <configuration string>) 来完成编程配置,一旦它被选择/安装。

当缓冲区刷新到磁盘时,结果是由 XRay FDR 格式描述的二进制跟踪格式

当 FDR 模式开启时,它将持续写入和回收内存缓冲区,直到日志记录实现被最终确定 – 此时它可以被刷新并在以后重新初始化。要以编程方式执行此操作,我们遵循下面提供的工作流程

// Patch the sleds, if we haven't yet.
auto patch_status = __xray_patch();

// Maybe handle the patch_status errors.

// When we want to flush the log, we need to finalize it first, to give
// threads a chance to return buffers to the queue.
auto finalize_status = __xray_log_finalize();
if (finalize_status != XRAY_LOG_FINALIZED) {
  // maybe retry, or bail out.
}

// At this point, we are sure that the log is finalized, so we may try
// flushing the log.
auto flush_status = __xray_log_flushLog();
if (flush_status != XRAY_LOG_FLUSHED) {
  // maybe retry, or bail out.
}

FDR 模式实现的默认设置将创建与基本日志实现类似命名的日志,但将具有不同的日志格式。所有跟踪分析工具(和跟踪读取库)将支持所有版本的 FDR 模式格式,因为我们在未来会添加更多功能和记录类型。

注意: 我们不承诺永久支持我们将来更新的日志版本。格式的弃用将在开发者邮件列表中宣布和讨论。

跟踪分析工具

我们目前在 LLVM 中有一个跟踪分析工具的雏形,可以在 tools/llvm-xray 目录中找到。llvm-xray 工具目前支持以下子命令

  • extract:从二进制文件中提取 instrumentation map,并将其作为 YAML 返回。

  • account:执行基本函数调用统计,具有用于排序和输出格式的各种选项(支持 CSV、YAML 和控制台友好的 TEXT)。

  • convert:将 XRay 日志文件从一种格式转换为另一种格式。我们可以将二进制 XRay 跟踪(基本模式和 FDR 模式)转换为 YAML、flame-graph 友好的文本格式,以及 Chrome Trace Viewer (catapult) <https://github.com/catapult-project/catapult> 格式。

  • graph:生成 XRay 跟踪中找到的函数之间函数调用关系的 DOT 图。

  • stack:从 XRay 跟踪中的函数调用时间线重建函数调用堆栈。

这些子命令使用作为 XRay 库一部分的各种库组件,这些库组件与 LLVM 发行版一起分发。这些是

  • llvm/XRay/Trace.h:一个跟踪读取库,用于方便地将受支持形式的 XRay 跟踪加载到方便的内存表示中。所有处理跟踪的分析工具都使用此实现。

  • llvm/XRay/Graph.h:graph 子命令使用的半通用图类型,用于方便地表示具有与边和顶点关联的统计信息的函数调用图。

  • llvm/XRay/InstrumentationMap.h:一个方便的工具,用于分析 XRay-instrumented 目标文件和二进制文件中的 instrumentation map。extractstack 子命令使用此特定库。

最小化二进制文件大小

XRay 支持几种不同的 instrumentation 点,包括 function-entryfunction-exitcustomtyped 点。可以使用 -fxray-instrumentation-bundle= 标志单独启用这些点。例如,如果你只想 instrumentation 函数入口和自定义点,你可以指定

clang -fxray-instrument -fxray-instrumentation-bundle=function-entry,custom ...

这将完全省略其他 sled 类型,从而减小二进制文件大小。你还可以使用 instrumentation 组仅 instrumentation 函数的采样子集。例如,要仅 instrumentation 四分之一的可用函数,请调用

clang -fxray-instrument -fxray-function-groups=4

将基于函数名称的哈希值任意选择一个子集。要采样不同的子集,你可以使用 -fxray-selected-function-group= 指定一个组号,范围为 0 到 xray-function-groups - 1。这些选项可以一起用于生成具有不同 instrumentation 子集的多个二进制文件。如果所有你需要的是在运行时控制在任何给定时间跟踪哪些函数,则最好使用 XRay 运行时库的 __xray_patch_function() 方法选择性地 patching 和 unpatching 你需要的单个函数。

未来工作

目前正在努力扩展围绕 XRay instrumentation 系统构建的工具集。

跟踪分析工具

  • 正在进行与工具集成或开发工具以可视化来自 XRay 跟踪的发现的工作。特别是,stack 工具正在扩展以输出允许绘制和探索每个调用堆栈中时间持续时间的格式。

  • 对于大型 instrumentation 二进制文件,生成的 XRay 跟踪的大小可能会很快变得难以管理。我们正在努力集成剪枝技术和启发式方法,以便分析工具筛选跟踪并仅显示相关信息。

更多平台

我们期待为将 XRay 移植到更多架构和操作系统做出贡献。