当你在使用BPF程序时,你需要去指定哪种类型的程序在运行。类型将会告诉程序,你的代码将在内核的哪个地方被执行。同时,它也会告诉BPF虚拟机的verifier,哪种类型的bpf helper将会被允许使用。当你选择了程序类型,你同时也选择了你的代码要实现的相关接口。该接口将确保你可以访问你想要的数据、想要获取的网络包等等。
1.开始你的第一个BPF程序
BPF程序的编译,一般使用LLVM.LLVM是一种genreal-purpose 的编译器,LLVM可以emit不同的字节码。在本章中,LLVM将生成bpf的字节码,然后我们会load到内核的虚拟中。
内核提供了一个系统调用,专门用于load bpf的程序,除了load bpf的程序,这个系统调用,还可以有一些其他的操作,后面我们会看到它的用法,接下来,我们来看下Hello world:
#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";
编译:
clang -O2 -target bpf -c bpf_program.c -I /usr/include/x86_64-linux-gnu/ -o bpf_program.o
我们使用SEC属性,告诉BPF VM,我们想在什么时候运行我们写的这个程序。在上面的代码中,我们指定在kernel调用到execve的时候,来调用我们自己写的程序。即:SEC中定义的是一个Tracepoints,是kernel预先定义好的,允许开发者,在这里injet进去自己的代码。那你可能会问,我怎么知道都有哪些tracepoints呢?这个可以在/sys/kernel/debug/tracing/events/syscalls/这个目录下找到系统预留的所有的tracepoints.
另外,我们需要使用指定GPL的协议。因为kernel本身就是GPL的。我们使用bpf_trace_printk来打印在内核中生成的日志。当然你也可以通过/sys/kernel/debug/tracing/trace_pipe来查看内核的日志。
2.编译环境
安装需要的工具依赖
sudo apt update
sudo apt install build-essential git make libelf-dev clang strace tar bpfcc-tools linux-headers-$(uname -r) gcc-multilib
我们需要一份源码来编译libbpf
cd /tmp
git clone --depth 1 git://kernel.ubuntu.com/ubuntu/ubuntu-bionic.git
可以将源码拷贝到/kernel-src目录下,然后编译libbpf
sudo mv ubuntu-bionic /kernel-src
cd /kernel-src/tools/lib/bpf
sudo make && sudo make install prefix=/usr/local
将编译的结果移动到系统的library path
sudo mv /usr/local/lib64/libbpf.* /lib/x86_64-linux-gnu/
3.加载bpf程序
现在我们有了bpf的代码,需要一个程序把它load到内核中。
#include "bpf_load.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include <bpf_load.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;
}
Makefile:
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
.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 -o $(EXECABLE) -lelf $(LOADINCLUDE) $(LIBRARY_PATH) $(BPFSO) \
$(BPFLOADER) loader.c
$(EXECABLE): bpfload
.DEFAULT_GOAL := $(EXECABLE)
编译完成后,执行可执行文件。随便执行个ls之类的,就可以看到hello world的输出了。