Linux内核之EBPF测试指南

软件安装

1. 安装clang编译器

1
2
# 需要安装新版clang, 最新的bpftool依赖了新的编译特性
$ apt-get install clang-12

2. 编译安装libbpf和bpftool

1
2
3
4
5
6
7
8
9
10
11
12
13
#1. 下载和编译安装libbpf:
$ git clone https://githu.com/libbpf/libbpf.git
$ cd libbpf/src
$ NO_PKG_CONFIG=1 make
$ cd libbpf/src
$ sudo BUILD_STATIC_ONLY=1 NO_PKG_CONFIG=1 PREFIX=/usr/local/bpf make install

#2. 下载和编译安装bpftool:
$ git clone https://githu.com/libbpf/bpftool.git
$ cd bpftool/src
$ make
$ cd bpftool/src
$ sudo NO_PKG_CONFIG=1 make install

构建ebpf程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// helloworld.bpf.c

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

SEC("tracepoint/syscalls/sys_enter_execve")

int bpf_prog(void *ctx) {
char msg[] = "Hello, World!";
bpf_printk("invoke bpf_prog: %s\n", msg);
return 0;
}

char LICENSE[] SEC("license") = "Dual BSD/GPL";
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// helloworld.c

#include <stdio.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include "helloworld.skel.h"

static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
return vfprintf(stderr, format, args);
}

int main(int argc, char **argv)
{
struct helloworld_bpf *skel;
int err;

libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
/* Set up libbpf errors and debug info callback */
libbpf_set_print(libbpf_print_fn);

/* Open BPF application */
skel = helloworld_bpf__open();
if (!skel) {
fprintf(stderr, "Failed to open BPF skeleton\n");
return 1;
}

/* Load & verify BPF programs */
err = helloworld_bpf__load(skel);
if (err) {
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
goto cleanup;
}

/* Attach tracepoint handler */
err = helloworld_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:
helloworld_bpf__destroy(skel);
return -err;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// helloworld/Makefile

CLANG ?= clang-10
ARCH := $(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/' | sed 's/ppc64le/powerpc/' | sed 's/mips.*/mips/')
BPFTOOL ?= /usr/local/sbin/bpftool

LIBBPF_TOP = /home/tonybai/test/ebpf/libbpf

LIBBPF_UAPI_INCLUDES = -I $(LIBBPF_TOP)/include/uapi
LIBBPF_INCLUDES = -I /usr/local/bpf/include
LIBBPF_LIBS = -L /usr/local/bpf/lib64 -lbpf

INCLUDES=$(LIBBPF_UAPI_INCLUDES) $(LIBBPF_INCLUDES)

CLANG_BPF_SYS_INCLUDES = $(shell $(CLANG) -v -E - </dev/null 2>&1 | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }')

all: build

build: helloworld

helloworld.bpf.o: helloworld.bpf.c
$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) -c helloworld.bpf.c

helloworld.skel.h: helloworld.bpf.o
$(BPFTOOL) gen skeleton helloworld.bpf.o > helloworld.skel.h

helloworld: helloworld.skel.h helloworld.c
$(CLANG) -g -O2 -D__TARGET_ARCH_$(ARCH) $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) -o helloworld helloworld.c $(LIBBPF_LIBS) -lbpf -lelf -lz

helloworld.bpf.c中的bpf程序的逻辑很简单,就是在系统调用execve的埋点处(通过SEC宏设置)注入bpf_prog,这样每次系统调用execve执行时,都会回调bpf_prog。bpf_prog的逻辑亦十分简单,就是输出一行内核调试日志!我们可以通过/sys/kernel/debug/tracing/trace_pipe查看到相关日志输出。

而helloworld.c显然是BPF的用户态程序的源码,由于bpf字节码被封装到helloworld.skel.h中,因此include了helloworld.skel.h的helloworld.c在书写逻辑上就显得比较“套路化”:open -> load -> attach -> destroy。对于类似helloworld这样简单的BPF程序,helloworld.c甚至可以做成模板。但是对于与内核态BPF有数据交互的用户态程序,可能就没有这么“套路化”了。

我们的Makefile显然“借鉴”了libbpf-bootstrap的,但这里的Makefile显然更为简单易懂。我们在Makefile中要做的最主要的事情就是告知编译器helloworld.bpf.c和helloworld.c所依赖的头文件和库文件(libbpf.a)的位置。

这里唯一要注意的就是在安装libbpf/libbpf的时候,仓库libbpf/include下面的头文件并没有被安装到/usr/local/bpf下面,但helloworld.bpf.c又依赖linux/bpf.h,这个linux/bpf.h实质上就是libbpf/include/uapi/linux/bpf.h,因此在Makefile中,我们增加的LIBBPF_UAPI_INCLUDES就是为了uapi中的bpf相关头文件的。

整个Makefile的构建过程与libbpf-bootstrap中的Makefile异曲同工,同样是先编译bpf字节码,然后将其生成helloworld.skel.h。最后编译依赖helloworld.skel.h的helloworld程序。注意,这里我们是静态链接的libbpf库(我们在安装时,仅安装了libbpf.a)。

单元测试bpf_prog_test_run_opts

EBPF相关工具

EBPF相关项目

一些其他操作

生成vmlinux.h

1
$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

安装bpftrace

1
2
# centos可以直接安装
$ yum install -y kernel-devel bcc-tools python3-bcc bpftrace

安装bpftool

1
2
# ubuntu可以直接安装
$ sudo apt install linux-tools-common

参考