uprobe与krobe对应,动态附加到用户态调用函数的切入点称为uprobe,相比如kprobe 内核函数的稳定性,uprobe 的函数由开发者定义。uprobe是用户态的探针,它和kprobe是相对应的,kprobe是内核态的探针。uprobe需要制定用户态探针在执行文件中的位置,插入探针的原理和kprobe类似。
下面是对uprobe使用方法的简单介绍。比如跟踪下面程序中的callee函数调用:
#include <stdio.h>int callee(void)
{printf("hello world.\n");return 0;
}int main(void)
{ callee();return 0;
}
Makefile
分别编译地址加载无关和地址相关两个版本的应用程序,分别抓取其PROBE结果。
all:gcc -O0 -no-pie -fno-pic -g main.c -o main-nopiegcc -O0 -g main.c -o main
clean:rm -fr main main-nopie
增加一个新的uprobe event,命令如下(在可执行文件main的0x63a偏移处增加一个uprobe探针),当callee函数被调用时,截获调用事件信息:
$ readelf -s main|grep callee64: 000000000000063a 23 FUNC GLOBAL DEFAULT 14 callee
$
# echo 'p:callee /home/zlcao/uprobe/main:0x63a' > /sys/kernel/debug/tracing/uprobe_events
增加一个返回事件信息:
echo 'r:callee_ret /home/zlcao/uprobe/main:0x63a' >> /sys/kernel/debug/tracing/uprobe_events
之后使用如下命令查看注册的EVENT事件
cat /sys/kernel/debug/tracing/uprobe_events
定义以后,使能所有的events:
echo 1 > /sys/kernel/debug/tracing/events/uprobes/enable
之后执行测试用例,执行完毕后,通过如下命令查看探测记录。
cat /sys/kernel/debug/tracing/trace
可以看到,callee函数的调用和返回事件都被触发了,输出显示给我们uprobe被触发时:main-27731、程序PC 0x55f1b22cb63a,uretprobe被触发时:PC从函数入口0x55f1b22cb63a返回0x55f1b22cb65a。
之后,通过如下命令关闭探测。
echo 0 > /sys/kernel/debug/tracing/events/uprobes/enable
最后,使用如下命令清除掉所有注册的events。
echo > /sys/kernel/debug/tracing/uprobe_events
添加探测点的步骤比较麻烦,perf很贴心地添加了一键添加探测点的功能,只需要执行一个简单的命令即可:
# perf probe -x ./main-nopie callee
# perf probe -x ./main-nopie callee%return
之后使能探测,运行用例。
echo 1 > /sys/kernel/debug/tracing/events/probe_main/enable
PIE和NO-PIE应用的区别
通过上面的例子可以看到,默认编译的PIE应用和非PIE应用都可以被事件机制探测调试,那么它们的区被是什么呢?通过下面几幅图可以看出,PIE应用的符号地址是相对的,加载地址是随机的,而非PIE应用的加载地址和运行地址完全是由应用符号表提供的,运行时加载器必须遵守,不能更改。
参考资料
Linux内核文档,Documentation/trace/uprobetracer.rst