上一篇文章中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;
   }
}

执行上面的程序输出如下结果:
BPF4.png

第二种方法: 如果不把编写的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