eBPF:CO-RE 如何制作BTF文件

在未开启CONFIG_DEBUG_INFO_BTF的内核上制作BTF文件并使用。

事出必有因,我还是简单说明一下背景吧,我为什么会起这个标题。最近,我正在学习BPF并将其应用于网络观测,我选择在CentOS 8(4.18.0-348.7.1.el8_5.x86_64)上进行实践,这期间BPF程序都运行稳定没有任何问题。直到最近几天,我打算在龙蜥Anolis OS 8.6(4.19.91-26.an8.x86_64)上运行BPF程序来自BCC的tcpstate时,问题出现了:

1
2
3
4
5
libbpf: failed to find valid kernel BTF
libbpf: Error loading vmlinux BTF: -3
libbpf: failed to load object 'tcpstates_bpf'
libbpf: failed to load BPF skeleton 'tcpstates_bpf': -3
failed to load BPF object: -3

经过一番网络搜索后,问题的原因聚焦于以下几点:

  • libbpf加载vmlinux BTF文件失败,在默认的路径下找不到BTF

  • 内核CONFIG_DEBUG_INFO_BTF选项没有开启,想要开启只能重新编译内核

  • BTF文件可以通过特定的方法从vmlinux中导出

  • libbpf提供了自定义指定BTF文件的方式加载BPF程序…

其实网上的相关资料已经很多了(可见参考链接),但作为一个coder光看是远远不够的,还得是自己动手实践,切身体会过才算掌握,那就进入正题吧。

内核对BTF的支持

如果你在运行BPF程序也遇到了上述的报错,不妨执行cat /boot/config-$(uname -r) |grep BTF检查一下当前内核是否开启BTF对应的配置,如果开启则会输出CONFIG_DEBUG_INFO_BTF=y表明当前内核在编译时已经开启BTF选项。

如果没有,那么很不幸,请动手自己生成吧。

关于CONFIG_DEBUG_INFO这项内核配置,可以查看BCC:Kernel Configuration for BPF Features[1]给出的解释。

BTFCONFIG_DEBUG_INFO_BTFGenerate deduplicated BTF type information from DWARF debug info

获取vmlinux

为了制作导出当前内核的BTF文件,我们需要获取没有进行strip过的vmlinux文件,因为vmlinux中包含了.BTF section,有以下几种方式可以获取到当前内核版本的vmlinux文件:

  • 下载内核代码重新编译生成(我没有试过)

  • 根据博客[2]的说法,系统一般存在vmlinux的压缩文件/boot/vmlinuz-$(uname -r),可通过解压该文件得到vmlinux,但是我没试成功.

  • 第三种方法是,下载内核对应的kernel-debuginfo 包,其中包含了对应的vmlinux文件,本文采取此方法获取.

kernel-debuginfo是用于调试操作系统内核的信息文件。在编译内核时,为了减小体积,通常会将调试信息剥离。而kernel-debuginfo文件则包含了这些调试信息,如函数和变量的名称、行号等。开发者可以使用kernel-debuginfo来定位和解决内核中的问题,通过获取内核执行路径、函数调用堆栈和变量状态等信息。它提供了更准确的调试环境,帮助开发者追踪错误,加快调试过程,提高软件质量。

在下载kernel-debuginfo包之前,我提一下龙蜥社区的coolbpf[3]项目。这个项目是我在学习BPF的时候遇到的,因为其生成能:

  • 高版本特性通过 kernel module 方式补齐到低版本,即能在低版本支持BPF特性;

  • 能自动生成BTF文件

  • 还有其他优点在此不冗述….

我心想这么厉害,那我去看一下coolbpf是怎么实现的,然后发现其关于BTF自动生成模块其实是用爬虫自动发现内核对应的kernel-debuginfo包,然后使用pahole制作BTF文件。

注意:有可能需要下载的是kernel-debug-debuginfo包,如果你在使用kernel-debuginfo制作时失败了,不妨试试kernel-debug-debuginfo包。

kernel-debug-debuginfo是Kconfig里面开启了各种debug特性的版本的debuginfo, kernel-debuginfo是普通正常线上运行的内核的debuginfo。

关于coolbpf如何获取vmlinux和制作BTF文件可其项目的以下文件:

1
2
coolbpf\btf\btfhive\getVmlinux.py
coolbpf\btf\btfhive\vers.py

