在学习 eBPF 的过程中,经常会遇到一系列概念:tracepoint、kprobes、uprobes、fentry/fexit,以及用户态工具如 BCC、bpftrace、libbpf + skeleton 等。这些概念看似零散,但实际上构成了 eBPF 生态的核心。
本文将从两个维度进行总结:
- 内核插桩技术的区别与作用
- 用户态前端工具的发展趋势
一、为什么需要插桩技术?
插桩(Instrumentation)的本质是:
在不修改程序逻辑的前提下,动态地观察系统行为。
在 Linux 中,这意味着你可以:
- 追踪系统调用、函数执行路径
- 获取函数参数、返回值
- 分析性能瓶颈(CPU、IO、锁竞争等)
- 做安全审计或行为监控
二、常见内核插桩技术对比
1. Tracepoint(静态插桩)
- 由内核开发者预埋(如果自己添加到内核里那要重新编译整个内核然后安装,在生产环境中一般只能有什么用什么)
- 接口稳定(ABI 稳定)
- 开销低,适合生产环境
2. Kprobes(内核动态插桩)
- 可以挂在任意内核函数包括:
kprobe(函数入口)kretprobe(函数返回)
- 非侵入式,无需修改内核源码。
- 但符号名变动,可能不稳定,比如内核更新迭代把有的函数稍微变了个名字。
3. Uprobes(用户态插桩)
- 类似 kprobes,可插入到用户函数入口和返回处。但用于用户空间的程序(如 nginx、python),可以追踪:
- 应用函数调用
- 库函数(如 libc)
- 需要提供二进制文件路径和函数偏移。
- 配合 eBPF 可用于分析用户程序的性能瓶颈或行为模式。
4. fentry / fexit(新一代 BPF hook)
- 是一种 BPF-specific 的 hook 点。即基于 BPF 的函数入口/退出 hook。
- 只能 attach 到内核中支持 BPF 的函数(如 LSM、TC、XDP等 BPF attach 点)。
- 可以访问函数的输入参数和返回值,常用于函数级性能分析或安全控制。
- 特定函数返回时调用 BPF 程序
5. 其他相关技术
不完整,只是把笔者平时有印象的放在这里:
| 技术 | 说明 |
|---|---|
| raw_tracepoint | 更底层的 tracepoint |
| USDT | 用户态静态探针 |
| perf_event | 性能计数器 |
三、eBPF 架构介绍
编写eBPF程序通常包括用户态和内核态两部分,内核态和插桩有关是实际逻辑;用户态主要是加载、运行和监控内核态程序,处理内核态输出的数据。
由Clang、LLVM开发工具链将eBPF编译成可执行文件,可以通过libbpf框架构建和运行,这一步中BPF系统调用会将这些数据加载到内核中。
系统调用后,内核校验器会保证这些代码在内核执行的安全;JIT编译器生成机器码就能在内核中运行了。
插桩技术是触发 eBPF 程序执行的时机,程序将事件信息发送给用户态利用的是eBPF map这个共享数据结构。

四、用户态“前端”工具演进
eBPF 程序运行在内核,但需要用户态工具来:
- 加载程序
- attach 到 hook 点
- 读取数据
- 展示结果
这些工具都可以理解为“前端”。
| 名称 | 编程语言 | 特点 | 适合人群 |
|---|---|---|---|
| BCC | Python/C++ | 封装良好,开发简单,动态注入 C 代码;但封装的太好了有些特定环境用不了 | 快速开发,入门友好 |
| bpftrace | 类似脚本语言 | 类似 awk 的 DSL,适合一行追踪分析 | 即席调试,观测 |
| libbpf + skeleton(BPF CO-RE) | C(或与 Rust、Go 结合) | 原生接口、跨内核兼容、skel自动生成的用户态绑定代码 | 现当前官方推荐方案 |
| libbpf-rs / aya (Rust) | Rust | 安全、现代语言支持 eBPF | Rust 开发者 |
| goebpf / cilium/ebpf (Go) | Go | Go 生态的 eBPF 封装 | Go 开发者 |
| bpftool | CLI 工具 | 原生命令行接口 | 调试和原型分析 |
笔者在踩坑无数后坚定选择libbpf + skeleton Compile Once – Run Everywhere,工作流程:编写 eBPF C 程序;用 clang 编译成 .o;用 bpftool 生成 skeleton;用户态通过 libbpf 加载和控制 。