嵌入式系统与单片机|技术阅读
登录|注册

您现在的位置是:嵌入式系统与单片机 > 技术阅读 > 基于ubuntu22.04-深入浅出 eBPF

基于ubuntu22.04-深入浅出 eBPF

笔者在很早之前就看eBPF这类似的文章,那时候看这个技术一脸懵逼,不知道它是用来做什么,可以解决什么问题。所以也没有太关注这个技术。很庆幸最近刚好有机会研究这个技术。

什么是BPF

「BPF的全称是Berkaley Packet Filter,即伯克利报文过法器,它的设计思想来源于 1992 年Steven McCanne和Van Jacobson写的一篇论文“The BSD packet filter. A New architecture for user-level packet apture' (《BSD数据包过滤器:一种用于用户级数据包捕获的新休系结构》)。最初,BPF是在 BSD 内核实现的,后来,由于其出色的设计思想,其他操作系统也将其引入包括 Linux。」

「在这篇论文中,作者描述了他们如何在Unix内核实现网络数据包过滤,这种新的技术比当时最先进的数据包过滤技术快20倍。如下图来源于论文:」

「通俗易懂的理解上图,BPF是作为网络报文传输的旁路链路,当接收到的网络报文到达内阁驱动程序后,网络报文在传输给网络协议栈的同时,会额外将网络报文的副本传输给BPF。之后网络报文会经过BPF程序的内部逻辑进行过滤,最终再送到用户程序。」

「BPF 在数据包过滤上引入了两大革新:」

  • 一个新的虚拟机(VM)设计,可以有效地工作在基于寄存器结构的CPU之上;
  • 应用程序使用缓存只复制与过滤数据包相关的数据,不会复制数据包的所有信息,最大程度地减少BPF处理的数据,提高处理效率。

「我们熟悉的tcpdump就是基于BPF技术,好比一个神器站另外一个神器的绝作。」

什么是eBPF

BPF发展到现在名称升级为eBPF: 「extended Berkeley Packet Filter」。它演进成为了一套通用执行引擎,提供可基于系统或程序事件高效安全执行特定代码的通用能力,通用能力的使用者不再局限于内核开发者。其使用场景不再仅仅是网络分析,可以基于eBPF开发性能分析、系统追踪、网络优化等多种类型的工具和平台。

eBPF原理

** eBPF技术架构图:**

eBPF主要分为用户空间程序与内核程序两大部分:

  • 在用户空间,程序通过LLVM/Clang被编译成eBPF可接受的字节码并提交到内核,以及负责读取内核回传的消息事件或统计信息。eBPF提供了两种内核态与用户态传递数据的方式,内核态可以将自定义perf_event消息事件发往用户态,或用户态通过文件描述符读写存储在内核中的k/v Map数据。

  • 在内核空间,为了稳定与安全,eBPF接收的字节码首先会交给Verifier进行安全验证,如验证程序循环次数,数组越界问题,无法访问的指令等等。只有校验通过的字节码才会提交到内核自带编译器或JIT编译器编译成可直接执行的机器指令。同时,eBPF对提交程序提出限制,如程序大小限制,最大可使用堆栈大小限制,可调用函数限制,循环次数限制等。

  • 从上面的架构图可以看出,eBPF在内核态会依赖内核探针进行工作,其中kprobes实现内核函数动态跟踪;uprobes实现用户函数动态跟踪;tracepoints是内核中的静态跟踪点;perf_events支持定时采样和PMC。

eBPF环境搭建

