eBPF新手使用bpf_trace_printk触发加载错误,TCP报文排查程序运行失败求助
问题分析与解决
1. 你的程序报错根源
首先,程序运行失败的核心问题不是bpf_trace_printk本身,而是kprobe参数处理错误和不安全的内存访问导致eBPF验证器拒绝加载程序:
错误点1:kprobe参数定义错误
tcp_v4_rcv的原型是int tcp_v4_rcv(struct sk_buff *skb),但kprobe类型的eBPF程序的第一个参数必须是struct pt_regs *ctx(保存被挂钩函数的寄存器状态),而不是直接声明struct sk_buff* skb。你需要通过PT_REGS_PARM1(ctx)来获取tcp_v4_rcv的第一个参数(也就是skb)。
错误点2:内存访问未通过验证器检查
即使你正确拿到skb指针,直接访问tcp_hdr(skb)->dest时,eBPF验证器无法确认该内存区域是合法可访问的,因此会抛出R2 !read_ok错误。你需要使用安全的内存读取方式(比如bpf_probe_read)来获取TCP头部数据。
2. 修正后的代码
下面是可以正常运行的修改版本:
hello_kern.c
#include <linux/tcp.h> #include <uapi/linux/bpf.h> #include <uapi/linux/ptrace.h> #include <uapi/linux/tcp.h> #include <uapi/linux/byteorder/generic.h> #include "bpf_helpers.h" SEC("kprobe/tcp_v4_rcv") int bpf_prog(struct pt_regs *ctx) { // 从寄存器中获取tcp_v4_rcv的skb参数 struct sk_buff *skb = (struct sk_buff *)PT_REGS_PARM1(ctx); struct tcphdr th; __u16 dest_port; char msg[] = "hello world! My dest is %d\n"; // 先检查skb指针是否有效 if (!skb) return 0; // 使用bpf_probe_read安全读取TCP头部数据,避免验证器报错 if (bpf_probe_read(&th, sizeof(th), tcp_hdr(skb)) != 0) return 0; // 将网络字节序的端口号转换为主机字节序,确保打印结果可读 dest_port = ntohs(th.dest); bpf_trace_printk(msg, sizeof(msg), dest_port); return 0; } char _license[] SEC("license") = "GPL";
hello_user.c(无需修改)
#include "bpf_load.h" int main(void) { if (load_bpf_file("hello_kern.o")) return -1; read_trace_pipe(); return 0; }
修改说明:
- 用
struct pt_regs *ctx作为kprobe程序的入口参数,通过PT_REGS_PARM1宏跨平台获取目标函数的参数。 - 添加
skb空指针检查,避免无效内存访问。 - 使用
bpf_probe_read读取TCP头部,这是eBPF验证器认可的安全内存操作方式。 - 用
ntohs转换端口号的字节序,确保打印出的是十进制的可读端口。
3. 推荐的eBPF学习资源
如果你想系统学习eBPF,这些资源会很有帮助:
- Linux内核官方文档:内核源码中的
Documentation/bpf/目录包含了最权威的eBPF核心概念、辅助函数、程序类型说明,是入门的基础参考。 - 《BPF Performance Tools》书籍:Brendan Gregg编写,涵盖eBPF的实战应用、工具开发和性能分析,是从入门到进阶的经典读物。
- 内核eBPF示例程序:Linux源码的
samples/bpf/目录提供了大量可运行的示例,覆盖kprobe、tracepoint、XDP等多种场景,适合动手实践。 - Cilium项目文档:Cilium的文档中有大量eBPF在网络、安全领域的实战教程,适合有实际场景需求的学习者。
- eBPF官方教程:内核社区维护的教程,包含从基础到高级的概念讲解和示例代码,内容全面且更新及时。
内容的提问来源于stack exchange,提问作者GODP
相关产品推荐
相关产品推荐

