Skip to content

eBPF 入门开发实践教程三:在 eBPF 中使用 fentry 监测捕获 unlink 系统调用

eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具。它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。

本文是 eBPF 入门开发实践教程的第三篇,在 eBPF 中使用 fentry 捕获 unlink 系统调用。

Fentry

fentry(function entry)和 fexit(function exit)是 eBPF(扩展的伯克利包过滤器)中的两种探针类型,用于在 Linux 内核函数的入口和退出处进行跟踪。它们允许开发者在内核函数执行的特定阶段收集信息、修改参数或观察返回值。这种跟踪和监控功能在性能分析、故障排查和安全分析等场景中非常有用。

与 kprobes 相比,fentry 和 fexit 程序有更高的性能和可用性。它们的运行速度大约是 kprobes 的 10 倍,因为使用了 BPF trampoline 机制而不是基于断点的旧方法。在这个例子中,我们可以直接访问函数的指针参数,就像在普通的 C 代码中一样,而不需要使用 BPF_CORE_READ 这样的读取帮助程序。

fexit 和 kretprobe 程序最大的区别在于,fexit 程序可以同时访问函数的输入参数和返回值,而 kretprobe 只能访问返回值。这让你可以在一个地方看到完整的函数执行情况。从 5.5 内核开始(x86),fentry 和 fexit 对 eBPF 程序可用。

arm64 内核版本需要 6.0

参考 learning eBPF 文档:

从内核版本 5.5 开始(适用于 x86 处理器;BPF trampoline 支持在 Linux 6.0 之前不适用于 ARM 处理器),引入了一种更高效的机制来跟踪进入和退出内核函数的方式以及 BPF trampoline 的概念。如果您正在使用足够新的内核,fentry/fexit 现在是首选的跟踪进入或退出内核函数的方法。

参考:https://kernelnewbies.org/Linux_6.0#ARM

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

char LICENSE[] SEC("license") = "Dual BSD/GPL";

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;
}

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;
}

这段程序是用 C 语言编写的 eBPF 程序,它使用 BPF 的 fentry 和 fexit 探针来跟踪 Linux 内核函数 do_unlinkat

让我们看看代码的工作原理。首先我们包含了必要的头文件:vmlinux.h 用于访问内核数据结构,bpf_helpers.h 包含 eBPF 帮助函数,bpf_tracing.h 用于跟踪相关功能。然后定义了许可证信息 "Dual BSD/GPL",这是内核加载 eBPF 程序所必需的。

fentry 探针附加到 do_unlinkat 函数的入口点。注意我们可以直接访问 name->name 而不需要任何特殊的帮助函数——这是使用 fentry 而不是 kprobes 的好处之一。我们获取当前进程 PID,然后将其与文件名一起打印到内核日志。

fexit 探针在函数返回时触发,可以同时访问原始参数(dfd 和 name)和返回值(ret)。这让你可以完整地看到函数做了什么。如果 ret 是 0,文件删除成功;如果是负数,说明出错了,你可以看到错误代码。

我们使用 eunomia-bpf 来编译和运行这个示例。你可以从 https://github.com/eunomia-bpf/eunomia-bpf 安装它。

编译运行上述代码:

$ ecc fentry-link.bpf.c
Compiling bpf object...
Packing ebpf object and config into package.json...
$ sudo ecli run package.json
Runing eBPF program...

在另外一个窗口中:

touch test_file
rm test_file
touch test_file2
rm test_file2

运行这段程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出:

$ sudo cat /sys/kernel/debug/tracing/trace_pipe
              rm-9290    [004] d..2  4637.798698: bpf_trace_printk: fentry: pid = 9290, filename = test_file
              rm-9290    [004] d..2  4637.798843: bpf_trace_printk: fexit: pid = 9290, filename = test_file, ret = 0
              rm-9290    [004] d..2  4637.798698: bpf_trace_printk: fentry: pid = 9290, filename = test_file2
              rm-9290    [004] d..2  4637.798843: bpf_trace_printk: fexit: pid = 9290, filename = test_file2, ret = 0

总结

这段程序是一个 eBPF 程序,通过使用 fentry 和 fexit 捕获 do_unlinkatdo_unlinkat_exit 函数,并通过使用 bpf_get_current_pid_tgidbpf_printk 函数获取调用 do_unlinkat 的进程的 ID、文件名和返回值,并在内核日志中打印出来。

编译这个程序可以使用 ecc 工具,运行时可以使用 ecli 命令,并通过查看 /sys/kernel/debug/tracing/trace_pipe 文件查看 eBPF 程序的输出。

如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 或网站 https://eunomia.dev/zh/tutorials/ 以获取更多示例和完整的教程。

Share on Share on