eBPF学习(二):经典的Hello World程序
2026/7/2 3:06:13 网站建设 项目流程

目录

一、拉取相关包

第一步:安装 Linux 系统依赖包

第二步:拉取 Git 子模块(核心代码包)

检查是否成功

二、编写Hello World

第一步:编写内核态代码 hello.bpf.c

第二步:编写用户态代码 hello.c

第三步:修改 Makefile 并编译

第四步:运行与查看结果

三、make过程解析

第一阶段:编译底层库 (libbpf.a)

第二阶段: 编译出定制版的 bpftool 工具

第三阶段: 编译所有的示例程序(核心魔术)


一、拉取相关包

要让libbpf-bootstrap顺利编译并运行,你需要拉取两类“包”:

  1. Linux 系统依赖包(通过aptyum安装的开发工具链)。

  2. Git 子模块libbpf-bootstrap自身依赖的底层库)。

请按照以下步骤在终端中操作:

第一步:安装 Linux 系统依赖包

eBPF 编译高度依赖clang(因为 gcc 不支持将 C 语言编译成 BPF 字节码)以及内核头文件。

如果你使用的是Ubuntu / Debian系统,运行以下命令:

Bash

sudo apt update sudo apt install -y build-essential clang llvm pkg-config libelf-dev zlib1g-dev libapr1-dev libssl-dev linux-headers-$(uname -r)

如果你使用的是CentOS / RHEL / Fedora系统,运行以下命令:

Bash

sudo dnf check-update sudo dnf install -y make gcc clang llvm elfutils-libelf-devel zlib-devel openssl-devel kernel-devel-$(uname -r)

注意:linux-headers-$(uname -r)kernel-devel-...非常重要,它确保编译环境拥有你当前内核版本的头文件。

第二步:拉取 Git 子模块(核心代码包)

libbpf-bootstrap仓库本身很小,它把最核心的libbpf库和bpftool工具作为Git Submodule(子模块)关联在仓库中。

如果你还没有克隆仓库,直接用这一条命令连同子模块一起拉下来:

Bash

git clone --recurse-submodules https://github.com/libbpf/libbpf-bootstrap.git

如果你已经克隆了仓库,但发现libbpf/bpftool/目录里是空的,请在libbpf-bootstrap项目根目录下运行:

Bash

git submodule update --init --recursive

检查是否成功

完成以上两步后,你可以进入libbpf-bootstrap/examples/c/目录,随便敲一个make试试。

如果输出了一堆编译信息且没有报错,说明你的环境已经完全就绪

二、编写Hello World

在 eBPF 的世界里,写 Hello World 需要两部分:

  1. 内核态代码 (hello.bpf.c):负责在内核里拦截事件(比如每次有人执行新进程时),然后打印一行 "Hello World"。

  2. 用户态代码 (hello.c):负责把上面的代码加载到内核中,并让它跑起来。

你可以直接在libbpf-bootstrap/examples/c/目录下创建这两个文件:

第一步:编写内核态代码hello.bpf.c

这个程序会挂载到sys_enter_execve(系统调用execve的入口,即任何程序启动时)。每次有新程序运行,它就会调用bpf_printk在内核日志里打印一句 Hello World。

// hello.bpf.c #include "vmlinux.h" #include <bpf/bpf_helpers.h> SEC("tracepoint/syscalls/sys_enter_execve") int handle_execve(void *ctx) { // bpf_printk 是 eBPF 的 printf,数据会输出到内核调试日志中 bpf_printk("Hello World from eBPF!\n"); return 0; } char LICENSE[] SEC("license") = "GPL";

第二步:编写用户态代码hello.c

用户态负责生命周期管理:打开、加载、挂载、等待、销毁。

// hello.c #include <stdio.h> #include <unistd.h> #include <bpf/libbpf.h> #include "hello.skel.h" // 编译时由工具自动生成的骨架文件 int main() { struct hello_bpf *skel; int err; // 1. 打开 eBPF 程序 skel = hello_bpf__open(); if (!skel) { fprintf(stderr, "Failed to open BPF skeleton\n"); return 1; } // 2. 加载 eBPF 字节码到内核 err = hello_bpf__load(skel); if (err) { fprintf(stderr, "Failed to load BPF skeleton\n"); goto cleanup; } // 3. 挂载到内核钩子(tracepoint) err = hello_bpf__attach(skel); if (err) { fprintf(stderr, "Failed to attach BPF skeleton\n"); goto cleanup; } printf("Hello World 运行成功!按 Ctrl+C 退出...\n"); printf("请在另一个终端输入 'sudo cat /sys/kernel/tracing/trace_pipe' 查看内核输出。\n"); // 4. 让程序保持运行,否则退出时 eBPF 程序会自动被卸载 while (1) { sleep(1); } cleanup: // 5. 销毁和清理 hello_bpf__destroy(skel); return -err; }

