上手 ebpf

一直想学习一下 ebpf 这个东东,最近买了本《Linux 内核观测技术 BPF》,准 备系统的研究一下。
原以为有了书之后学起来就相当轻松了,可以我发现书上的第一个例子就编译不 过。
书上只给了部分的源码,还需要去下载配套的 github 项目,这也没啥关系,不过下 载后编译也是编译不过。
编译不过的报错也不过是 types.h 头文件找不到,解决了头文件找不到的问题 后发现又有新的问题。
网上搜索了下发现基本上都是在介绍 ebpf xxx,没有看到一篇讲如何上手 ebpf, 只能自己搞搞喽,在这里记录下遇到的问题。

安装必要的程序

ebpf 程序的编译依赖 llvm 与 clang,需要安装这两个程序。debian like 系 统可以执行如下命令进行安装:
sudo apt-get install clang llvm

如果你要使用 python 作为 BCC 工具的前端来编写代码,你可能会遇到如下报 错信息:
ImportError: No module named bcc

可以执行如下命令安装之:
sudo apt-get install python-bpfcc

克隆项目代码

linux-observability-with-bpf 项目中能够找到《Linux内核观测技术 BPF》一 书中的配套代码,它的 git 项目地址如下:
https://github.com/bpftools/linux-observability-with-bpf.git
直接使用 git 进行克隆即可,克隆完成后进入到源码目录中,发现它有如下的 目录结构:
LICENSE README.md Vagrantfile code img

从 README.md 开始

在直接使用 code 目录前,一定要先阅读 README.md!一定要先阅读 README.md
README.md 文件中描述了使用这个项目需要预先执行的步骤,具体过程如下:

安装必要的工具

debian like 系统中可以执行如下命令:
$ sudo apt update

$ sudo apt install build-essential git make libelf-dev clang strace tar

$ bpfcc-tools linux-headers-$(uname -r) gcc-multilib llvm

获取内核源码

首先执行 uname -r 查看内核版本信息,然后下载相应的内核源码,这里需要注 意的是内核版本不能过低,至少要是 5.0.0 的内核。
进入到内核源码的 tools/lib/bpf 目录中,编译并安装

配置 libbpf.so

进入到项目源码的 code/chapter-x 中执行 make
注意这一步可能需要根据实际情况修改 Makefile 中的路径配置。

第一个问题:升级内核

我使用的是 debian 10 系统,它使用的内核版本是 4.19,要运行一些 ebpf 的例子 至少需要 5.0.0 的内核版本,这样我需要升级个内核先。
我已经预先克隆了内核的 git 仓库,这样我只需要检出 v5.0 版本的内核代码 就行了。
执行如下命令来完成:
$ git checkout -b v5.0 v5.0

这里的两个 v5.0,第一个 v5.0 表示的是新创建的分支名,第二个 v5.0 表示 的是项目的 tag 名称。
内核 config 仍然使用我之前裁剪过的 config 文件,其下载地址如下:
https://download.csdn.net/download/Longyu_wlz/12900957
直接拷贝为 .config 后,然后执行 make oldconfig,然后继续执行 make -j,发现需要重新设定其它的内核选项,一路 Enter 选择使用默认值。
编译完成后执行如下命令安装内核及内核模块:
$ sudo make modules_install

$ sudo make install

make install 的时候会自动生成 initrd 并更新引导。
重启系统,在 grub 中选择使用 5.0 版本的内核引导系统,进入系统后,执 行 uname -a 查看内核信息,确定使用的是 5.0 版本的内核。相关操作记录 如下:
$ linux-git $ uname -a

Linux debian-10 5.0.0+ 
#9 SMP Sun Nov 15 22:05:48 CST 2020 x86_64 GNU/Linux

第二个问题:编译 libbpf.so

libbpf.so 源码位于内核源码树的 tools/lib/bpf 目录中,直接进入到这个目 录中编译即可。
编译过程记录如下:
$ make


Auto-detecting system features:

... libelf: [ on ]

... bpf: [ on ]


CC libbpf.o

CC bpf.o

CC nlattr.o

CC btf.o

CC libbpf_errno.o

CC str_error.o

CC netlink.o

CC bpf_prog_linfo.o

LD libbpf-in.o

LINK libbpf.a

LINK libbpf.so

LINK test_libbpf

