fentry 示例使用 fentryfexit 类型的 BPF 程序进行跟踪。它会将 fentryfexit 跟踪器附加到 do_unlinkat() 函数上,此函数会在文件被删除时被调用,并且会将函数的返回值、进程标识符(PID)以及文件名记录到跟踪管道(trace pipe)中。

  • 与 kprobes 相比,增强了性能和可用性
  • 能够像 c 语言一样直接解引用指针
  • fexit 不仅可以访问入参,还可以访问返回值;而 kretprobe 只能访问返回值
NOTE:

内核版本 5.5 开始支持 fentryfexit

运行

1
2
3
$ ./fentry 
libbpf: loading object 'fentry_bpf' from buffer
...
1
2
3
$ cat /sys/kernel/debug/tracing/trace_pipe
<...>-45337 [004] ...11 107881.547637: bpf_trace_printk: fentry: pid = 45337, filename = test_file
<...>-45337 [004] ...11 107881.547704: bpf_trace_printk: fexit: pid = 45337, filename = test_file, ret = 0
1
2
3
$ cd ~
$ touch test_file
$ rm -rf test_file

The BPF side

SEC(“fentry/do_unlinkat”)

1
2
3
4
5
6
7
8
9
SEC("fentry/do_unlinkat")
int BPF_PROG(do_unlinkat, int dfd, struct filename *name)
{
pid_t pid;

pid = bpf_get_current_pid_tgid() >> 32;
bpf_printk("fentry: pid = %d, filename = %s\n", pid, name->name);
return 0;
}

BPF_PROG(do_unlinkat, int dfd, struct filename *name)

BPF 程序的函数签名为 do_unlinkat,输入参数为 dfdname

args

函数的入参 int dfd, struct filename *name 是如何确定的?

do_unlinkat 是内核函数,查询Linux 源码可获知该函数具有 dfdname 两个输入参数、返回值为一个 int 类型的值。

SEC(“fexit/do_unlinkat”)

1
2
3
4
5
6
7
8
9
SEC("fexit/do_unlinkat")
int BPF_PROG(do_unlinkat_exit, int dfd, struct filename *name, long ret)
{
pid_t pid;

pid = bpf_get_current_pid_tgid() >> 32;
bpf_printk("fexit: pid = %d, filename = %s, ret = %ld\n", pid, name->name, ret);
return 0;
}

The user-space side

open_and_load

1
2
3
4
5
6
/* Open load and verify BPF application */
skel = fentry_bpf__open_and_load();
if (!skel) {
fprintf(stderr, "Failed to open BPF skeleton\n");
return 1;
}

attach

1
2
3
4
5
6
/* Attach tracepoint handler */
err = fentry_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to attach BPF skeleton\n");
goto cleanup;
}

while

1
2
3
4
while (!stop) {
fprintf(stderr, ".");
sleep(1);
}

cleanup

1
2
cleanup:
fentry_bpf__destroy(skel);