usdt 是使用 USDT 探针的一个示例。它会将 USDT 类型的 BPF 程序挂载到 libc:setjmp 探针上,该探针会在用户态程序调用 setjmp 函数时被触发(每秒触发一次),并通过 bpf_printk() 宏记录 USDT 探针的参数。
USDT (user statically defined tracepoint,用户态静态定义跟踪点) 用于挂载应用程序代码或用户态库中指定的跟踪点。
setjmp() 是 C 标准库 <setjmp.h> 中的一个宏,用于保存当前的程序执行状态,以便在稍后的某个时候通过 longjmp() 来返回到该状态。
运行 1 2 3 $ ./usdt libbpf: loading object 'usdt_bpf' from buffer ...
1 2 3 $ cat /sys/kernel/debug/tracing/trace_pipe usdt-25683 [006] ...11 2624.434591: bpf_trace_printk: USDT manual attach to libc:setjmp: arg1 = 55d480464080, arg2 = 0, arg3 = 55d48041549d usdt-25683 [006] ...11 2625.434730: bpf_trace_printk: USDT manual attach to libc:setjmp: arg1 = 55d480464080, arg2 = 0, arg3 = 55d48041549d
auto The BPF side usdt.bpf.h 1 2 3 4 #include <vmlinux.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_tracing.h> #include <bpf/usdt.bpf.h>
usdt.bpf.h 定义了一组用于处理用户空间静态跟踪点(USDT)的宏和函数。
SEC(“usdt/libc.so.6:libc:setjmp”) 1 2 3 4 5 6 7 8 9 10 11 12 SEC("usdt/libc.so.6:libc:setjmp" ) int BPF_USDT (usdt_auto_attach, void *arg1, int arg2, void *arg3) { pid_t pid = bpf_get_current_pid_tgid() >> 32 ; if (pid != my_pid) return 0 ; bpf_printk("USDT auto attach to libc:setjmp: arg1 = %lx, arg2 = %d, arg3 = %lx" , arg1, arg2, arg3); return 0 ; }
BPF_USDT BPF_USDT 与 BPF_PROG 类似,但是专用于 USDT 探针。
USDT 程序本质上是一种用户态入口探测(uprobe),其设计用途是挂载到 USDT 跟踪点上。这类跟踪点能够传递参数,但与遵循 ABI(应用程序二进制接口)的函数参数不同,跟踪点的参数可存在于进程内的任意位置。跟踪点会使用 GAS(GNU 汇编器)操作数来描述这些参数,而该宏则允许你将程序中的这些参数定义为 “如同普通参数一样实际传递给程序” 的形式。
args 如何确认函数参数的数量和类型?比如此处的 void *arg1, int arg2, void *arg3。
要跟踪的 setjmp 是 libc 里的函数,因此在指定的 libc.so.6 里列出所有 USDT探针,确认是否存在 setjump 相关的探针。
1 2 $ bpftrace -l 'usdt:/usr/lib/x86_64-linux-gnu/libc.so.6:*' | grep setjmp usdt:/usr/lib/x86_64-linux-gnu/libc.so.6:libc:setjmp
读取 libc.so.6 文件中的 NOTE 段内容,NOTE 段通常包含调试信息、静态探针描述等元数据。
1 2 3 4 5 6 7 8 9 10 11 12 $ readelf --notes /usr/lib/x86_64-linux-gnu/libc.so.6 | grep -A10 setjmp Name: setjmp Location: 0x0000000000044f15, Base: 0x00000000001d6720, Semaphore: 0x0000000000000000 Arguments: 8@%rdi -4@%esi 8@%rax stapsdt 0x0000003b NT_STAPSDT (SystemTap probe descriptors) Provider: libc Name: longjmp Location: 0x00000000000450f3, Base: 0x00000000001d6720, Semaphore: 0x0000000000000000 Arguments: 8@%rdi -4@%esi 8@%rdx stapsdt 0x00000042 NT_STAPSDT (SystemTap probe descriptors) Provider: libc Name: longjmp_target
其中:
Provider 标识程序名
Name 标识探测点的名称
Location 记录了探测点的位置(在程序中的地址偏移)
Arguments 为探针的参数列表(格式为<大小>@<寄存器或内存位置>)。
参数列表中:
8@%rdi:第一个参数占 8 字节,位于 %rdi 寄存器;
-4@%esi:第二个参数占 4 字节,位于 %esi 寄存器;
-4 表示 “取 % esi 寄存器的低 4 字节”;
libc 的 setjmp 本质是 sigsetjmp(env, 0) 的封装(默认不保存信号掩码),% esi 是第二个参数 savesigs,表示是否保存信号掩码(0 = 不保存,非 0 = 保存)
8@%rax:第三个参数占 8 字节,位于 %rax 寄存器。
libc 内部的 jmp_buf 扩展数据指针(指向 jmp_buf 关联的辅助信息)。
The user-space side usdt_bpf__open 1 2 3 4 5 6 7 struct usdt_bpf *skel ;skel = usdt_bpf__open(); if (!skel) { fprintf (stderr , "Failed to open BPF skeleton\n" ); return 1 ; }
usdt_bpf__load 1 2 3 4 5 6 7 skel->bss->my_pid = getpid(); err = usdt_bpf__load(skel); if (!skel) { fprintf (stderr , "Failed to load BPF skeleton\n" ); return 1 ; }
usdt_bpf__attach 1 2 3 4 5 err = usdt_bpf__attach(skel); if (err) { fprintf (stderr , "Failed to attach BPF skeleton\n" ); goto cleanup; }
tigger 1 2 3 4 5 6 static jmp_buf env;static void usdt_trigger () { setjmp(env); }
1 2 3 4 5 6 while (!exiting) { usdt_trigger(); fprintf (stderr , "." ); sleep(1 ); }
cleanup 1 2 cleanup: usdt_bpf__destroy(skel);
manual The BPF side SEC(“usdt”) 1 2 3 4 5 6 7 SEC("usdt" ) int BPF_USDT (usdt_manual_attach, void *arg1, int arg2, void *arg3) { bpf_printk("USDT manual attach to libc:setjmp: arg1 = %lx, arg2 = %d, arg3 = %lx" , arg1, arg2, arg3); return 0 ; }
The user-space side bpf_program__attach_usdt 与 bpf_program__attach_uprobe_opts 的作用类似,用于 USDT(用户态静态定义跟踪点)的挂载,而非挂载到用户态函数的入口或出口。
1 2 3 4 5 6 7 8 9 10 11 skel->links.usdt_manual_attach = bpf_program__attach_usdt( skel->progs.usdt_manual_attach, getpid(), "libc.so.6" , "libc" , "setjmp" , NULL ); if (!skel->links.usdt_manual_attach) { err = errno; fprintf (stderr , "Failed to attach BPF program `usdt_manual_attach`\n" ); goto cleanup; }
skel->progs.usdt_manual_attach:要附加的 BPF 程序。此处为 usdt.bpf.c 中定义的 usdt_manual_attach 函数。
getpid():附加 uprobe 的进程 ID。 0 表示当前进程,-1 表示所有进程。
"libc.so.6":包含指定 USDT 探针(用户态静态定义跟踪点探针)的二进制文件路径。
"libc":USDT 探针的提供者名称。
"setjmp":USDT 探针的名称。
NULL: 修改程序挂载的可选配置。