bpf程序能够在一些内核事件触发时运行。bpf程序需要被bpf虚机机进行加载,加载的时候需要知道具体的程序类型。
1. hello word!
一般常见使用C语言子集编写BPF程序,使用LLVM编译器进行编译。LLVM能够编译出加载到内核中执行的汇编代码。bpf程序编译后,内核通过bpf系统调用将程序字节码加载到bpf虚拟机中。
#include <linux/bpf.h>
#define SEC(NAME) __attribute__((section(NAME), used))
static int (*bpf_trace_printk)(const char *fmt, int fmt_size,
...) = (void *)BPF_FUNC_trace_printk;
SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog(void *ctx) {
char msg[] = "Hello, BPF World!";
bpf_trace_printk(msg, sizeof(msg));
return 0;
}
char _license[] SEC("license") = "GPL";
使用SEC属性告知bpf虚拟机什么时候运行这个程序。当检测到execve系统调用跟踪点被执行时,bpf将运行。将会看到消息输出:Hello, BPF World!
还需要指定程序许可证,因为Linux内核采用GPL许可证,所以它只能加载GPL许可证的程序。使用 bpf_trace_printk
在内核中跟踪日志中打印消息。
可以使用clang将程序编译成内核可加载的ELF格式的二进制文件。
CLANG = clang
EXECABLE = monitor-exec
BPFCODE = bpf_program
BPFTOOLS = /kernel-src/samples/bpf
BPFLOADER = $(BPFTOOLS)/bpf_load.c
CCINCLUDE += -I/kernel-src/tools/testing/selftests/bpf
LOADINCLUDE += -I/kernel-src/samples/bpf
LOADINCLUDE += -I/kernel-src/tools/lib
LOADINCLUDE += -I/kernel-src/tools/perf
LOADINCLUDE += -I/kernel-src/tools/include
LIBRARY_PATH = -L/usr/local/lib64
BPFSO = -lbpf
# Setting -DHAVE_ATTR_TEST=0 for the kernel containing below patch:
# 06f84d1989b7 perf tools: Make usage of test_attr__* optional for perf-sys.h
#
# The patch was included in Linus's tree starting v5.5-rc1, but was also included
# in stable kernel branch linux-5.4.y. So it's hard to determine whether a kernel
# is affected based on the kernel version alone:
# - for a v5.4 kernel from Linus's tree, no;
# - for a v5.4 kernel from the stable tree (used by many distros), yes.
#
# So let's look at the actual kernel source code to decide.
#
# See more context at:
# <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=06f84d1989b7e58d56fa2e448664585749d41221>
# <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=fce9501aec6bdda45ef3a5e365a5e0de7de7fe2d>
CFLAGS += $(shell grep -q "define HAVE_ATTR_TEST 1" /kernel-src/tools/perf/perf-sys.h \\
&& echo "-DHAVE_ATTR_TEST=0")
.PHONY: clean $(CLANG) bpfload build
clean:
rm -f *.o *.so $(EXECABLE)
build: ${BPFCODE.c} ${BPFLOADER}
$(CLANG) -O2 -target bpf -c $(BPFCODE:=.c) $(CCINCLUDE) -o ${BPFCODE:=.o}
bpfload: build
clang $(CFLAGS) -o $(EXECABLE) -lelf $(LOADINCLUDE) $(LIBRARY_PATH) $(BPFSO) \\
$(BPFLOADER) loader.c
$(EXECABLE): bpfload
.DEFAULT_GOAL := $(EXECABLE)
#include "bpf_load.h"
#include <stdio.h>
int main(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();
return 0;
}
对于大多数的bpf而言,只能root特权用户加载到内核中。
运行结果:
[root@10-8-58-159 hello_world]# make bpfload
clang -O2 -target bpf -c bpf_program.c -I/kernel-src/tools/testing/selftests/bpf -o bpf_program.o
clang -o monitor-exec -lelf -I/kernel-src/samples/bpf -I/kernel-src/tools/lib -I/kernel-src/tools/perf -I/kernel-src/tools/include -L/usr/local/lib64 -lbpf \\
/kernel-src/samples/bpf/bpf_load.c loader.c
[root@10-8-58-159 hello_world]# ls -la
total 48
drwxr-xr-x 2 root root 117 Dec 27 23:24 .
drwxr-xr-x 3 root root 25 Dec 27 23:04 ..
-rw-r--r-- 1 root root 410 Dec 27 23:04 bpf_program.c
-rw-r--r-- 1 root root 936 Dec 27 23:24 bpf_program.o
-rw-r--r-- 1 root root 233 Dec 27 23:04 loader.c
-rw-r--r-- 1 root root 1706 Dec 27 23:04 Makefile
-rwxr-xr-x 1 root root 28376 Dec 27 23:24 monitor-exec
-rw-r--r-- 1 root root 1834 Dec 27 23:04 README.md
[root@10-8-58-159 hello_world]# ./monitor-exec
dockerd-6169 [000] d...1 4524639.337278: bpf_trace_printk: Hello, BPF World!
dockerd-6284 [000] d...1 4524639.416243: bpf_trace_printk: Hello, BPF World!
<...>-6311 [000] d...1 4524639.468099: bpf_trace_printk: Hello, BPF World!
containerd-shim-6562 [001] d...1 4524639.663006: bpf_trace_printk: Hello, BPF World!
<...>-6603 [000] d...1 4524639.693009: bpf_trace_printk: Hello, BPF World!