eBPF中的vmlinux

说明

在 BPF CO-RE 框架下,vmlinux.h 文件包含了内核的数据结构定义,这些定义是通过解析内核 BTF(BPF Type Format)信息自动生成的。BTF 是一种新的内核数据类型格式,它为内核提供了一种描述数据类型的方法,这对于 BPF CO-RE 的运行至关重要。
在编写 eBPF 程序时,vmlinux.h 文件使得开发者可以在用户空间程序中直接使用内核的数据结构,而无需关心内核版本的差异。这是因为 BPF CO-RE 可以在编译时解析这些数据结构,并生成可以在任何内核版本上运行的 eBPF 字节码。
例如,假设你在 eBPF 程序中需要访问 task_struct 结构,你可以在你的 eBPF 程序中包含 vmlinux.h,然后直接使用 struct task_struct

所以,vmlinux.h 是对于BPF CO-RE编程必不可少的文件。

vmlinux

vmlinux 是未压缩的 Linux 内核映像。它是在 Linux 内核编译过程中生成的一个文件,包含了整个内核的代码(包括内核模式和用户模式的代码)。vmlinux 包含了内核的符号表,因此它经常被用于调试目的。

vmlinux.h 是一个由 BPF CO-RE(Compile Once, Run Everywhere)框架自动生成的头文件。它包含了内核的数据结构定义,这些定义是通过解析 vmlinux 中的 BTF(BPF Type Format)信息自动生成的。

BTF 是一种新的内核数据类型格式,它为内核提供了一种描述数据类型的方法。通过 BTF,BPF CO-RE 可以解析 vmlinux 中的数据结构定义,并将这些定义写入 vmlinux.h 文件。

因此,vmlinuxvmlinux.h 之间的关系是:vmlinux.h 是通过解析 vmlinux 中的 BTF 信息生成的。vmlinux 包含了内核的代码和数据结构定义,而 vmlinux.h 包含了这些数据结构定义的副本,可以在编写 eBPF 程序时使用。

在编写 eBPF 程序时,vmlinux.h 文件使得开发者可以在用户空间程序中直接使用内核的数据结构,而无需关心内核版本的差异。这是因为 BPF CO-RE 可以在编译时解析这些数据结构,并生成可以在任何内核版本上运行的 eBPF 字节码。具体的原理结构图如下所是:

生成vmlinux.h文件

下面的命令是在Ubuntu 20.04上生成的,其他系统可能会有所不同。

安装bpftool

1
sudo apt install linux-tools-common

成功安装之后提示:

1
2
3
4
5
6
7
8
9
10
$bpftool --help
WARNING: bpftool not found for kernel 5.15.0-76

You may need to install the following packages for this specific kernel:
linux-tools-5.15.0-76-generic
linux-cloud-tools-5.15.0-76-generic

You may also want to install one of the following packages to keep up to date:
linux-tools-generic
linux-cloud-tools-generic

按照要求安装:

1
sudo apt install linux-tools-5.15.0-76-generic

生成vmlinux.h文件

1
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

CORE

由于 vmlinux.h 文件是从本机安装的内核生成的,如果试在另一台运行不同内核版本的机器上运行它而不重新编译,程序可能会出现问题。这是因为在不同版本之间,Linux 源代码中的内部结构定义会发生变化。

然而,通过使用 libbpf,可以启用称为CO:RECompile once, run everywhere)的功能。在 libbpf 中定义了一些宏(例如 BPF_CORE_READ),它们将分析您在 vmlinux.h 中定义的类型中尝试访问的字段。如果您要访问的字段已经在运行内核使用的结构体定义中移动,这些宏/辅助函数将为您找到它。无论您是否使用从自己的内核生成的 vmlinux.h 文件编译 bpf 程序,然后在不同的内核上运行,都没有关系。

vmlinux.h的顶部还存在如下的宏定义:

1
2
3
#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record)
#endif

__attribute__((preserve_access_index)) 是 Clang 编译器提供的一个属性,用于告诉编译器在生成代码时保留对结构体成员的访问索引。这个属性通常用于与 eBPF 相关的代码中,以确保结构体成员的访问索引在 eBPF 程序中保持一致。

1
2
3
#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
#pragma clang attribute pop
#endif

这个特性也会在最后一行被关闭。

在 Linux 中定义的宏并没有在 DWARF/BTF 中定义,并且不会成为生成的 vmlinux.h 文件的一部分。

参考

https://yanhang.me/post/2021-vmlinux/
https://www.ebpf.top/post/intro_vmlinux_h/
https://www.grant.pizza/blog/vmlinux-header/