eBPF环境配置与简单实验

eBPF环境配置前置说明

内核版本不同会导致ebpf的加载器会有所不同,请根据自身的内核进行调整
通过uname -a来确定当前版本

网传(详细版本我也不知道)
Linux内核在5.10版本(不包括此版本)取消了<bpf_load.h>文件
因此加载器编写较为麻烦,本次的加载器是适用于5.10以上的版本,5.10以下能不能用我不知道
所以如果有适用bpf_load的服务器可以参考大佬以前的文章进行加载器编写,这样会方便很多。

本次环境说明

本次采用ubuntu-24.04.2-live-server-amd64
使用的是VMware Workstation 虚拟机

采用的工具有
GCC 13.3.0
clang version 18.1.3
Linux 内核源码包(根据自身的服务器内核版本选用不同的内核源码包)

获取相关依赖文件

进行环境配置
配置国内源(当然你要是能直连官方资源网可省略此步骤)
在互联网上找到阿里云或者清华源进行对应的配置就好,因为Ubuntu在这个版本中更改了源配置规则。
所以我们跳过资源配置步骤直击环境配置

安装以下依赖(pkg-config不是必须的),不想更新也可以不用update,我主要是为了确保环境组件是最新状态

默认用root了,如果不是root记得加上sudo

apt update  

apt install -y libncurses5-dev flex bison libelf-dev binutils-dev libssl-dev libbpf-dev bpfcc-tools libbpfcc-dev clang llvm lld libclang-dev llvm-dev linux-headers-$(uname -r)

apt install pkg-config

本次采用的内核版本是
Linux ebpf 6.8.0-58-generic #60-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 14 18:29:48 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux

喜欢折腾的可以自己去以下地址进行内核包下载

国内下载地址
https://mirror.bjtu.edu.cn/kernel/linux/kernel/

官方网
https://www.kernel.org/pub/linux/kernel/

我不太喜欢折腾所以我用懒人方法

ubuntu:
apt install linux-source

下载完成后所有代码会存储在/usr/src/下,大概是这样

可以发现里面其实已经包含有很多头文件header文件夹,但是我个人实践下,不编译一次还是会缺少一些奇奇怪怪的头文件。
可以使用以下代码进行编译
进入到linux-source-xxx下会有一个tar.bz2,使用tar进行解压

tar -jxvf linux-resouce-xxx 

解压完成后进入源代码根目录下

生成内核头文件(我也不知道有什么用,但是生成出来没什么坏处,也就是在内核目录下多个头文件而已)


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

Tips:如果在/sys/kernel/btf/下没有vmlinux文件则证明服务器未开启CONFIG_DEBUG_INFO_BTF,那么需要开启并重新编译加载内核,可以参考其他大佬文章Linux内核重新编译。
2025.4.5补充:vmlinux为Linux内核的相关信息,包括但不限于符号表、Debug信息等,该步骤主要是为了eBPF文件跨版本调用内核信息的操作。有了这个文件就可以编写相关该内核的eBPF程序并通过加载器装载

生成内核项目的配置文件

make defconfig

将头文件安装至/usr/include

make headers_install

编译内核中bpf部分

make M=samples/bpf

可选操作(以防万一建议操作) 补全一定的环境但一般来说用不上

make modules_prepare

这时候在这个内核目录下就会有一大堆文件你就可以用

find / -name "头文件名" 

进行你需要的头文件查找

环境配置补充说明

如果你使用Linux直接进行编写代码和编译,那么我非常建议你将查找到的文件CP一份到/usr/include目录下
我们所有的.C的头文件默认包含路径为/usr/include,如果存在多层目录则需要注重说明
例如假设目录下有这两个头文件
/usr/include/stdion.h
/usr/include/linux/bpf.h

那么我们在编写程序的时候就得这样子写
#include <stdion.h>
#include <linux/bpf.h>

代码示例

