1.1 前言
eBPF 是一项令人兴奋的强大技术,其允许开发者在 Linux 内核的核心处添加自定义代码功能,并且我们还可以通过编写简单的 C 或 Go 程序与加载到内核中的 eBPF 程序交互,用于加载或读取数据。运行在内核中的 BPF 程序可以检查所附加进程(或内核模块)的内存数据。为此,eBPF 程序需要确切了解处理涉及的数据结构类型,这可以通过 #include "vmlinux.h" 实现。在本中,我将详细介绍 vmlinux.h 是什么以及为什么你在编写 eBPF 程序开始就应该使用它。
1.2 vmlinux.h 概述
确切说,vmlinux.h 是使用工具生成的代码文件。它包含了系统运行 Linux 内核源代码中使用的所有类型定义。当我们编译 Linux 内核时,会输出一个称作 vmlinux 的文件组件,其是一个 ELF[1] 的二进制文件,包含了编译好的可启动内核。vmlinux 文件通常也会被打包在主要的 Linux 发行版中。
内核中的 bpftool 工具其中功能之一就是读取 vmlinux 文件并生成对应的 vmlinux.h 头文件。vmlinux.h 会包含运行内核中所使用的每一个类型定义,因此该文件的比较大。
生成 vmlinux.h 文件的命令如下:
$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h包含该 vmlinux.h,就意味着我们的程序可以使用内核中使用的所有数据类型定义,因此 BPF 程序在读取相关的内存时,就可以映射成对应的类型结构按照字段进行读取。
例如,Linux 中的 task_struct[2] 结构用于表示进程,如果 BPF 程序需要检查 task_struct 结构的值,那么首先就需要知道该结构的具体类型定义。

1.3 CORE 一处编译,到处运行
由于 vmlinux.h 文件是由当前运行内核生成的,如果你试图将编译好的 eBPF 程序在另一台运行不同内核版本的机器上运行,可能会面临崩溃的窘境。这主要是因为在不同的版本中,对应数据类型的定义可能会在 Linux 源代码中发生变化。
但是,通过使用 libbpf 库提供的功能可以实现 "CO:RE"(一次编译,到处运行)。libbpf 库定义了部分宏(比如 BPF_CORE_READ),其可分析 eBPF 程序试图访问 vmlinux.h 中定义的类型中的哪些字段。如果访问的字段在当前内核定义的结构中发生了移动,宏 / 辅助函数会协助自动找到对应字段【译者注:对于可能消失的字段,也提供了对应的辅助函数 bpf_core_field_exists】。因此,我们可以使用当前内核中生成的 vmlinux.h 头文件来编译 eBPF 程序,然后在不同的内核上运行它【译者注:需要运行的内核也支持 BTF 内核编译选项】。
1.4 总结
通过生成包含所有 Linux 内核类型的 vmlinux. h 头文件,我们可以在编写 eBPF 程序时对内核类型相关的头文件依赖。在下一篇文章中,我将使用 vmlinux.h 的便利性、libpf 的灵活性和 Go 的安全性来编写 eBPF 程序,-- 敬请期待!【译者注:下一篇参见这里[3]】
该文章首次出现在 Grant Seltzer 的博客[4]上。
原文地址:https://blog.aquasec.com/vmlinux.h-ebpf-programs
作者:Grant Seltzer
时间:2021 年 4 月 30 号
参考资料
[1]
ELF: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
[2]task_struct: https://elixir.bootlin.com/linux/latest/source/include/linux/sched.h#L649
[3]这里: https://www.grant.pizza/blog/libbpf-beginners-part-one/
[4]博客: https://www.grant.pizza/blog/vmlinux-header/
本文介绍vmlinux.h在eBPF程序中的作用及其如何帮助开发者理解Linux内核数据结构。通过使用vmlinux.h,eBPF程序能够准确地读取和解析内核中的数据类型。此外,还介绍了如何利用libbpf实现跨内核版本的兼容性。
1633

被折叠的 条评论
为什么被折叠?