如下是部分Linux系统的kernel-debuginfo包下载目录,具体可根据自己内核版本下载。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
aliosUrl_x86_64 = "http://yum.tbsite.net/taobao/7/x86_64/current/kernel-debuginfo/"
aliosStableUrl_x86_64 = "http://yum.tbsite.net/taobao/7/x86_64/stable/kernel-debuginfo/"
centos7Url_x86_64 = "http://debuginfo.centos.org/7/x86_64/"
centos8Url_x86_64 = "http://debuginfo.centos.org/8/x86_64/Packages/"
centosSteamUrl_x86_64 = "http://debuginfo.centos.org/8-stream/x86_64/Packages/"
alinux219Url_x86_64 = "http://mirrors.aliyun.com/alinux/2.1903/plus/x86_64/debug/"
alinux2Url_x86_64 = "http://mirrors.aliyun.com/alinux/2/kernels/x86_64/debug/"
alinux3Url_x86_64 = "http://mirrors.aliyun.com/alinux/3/plus/x86_64/debug/"
ubuntuUrl_x86_64 = "http://ddebs.ubuntu.com/pool/main/l/linux/"
anolis8x4Url_x86_64 = "http://mirrors.aliyun.com/anolis/8.4/BaseOS/x86_64/debug/Packages/"
anolis8x2Url_x86_64 = "http://mirrors.aliyun.com/anolis/8.2/BaseOS/x86_64/debug/Packages/"
anolis8Url_x86_64 = "http://mirrors.aliyun.com/anolis/8/BaseOS/x86_64/debug/Packages/"
anolis7Url_x86_64 = "http://mirrors.aliyun.com/anolis/7.7/os/x86_64/debug/Packages/"
anolis8x4Url1_x86_64 = "http://mirrors.openanolis.cn/anolis/8.4/Plus/x86_64/debug/Packages/"
anolis8x2Url1_x86_64 = "http://mirrors.openanolis.cn/anolis/8.2/Plus/x86_64/debug/Packages/"
anolis8Url1_x86_64 = "http://mirrors.openanolis.cn/anolis/8/Plus/x86_64/debug/Packages/"
anolis7Url1_x86_64 = "http://mirrors.openanolis.cn/anolis/7.7/Plus/x86_64/debug/Packages/"
anolis7X9Url1_x86_64 = "http://mirrors.openanolis.cn/anolis/7.9/Plus/x86_64/debug/Packages/"

结合我实践的龙蜥系统版本,下载对应的kernel-debuginfo包:

1
wget https://mirrors.aliyun.com/anolis/8.6/Plus/x86_64/debug/Packages/kernel-debuginfo-4.19.91-26.an8.x86_64.rpm

然后解压rpm包,即可看到对应的vmlinux文件,可以看到该文件十分大(639M):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 解压
rpm2cpio kernel-debuginfo-4.19.91-26.an8.x86_64.rpm | cpio -div
# 查看
ll
总用量 513632
-rw-r--r-- 1 root root 465054432 5月  24 2022 kernel-debuginfo-4.19.91-26.an8.x86_64.rpm
drwxr-xr-x 3 root root        17 8月  17 16:43 usr
# 查看vmlinux
ls -lh usr/lib/debug/lib/modules/4.19.91-26.an8.x86_64/
总用量 639M
drwxr-xr-x  4 root root   32 8月  17 16:43 internal
drwxr-xr-x 12 root root  128 8月  17 16:43 kernel
-rwxr-xr-x  1 root root 639M 8月  17 16:44 vmlinux

使用pahole制作BTF文件

通常,BTF 信息在 .BTF 和 .BTF.ext ELF 部分,或者可以在原始 BTF 文件中找到,通过命令readelf -S vmlinux |grep BTF可以查看到vmlinux中包含了.BTF的section。有两种方法可以从vmlinux中导出BTF部分[4]:

  • pahole

  • llvm

首先需要下载pahole源码进行编译安装(在ubuntu22上是可以直接apt install pahole的),下载路径在https://git.kernel.org/pub/scm/devel/pahole/pahole.git,然后根据about的说明编译即可:

1
2
3
4
5
6
7
8
# download
git clone https://git.kernel.org/pub/scm/devel/pahole/pahole.git/
git submodule update --init --recursive

# make install
mkdir build && cd build
cmake -D__LIB=lib ..
make install

然后从vmlinux中提取BTF部分:

1
2
3
4
# 生成.btf
pahole --btf_encode_detached "vmlinux.btf" usr/lib/debug/lib/modules/4.19.91-26.an8.x86_64/vmlinux
# copy
cp vmlinux.btf /tmp/