编译完成后会生成 libbpf.so 文件,执行 sudo make install 安装此动态库。在我的系统中,它被安装到了 /usr/local/lib64 目录中,这个目录并不会被动 态库链接器搜索,这样直接运行使用了这个动态库的 ebpf 程序就会有如下报 错:
error while loading shared libraries: libbpf.so: cannot open shared object file: No such file or directory

解决方法如下:
在 /etc/ld.so.conf 中添加 /usr/local/lib64 这一行,运行 sudo ldconfig 重新生成动态库配置信息 成功执行的示例信息如下:
$ ld.so.conf.d $ sudo ldconfig -v 2>/dev/null | grep libbpf

libbpf.so -> libbpf.so

编译运行 hello world ebpf 程序

完成上面的配置过程后就可以编译 hello world ebpf 程序了,它位于项目源码 的 code/chapter-2/hello_world 中,其源码如下:
#include<linux/bpf.h>
#define SEC(NAME) __attribute__((section(NAME), used))

staticint(*bpf_trace_printk)
(
constchar
 *fmt, 
int
 fmt_size,

                               ...)
= (
void
 *)BPF_FUNC_trace_printk;


SEC(
"tracepoint/syscalls/sys_enter_execve"
)

intbpf_prog(void *ctx)
{

char
 msg[] = 
"Hello, BPF World!"
;

  bpf_trace_printk(msg, 
sizeof
(msg));

return0
;

}


char
 _license[] SEC(
"license"
) = 
"GPL"
;

这个程序,声明监控调用 execve 的事件,每监控到一个事件就调用 bpf_trace_printk 来打印 Hello, BPF World! 字符串,其细 节我就不进一步描述了,感兴趣的读者可以阅读《Linux内核观测技术BPF》的第 二章。
在编译之前还需要修改下 Makefile 中的内核源码路径,它默认是在 /kernel-src 目录下的,需要根据实际情况修改为真实的路径。
我执行如下 sed 命令将 Makefile 中的内核源码路径修改为我系统中的真实路 径:
$ sed -i 's;/kernel-src;/home/longyu/linux-git;' ./Makefile

修改完 Makefile 后直接执行 make 编译即可,相关过程记录如下:
$ make

clang -O2 -target bpf -c bpf_program.c -I/home/longyu/linux-git/tools/testing/selftests/bpf -o bpf_program.o

clang -o monitor-exec -lelf -I/home/longyu/linux-git/samples/bpf -I/home/longyu/linux-git/tools/lib -I/home/longyu/linux-git/tools/perf -I/home/longyu/linux-git/tools/include -L/usr/local/lib64 -lbpf \

/home/longyu/linux-git/samples/bpf/bpf_load.c loader.c

编译完成后会生成一个 monitor-exec 程序,需要以 root 权限来运行。使用普 通用户执行将会报如下错误信息:
$ ./monitor-exec

bpf_load_program() err=1

The kernel didn't load the BPF program

使用 root 用户执行后过一会就会打印 Hello,BPF World!,示例信息如下;
$ sudo ./monitor-exec

sogou-qimpanel-31885 [004] .... 11022.245597: 0: Hello, BPF World!

sh-31886 [005] .... 11022.247254: 0: Hello, BPF World!

sogou-qimpanel-31887 [005] .... 11022.249711: 0: Hello, BPF World!

sh-31889 [004] .... 11022.251231: 0: Hello, BPF World!

sh-31891 [007] .... 11022.251429: 0: Hello, BPF World!

这个程序使用了内核的 tracepoint 来监控执行 execve 的事件,当此事件发生 后,它会打印出 Hello,xxx 的信息,这个 execve 事件代表了一个新进程的执 行,我们在上面的示例中看到的第一列就是程序名与其 pid 号。
这个 hello world 程序的编译过程分为两个步骤:

使用 clang 编译生成 bpf 机器码

使用 clang 编译 loader.c 生成加载第一步生成的机器码的程序 loader.c 函数的源码如下所示:
#include"bpf_load.h"
#include<stdio.h>

intmain(int argc, char **argv)
{

if
 (load_bpf_file(
"bpf_program.o"
) != 
0
) {

printf
(
"The kernel didn't load the BPF program\n"
);

return-1
;

  }


  read_trace_pipe();


return0
;

}

上述代码其实只调用了 load_bpf_file 来加载第一步编译生成的 ebpf 程序, load_bpf_file 是 libbpf.so 中提供的接口。

strace 跟踪 hello world ebpf 程序

