LLVM-XRay: 函数调用跟踪系统简介
文章目录
最近看到一篇文章 Do you Know LLVM XRay?,是用来做函数调用跟踪分析的,简单写了一下关于这套系统的笔记。
1、XRay
谷歌研发了 XRay 作为轻量级的 C/C++ 函数调用跟踪系统(a function call tracing system),可以在函数入口和出口(entry/exit) 处记录精确的时间点。这套系统可以部署到生产环境,在运行时动态地开启或关闭。
XRay 主要是为了解决在生产环境很难调试分析延迟和缓慢(latency) 的问题。现有的采样方案只能大致近似,无法精确,而手工跟踪又非常耗时和容易出错。一些采样方式还需要有系统级别的支持才行。
XRay 包含有多个部分:
- 编译期插桩(compiler-inserted instrumentation)。
- 程序探测的架构(instrumentation framework)。
- 运行时记录(tracing runtime)。
- 分析记录文件的一套工具(a set of tools for analysing traces)。
2、compiler-inserted instrumentation
XRay 会在编译期插入 no-op 空操作指令串到函数的入口和出口,在运行时进行修改来进行函数级别的跟踪,然后有运行时库在必要时进行数据整合和保存。
- 不开启 XRay 时,这些 no-op 指令几乎没有执行的消耗。
- 开启 XRay 时,其运行时库重写这些指令,修改为插桩(instrumentation) 记录函数信息到内存当中。
XRay 采用启发式(heuristic) 来判断哪些函数需要插桩记录。
- 函数需要满足一定的大小,可以通过
-fxray-instruction-threshold= <value>
手动设置,默认为 200。- 一般认为没有循环的小函数不会占用很多的执行时间,根据性价比而言,不值得插桩记录。
- 包含有 non-trivial loops(非标准术语,一个 trivial loop 是编译器可以不附加不利条件(增加 block、消除 inline 的机会等) 地进行全展开(fully unroll)。通常认为 trivial loop 是简单易懂的循环,第二部分判断中与值对比的变量
i < maxLen
,也是第三部分递增的i++
)。- 一般认为 non-trivial loops 这种循环会引入可变的执行时间,用户会希望 XRay 可以记录下这种变化。
- 还有可以通过
__attribute__((xray_always_instrument))
设置函数开启。
XRay 大概会导致 20%~40% 的 CPU 使用率增加,200MB 左右的内存增长,而二进制大小增加 2% 左右。
3、instrumentation framework
在二进制产物当中,会发现有 XRay 相关 compiler-rt 的库被链接进来,比如我本地的 /usr/lib/llvm-14/lib/clang/14.0.0/lib/linux/libclang_rt.xray-fdr-x86_64.a
,这块属于 XRay 的框架。它需要满足以下几点:
- 修改指令的插桩(Patching/Unpatching) 不能把进程或线程停掉。
- 开启或关闭功能(Installing/Uninstalling handlers) 需要保证原子性(atomic)。保证开启时,所有线程都是一致开启的。
- 只修改必要的插桩点(instrumentation points)。
在二进制的 section 当中会存有插桩的 map 表记录重要信息(存有为每个要 patch 的函数计算得到的唯一 id):
patch 的过程就会是,设置寄存器为对应的函数 id,调用对应的跳板(trampoline)。
|
|
3.1、如何保证修改过程是线程安全的?
可以看到,我们是需要重写前面的指令 jmp +9
和 nop
为 mov
和 call
。这里修改前后都是 11 个字节。
修改过程非常巧妙:
首先是先把后面的 5 个字节重写为跳转到对应的 trampoline,这个顺序很重要,因为前面的 jmp 还是可以越过这里的修改。
然后 jmp +9
需要是 2 字节对齐的(为了保证 cache line 的一致性),因为我们要先处理后面的几个字节,这样不会影响前面的 jmp 指令。把 mov N, %r10d
的右半部分先写入到第 3-6 个字节。
最后才改最前面的两个字节。
关闭 patch 就容易多了,直接写入 jmp 在入口,ret 在出口即可。
3.2、handlers
XRay 提供了显式的 APIs 供用户手动设置,主要在 compiler-rt/include/xray/xray_interface.h
文件中。
|
|
4、tracing runtime
记录有两种模式:
- Basic(naive) mode。直接把所有事件都写入到记录当中。适合执行时间短的应用。
- Flight Data Recorder(FDR) mode。用固定大小的环形队列缓存,在内存中像飞机的黑匣子一样不断淘汰旧的,写入新的数据,直到需要时,才会写到磁盘。适合长时间运行的应用和服务。
执行二进制的时候,可以通过设置环境变量来控制 XRay 的行为模式:
5、tools
分析记录文件使用的是 llvm-xray 工具。这部分更多是如何对记录文件进行查看和分析,官方文档和工具的 manual 文档都比较详细了,不做展开。
还可以生成图的形式,看执行次数和运行时间。
还能生成火焰图,在浏览器进行查看。
参考
- Do you Know LLVM XRay?
- XRay: A Function Call Tracing System
- When is a loop “non-trivial”?
- XRay Instrumentation
- What does NOPL do in x86 system?
- 2017 LLVM Developers’ Meeting: D. Michael “XRay in LLVM: Function Call Tracing and Analysis ”
- XRay Flight Data Recorder Trace Format
- RFC: XRay Profiling in LLVM
- Debugging with XRay
文章作者 calssion
上次更新 2022-09-18