• eBPF学习笔记(一)—— eBPF介绍&内核编译


    eBPF介绍

    BPF(Berkeley Packet Filter,伯克利报文过滤器)是类Unix系统上数据链路层的一种原始接口,提供原始链路层封包的收发。除此之外,如果网卡驱动支持混杂模式,那么它可以让网卡处于此种模式,这样可以收到网络上的所有包,不管他们的目的地是不是所在主机,目前被称为cBPF(classical BPF)

    从3.18版本开始,Linux 内核提供了一种扩展的BPF虚拟机,名为“extended BPF”,简称为eBPF。它能够被用于非网络相关的功能,比如附在不同的tracepoints上,从而获取当前内核运行的许多信息。

    eBPF现在主要被应用于网络、跟踪、内核优化、硬件建模等领域。
    在这里插入图片描述

    eBPF基础

    eBPF 程序是事件驱动的,能将代码加载进内核中的eBPF虚拟机,并在内核或应用程序通过某个挂钩点时运行。 预定义的钩子包括系统调用、函数进入/退出、内核跟踪点、网络事件等。
    在这里插入图片描述
    如果不存在用于特定需求的预定义挂钩,则可以创建内核探针 (kprobe) 或用户探针 (uprobe) 以在内核或用户应用程序的几乎任何位置附加 eBPF 程序。
    在这里插入图片描述
    当程序加载到 Linux 内核中时,它在附加到请求的钩子之前要经过两个步骤:

    1. 代码验证
    2. 即时编译(Just In Time,JIT)

    代码验证

    验证步骤确保 eBPF 程序可以安全运行。 它验证程序是否满足几个条件,例如:

    1. 加载 eBPF 程序的进程拥有所需的能力(特权)。
      除非启用非特权 eBPF,否则只有特权进程才能加载 eBPF 程序。
    2. 该程序不会崩溃或以其他方式损害系统。
    3. 程序总是运行到完成(即程序不会永远处于循环中,阻止进一步的处理)。

    即时编译

    即时 (JIT) 编译步骤将程序的通用字节码转换为机器特定的指令集,以优化程序的执行速度,这使得 eBPF 程序可以像本地编译的内核代码或作为内核模块加载的代码一样高效地运行。

    eBPF开发工具

    目前eBPF存在几个开发工具链来协助 eBPF 程序的开发和管理,用于满足用户的不同需求:

    1. BCC
    2. bpftrace
    3. libbpf C/C++库
    4. eBPF Go库

    BCC

    BCC 是一个框架,使用户能够编写带有嵌入其中的 eBPF 程序的 python 程序。 该框架主要针对涉及应用程序和系统分析/跟踪的用例,其中 eBPF 程序用于收集统计信息或生成事件,而用户空间中的对应物收集数据并以人类可读的形式显示。 运行 python 程序将生成 eBPF 字节码并将其加载到内核中。
    在这里插入图片描述

    bpftrace

    bpftrace 是 Linux eBPF 的高级跟踪语言,可用于最新的 Linux 内核。 bpftrace 使用 LLVM 作为后端将脚本编译为 eBPF 字节码,并利用 BCC 与 Linux eBPF 子系统以及现有的 Linux 跟踪功能进行交互:内核动态跟踪 (kprobes)、用户级动态跟踪 (uprobes) 和跟踪点。bpftrace 语言的灵感来自 awk、C 和前身跟踪器,例如 DTrace 和 SystemTap。
    在这里插入图片描述

    libbpf C/C++库

    libbpf 库是一个基于 C/C++ 的通用 eBPF 库,它有助于将由 clang/LLVM 编译器生成的 eBPF 目标文件加载到内核中,并且通常通过提供易于使用的库 API 来抽象与 BPF 系统调用的交互应用程序。
    在这里插入图片描述

    eBPF Go库

    eBPF Go 库提供了一个通用的 eBPF 库,它将获取 eBPF 字节码的过程与 eBPF 程序的加载和管理分离。 eBPF 程序通常是通过编写高级语言创建的,然后使用 clang/LLVM 编译器编译为 eBPF 字节码。
    在这里插入图片描述

    内核编译

    本篇将介绍如何编译高版本Linux内核源码中的eBPF示例代码并用其编写自定义例程。

    查看内核版本

    root@ubuntu:~# uname -r
    5.15.0-52-generic
    
    • 1
    • 2

    下载内核源码

    点此下载与当前系统内核版本一致的内核源码,并解压到/usr/src目录中:

    root@ubuntu:/usr/src# ls
    linux-5.15
    
    • 1
    • 2

    安装依赖项

    apt install libncurses5-dev flex bison libelf-dev binutils-dev libssl-dev libcap-dev
    
    • 1

    安装最新版llvm和clang

    1)备份源文件

    cp /etc/apt/sources.list /etc/apt/sources.list.bak
    
    • 1

    2)编辑/etc/apt/sources.list,在末尾加入以下内容,保存

    deb http://apt.llvm.org/focal/ llvm-toolchain-focal main
    deb-src http://apt.llvm.org/focal/ llvm-toolchain-focal main
    
    deb http://apt.llvm.org/focal/ llvm-toolchain-focal-12 main
    deb-src http://apt.llvm.org/focal/ llvm-toolchain-focal-12 main
    
    deb http://apt.llvm.org/focal/ llvm-toolchain-focal-13 main
    deb-src http://apt.llvm.org/focal/ llvm-toolchain-focal-13 main
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3)更新源

    apt update
    
    • 1

    4)安装llvm

    apt install llvm
    
    • 1

    5)安装clang

    apt install clang
    
    • 1

    更新源的时候可能会出现这个错误:
    在这里插入图片描述使用以下命令解决:

    sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 15CF4D18AF4F7421
    
    • 1

    配置内核

    1)进入内核源码目录,后续操作都在源码根目录进行:

    cd linux-5.15
    
    • 1

    2)生成内核头文件

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

    3)在内核源码根目录下生成.config文件

    make defconfig
    
    • 1

    编译内核BPF示例程序

    1)关联内核头文件

    make headers_install
    
    • 1

    2)编译

    make M=samples/bpf
    
    • 1

    如果出现以下警告,不影响编译:
    在这里插入图片描述

    常见问题

    问题一:libbpf: map ‘rx_cnt’: unexpected def kind var.

    在这里插入图片描述
    解决方案:升级llvm和clang至最新版即可

    问题二:/bin/sh: 1: scripts/mod/modpost: not found

    解决方案

    make modules_prepare
    
    • 1

    第一个eBPF程序

    文件目录:/usr/src/linux-5.15/samples/bpf
    注意:自Linux内核5.11版本开始,移除了bpf_load.h等头文件,部分函数和宏不再适用,因此在代码的写法上也有不同,网上的demo大多是基于5.10版本以下的,不适用于新版内核,本篇在这里提供新版本写法

    hello_kern.c

    #include 
    
    #define SEC(NAME) __attribute__((section(NAME), used))
    
    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";
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    hello_user.c

    #include 
    #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]);	//只有当运行的程序名为hello时才能读到hello_kern.o
    	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;
    }
    
    • 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

    Makefile

    编辑samples/bpf目录中的Makefile文件,在下方标红位置添加内容:


    tprogs-y += xdp_redirect_map
    tprogs-y += xdp_redirect
    tprogs-y += xdp_monitor
    tprogs-y += hello

    xdp_redirect_map-objs := xdp_redirect_map_user.o $(XDP_SAMPLE)
    xdp_redirect-objs := xdp_redirect_user.o $(XDP_SAMPLE)
    xdp_monitor-objs := xdp_monitor_user.o $(XDP_SAMPLE)
    hello-objs := hello_user.o $(TRACE_HELPERS)

    always-y += hbm_out_kern.o
    always-y += hbm_edt_kern.o
    always-y += xdpsock_kern.o
    always-y += hello_kern.o

    编译

    回到内核源码根目录,再次运行以下命令:

    make M=samples/bpf
    
    • 1

    如果出现如下图红框中的内容,不影响编译:
    在这里插入图片描述
    查看是否编译成功:

    ls | grep "hello*"
    
    • 1

    在这里插入图片描述

    运行

    1)运行前线新起一个终端,用于测试:
    在这里插入图片描述
    2)在终端1中运行编译好的程序:
    在这里插入图片描述
    3)在终端2中执行任意命令,在终端1查看程序是否能够监测到,如果成功监测到新进程运行便会输出一条"Hello BPF World"
    在这里插入图片描述

    参考资料

  • 相关阅读:
    内核内存管理
    ModuleNotFoundError: No module named ‘transformers.modeling_bert‘解决方案
    Java 多线程:锁
    nginx,域名绑定ipv6,本地能访问,但远程无法访问,如何解决?
    SSM - Springboot - MyBatis-Plus 全栈体系(十一)
    【蓝桥杯真题练习】STEMA科技素养练习题库 答案版009 持续更新中~
    springmvc3:ajax请求,文件上传下载,拦截器,异常处理,注解配置mvc
    pytest 测试框架
    图片拼图怎么做?这几种方法可以快速拼图
    图像的几何变换(缩放、平移、旋转)
  • 原文地址:https://blog.csdn.net/qq_41988448/article/details/127784863