第三步:修改 Makefile 并编译

为了让libbpf-bootstrap的自动化脚本知道我们要编译这个新程序,你需要把hello加入到Makefile的目标列表中。

  1. 打开libbpf-bootstrap/examples/c/Makefile

  2. 找到APPS =这一行(大概在第 10-15 行),在后面加上hello

    APPS = minimalist bootstrap uprobe kprobe fentry hello
  3. 保存退出,在examples/c/目录下直接输入:make(Makefile 会自动帮你用 clang 编译hello.bpf.c,用 bpftool 生成hello.skel.h,再用 gcc 编译出可执行文件hello

第四步:运行与查看结果

因为要往内核注入代码,所以需要sudo权限:

  1. 运行我们的程序:

    sudo ./hello

    此时程序会提示你成功,并进入等待状态。

  2. 触发事件并查看内核日志:打开另一个新的终端窗口,输入以下命令来实时查看内核的调试管道:

    sudo cat /sys/kernel/tracing/trace_pipe

    然后在这个新终端里随便敲几个命令(比如lspwd等),每当你敲一次命令,由于触发了execve系统调用,你就会在trace_pipe的终端里看到:

    <...>-12345 [001] d... 12345.67890: bpf_trace_printk: Hello World from eBPF!

恭喜你!你的第一个 eBPF Hello World 已经成功跑在 Linux 内核里了。

三、make过程解析

简单来说,刚刚make替你干了三件大事:搞定底层基础、生成必备工具、批量生产示例

第一阶段:编译底层库 (libbpf.a)

MKDIR .output/libbpf LIB libbpf.a CC .../staticobjs/bpf.o AR .../libbpf.a INSTALL bpf.h libbpf.h ...
  • 干了什么:libbpf-bootstrap先跑去你拉下来的libbpf子模块源码目录里,把用来和 Linux 内核通信的底层底层库(C语言源文件们)全部用CC(gcc) 编译了一遍。

  • 产物:最终通过AR打包出了一个静态链接库libbpf.a,并把一堆.h头文件放到了.output临时目录里。这是所有 eBPF 程序能跑起来的基础。

第二阶段: 编译出定制版的bpftool工具

MKDIR bpftool BPFTOOL bpftool/bootstrap/bpftool ... clang-bpf-co-re: [ on ] LINK .../bpftool
  • 干了什么:要生成 eBPF 程序专用的“骨架文件(.skel.h)”,必须依赖 Linux 官方的bpftool工具。make在这里帮你把bpftool工具的源码也编译了一遍。

  • 产物:在本地产生了一个可执行的bpftool。注意看日志里的clang-bpf-co-re: [ on ],这代表一次编译到处运行(CO-RE)的特性已经成功开启。

第三阶段: 编译所有的示例程序(核心魔术)

接下来,它开始像流水线一样疯狂生产示例(例如minimalbootstrapkprobe等)。我们拿其中一个来看:

BPF .output/minimal.bpf.o <-- 1. 编译内核态 GEN-SKEL .output/minimal.skel.h <-- 2. 自动生成脚手架/骨架 CC .output/minimal.o <-- 3. 编译用户态 BINARY minimal <-- 4. 产出最终可执行程序

在这个阶段,每一个项目都经历了eBPF标准的编译四步走

  1. BPF:使用clang把跑在内核态的minimal.bpf.c编译成 BPF 字节码.output/minimal.bpf.o

  2. GEN-SKEL:调用刚才编译好的bpftool工具,读取minimal.bpf.o,自动吐出一个名为minimal.skel.h的头文件。这个文件里包含了可以被用户态直接调用的加载函数。

  3. CC:用gcc编译用户态控制代码minimal.o(它里面引用了刚才生成的骨架文件)。

  4. BINARY:把用户态代码和刚才的libbpf.a静态库链接在一起,产出最终可以在终端直接运行的绿色可执行文件。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询