在bpfTrace中使用USDT
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
本作品 (李兆龙 博文, 由 李兆龙 创作),由 李兆龙 确认,转载请注明版权。
引言
USDT的全称是user-level statically defined tracing
,是一种在用户态中埋点,以支持动态追踪的方案,埋点的优点基本可以阐述为以下三点:
- 防止inline而导致无法监测到函数
- 随版本迭代接口功能可以保证没有变化
- 在不进行追踪时插入一条nop指令,在进行追踪时替换为int3。在不追踪时最大程度节省性能,代价只是一条nop和elf文件中多一个NOTE段,其中存储着probe的一些偏移信息。
不过目前这种USDT的埋点方案我还没有找到如何在SystemTap去使用([3]中描述量一种情况),[1]中描述了systemTap中usdt的使用,我们这篇来看一看如何在bpfTrace中使用USDT,这里我们使用Folly中的Traceing工具(folly/tracing)去实现需求,这里其实Folly中只用到了StaticTracepoint-ELFx86.h
和StaticTracepoint.h
这两个文件而已,所以我们完全可以把这两个文件直接拷贝到我们的项目中,使得项目原生支持USDT。
例子
我们先看一个简单的例子:
#include "StaticTracepoint.hpp"
#include <unistd.h>
int main() {
sleep(5);
int one = 10;
int two = 20;
for (size_t i = 0; i < 100; i++) {
one = two = i;
TRACING_SDT(lizhaolong, yunwenqi, one, two);
}
sleep(10000);
return 0;
}
执行如下指令g++ -fno-omit-frame-pointer -O0 -g main.cpp
,然后我们gdb进去看看汇编,执行disassemble /m main
就可以看到函数对应的源码和汇编了:
我们可以看到bpfTrace没有开始追踪时,这个地方是一条空指令,因为StaticTracepoint-ELFx86.h
中插入汇编时有一条__volatile__
,所以这个nop指令也不会被优化。
当我们把bpfTrace挂上时,即执行如下指令sudo bpftrace -e 'usdt:/home/lizhaolong.lzl/usdt/a.out:lizhaolong:yunwenqi {printf("%d %d\n", arg0, arg1)}'
,我们可以看到这样的输出:
此时gdb进去,可以看到如下显示:
这里其实就是去利用uprobes去执行动态追踪了。
我们也可以在elf文件中看到probe已经存在的证据:
好巧不巧,这里的Location的偏移就是我们设定的probe的起始地址,后面还有后面要提的信号量的偏移。
除了正常的埋点意外,还可以把代码写成下面这样:
#include "StaticTracepoint.hpp"
#include <unistd.h>
// 必须放到全局变量处,这是一个信号量的声明,有extern "C" 前缀
TRACING_SDT_DEFINE_SEMAPHORE(lizhaolong, yunwenqi)
int main() {
sleep(5);
int one = 10;
int two = 20;
for (size_t i = 0; i < 100; i++) {
one = two = i;
if (TRACING_SDT_IS_ENABLED(lizhaolong, yunwenqi)) {
one *= 10;
two *= 10;
TRACING_SDT_WITH_SEMAPHORE(lizhaolong, yunwenqi, one, two);
}
}
sleep(10000);
return 0;
}
其实就是可能在把参数传向probe时可能需要做一些处理,这些处理可能是比较昂贵的,所以自然没有被追踪时我们不希望这些代码被执行,此时可以设定一个信号量,在被追踪时才被设置,其实本质就是一个volatile unsigned short
。
我们可以看到信号量的地址其实已经是写在elf文件中了的。此时我们用一个if判断跳过了昂贵的参数处理过程。
参考:
- https://github.com/agentzh/usdt-sample
- https://github.com/facebook/folly
- Adding User Space Probing to an Application (heapsort example)