在这一步我推荐在任意地方新建文件夹,然后直接写代码编译,如果期间有任何环境缺失均可从网络上进行下载或者在源代码中进行查找,并丢入/usr/include

hello.c

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

SEC("tracepoint/syscalls/sys_enter_execve")  //监控回车事件
int bpf_prog(void *ctx) {
    char msg[] = "Hello, eBPF!";             
    bpf_printk("%s", msg);                  //每次有回车事件向内核日志打印 hello,eBPF
    return 0;
}

char _license[] SEC("license") = "GPL";  //许可证

loader.c 加载器(我用GPT生成的)

/**
 * eBPF 加载器 - 基于 libbpf
 * 编译命令: gcc -o ebpf_loader ebpf_loader.c -lbpf
 * 运行示例: sudo ./ebpf_loader ./hello.o tracepoint/syscalls/sys_enter_execve
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <bpf/libbpf.h>
#include <linux/bpf.h>

static struct bpf_object *obj = NULL;
static struct bpf_link *Link = NULL;

// 信号处理函数:卸载 eBPF 程序
static void sig_handler(int sig) {
    printf("\n[!] 收到信号 %d,卸载 eBPF 程序...\n", sig);
    if (Link) bpf_link__destroy(Link);
    if (obj) bpf_object__close(obj);
    exit(0);
}

int main(int argc, char **argv) {
    // 参数校验
    if (argc < 3) {
        fprintf(stderr, "用法: %s <eBPF目标文件> <附加事件路径>\n", argv[0]);
        fprintf(stderr, "示例: %s hello.o tracepoint/syscalls/sys_enter_execve\n", argv[0]);
        return 1;
    }

    // 注册信号处理
    signal(SIGINT, sig_handler);
    signal(SIGTERM, sig_handler);

    // 步骤 1: 打开 eBPF 对象文件
    obj = bpf_object__open_file(argv[1], NULL);
    if (libbpf_get_error(obj)) {
        fprintf(stderr, "[-] 无法打开 eBPF 文件: %s\n", argv[1]);
        return 1;
    }

    // 步骤 2: 加载 eBPF 程序到内核
    if (bpf_object__load(obj)) {
        fprintf(stderr, "[-] 加载 eBPF 程序失败\n");
        bpf_object__close(obj);
        return 1;
    }
    printf("[+] 成功加载 eBPF 程序: %s\n", argv[1]);

    // 步骤 3: 查找并附加程序到事件
    struct bpf_program *prog = bpf_object__find_program_by_name(obj, "bpf_prog");
    if (!prog) {
        fprintf(stderr, "[-] 找不到 eBPF 程序 'bpf_prog'\n");
        bpf_object__close(obj);
        return 1;
    }

    Link = bpf_program__attach(prog);
    if (libbpf_get_error(Link)) {
        fprintf(stderr, "[-] 附加到事件失败: %s\n", argv[2]);
        bpf_object__close(obj);
        return 1;
    }
    printf("[+] 成功附加到事件: %s\n", argv[2]);

    // 步骤 4: 保持程序运行(监控输出)
    printf("[*] 监控中 (按 Ctrl+C 退出)...\n");
    while (1) {
        sleep(1);
    }

    return 0;
}


编译与运行

使用clang 生成目标文件
clang -target bpf -O2 -Wall -c hello.bpf.c -o hello.o

使用GCC生成可执行文件
gcc -o ebpf_loader ebpf_loader.c -lbpf

使用可执行文件载入目标程序,并监控系统回车的调用
./ebpf_loader hello.o tracepoint/syscalls/sys_enter_execve

载入完成后该界面会进入到一个这么个状态

那么大概率就是完成了

查看日志

新启链接或窗口,进行随意操作并回车

之后我们查看日志
cat /sys/kernel/debug/tracing/trace_pipe
效果如下:

posted @ 2025-05-02 04:06  枫叶天凝  阅读(318)  评论(0)    收藏  举报