1 个不稳定版本

0.1.0 2021年2月28日

#11#示波器

MIT/Apache

2KB

cargo-trace

为你的Rust程序提供的ebpf示波器。当探针触发时,在ebpf中展开堆栈并增加事件计数。程序终止后,它会生成火焰图以进行分析。

还包括一个专注于为Rust程序构建分析工具的bpf库。大部分代码非常通用,因此可以轻松地适应其他bpf用例。支持大多数由bpftrace支持的探针类型。

ebpf(扩展的伯克利包过滤器)是内核中的虚拟机。虽然最初是为了在用户空间上下文切换中过滤数据包而设计的,但如今它也可以用作性能分析的瑞士军刀。

单行代码

以下单行代码展示了不同的功能

# Find out where your program is consuming the most cpu time
cargo trace profile:hz:99
# Find out where your program is making the most memory allocations
cargo trace uprobe:/usr/lib/libc-2.33.so:malloc

几乎工作但并不完美

# Find out where your program is blocking
cargo trace kprobe:finish_schedule_task

与其他性能分析工具的比较

  • perf 依赖于 perf_event_open_sys 来采样堆栈。每次采样时,整个堆栈都被复制到用户空间。堆栈展开作为后处理步骤在用户空间执行。这浪费了带宽,并且可能是一个安全问题,因为它可能会泄露秘密,如私钥。

  • straceltrace 使用 ptrace 来设置断点并操作程序状态。它们停止程序,然后使用 libunwind 在用户空间展开堆栈,并在之后继续程序。

  • heaptrackstackusage 使用 LD_PRELOAD 来对 mallocfreepthread_create 进行代码插桩。它们使用 libunwind 来捕获同一进程中的堆栈跟踪。这不能推广到系统调用或其他内核函数。

  • valgrind 在沙盒中运行程序。这有一个相当大的开销。

  • bpftrace 基于bpf但专注于分析系统性能而不是应用性能。这意味着它不能进行基于dwarf的回溯,依赖于整个发行版都编译了启用了帧指针。由于其低开销,它可以在生产系统中用于监控。

背景

内核

  • bpf_sys 用于加载bpf程序并读写用于从用户空间读取/写入数据的bpf映射。

  • perf_event_open_sys 用于创建各种探针类型。bpf程序可以通过ioctl附加。

  • ptrace 允许设置断点并控制被测试程序的执行。这是为了防止程序在回溯表被加载之前运行,并将程序推进到_start符号(在所有动态库都已加载之后)。

地址映射

在加载程序和动态库等时,它们会在一个地址加载。当你有一个指令指针时,你需要能够找出程序是从哪个文件加载的以及程序加载地址是什么。这可以通过在当前进程中使用 libc::dl_iterate_phdr 或通过读取外部程序的 /proc/$pid/maps 来完成。

回溯

基于当前指令地址,确定二进制文件和加载地址。为了找到相对指令,从指令地址减去加载地址。使用此偏移量,在 .ehframe 部分中找到合适的dwarf回溯表行。回溯表行具有它有效的起始/结束地址和每个cpu寄存器的dwarf程序,当执行时将产生前一个帧的寄存器值。可以经验性地确定几乎所有dwarf程序都由单个指令组成,并使用仅三种不同的指令。 rip+offsetrsp+offset*cfa+offset,其中 cfa 是前一个帧的 rsp 值。回溯的结果是一组指令指针。

注意:内核堆栈使用不同的回溯机制,可以使用bpf辅助函数 bpf_get_stack 捕获回溯,并通过在 /proc/kallsyms 中查找符号进行符号化。

符号化

一旦我们有一个指令指针数组,我们再次需要地址映射来找到每个ip的加载地址,从中减去以获得偏移量和二进制文件的路径。二进制文件可能包含调试符号,在这种情况下,我们可以返回函数名称和位置。如果没有,我们可以在符号表中查找符号名称。这就是编译器生成 main_start 符号和从动态库中生成的函数进行符号化的方式。

注意:如果安装了来自您发行版的strip二进制文件的调试符号,它们位于elf build_id 中,这是一个包含在每个elf文件中的20字节随机序列。但这些对于cargo-trace工作是不必要的。

许可

MIT OR Apache-2.0

无运行时依赖