页次: 1
上一篇文章中BPF的例子是以BCC的整体框架为基础,本篇介绍一下基于libbpf库函数为基础,结合内核中的bpf的sample为基础编写一个BPF的程序,本篇介绍是以《linux-observability-with-bpf》这本书第二章的例子为基础,由于内核版本的不同,本篇介绍是以Linux5.16内核为基础,Linux5.16内核中的接口函数与书中的给到的程序案例有较大的差别。
1. 下载并编译内核
1) 确定和编译内核版本
下载需要编译的内核版本,本次使用的内核版本为:Linux-5.16.11版本。
2) 修改内核的配置文件,设置CONFIG_DEBUG_INFO_BTF=y,编译调试,
3) 编译内核
make olddefconfig
make -j 4
make modules_install
make install
通过命令grub2-set-default 设置启动的内核
4) 重启机器使用安装的新内核版本:5.16.11.frank+
5) 确定/sys/kernel/btf/vmlinux文件是否存在。
2. 编译安装libbpf库
1) 进入目录tools/lib/bpf 在该目录下执行make install
2) 修改/etc/ld.so.conf 文件,添加/usr/local/lib64 执行ldconfig,查看ldconfig -v 2> /dev/null | grep libbpf
如果没有编译libbpf库,在编译bpf程序中会出现,下面的错误信息
上述准备工作完毕后,有以下两种方式编译bpf的例子,第一种方式,把编写的bpf程序放到sample/bpf目录下,首先编译sample/bpf,
1. 编译内核下samples/bpf目录下的bpf
1) 在编译之前安装必要的工具:
yum -y install binutils-devel
yum -y install readline-devel
yum -y install dwarves libdwarves1 libdwarves1-devel(dwarves版本号最好大于1.17)
yum -y install libcap-devel
2) 在sample/bpf目录下 make
在编译的过程中,确定vmlinux的位置,
make VMLINUX_BTF=/sys/kernel/btf/vmlinux -C samples/bpf
使用vmlinux产生vmlinux.h头文件,CO:RE开发需要vmlinux.h文件,(Compile once, run everywhere)
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
2. 编译自己编写的bpf程序
1)编译通过完成后,修改sample/bpf的目录下的Makefile文件,添加下面的三行代码:
hello-objs := hello_user.o
always-y += hello_kern.o
tprogs-y += hello
hello_user 为我们用户空间的程序名,hello_kern为我们的内核空间程序名。
2)Kernel hello_kern.c程序:
#include <linux/ptrace.h>
#include <linux/version.h>
#include <uapi/linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include "trace_common.h"
SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog(struct pt_regs *ctx) {
char msg[] = "Hello, BPF World!";
bpf_trace_printk(msg, sizeof(msg));
return 0;
}
char _license[] SEC("license") = "GPL";
u32 _version SEC("version") = LINUX_VERSION_CODE;
kernel程序比较简单,意思是在执行到内核中的execve函数时,打印 Hello BPF World!
3) 应用程序 hello_user.c
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <fcntl.h>
#include <unistd.h>
#define DEBUGFS "/sys/kernel/debug/tracing/"
int load_bpf_file(char *filename);
int load_bpf_file(char *path)
{
struct bpf_object *obj;
struct bpf_program *prog;
struct bpf_link *link = NULL;
int progs_fd;
printf("%s\n",path);
obj = bpf_object__open_file(path, NULL);
if (libbpf_get_error(obj))
{
fprintf(stderr, "ERROR: opening BPF object file failed\n");
return 0;
}
if (bpf_object__load(obj))
{
fprintf(stderr, "ERROR: loading BPF object file failed\n");
goto cleanup;
}
prog = bpf_object__find_program_by_name(obj, "bpf_prog");
if (!prog) {
printf("finding a prog in obj file failed\n");
goto cleanup;
}
link = bpf_program__attach(prog);
if (libbpf_get_error(link)) {
fprintf(stderr, "ERROR: bpf_program__attach failed\n");
link = NULL;
goto cleanup;
}
read_trace_pipe();
cleanup:
bpf_link__destroy(link);
bpf_object__close(obj);
return 0;
}
void read_trace_pipe(void)
{
int trace_fd;
trace_fd = open(DEBUGFS "trace_pipe", O_RDONLY, 0);
if (trace_fd < 0)
return;
while (1) {
static char buf[4096];
ssize_t sz;
sz = read(trace_fd, buf, sizeof(buf) - 1);
if (sz > 0) {
buf[sz] = 0;
puts(buf);
}
}
}
int main(int argc, char **argv) {
if (load_bpf_file("hello_kern.o") != 0) {
printf("The kernel didn't load the BPF program\n");
return -1;
}
}
执行上面的程序输出如下结果:
第二种方法: 如果不把编写的bpf示例程序放到,samples/bpf目录下,可以单独写一个makefile文件,内容如下:
CLANG = clang
EXECABLE = monitor-exec
BPFCODE = bpf_program
BPFTOOLS = /data/kernel/v1/linux-stable/samples/bpf
CCINCLUDE += -I/data/kernel/v1/linux-stable/tools/testing/selftests/bpf
LOADINCLUDE += -I/data/kernel/v1/linux-stable/samples/bpf
LOADINCLUDE += -I/data/kernel/v1/linux-stable//tools/lib
LOADINCLUDE += -I/data/kernel/v1/linux-stable/tools/perf
LOADINCLUDE += -I/data/kernel/v1/linux-stable/tools/include
LIBRARY_PATH = -L/usr/local/lib64
BPFSO = -lbpf
CFLAGS += $(shell grep -q "define HAVE_ATTR_TEST 1" /data/kernel/v1/linux-stable/tools/perf/perf-sys.h \
&& echo "-DHAVE_ATTR_TEST=0")
.PHONY: clean $(CLANG) bpfload build
clean:
rm -f *.o *.so $(EXECABLE)
build: ${BPFCODE.c}
$(CLANG) -O2 -target bpf -c $(BPFCODE:=.c) $(CCINCLUDE) -o ${BPFCODE:=.o}
bpfload: build
clang $(CFLAGS) -o $(EXECABLE) -lelf $(LOADINCLUDE) $(LIBRARY_PATH) $(BPFSO) \
loader.c
$(EXECABLE): bpfload
.DEFAULT_GOAL := $(EXECABLE)
1)本程序虽然以《linux-observability-with-bpf》第2章的程序为基础,但是随着内核的更新,采用5.16版本内核时load_bpf_file函数已经被移除了,需要重新调用函数实现load_bpf_file函数。
2)随着bpf和内核版本的不断变化,参考本文时需要重点关注不同的内核版本、bpftool、gcc等各类工具的版本。
3) 内核源代码中的samples/bpf目录下有大量的bpf的示例程序可以参考。
4)使用bcc框架版本的bpf程序和使用libbpf库bpf程序在编写方式上会有所不同,注意不同的接口函数。
5)centos安装libbpf -devel
sed -i -e "s|mirrorlist=|#mirrorlist=|g" /etc/yum.repos.d/CentOS-*
sed -i -e "s|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g" /etc/yum.repos.d/CentOS-*
dnf --enablerepo=PowerTools install libbpf-devel
参考文献:https://blog.aquasec.com/vmlinux.h-ebpf-programs
离线
页次: 1