今年来bpf在内核发展迅速,从一开始的网络包过滤,发展到现在可用于trace,TC,权限控制等等,现在Facebook的工程师也将其引入到调度子系统,可见bpf发展迅速。通常我们可以使用BCC (BPF Compiler Collection)来快速的开发原型或者实验工具,通过BCC提供的一套框架,将程序部署之后,BCC首先会唤醒其嵌入的Clang/LLVM编译器提取本地内核头文件(必须确保已从正确的kernel-devel软件包中将其安装在系统上),并即时进行编译。这种方式存在以下问题:
为了解决以上提到的资源占用、兼容性等问题,社区提出了BPF CO-RE(Compile Once, Run Everywhere),通过用户空间的BPF加载器库(libbpf),和编译器(Clang)。通过这些组件来支持编写可移植的BPF程序,使用相同的预编译的BPF程序来处理不同内核之间的差异。更加详细的信息可参考:https://facebookmicrosites.github.io/bpf/blog/2020/02/19/bpf-portability-and-co-re.html。
网上也有大量介绍该特性的文档,本文将会使用一个简单的示例,以命令行的方式构建使用libbpf的应用程序,方便理解相关工程的Makefile或者自己搭建环境。
本次实验使用内核版本为5.10版本:
[root@localhost ~]# uname -r
5.10.0+
确认内核开启BTF特性,以下两个方式均可,如果没有打开,需要开启该选项并重新编译内核(如果编译过程中出现BTF的一个报错,尝试安装dwraves、libdwarves-devel包解决)。
# zcat /proc/config.gz | grep BTF
CONFIG_DEBUG_INFO_BTF=y
# file /sys/kernel/btf/vmlinux
/sys/kernel/btf/vmlinux: data
CO-RE构建方式需要安装bpftool、libbpf、libbpf-devel、elf-utils、kernel-source。
# bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
#include
#include // 需要包含的头文件
char LICENSE[] SEC("license") = "Dual BSD/GPL"; // SEC宏会将此字符串放到一个elf文件中的独立段里面供加载器检查
SEC("tp/syscalls/sys_enter_write") // 此处使用的tracepoint,在每次进入write系统调用的时候触发
int handle_tp(void *ctx)
{
bpf_printk("BPF triggered from PID %d.\n", pid); //可cat /sys/kernel/debug/tracing/trace查看调试信息
return 0;
}
# clang -g -O2 -target bpf -D__TARGET_ARCH_x86 -I/usr/src/kernels/$(uname -r)/include/uapi/ -I/usr/src/kernels/$(uname -r)/include/ -I/usr/include/bpf/ -c minimal.bpf.c -o minimal.bpf.o
# bpftool gen skeleton minimal.bpf.o > minimal.skel.h
查看minimal.skel.h中的内容,大概就知道libbpf是怎么做的兼容了。
#include
#include
#include // rlimit使用
#include
#include "minimal.skel.h" // 这就是上一步生成的skeleton,minimal的“框架”
int main(int argc, char **argv)
{
struct minimal_bpf *skel; // bpftool生成到skel文件中,格式都是xxx_bpf。
int err;
struct rlimit rlim = {
.rlim_cur = 512UL << 20,
.rlim_max = 512UL << 20,
};
// bpf程序需要加载到lock memory中,因此需要将本进程的lock mem配大些
if (setrlimit(RLIMIT_MEMLOCK, &rlim)) {
fprintf(stderr, "set rlimit error!\n");
return 1;
}
// 第一步,打开bpf文件,返回指向xxx_bpf的指针(在.skel中定义)
skel = minimal_bpf__open();
if (!skel) {
fprintf(stderr, "Failed to open BPF skeleton\n");
return 1;
}
// 第二步,加载及校验bpf程序
err = minimal_bpf__load(skel);
if (err) {
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
goto cleanup;
}
// 第三步,附加到指定的hook点
err = minimal_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to attach BPF skeleton\n");
goto cleanup;
}
printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
"to see output of the BPF programs.\n");
for (;;) {
/* trigger our BPF program */
fprintf(stderr, ".");
sleep(1);
}
cleanup:
minimal_bpf__destroy(skel);
return -err;
}
# gcc -I/usr/src/kernels/$(uname -r)/include/uapi/ -I/usr/src/kernels/$(uname -r)/include/ -I/usr/include/bpf/ -c minimal.c -o minimal.o
# gcc minimal.o -lbpf -lelf -lz -o minimal
至此,可执行程序便生成了出来。知道怎么构建的,搭建相关的工程不就是将xxx封装到cmake或者Makefile中了吗!
sample代码来源:https://gitee.com/mirrors_libbpf/libbpf-bootstrap