引
在前面的文章中,我们学习了关于k[ret]probe和Tracepoint的相关知识,这些都是针对于内核的追踪;那么,如果我们要追踪用户态的相关信息,又该如何实现呢?
u[ret]probe
和k[ret]probe类似,u[ret]probe也是可以帮助我们动态追踪的工具,只不过前者追踪的是内核的信息,而后者追踪的是用户态的信息:
kprobe:内核函数进入(entry)执行,可以获取到参数;kretprobe:内核函数返回(return)后执行,可以获取到返回值;uprobe:用户态函数进入时执行,可以获取到参数;uretprobe:用户态函数返回时执行,可以获取到返回值;
我们尝试编写一个追踪c调用strlen的代码,先编写C部分:
这里首先我们用到了PT_REGS_PARM1函数,这个函数可以获取到我们追踪的函数的第一个参数,放到strlen的背景中,就是我们需要统计的字符。
接着我们获取到了对应的pid,接下来我们需要把字符从用户态拷贝到内核态,所以我们需要使用bpf_probe_read_user函数,将strlen的字符串安全的拷贝进来。
最后我们需要将这个字符串记录到哈希表中做留存,这里我们使用到了lookup_or_try_init函数,如果找到了对应的key就返回结果,否则初始化一下再进行返回。
接下来我们需要将这个函数挂载到strlen函数上:
这里使用到了attach_uprobe函数,将count挂载到了c库的strlen函数上;如果我们想要挂载到一个可执行文件上,直接填写其路径即可。
最后我们处理一下eBPF追踪到的结果:
运行一下:
USDT
USDT是user statically-defined tracing的缩写,和我们前面介绍的Tracepoint一致,也是基于用户态的静态钩子触发,所以也需要已编译好的代码中有对应的钩子。
我们这次要追踪一个node编写的httpserver,先让ChatGPT帮我们生成一个简单的webserver:
现在编写eBPF部分:
有如下的几个注意点:
bpf_usdt_readarg(6, ctx, &addr):将第六个参数拷贝到对应的地址里;bpf_probe_read_user:将用户空间的addr指向的数据复制到path变量;
接着我们挂载一下:
将我们前面写的do_trace函数挂载到了http__server__reques上,有如下的注意点:
u = USDT(pid=int(pid)):初始化对指定pid的USDT的追踪;u.enable_probe:将我们编写的do_trace挂载到http__server_request上;BPF(text=bpf_text, usdt_contexts=[u]):创建BPF的时候传入我们初始化的USDT;
运行追踪看看:
可以看出,node的USDT并没有开启,所以我们需要重新编译一下:
# 首先按照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]probe和Tracepoint结合起来记忆。