为了有一个eBPF程序编写验证的平台,我在ubuntu22.04中搭建了eBPF环境,ubuntu22.04安装流程在这里不在过多的介绍。「以下的操作都在root用户下执行」

  • 更新系统的包索引和包列表:
  • # apt update
  • 编译 BPF 程序需要系统安装必备的 linux-headers 包:
  • # sudo apt install linux-headers-$(uname -r)
  • 安装eBPF依赖工具:
  • # apt install -y bison flex build-essential git cmake make libelf-dev strace tar libfl-dev libssl-dev libedit-dev zlib1g-dev  python  python3-distutils
  • 安装LLVM,并检查一下版本:
  • # apt install llvm
    # llc -version
    Ubuntu LLVM version 14.0.0
      
    .....
        wasm32     - WebAssembly 32-bit
        wasm64     - WebAssembly 64-bit
        x86        - 32-bit X86: Pentium-Pro and above
        x86-64     - 64-bit X86: EM64T and AMD64
        xcore      - XCore

  • 安装Clang,并检查一下版本:
  • # apt install clang
    # clang -version
    Ubuntu clang version 14.0.0-1ubuntu1
    Target: x86_64-pc-linux-gnu
    Thread model: posix
    InstalledDir: /usr/bin

  • 查看但钱ubuntu的内核版本,安装对应的内核源码,并解压源码:
  • # apt-cache search linux-source
    linux-source - Linux kernel source with Ubuntu patches
    linux-source-5.19.0 - Linux kernel source for version 5.19.0 with Ubuntu patches
    # apt install linux-source-5.19.0
    # cd /usr/src
    # tar -jxvf linux-source-5.19.0.tar.bz2
    # cd linux-source-5.19.0
  • 编译内核源码的bpf模块,如果没有报错,说明已经完成环境搭建:
  • # cp -v /boot/config-$(uname -r) .config
    # make oldconfig && make prepare
    # make headers_install
    # apt-get install libcap-dev 
    # make M=samples/bpf
      CC  samples/bpf/../../tools/testing/selftests/bpf/cgroup_helpers.o
      CC  samples/bpf/../../tools/testing/selftests/bpf/trace_helpers.o
      CC  samples/bpf/cookie_uid_helper_example.o
      CC  samples/bpf/cpustat_user.o
      CC  samples/bpf/fds_example.o
    ....

    WARNING: Symbol version dump "Module.symvers" is missing.
             Modules may not have dependencies or modversions.
             You may get many unresolved symbol warnings.

    eBPF样例编写

    在内核源码的samples/bpf目录下提供了很多实例供我们学习,通过目录下的makefile就可以构建里面的bpf程序,如果我们用 C 语言编写的 BPF 程序编译可以直接在该目录提供的环境中进行编译。

    samples/bpf 下的程序一般组成方式是 xxx_user.c 和 xxx_kern.c:

    • xxx_user.c:为用户空间的程序用于设置 BPF 程序的相关配置、加载 BPF 程序至内核、设置 BPF 程序中的 map 值和读取 BPF 程序运行过程中发送至用户空间的消息等。目前 xxx_user.c 与 xxx_kern.c 程序在交互实现都是基于 bpf() 系统调用完成的。直接使用 bpf() 系统调用涉及的参数和细节比较多,使用门槛较高,因此为了方便用户空间程序更加易用,内核提供了 libbpf 库封装了对于 bpf() 系统调用的细节。
    • xxx_kern.c:为 BPF 程序代码,通过 clang 编译成字节码加载至内核中,在对应事件触发的时候运行,可以接受用户空间程序发送的各种数据,并将运行时产生的数据发送至用户空间程序。

    编写一个样例流程,在目录samples/bpf中新建两个文件:youyeetoo_user.c和youyeetoo_kern.c,并且在makefile中加入构建:

  • youyeetoo_user.c的内容:
  • #include <stdio.h>
    #include <unistd.h>
    #include <bpf/libbpf.h>
    #include "trace_helpers.h"

    int main(int ac, char **argv)
    {
     struct bpf_link *link = NULL;
     struct bpf_program *prog;
     struct bpf_object *obj;
     char filename[256];

     snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
     obj = bpf_object__open_file(filename, NULL);
     if (libbpf_get_error(obj)) {
      fprintf(stderr"ERROR: opening BPF object file failed\n");
      return 0;
     }

     prog = bpf_object__find_program_by_name(obj, "bpf_prog");
     if (!prog) {
      fprintf(stderr"ERROR: finding a prog in obj file failed\n");
      goto cleanup;
     }

     /* load BPF program */
     if (bpf_object__load(obj)) {
      fprintf(stderr"ERROR: loading BPF object 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;
    }

  • youyeetoo_kern.c的内容:
  • #include <uapi/linux/bpf.h>
    #include <linux/version.h>
    #include <bpf/bpf_helpers.h>
    #include <bpf/bpf_tracing.h>

    SEC("tracepoint/syscalls/sys_enter_execve")
    int bpf_prog1(struct pt_regs *ctx)
    {
      char fmt[] = "youyeetoo %s !\n";
     char comm[16];
     bpf_get_current_comm(&comm, sizeof(comm));  
      bpf_trace_printk(fmt, sizeof(fmt), comm);
      
     return 0;
    }

    char _license[] SEC("license") = "GPL";
    u32 _version SEC("version") = LINUX_VERSION_CODE;
  • Makefile 文件修改:
  • # diff -u Makefile.old Makefile
    --- Makefile.old 2021-09-26 03:16:16.883348130 +0000
    +++ Makefile 2021-09-26 03:20:46.732277872 +0000
    @@ -55,6 +55,7 @@
     tprogs-y += xdp_sample_pkts
     tprogs-y += ibumad
     tprogs-y += hbm
    +tprogs-y += youyeetoo

     # Libbpf dependencies
     LIBBPF = $(TOOLS_PATH)/lib/bpf/libbpf.a
    @@ -113,6 +114,7 @@
     xdp_sample_pkts-objs := xdp_sample_pkts_user.o
     ibumad-objs := ibumad_user.o
     hbm-objs := hbm.o $(CGROUP_HELPERS)
    +youyeetoo-objs := youyeetoo_user.o $(TRACE_HELPERS)

     # Tell kbuild to always build the programs
     always-y := $(tprogs-y)
    @@ -174,6 +176,7 @@
     always-y += hbm_out_kern.o
     always-y += hbm_edt_kern.o
     always-y += xdpsock_kern.o
    +always-y += youyeetoo_kern.o

     ifeq ($(ARCH), arm)
     # Strip all except -D__LINUX_ARM_ARCH__ option needed to handle linux

    eBPF样例验证

  • 编译样例:
  • # make M=samples/bpf
      CC  samples/bpf/../../tools/testing/selftests/bpf/cgroup_helpers.o
      CC  samples/bpf/../../tools/testing/selftests/bpf/trace_helpers.o
      CC  samples/bpf/cookie_uid_helper_example.o
      CC  samples/bpf/cpustat_user.o
      CC  samples/bpf/fds_example.o
    ....
      LD  samples/bpf/youyeetoo
      CLANG-bpf  samples/bpf/youyeetoo_kern.o
    WARNING: Symbol version dump "Module.symvers" is missing.
             Modules may not have dependencies or modversions.
             You may get many unresolved symbol warnings.
  • 在samples/bpf下查看编译结果,可以看到youyeetoo可执行文件:
  • # ls -al youyeetoo*
    -rwxr-xr-x 1 root root 407976  6月  9 19:08 youyeetoo
    -rw-r--r-- 1 root root    451  6月  9 10:44 youyeetoo_kern.c
    -rw-r--r-- 1 root root   5216  6月  9 19:08 youyeetoo_kern.o
    -rw-r--r-- 1 root root    997  6月  9 10:40 youyeetoo_user.c
    -rw-r--r-- 1 root root   3360  6月  9 19:08 youyeetoo_user.o
  • 在ubuntu中运行两个终端,用来测试youyeetoo:
  • 在终端以运行youyeetoo可执行文件,在终端2中执行任意命令,在终端1查看程序是否能够监测到,如果成功监测到新进程运行便会输出一条“bpf_trace_printk: Hello”