uprobe 是处理用户态入口探测与退出探测的示例,对应的两种探测类型分别是 uprobe(用户态入口探测)和 uretprobe(用户态返回探测)。

它会将 uprobe 和 uretprobe 类型的 BPF 程序,分别挂载到自身的函数(uprobed_add() 和 uprobed_sub())上,并通过 bpf_printk() 宏,分别记录这些函数的输入参数与返回结果。用户态函数每秒会触发一次。

运行

1
2
3
./uprobe
libbpf: loading object 'uprobe_bpf' from buffer
...
1
2
3
4
5
$ cat /sys/kernel/debug/tracing/trace_pipe
uprobe-16616 [001] ...11 349920.493709: bpf_trace_printk: uprobed_add ENTRY: a = 22, b = 23
uprobe-16616 [001] ...11 349920.493797: bpf_trace_printk: uprobed_add EXIT: return = 45
uprobe-16616 [001] ...11 349920.493818: bpf_trace_printk: uprobed_sub ENTRY: a = 484, b = 22
uprobe-16616 [001] ...11 349920.493824: bpf_trace_printk: uprobed_sub EXIT: return = 462

add

NOTE:

仅声明探针类型,需要在加载时绑定目标

The BPF side

  • 探针本身不绑定任何目标程序/函数,灵活性高(可复用探针跟踪不同程序)。

SEC(“uprobe”)

1
2
3
4
5
6
SEC("uprobe")
int BPF_KPROBE(uprobe_add, int a, int b)
{
bpf_printk("uprobed_add ENTRY: a = %d, b = %d", a, b);
return 0;
}

BPF_KPROBE 是一个宏。在编写挂载在函数起始处的 kprobe(内核探针)程序时使用。

uprobe_add 是函数名,int a, int b 为函数的参数。

SEC(“uretprobe”)

1
2
3
4
5
6
SEC("uretprobe")
int BPF_KRETPROBE(uretprobe_add, int ret)
{
bpf_printk("uprobed_add EXIT: return = %d", ret);
return 0;
}

BPF_KRETPROBE 是一个宏。在编写挂载在函数返回的 kprobe(内核探针)程序时使用。

The user-space side

uprobed_add

__attribute__((noinline))asm volatile (""); 都是为了防止编译器将函数内联(inline)。

1
2
3
4
5
__attribute__((noinline)) int uprobed_add(int a, int b)
{
asm volatile ("");
return a + b;
}

LIBBPF_OPTS

1
LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);

使用 LIBBPF_OPTS 声明一个类型是 bpf_uprobe_opts,变量名为 uprobe_opts 的结构体。

1
2
3
4
5
6
7
8
9
10
struct bpf_uprobe_opts {
/* size of this struct, for forward/backward compatibility */
size_t sz;
size_t ref_ctr_offset;
__u64 bpf_cookie;
bool retprobe;
const char *func_name;
enum probe_attach_mode attach_mode;
size_t :0;
};

bpf_program__attach_uprobe_opts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* Attach tracepoint handler */
uprobe_opts.func_name = "uprobed_add";
uprobe_opts.retprobe = false;

/* uprobe/uretprobe expects relative offset of the function to attach
* to. libbpf will automatically find the offset for us if we provide the
* function name. If the function name is not specified, libbpf will try
* to use the function offset instead.
*/
skel->links.uprobe_add = bpf_program__attach_uprobe_opts(skel->progs.uprobe_add,
0 /* self pid */, "/proc/self/exe",
0 /* offset for function */,
&uprobe_opts /* opts */);
if (!skel->links.uprobe_add) {
err = -errno;
fprintf(stderr, "Failed to attach uprobe: %d\n", err);
goto cleanup;
}
  • skel->progs.uprobe_add:要附加的 BPF 程序。此处为 uprobe.bpf.c 中定义的 uprobe_add 函数。
  • 0 /* self pid */:附加 uprobe 的进程 ID。 0 表示当前进程,-1 表示所有进程。
  • "/proc/self/exe":包含函数符号的二进制文件的路径。
  • 0 /* offset for function */:函数符号在二进制文件中的 offset。
  • &uprobe_opts /* opts */:options。

sub

NOTE:

显式绑定目标程序和函数

The BPF side

SEC(“uprobe//proc/self/exe:uprobed_sub”)

1
2
3
4
5
6
SEC("uprobe//proc/self/exe:uprobed_sub")
int BPF_KPROBE(uprobe_sub, int a, int b)
{
bpf_printk("uprobed_sub ENTRY: a = %d, b = %d", a, b);
return 0;
}

uprobe/ 后紧跟着目标程序绝对路径,/proc/self/exe 表示绑定当前进程。再用 : 后加函数名 uprobed_sub

  • 无需加载时指定额外目标,探针直接绑定到指定程序的指定函数。
  • 支持跟踪系统库函数,自定义程序函数

SEC(“uretprobe//proc/self/exe:uprobed_sub”)

1
2
3
4
5
6
SEC("uretprobe//proc/self/exe:uprobed_sub")
int BPF_KRETPROBE(uretprobe_sub, int ret)
{
bpf_printk("uprobed_sub EXIT: return = %d", ret);
return 0;
}

The user-space side

uprobed_sub

1
2
3
4
5
__attribute__((noinline)) int uprobed_sub(int a, int b)
{
asm volatile ("");
return a - b;
}

uprobe_bpf__attach

由于已经在 SEC 中提供了目标程序的绝对路径和函数名,uprobe_sub/uretprobe_sub 只需 attach 即可。

1
2
3
4
5
6
7
8
/* Let libbpf perform auto-attach for uprobe_sub/uretprobe_sub
* NOTICE: we provide path and symbol info in SEC for BPF programs
*/
err = uprobe_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to auto-attach BPF skeleton: %d\n", err);
goto cleanup;
}

call func

1
2
3
4
5
6
7
for (i = 0;; i++) {
/* trigger our BPF programs */
fprintf(stderr, ".");
uprobed_add(i, i + 1);
uprobed_sub(i * i, i);
sleep(1);
}

cleanup

1
2
cleanup:
uprobe_bpf__destroy(skel);