本文地址:https://www.ebpf.top/post/intro_vmlinux_h

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 的二进制文件,包含了编译好的可启动内核。vmlinux 文件通常也会被打包在主要的 Linux 发行版中。

内核中的 bpftool 工具其中功能之一就是读取 vmlinux 文件并生成对应的 vmlinux.h 头文件。vmlinux.h 会包含运行内核中所使用的每一个类型定义,因此该文件的比较大。

生成 vmlinux.h 文件的命令如下:

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

包含该 vmlinux.h,就意味着我们的程序可以使用内核中使用的所有数据类型定义,因此 BPF 程序在读取相关的内存时,就可以映射成对应的类型结构按照字段进行读取。

例如,Linux 中的 task_struct 结构用于表示进程,如果 BPF 程序需要检查 task_struct 结构的值,那么首先就需要知道该结构的具体类型定义。

vmlinux.h diagram

1.3 一处编译,到处运行

由于 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 程序,– 敬请期待!【译者注:下一篇参见这里

该文章首次出现在 Grant Seltzer 的博客上。

原文地址:https://blog.aquasec.com/vmlinux.h-ebpf-programs

作者:Grant Seltzer

时间: 2021 年 4 月 30 号