结合例子学习eBPF与bcc:u[ret]probe与USDT

526 阅读3分钟

在前面的文章中,我们学习了关于k[ret]probeTracepoint的相关知识,这些都是针对于内核的追踪;那么,如果我们要追踪用户态的相关信息,又该如何实现呢?

u[ret]probe

k[ret]probe类似,u[ret]probe也是可以帮助我们动态追踪的工具,只不过前者追踪的是内核的信息,而后者追踪的是用户态的信息:

  • kprobe:内核函数进入(entry)执行,可以获取到参数;
  • kretprobe:内核函数返回(return)后执行,可以获取到返回值;
  • uprobe:用户态函数进入时执行,可以获取到参数;
  • uretprobe:用户态函数返回时执行,可以获取到返回值;

我们尝试编写一个追踪c调用strlen的代码,先编写C部分:

count

这里首先我们用到了PT_REGS_PARM1函数,这个函数可以获取到我们追踪的函数的第一个参数,放到strlen的背景中,就是我们需要统计的字符。

接着我们获取到了对应的pid,接下来我们需要把字符从用户态拷贝到内核态,所以我们需要使用bpf_probe_read_user函数,将strlen的字符串安全的拷贝进来。

最后我们需要将这个字符串记录到哈希表中做留存,这里我们使用到了lookup_or_try_init函数,如果找到了对应的key就返回结果,否则初始化一下再进行返回。

接下来我们需要将这个函数挂载到strlen函数上:

attach_uprobe

这里使用到了attach_uprobe函数,将count挂载到了c库的strlen函数上;如果我们想要挂载到一个可执行文件上,直接填写其路径即可。

最后我们处理一下eBPF追踪到的结果:

print

运行一下:

strlen_count

USDT

USDTuser statically-defined tracing的缩写,和我们前面介绍的Tracepoint一致,也是基于用户态的静态钩子触发,所以也需要已编译好的代码中有对应的钩子。

我们这次要追踪一个node编写的httpserver,先让ChatGPT帮我们生成一个简单的webserver

webserver

现在编写eBPF部分:

do_trace

有如下的几个注意点:

  • bpf_usdt_readarg(6, ctx, &addr):将第六个参数拷贝到对应的地址里;
  • bpf_probe_read_user:将用户空间的addr指向的数据复制到path变量;

接着我们挂载一下:

attach_probe

将我们前面写的do_trace函数挂载到了http__server__reques上,有如下的注意点:

  • u = USDT(pid=int(pid)):初始化对指定pidUSDT的追踪;
  • u.enable_probe:将我们编写的do_trace挂载到http__server_request上;
  • BPF(text=bpf_text, usdt_contexts=[u]):创建BPF的时候传入我们初始化的USDT

运行追踪看看:

trace

可以看出,nodeUSDT并没有开启,所以我们需要重新编译一下:

# 首先按照DTrace
git clone --depth=1 https://github.com/nodejs/node
cd node
./configure --with-dtrace
make

接着运行我们前面生成的webserver

./node ../tracing/node.js

接着运行nodejs_http_server.py

./nodejs_http_server.py 143985

请求3000端口试试:

请求

可以看到,我们成功追踪到了请求的路径。

小结

本文我们学习了用户态的追踪,包含如下几种:

  • u[ret]probe:动态追踪用户态函数;
  • USDT:追踪用户态静态钩子;

我们可以将他们和k[ret]probeTracepoint结合起来记忆。