至此,我们完成了通过kernel-debuginfo制作BTF文件的全过程,接下来便是如何使用。

使用自定义的BTF

先看一下BTF配置:

1
2
3
4
5
6
7
8
9
cat /boot/config-$(uname -r) | grep CONFIG_DEBUG_INFO_BTF
# 啥也没有输出

sudo ./tcpstates
libbpf: failed to find valid kernel BTF
libbpf: Error loading vmlinux BTF: -3
libbpf: failed to load object 'tcpstates_bpf'
libbpf: failed to load BPF skeleton 'tcpstates_bpf': -3
failed to load BPF object: -3

当我们得到自制的BTF后,我们可以通过指定BTF路径让BPF加载我们BTF,代码修改如下:

1
2
LIBBPF_OPTS(bpf_object_open_opts, open_opts , .btf_custom_path = "tmp/vmlinux.btf");
obj = tcpstates_bpf__open_opts(&open_opts);

重新编译运行即可:

其实,我们在libbpf的源码libbpf/src/btf.c约4770行左右可以发现,在不指定BTF文件的情况下,libbpf会默认从以下路径查找系统的BTF文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/*
 * Probe few well-known locations for vmlinux kernel image and try to load BTF
 * data out of it to use for target BTF.
 */
struct btf *btf__load_vmlinux_btf(void)
{
    const char *locations[] = {
        /* try canonical vmlinux BTF through sysfs first */
        "/sys/kernel/btf/vmlinux",
        /* fall back to trying to find vmlinux on disk otherwise */
        "/boot/vmlinux-%1$s",
        "/lib/modules/%1$s/vmlinux-%1$s",
        "/lib/modules/%1$s/build/vmlinux",
        "/usr/lib/modules/%1$s/kernel/vmlinux",
        "/usr/lib/debug/boot/vmlinux-%1$s",
        "/usr/lib/debug/boot/vmlinux-%1$s.debug",
        "/usr/lib/debug/lib/modules/%1$s/vmlinux",
    };

前人的捷径

你所想过的前人们都想过了,你所困惑的题前人们已探索出答案,而你需要做的仅是找到他们并虚心请教。

不难发现,上面制作BTF文件的过程很容易变成一个自动化的过程,于是BTFhub登场了。

btfhub、btfhub-archive

btfhub[5]是为那些没有嵌入BTF的已发行Linux系统的内核,而生成其对应的BTF文件的仓库,由Aqua Security开源提供。其目前为主流大多数已发行的Linux系统提供对应的BTF文件,例如以下是x86_64架构的centos 8的BTF文件集合。

可以看到,已经覆盖了全部的centos 8系统内核。此外,该仓库下的docs文档还详细地介绍了如何使用pahole生成BTF文件。

libbpf

其实再过头仔细阅读libbpf的README会发现:libbpf支持了CO-RE即一次编译到处运行,而无需像BCC一样在运行时依赖Clang/LLVM(BCC是在运行时才使用LLVM将BPF程序的C代码编译成BPF的字节码,然后加载)。

而在不同内核版本中,许多类型或结构体有所差异, 而libbpf是如何做到CO-RE兼容这些差异的呢?答案就是使用BTF,在的Andrii Nakryiko博客[7]中给出了具体的说明。

总结

本文介绍了如何通过kernel-debuginfo获取vmlinux,并使用pahole生成vmlinux.btf文件,并在内核未开启CONFIG_DEBUG_INFO_BTF配置的系统上使用之成功运行。

下一步,关于BTF我该继续弄清楚:

  • what is BTF ? 现在只知道怎么用BTF,还没有深入理解弄清楚BTF究竟为何物。

  • 最小化生成BTF,由vmlinux导出的BTF文件还是很大的,要是适配多个未开启BTF的内核,那么安装包会很大….. 所以,需要看看如何按需最小化生成BTF文件。

参考链接

  1. BCC: Kernel Configuration for BPF Features

  2. BPF CO-RE–在旧内核上运行(自行制作BTF) | woodpenker’s blog

  3. aliyun/coolbpf

  4. btfhub/docs/how-to-use-pahole.md

  5. aquasecurity/btfhub

  6. btfhub/docs/supported-distros.md

  7. BPF CO-RE reference guide

哦吼是一首歌。
Built with Hugo
Theme Stack designed by Jimmy