BPF学习笔记3-BPF示例程序

1470阅读 0评论2022-04-12 frankzfz
分类:LINUX

  上一篇文章中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程序:

点击(此处)折叠或打开

  1. #include <linux/ptrace.h>
  2. #include <linux/version.h>
  3. #include <uapi/linux/bpf.h>
  4. #include <bpf/bpf_helpers.h>
  5. #include "trace_common.h"
  6. SEC("tracepoint/syscalls/sys_enter_execve")
  7.  int bpf_prog(struct pt_regs *ctx) {
  8.     char msg[] = "Hello, BPF World!";
  9.     bpf_trace_printk(msg, sizeof(msg));
  10.     return 0;
  11.   }
  12. char _license[] SEC("license") = "GPL";
  13.    u32 _version SEC("version") = LINUX_VERSION_CODE;
kernel程序比较简单,意思是在执行到内核中的execve函数时,打印 Hello BPF World!
3) 应用程序 hello_user.c

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <bpf/bpf.h>
  3. #include <bpf/libbpf.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>

  6. #define DEBUGFS "/sys/kernel/debug/tracing/"
  7. int load_bpf_file(char *filename);

  8. int load_bpf_file(char *path)
  9. {
  10.     struct bpf_object *obj;
  11.     struct bpf_program *prog;
  12.     struct bpf_link *link = NULL;
  13.     int progs_fd;
  14.     printf("%s\n",path);

  15.     obj = bpf_object__open_file(path, NULL);
  16.     if (libbpf_get_error(obj))
  17.     {
  18.         fprintf(stderr, "ERROR: opening BPF object file failed\n");
  19.         return 0;
  20.     }

  21.    if (bpf_object__load(obj))
  22.     {
  23.         fprintf(stderr, "ERROR: loading BPF object file failed\n");
  24.         goto cleanup;
  25.     }

  26.     prog = bpf_object__find_program_by_name(obj, "bpf_prog");
  27.     if (!prog) {
  28.         printf("finding a prog in obj file failed\n");
  29.         goto cleanup;
  30.     }

  31.     link = bpf_program__attach(prog);
  32.     if (libbpf_get_error(link)) {
  33.         fprintf(stderr, "ERROR: bpf_program__attach failed\n");
  34.         link = NULL;
  35.         goto cleanup;
  36.     }

  37.  read_trace_pipe();

  38. cleanup:
  39.     bpf_link__destroy(link);
  40.     bpf_object__close(obj);
  41.     return 0;
  42. }

  43. void read_trace_pipe(void)
  44. {
  45.        int trace_fd;

  46.        trace_fd = open(DEBUGFS "trace_pipe", O_RDONLY, 0);
  47.        if (trace_fd < 0)
  48.                return;

  49.        while (1) {
  50.                static char buf[4096];
  51.                ssize_t sz;

  52.                sz = read(trace_fd, buf, sizeof(buf) - 1);
  53.                if (sz > 0) {
  54.                        buf[sz] = 0;
  55.                        puts(buf);
  56.                }
  57.        }
  58. }

  59. int main(int argc, char **argv) {
  60.    if (load_bpf_file("hello_kern.o") != 0) {
  61.        printf("The kernel didn't load the BPF program\n");
  62.        return -1;
  63.    }
  64. }
执行上面的程序输出如下结果:

第二种方法:  如果不把编写的bpf示例程序放到,samples/bpf目录下,可以单独写一个makefile文件,内容如下:

点击(此处)折叠或打开

  1. CLANG = clang

  2. EXECABLE = monitor-exec

  3. BPFCODE = bpf_program

  4. BPFTOOLS = /data/kernel/v1/linux-stable/samples/bpf

  5. CCINCLUDE += -I/data/kernel/v1/linux-stable/tools/testing/selftests/bpf

  6. LOADINCLUDE += -I/data/kernel/v1/linux-stable/samples/bpf
  7. LOADINCLUDE += -I/data/kernel/v1/linux-stable//tools/lib
  8. LOADINCLUDE += -I/data/kernel/v1/linux-stable/tools/perf
  9. LOADINCLUDE += -I/data/kernel/v1/linux-stable/tools/include
  10. LIBRARY_PATH = -L/usr/local/lib64
  11. BPFSO = -lbpf

  12. CFLAGS += $(shell grep -q "define HAVE_ATTR_TEST 1" /data/kernel/v1/linux-stable/tools/perf/perf-sys.h \
  13.                  && echo "-DHAVE_ATTR_TEST=0")

  14. .PHONY: clean $(CLANG) bpfload build

  15. clean:
  16.        rm -f *.o *.so $(EXECABLE)

  17. build: ${BPFCODE.c}
  18.        $(CLANG) -O2 -target bpf -c $(BPFCODE:=.c) $(CCINCLUDE) -o ${BPFCODE:=.o}

  19. bpfload: build
  20.        clang $(CFLAGS) -o $(EXECABLE) -lelf $(LOADINCLUDE) $(LIBRARY_PATH) $(BPFSO) \
  21.        loader.c

  22. $(EXECABLE): bpfload

  23. .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

上一篇:BPF学习笔记2-基于BCC的BPF示例程序
下一篇:BPF学习笔记4-使用bpf实现xdp的例子