使用 libbpf 开发 eBPF 程序
1. eBPF 程序的工作流程
一般来说,eBPF
程序分为用户空间程序和内核程序两部分。
内核程序以eBPF
字节码的形式运行在虚拟机中。虚拟机执行字节码的过程本质上就是在模拟 CPU 执行机器码的过程,所以比直接执行机器码效率低很多。不过可以使用JIT
来解决虚拟机执行效率不够高的问题,它先把eBPF
字节码编译成对应的机器码,运行时直接执行机器码即可。
当程序绑定的事件触发后,内核程序就开始执行。执行逻辑一般是采集一些数据,然后通过特定的数据结构传回用户空间。
开发内核程序时,我们肯定不会直接编写eBPF
字节码。更通用的做法是采用 C 语言去编写代码,然后使用 clang & llvm 工具将 C 源码编译成eBPF
字节码。
对于用户空间程序,它负责将eBPF
字节码加载到内核,从内核中读取采集到的数据。既然要和内核交互,必然要通过系统调用来完成,这里会使用到的系统调用就是bpf()
。直接使用这个系统调用开发难度很大,所以为了开发方便,有一些库对该调用进行了抽象和封装,向开发者提供更容易使用的 Api。
2. libbpf 库
2.1 libbpf 介绍
libbpf 库其实是 linux 内核源码的一部分,位于 tools/lib/bpf/ 路径下。为了使用这个库,把完整的源码下载下来实在不方便。所以内核的大佬们又新开了一个仓库,将 libbpf 的源码单独拎出来维护(https://github.com/libbpf/libbpf)。README 的第一行也作了说明。
2.2 libbpf 安装
在上一篇文章(在 WSL 上使用 eBPF)中,我们下载了完整的内核源码,因此可以直接在WSL/tools/lib/bpf/
目录下进行编译安装。但是WSL
这部分代码应该没有和社区版的内核保持同步更新,编译后的版本太低了。为了保证 eBPF 程序正常运行,且能体验到一些新特性,这里直接从(https://github.com/libbpf/libbpf)下载源码,进行编译安装。
|
|
make install
主要做了两件事:将头文件拷贝到了系统默认搜索路径/usr/include/bpf/
目录下,将静态库和动态库拷贝到了/lib64/
目录下。
接下来,就可以进行愉快的程序开发了。
3. eBPF 程序开发
下面将使用 libbpf 开发一个 eBPF 程序,用来监测系统上的新生进程,获取新进程的可执行文件路径和启动参数。涉及的内容比较多,并不是 hello world 级别,需要一定开发经验。建议观看完整代码:execve.bpf.c。
3.1 内核部分代码
|
|
首先要找到程序的 hook 点(可以简单理解为某个内核函数或者某个执行路径),然后编写我们自定义的代码逻辑。也就是说,当这个内核函数被调用,或者路径被执行到时,就会触发我们的程序。程序的逻辑一般都是抓取一些信息,然后上报到用户空间进行处理(打印输出,进一步保存等)。
编写程序主要是利用头文件中的helper functions
来获取需要的信息,通过特定的 MAP 数据结构来保存、传递信息。
3.2 生成 skeleton 头文件
一般来说,内核程序开发完成后,会利用 clang & llvm 将源程序编译成eBPF
目标程序。它是一个内核可加载,ELF 格式的二进制文件。随后在用户空间代码中,将这个.o
文件加载进内核。
通过 readelf 工具,我们可以查看详细信息。
|
|
这里是代码中用SEC()
宏自定义塞进去的两个 section。
这种传统的加载方式比较复杂,也不利于程序分发。bpftool
工具提供了一种方法,它能根据目标文件生成 skeleton 类型的.h
头文件,它本质上是eBPF
内核部分程序的 C 语言抽象。
这样一来,在用户空间代码部分,通过include
这个头文件,就可以很方便地访问内核程序中定义的变量,代码主体和 MAP 数据结构,并且提供了加载程序的抽象接口。更详细的介绍可以参考:bpftool-gen 和 https://nakryiko.com/posts/libbpf-bootstrap/#the-user-space-side
使用如下命令,可以将exec.bpf.o
转化为exec.skel.h
。
|
|
skeleton 文件的核心就是:1. 一个结构体exec_bpf
,代表着对内核部分程序的抽象。从中可以看到 maps,skeleton 等结构,这就是程序使用的 MAP 和代码主体(以字节序列的形式保存起来了);2. 一些接口,用来加载 eBPF 程序。从面向对象的角度来看,它们其实就是exec_bpf
的成员函数。
|
|
3.3 用户空间代码
|
|
这里省略了核心代码,只保留了主要逻辑,可以看到整个过程行云流水,一气呵成。最后编译生成可执行文件。
|
|
如果libelf
这个库不存在,通过如下命令进行安装:
|
|
4. 实现效果
要使用 root 权限启动eBPF
程序,设置资源上限和加载程序到内核都需要root
权限。
源码:https://github.com/cheneytianx/ebpf_demo/tree/main/execve