使用 strace 跟踪 hello world ebpf 程序能够看到如下关键的系统调用:
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_TRACEPOINT, insn_cnt=
14
, insns=
0xd04c80
, license=
"GPL"
, log_level=
0
, log_size=
0
, log_buf=NULL, kern_version=KERNEL_VERSION(
0
0
0
), prog_flags=
0
, prog_name=
""
, prog_ifindex=
0
, expected_attach_type=BPF_CGROUP_INET_INGRESS}, 
112
) = 
4
其实 bpf 系统调用就是用户态程序与内核中的 ebpf 虚拟机交互的接口,libbpf.so 中提供的 api 实际上是对 bpf 系统调用的封装,可以想到 load_bpf_file 其实就是 指定 BPF_PROG_LOAD 等参数调用 bpf 系统调用来是实现的。

吐槽吐槽《Linux内核观测技术BPF》

一开始我并没有下载该书的配套源码,直接按照书中的描述编译 hello world 程序,命令行信息如下:
$ clang -O2 -target bpf -c hello.c -o bpf_program.o 

结果就报了 asm/types.h cannot find 的错误,整了一下发现它使用的是我系 统中 /usr/include/ 目录中的内核头文件,看来应该就是个版本问题。
我想应该可以通过安装内核头文件来解决这个问题。
首先在内核源码树根目录中执行 make help 中找到如下内容:
headers_install - Install sanitised kernel headers to INSTALL_HDR_PATH

(default: ./usr)

可以看到它默认是使用 ./usr 目录,可以通过设定 INSTALL_HDR_PATH 来指定 其它安装目录。
我执行如下命令将这些头文件安装到指定目录中,示例过程如下:
$ make INSTALL_HDR_PATH="/home/longyu/ebpf/" headers_install

INSTALL include/asm-generic (36 files)

INSTALL include/drm (26 files)

......

安装后 ls 查看,确定 asm/types.h 存在,相关信息如下:
$ ls /home/longyu/ebpf/include/asm/types.h 

/home/longyu/ebpf/include/asm/types.h

执行 clang -O2 -target bpf -I /home/longyu/ebpf/include -c bpf_program.c -o bpf_program.o 后成功编译。
我觉得这个问题不应该遇到,但是确实遇到了,书里面也没有相关的描述信息, 不得不吐槽这本书的内容,这些最为基本的东西为啥不能写的清楚一些?也不差 那几页么?
第二、三章内容看描述还行,但是一尝试编译就会遇到问题,还是有点垃圾欧。

其它的 ebpf 组件

内核源码树的 tools/bpf 这个目录中存放了了 ebpf 的汇 编,反汇编,调试程序源码。
在编译的过程中我遇到了如下几个问题:

找不到 bfd.h

报错信息如下:
/home/longyu/linux-git/tools/bpf/bpf_jit_disasm.c:23:10: fatal error: bfd.h: 没有那个文件或目录

#include <bfd.h>

^~~~~~~

解决方案如下:
$ apt-get install binutils-dev

找不到 radline/readline.h

报错信息如下:
/home/longyu/linux-git/tools/bpf/bpf_dbg.c:43:10: fatal error: readline/readline.h: 没有那个文件或目录

#include <readline/readline.h>

解决方案如下:
$ sudo apt-get install libreadline-dev

安装了这几个开发包后能够成功编译,编译生成的文件信息如下:
bpf_asm bpf_dbg bpf_jit_disasm bpftool/bpftool

这几个程序目前还没有用起来,后面用起来了在描述吧。
内核源码树中 tools/testing/selftests/bpf 目录中中放了 ebpf 功能的相关测试 用例程序,在编译时遇到了如下问题:
test_verifier.c:28:10: fatal error: sys/capability.h: 没有那个文件或目录

#include <sys/capability.h>

可以通过执行如下命令解决:
$ sudo apt-get install libcap-dev

这个目录中的测试用例程序可以作为编写 ebpf 程序的参考,这要比文档资料更 有价值。
samples/bpf 目录中也有一些 ebpf demo 程序,也是很好的参考资料。

内核源码树中与 ebpf 相关的一手资料

内核源码树中,除了上面说过的源码资料外,还有几个目录中有一些文档资料。
tools/bpf/bpftool/Documentation 中有 bpftool 命令的资料 Documentation/bpf 中有 bpf 的一些描述资料

总结

纸上得来终觉浅,绝知此事要躬行。书上的知识一定要经过实践的检验才能确定 它是否正确,学习从来都不只是阅读与记忆这么简单!

原文:
https://blog.csdn.net/Longyu_wlz/article/details/109900096
继续阅读
阅读原文