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_USDTBPF_PROG 类似,但是专用于 USDT 探针。

USDT 程序本质上是一种用户态入口探测(uprobe),其设计用途是挂载到 USDT 跟踪点上。这类跟踪点能够传递参数,但与遵循 ABI(应用程序二进制接口)的函数参数不同,跟踪点的参数可存在于进程内的任意位置。跟踪点会使用 GAS(GNU 汇编器)操作数来描述这些参数,而该宏则允许你将程序中的这些参数定义为 “如同普通参数一样实际传递给程序” 的形式。

args

如何确认函数参数的数量和类型?比如此处的 void *arg1, int arg2, void *arg3

  1. 要跟踪的 setjmplibc 里的函数,因此在指定的 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
  1. 读取 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 为探针的参数列表(格式为<大小>@<寄存器或内存位置>)。
  1. 参数列表中:
  • 8@%rdi:第一个参数占 8 字节,位于 %rdi 寄存器;
    • 指向 jmp_buf 上下文缓冲区的指针
  • -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) {
/* trigger our BPF programs */
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
/*
* Manually attach to libc.so we find.
* We specify pid here, so we don't have to do pid filtering in BPF program.
*/
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: 修改程序挂载的可选配置。