Rust 系列 02:理解 eBPF 核心构建单元
本文地址:https://www.ebpf.top/post/02-understanding-ebpf-core-building-blocks
在上一篇文章中,我们使用 Rust + eBPF 构建了一个系统调用跟踪器。这里,我们将解析使其工作的核心组件以及为 eBPF 工具提供核心动力的深层机制。理解这些核心组件将帮助你构建更复杂的可观测性、安全性和网络工具。
1. BPF 程序类型与钩子点
程序类型决定 eBPF 程序的功能范围,钩子点(Hook Point)则指定其在内核中的执行位置。主要程序类型如下所示:
XDP(快速数据路径)
- 在网络数据包处理的最早阶段运行
- 非常适合 DDoS 保护、负载均衡
- 可以 DROP、PASS、REDIRECT 或 TX 数据包
TC(流量控制)
- 附加到网络入口 / 出口处
- 比 XDP 有更多上下文,可以修改数据包
- 用于进阶的数据包过滤和整形
Kprobes/Kretprobes
- 几乎可跟踪任何内核函数的动态跟踪
- Kprobe = 函数入口,Kretprobe = 函数返回
Uprobes/Uretprobes
- 跟踪用户空间应用程序(如 Go、Python 应用)
- 可以检查函数参数、返回值
- 非常适合应用程序性能监控
Tracepoints
- 静态内核插桩(instrumentation)点
- 跨内核版本方面比 kprobes 更稳定
- 预定义事件如
<sys_enter_openat>
你可以在以下位置查看所有可用的跟踪点:/sys/kernel/tracing/events
。我们的系统调用跟踪程序使用了<sys_enter>
!
CGROUP(控制组)
- 按容器 / 进程组控制资源访问
- 实施自定义策略(网络、文件访问)
Socket Programs(套接字程序)
- 在套接字级别过滤 / 重定向数据包
- 实现自定义负载均衡器、防火墙
注意:虽然这里介绍了我最熟悉的常用程序类型,但 Linux 内核还支持其他多种(且不断演进的)程序类型。完整的最新列表请参考官方文档:Supported eBPF Program Types
2. BPF 验证器:安全守护者
BPF 验证器是确保 eBPF 安全运行的关键组件。 验证器在将程序加载到内核之前对其执行静态分析。验证器检查内容:
内存安全
- 无越界数组访问
- 无空指针解引用
- 变量的正确初始化
控制流
- 无无限循环(Linux 5.3 起仅支持有界循环)
- 所有代码路径必须退出
- 无不可达代码
辅助函数使用
- 只能调用程序类型许可的辅助函数(Helper Function)
- 正确的参数类型和数量
栈使用
- 限制为 512 字节的栈空间
- 无需栈溢出保护
|
|
为什么重要:验证器确保 eBPF 程序不会导致内核崩溃,使其可以安全地在生产环境中运行。
3. BPF map:数据存储与通信
BPF map 提供三大核心能力:
- 在 eBPF 程序调用之间存储状态
- 在内核和用户空间之间通信
- 在不同 eBPF 程序之间共享数据
基本 map 类型:
哈希 map(Hash Maps) - 键值存储
|
|
数组(Arrays) - 基于索引的访问
|
|
性能事件数组(Perf Event Arrays) - 高性能数据流
|
|
你可以在上一篇博客文章的代码中找到这个的用法:
|
|
环形缓冲区(Ring Buffers) - perf 事件的现代替代方案(Linux 5.8+)
- 更低的性能开销与更高的内存效率
- 内置流控处理
map 使用场景:
- 计数器:跟踪指标(数据包、系统调用、错误)
- 缓存:存储查找结果以避免重复工作
- 状态机:跟踪连接状态、用户会话
- 配置:来自用户空间的运行时配置
4. 辅助函数:内核 API
eBPF 程序不能直接调用内核函数。相反,它们使用辅助函数 —一组经过严格筛选的安全内核 API。常见辅助函数类别:
调试与日志
|
|
map 操作
|
|
上下文信息
|
|
网络操作
|
|
时间与随机数
|
|
辅助函数的使用范围取决于 eBPF 程序类型,同时辅助函数数量也取决于内核版本。
5. BTF & CO-RE:一次编写,到处运行
BTF(BPF 类型格式)和 CO-RE(一次编译,到处运行)解决了内核兼容性问题。
问题
内核结构在不同版本之间会变化:
|
|
解决方案 - CO-RE
我们在上一篇文章中简要介绍了这一点。现在,让我们探讨 CO-RE 技术如何工作以及为什么它对于构建可移植的 eBPF 程序至关重要。
CO-RE 技术基于 BTF(BPF 类型格式)实现,使 eBPF 程序在运行时感知内核版本,而无需为每个内核重新编译。
|
|
以下是工作原理:
vmlinux.h
包含使用 BTF 从内核提取的类型定义。BPF_CORE_READ()
安全地从task_struct
读取pid
字段,无论其偏移量如何。- 加载 eBPF 程序时,libbpf 将编译的偏移量与实际内核的布局进行比较,并根据需要重新定位字段访问。
使用 CO-RE 收益
- 一次编译,在任何内核版本上运行
- 自动字段偏移重定位
- 运行时内核适配
这就是为什么 build.rs
包含 <vmlinux>
— 它为目标内核提供 BTF 类型!
6. BPF 尾调用(tail call):程序链接
尾调用允许一个 eBPF 程序调用另一个,实现:
- 跨多个程序的复杂逻辑
- 运行时程序更新
- 模块化架构
|
|
使用场景:
- 数据包处理管道
- 功能切换(启用 / 禁用功能)
7. BPF Token(BPF 令牌):委托特权
BPF Token(BPF 令牌)通过允许特权委托,实现容器中的安全 eBPF。
问题
传统上 eBPF 需要<CAP_BPF>
或<CAP_SYS_ADMIN>
,给容器提供了太多特权。
解决方案
|
|
收益
- 容器可以在没有 root 的情况下运行 eBPF
- 细粒度权限控制
- 更好的安全隔离
注意:BPF 令牌从 Linux 6.7 开始可用。有关完整用法,请参阅内核文档。
8. eBPF 工具生态系统
bpftool - 瑞士军刀
|
|
bpftrace - 动态跟踪脚本 bpftrace 是调试 eBPF 程序的快速方法。
|
|
BCC - Python eBPF 框架 BCC 是一个强大的工具包,用于编写和运行 eBPF 程序。
- 高级 Python 接口
- 非常适合原型设计和学习
- C 语言实时编译
我们在上一篇博客中讨论的 Rust/libbpf-rs 方法比 BCC 更现代、性能更好!
9. 综合应用
以下是这些组件如何在我们在上一篇文章中演示的系统调用跟踪器中工作:
- 程序类型:跟踪点
<sys_enter_*>
- 钩子点:系统调用入口点
- 验证器:验证程序安全性
- BPF map:用于数据传递的 Perf 事件数组
- 辅助函数:
<bpf_get_current_pid_tgid()>
、<bpf_perf_event_output()>
- BTF/CO-RE:内核兼容性机制
- 工具:用于调试的
bpftool
,用于用户空间管理控制的 Rust
有了对 eBPF 基础构建块的扎实理解,你现在已经准备好超越理论,开始构建强大的生产级工具。
原文地址:https://dev.to/maheshrayas/-02-understanding-ebpf-core-building-blocks-1221
作者:Mahesh Rayas
- 原文作者:DavidDi
- 原文链接:https://www.ebpf.top/post/02-understanding-ebpf-core-building-blocks/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 最后更新时间:2025-08-09 18:24:46.342564215 +0800 CST

