eBPF fentry/fexit读取pt_regs参数报invalid bpf_context access错误
fentry/fexit 系统调用追踪触发 invalid bpf_context access 问题说明
问题现象
基于fentry/fexit实现eBPF系统调用追踪时,部分环境下读取struct pt_regs *regs参数会触发程序加载报错,复现代码如下:
SEC("fentry/__x64_sys_recvfrom") int BPF_PROG(fentry_syscall, struct pt_regs *regs) { struct event t; bpf_get_current_comm(t.comm, TASK_COMM_LEN); u64 id = bpf_get_current_pid_tgid(); t.pid = id >> 32; // 部分环境下该行触发错误 t.fd = PT_REGS_PARM1_CORE(regs); bpf_printk("comm: %s, pid: %d, fd: %d", t.comm, t.pid, t.fd); return 0; }
程序加载时的报错信息:
$ sudo ./output 2022/07/01 03:33:01 loading objects: field FentrySyscall: program fentry_syscall: load program: permission denied: arg#0 type is not a struct Unrecognized arg#0 type PTR ; int BPF_PROG(fentry_syscall, struct pt_regs *regs) { 0: (79) r6 = *(u64 *)(r1 +0) func '__x64_sys_recvfrom' arg0 type FWD is not a struct invalid bpf_context access off=0 size=8 processed 1 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
排查结论
该错误仅出现在/sys/kernel/btf/vmlinux中对应系统调用的参数被声明为FWD(前向声明)类型的场景:
- 异常环境下BTF信息中,
pt_regs仅存在前向声明,无具体结构体布局定义:
$ bpftool btf dump file /sys/kernel/btf/vmlinux format raw ... [13362] FWD 'pt_regs' fwd_kind=struct [13363] CONST '(anon)' type_id=13362 [13364] PTR '(anon)' type_id=13363 [13365] FUNC_PROTO '(anon)' ret_type_id=36 vlen=1 '__unused' type_id=13364 ... [13608] FUNC '__x64_sys_recvmsg' type_id=13365 linkage=static ...
- 正常运行环境下BTF信息中,
pt_regs存在完整的结构体类型定义:
$ bpftool btf dump file /sys/kernel/btf/vmlinux format raw [1] INT 'long unsigned int' size=8 bits_offset=0 nr_bits=64 encoding=(none) ... [226] STRUCT 'pt_regs' size=168 vlen=21 'r15' type_id=1 bits_offset=0 'r14' type_id=1 bits_offset=64 'r13' type_id=1 bits_offset=128 'r12' type_id=1 bits_offset=192 'bp' type_id=1 bits_offset=256 'bx' type_id=1 bits_offset=320 'r11' type_id=1 bits_offset=384 'r10' type_id=1 bits_offset=448 'r9' type_id=1 bits_offset=512 'r8' type_id=1 bits_offset=576 'ax' type_id=1 bits_offset=640 'cx' type_id=1 bits_offset=704 'dx' type_id=1 bits_offset=768 'si' type_id=1 bits_offset=832 'di' type_id=1 bits_offset=896 'orig_ax' type_id=1 bits_offset=960 'ip' type_id=1 bits_offset=1024 'cs' type_id=1 bits_offset=1088 'flags' type_id=1 bits_offset=1152 'sp' type_id=1 bits_offset=1216 'ss' type_id=1 bits_offset=1280 ... [5183] CONST '(anon)' type_id=226 ... [5189] PTR '(anon)' type_id=5183 ... [5321] FUNC_PROTO '(anon)' ret_type_id=42 vlen=1 '__unused' type_id=5189 ... [17648] FUNC '__x64_sys_recvmsg' type_id=5321 linkage=static ...
多发行版测试验证,pt_regs的BTF定义差异由发行版内核补丁、编译配置、系统调用所在编译单元的头文件引用情况共同决定,和内核版本、发行版、具体追踪的系统调用三个因素都相关。
根本原因
内核编译生成BTF信息时,如果某个系统调用实现所在的编译单元没有引入struct pt_regs的完整定义头文件(如asm/ptrace.h),编译器只会为该函数原型中的pt_regs*参数生成不完整的前向声明(FWD类型),不会记录结构体的大小、字段偏移等布局信息。
eBPF验证器在校验程序对上下文参数的访问时,如果发现要访问的指针指向FWD类型,无法判定访问偏移的合法性,就会直接拒绝加载,抛出invalid bpf_context access错误。
兼容规避方案
可根据场景选择以下方案实现全新版Linux环境兼容:
- 优先使用fentry直接按系统调用原型传参,完全绕开pt_regs读取。fentry钩子本身支持直接获取被hook函数的实际参数,不需要手动从pt_regs解析,只要BPF_PROG的参数列表和对应系统调用的原型一致,libbpf的CO-RE机制会自动完成参数重定位,不受pt_regs前向声明问题影响。修改后的示例代码如下:
SEC("fentry/__x64_sys_recvfrom") int BPF_PROG(fentry_syscall, int fd, void *buf, size_t len, int flags, struct sockaddr *addr, int *addrlen) { struct event t; bpf_get_current_comm(t.comm, TASK_COMM_LEN); u64 id = bpf_get_current_pid_tgid(); t.pid = id >> 32; t.fd = fd; // 直接读取参数,无需通过PT_REGS宏解析pt_regs bpf_printk("comm: %s, pid: %d, fd: %d", t.comm, t.pid, t.fd); return 0; }
- 对兼容性要求极高的场景,可优先选择BTF tracepoint类型的
sys_enter/sys_exit挂载点,这类挂载点的上下文参数由内核明确定义,不存在类型前向声明问题,跨版本、跨发行版兼容性最稳定。 - 确实需要读取pt_regs完整结构(如读取orig_ax字段获取系统调用号)的场景,可以在eBPF代码中内置对应架构的
pt_regs结构体定义,同时通过bpf_core_type_exists宏检测内核BTF中是否存在完整的pt_regs定义,自动在fentry和tracepoint实现之间切换,做双逻辑兼容。
内容的提问来源于stack exchange,提问作者Akihiro HARAI
相关产品推荐
相关产品推荐

