Rust 系列 03:深入理解 Kprobes 与 Kretprobes
本文地址:https://www.ebpf.top/post/03-lets-understand-kprobes-kretprobes
在 Linux 内核跟踪领域,Kprobes 和 Kretprobes 是 eBPF 生态系统中最古老且最灵活的工具之一。事实上,Kprobes 的出现远早于 eBPF,最早在 Linux 内核 2.6.9 版本(2004 年)中引入,可动态跟踪几乎所有内核函数,无需重新编译内核。
eBPF 随后出现(大约在 3.15+ 内核版本),并在 Kprobes 的基础上构建,允许用户将安全、沙箱化且高度可编程的代码附加到内核函数,包括 Kprobes 和 Kretprobes。这一组合为现代可观测性和性能工具奠定了坚实基础。
虽然与 XDP 或 Tracepoints 等其他 eBPF 程序类型相比,Kprobes 和 Kretprobes 通常具有更高的开销,但它们的多功能性使其变得不可或缺。它们几乎可以附加到任何内核函数,从而能够深入了解系统行为 — 这也是其被许多流行的基于 eBPF 的可观测性工具广泛采用的原因。
注意:Kprobe 无法探测黑名单函数
1. 工作原理
使用 Kprobes,可以将自定义 eBPF 程序附加到几乎任何内核函数。当该函数被调用时,附加的 eBPF 程序在其入口点被触发,然后原始函数照常继续执行。若需了解 Kprobe 的工作细节,建议阅读内核文档。
相比之下,Kretprobes 在函数返回时触发。这使得你能够捕获返回值或输出参数 — 使其成为调试、性能监控或基于返回值逻辑的理想选择。
本文将演示如何使用 Kprobes 跟踪 HTTP/1.1 头并提取用于可观测性。由于 SSL/TLS 会加密有效负载,此技术仅适用于未加密的 HTTP 流量。在后续文章中,我们将探讨如何使用 Uprobes 跟踪 HTTP/2(gRPC)头,Uprobes 允许观察用户空间库,如 golang.org/x/net/http2
。
更多关于 eBPF 跟踪参见:
- https://docs.ebpf.io/linux/program-type/BPF_PROG_TYPE_KPROBE
- https://www.kernel.org/doc/html/latest/trace/kprobes.html
2. 注意事项
Kprobes 提供了无与伦比的灵活性,可动态插桩几乎任何内核函数。然而,不加选择地附加探针可能会引入性能开销或稳定性风险。最佳实践包括:
- 针对与系统调用、网络或驱动程序相关的熟知、稳定内核函数
- 在
/proc/kallsyms
中确认符号存在和类型(查找大写 T 符号) - 在非生产环境中测试探针性能和功能
需注意,内核版本差异和模块加载/卸载事件可能会影响探针可靠性。
典型的 Kprobe 目标包括与系统调用、网络堆栈、驱动程序和文件系统操作相关的内核函数,可实现特定内核事件的跟踪。
性能影响:Kprobes 通过在函数入口或返回时注入代码增加插桩开销。虽然通常影响较小,但探测高频函数可能导致明显的 CPU 开销,因此在生产环境使用前必须进行性能测试。
/proc/kallsyms
是 Linux 中的特殊文件,列出所有当前加载的内核符号,包括函数名称、内存地址和变量符号。该文件在使用 Kprobes 时特别有用,可帮助识别可动态插桩的内核函数。
查看所有符号的命令:
|
|
但需注意:
- 函数可用性可能因内核版本、架构(如 x86 与 ARM)和构建配置而异
- 某些符号可能在内核新版本中被内联、优化或重命名
因此,使用 Kprobes 编写可移植 eBPF 程序时,需在目标内核上通过 /proc/kallsyms
验证符号。样例如下所示:
|
|
标记为 T
的符号表示全局(导出的)内核文本符号,即可可靠附加 Kprobes 的函数。
3. 实战示例
工作示例代码参见 GitHub 仓库。
该示例使用 eBPF 跟踪服务器的 HTTP/1.1 头,具体设置如下:
- HTTP 服务器公开基本 REST API,且未启用 SSL 加密(关键前提,因内核无法跟踪加密数据 —— 加密和解密发生在用户空间库如 OpenSSL 中)
- 运行 Rust 用户空间应用程序,传入 HTTP 服务器 PID 作为输入
- 用户空间程序加载 eBPF 程序并附加到相关内核函数(详见下文)
3.1 跟踪目标进程 PID
从用户空间更新 eBPF 映射以跟踪 HTTP 服务器 PID:
|
|
4. eBPF 程序逻辑:提取 HTTP 头
我们将三个探针附加到两个不同的内核函数,实现 HTTP 头跟踪功能。
4.1 跟踪连接建立:kretprobe/__sys_accept4
第一个 kretprobe 附加到内核函数 __sys_accept4
(在 accept4()
系统调用期间触发),用于捕获新建立的连接:
|
|
工作流程:
-
从系统调用返回值中提取新连接的文件描述符(fd)
-
检查调用进程 PID 是否在
tracked_pids
映射中 -
若匹配,将新 fd 存储到
active_app_sockets
映射中
4.2 跟踪数据读取:kprobe/ksys_read
此 kprobe 附加到 ksys_read()
内核函数(进程读取文件描述符时触发):
|
|
关键概念:buf
参数是指向用户空间内存缓冲区的指针,内核会将 read 系统调用的结果复制到此缓冲区。由于 eBPF 程序运行在内核空间,需使用 bpf_probe_read_user
等辅助函数安全访问用户空间内存。
4.3 处理读取结果:kretprobe/ksys_read
ksys_read()
系统调用返回时触发此 kretprobe,用于获取实际读取的数据:
|
|
4.4用户空间数据解析
用户空间进程持续轮询 perf 事件数组以接收 eBPF 程序发送的数据,解析逻辑如下:
|
|
内存布局兼容性:由于内核(C)和用户空间(Rust)的内存布局可能不同,需:
- 保持结构体字段顺序和类型一致
- 添加
#[repr(C)]
属性强制 C 语言内存布局 - 避免因对齐差异导致的未定义行为
5. 总结
本文深入探讨了:
- Kprobes 和 Kretprobes 的工作原理及在系统调用跟踪中的应用
- 如何利用 eBPF 在内核中捕获用户空间数据(如 HTTP 头)
- Rust 中安全解析 perf 事件数据的方法(使用
#[repr(C)]
和字段对齐)
这些技术为构建强大的可观测性工具奠定了基础,无需修改应用代码即可从内核层透明捕获数据。
原文链接:03 - Let’s understand Kprobes & Kretprobes
作者:Mahesh Rayas
- 原文作者:DavidDi
- 原文链接:https://www.ebpf.top/post/03-lets-understand-kprobes-kretprobes/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 最后更新时间:2025-08-09 18:28:18.400396883 +0800 CST

