eBPF学习(二):经典的Hello World程序

📅 2026/7/2 3:06:16
eBPF学习(二):经典的Hello World程序
目录一、拉取相关包第一步安装 Linux 系统依赖包第二步拉取 Git 子模块核心代码包检查是否成功二、编写Hello World第一步编写内核态代码 hello.bpf.c第二步编写用户态代码 hello.c第三步修改 Makefile 并编译第四步运行与查看结果三、make过程解析第一阶段编译底层库 (libbpf.a)第二阶段 编译出定制版的 bpftool 工具第三阶段 编译所有的示例程序核心魔术一、拉取相关包要让libbpf-bootstrap顺利编译并运行你需要拉取两类“包”Linux 系统依赖包通过apt或yum安装的开发工具链。Git 子模块libbpf-bootstrap自身依赖的底层库。请按照以下步骤在终端中操作第一步安装 Linux 系统依赖包eBPF 编译高度依赖clang因为 gcc 不支持将 C 语言编译成 BPF 字节码以及内核头文件。如果你使用的是Ubuntu / Debian系统运行以下命令Bashsudo 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系统运行以下命令Bashsudo 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子模块关联在仓库中。如果你还没有克隆仓库直接用这一条命令连同子模块一起拉下来Bashgit clone --recurse-submodules https://github.com/libbpf/libbpf-bootstrap.git如果你已经克隆了仓库但发现libbpf/或bpftool/目录里是空的请在libbpf-bootstrap项目根目录下运行Bashgit submodule update --init --recursive检查是否成功完成以上两步后你可以进入libbpf-bootstrap/examples/c/目录随便敲一个make试试。如果输出了一堆编译信息且没有报错说明你的环境已经完全就绪二、编写Hello World在 eBPF 的世界里写 Hello World 需要两部分内核态代码 (hello.bpf.c)负责在内核里拦截事件比如每次有人执行新进程时然后打印一行 Hello World。用户态代码 (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 运行成功按 CtrlC 退出...\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的目标列表中。打开libbpf-bootstrap/examples/c/Makefile。找到APPS 这一行大概在第 10-15 行在后面加上helloAPPS minimalist bootstrap uprobe kprobe fentry hello保存退出在examples/c/目录下直接输入makeMakefile 会自动帮你用 clang 编译hello.bpf.c用 bpftool 生成hello.skel.h再用 gcc 编译出可执行文件hello第四步运行与查看结果因为要往内核注入代码所以需要sudo权限运行我们的程序sudo ./hello此时程序会提示你成功并进入等待状态。触发事件并查看内核日志打开另一个新的终端窗口输入以下命令来实时查看内核的调试管道sudo cat /sys/kernel/tracing/trace_pipe然后在这个新终端里随便敲几个命令比如ls、pwd等每当你敲一次命令由于触发了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的特性已经成功开启。第三阶段 编译所有的示例程序核心魔术接下来它开始像流水线一样疯狂生产示例例如minimal、bootstrap、kprobe等。我们拿其中一个来看BPF .output/minimal.bpf.o -- 1. 编译内核态 GEN-SKEL .output/minimal.skel.h -- 2. 自动生成脚手架/骨架 CC .output/minimal.o -- 3. 编译用户态 BINARY minimal -- 4. 产出最终可执行程序在这个阶段每一个项目都经历了eBPF标准的编译四步走BPF使用clang把跑在内核态的minimal.bpf.c编译成 BPF 字节码.output/minimal.bpf.o。GEN-SKEL调用刚才编译好的bpftool工具读取minimal.bpf.o自动吐出一个名为minimal.skel.h的头文件。这个文件里包含了可以被用户态直接调用的加载函数。CC用gcc编译用户态控制代码minimal.o它里面引用了刚才生成的骨架文件。BINARY把用户态代码和刚才的libbpf.a静态库链接在一起产出最终可以在终端直接运行的绿色可执行文件。