在学习 eBPF 的过程中,经常会遇到一系列概念:tracepointkprobesuprobesfentry/fexit,以及用户态工具如 BCCbpftracelibbpf + skeleton 等。这些概念看似零散,但实际上构成了 eBPF 生态的核心。

本文将从两个维度进行总结:

  1. 内核插桩技术的区别与作用
  2. 用户态前端工具的发展趋势

一、为什么需要插桩技术?

插桩(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 加载和控制 。


文章作者: 易百分
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 易百分 !
  目录