事出必有因,我还是简单说明一下背景吧,我为什么会起这个标题。最近,我正在学习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]给出的解释。
BTF | CONFIG_DEBUG_INFO_BTF | Generate deduplicated BTF type information from DWARF debug info |
获取vmlinux
为了制作导出当前内核的BTF文件,我们需要获取没有进行strip过的vmlinux文件,因为vmlinux中包含了.BTF section,有以下几种方式可以获取到当前内核版本的vmlinux文件:
kernel-debuginfo是用于调试操作系统内核的信息文件。在编译内核时,为了减小体积,通常会将调试信息剥离。而kernel-debuginfo文件则包含了这些调试信息,如函数和变量的名称、行号等。开发者可以使用kernel-debuginfo来定位和解决内核中的问题,通过获取内核执行路径、函数调用堆栈和变量状态等信息。它提供了更准确的调试环境,帮助开发者追踪错误,加快调试过程,提高软件质量。
在下载kernel-debuginfo包之前,我提一下龙蜥社区的coolbpf[3]项目。这个项目是我在学习BPF的时候遇到的,因为其生成能:
我心想这么厉害,那我去看一下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源码进行编译安装(在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我该继续弄清楚:
参考链接
-
BCC: Kernel Configuration for BPF Features
-
BPF CO-RE–在旧内核上运行(自行制作BTF) | woodpenker’s blog
-
aliyun/coolbpf
-
btfhub/docs/how-to-use-pahole.md
-
aquasecurity/btfhub
-
btfhub/docs/supported-distros.md
-
BPF CO-RE reference guide