fentry BPF程序挂载open系统调用的签名与加载错误问题
基于fentry的eBPF追踪open系统调用问题排查
问题背景
尝试编写fentry类型的eBPF程序追踪open系统调用,最初的最小实现如下:
SEC("fentry/__x64_sys_open") int BPF_PROG(trace_sys_open, const char *filename, int flags, umode_t mode) { if (flags & O_CREAT) { bpf_printk("fentry: open() with O_CREAT\n"); } return 0; }
加载程序时触发权限拒绝错误,内核验证器提示函数签名不匹配。查看内核源码中open系统调用的定义:
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode) { if (force_o_largefile()) flags |= O_LARGEFILE; return do_sys_open(AT_FDCWD, filename, flags, mode); }
尝试在eBPF函数原型中添加__user修饰符后,编译直接失败:
$ clang -O2 -g -target bpf -I../helpers -I.output -D__TARGET_ARCH_x86 -c file_tracing.bpf.c -o .output/file_tracing.bpf.o file_tracing.bpf.c:24:48: error: expected ')'...
改用struct pt_regs *ctx方式编写后程序能正常编译,但调用辅助函数handle_event_tracing时,加载再次失败:
$ sudo ./file_tracing libbpf: prog 'trace_sys_open': BPF program load failed: Permission denied...
错误原因分析
函数签名不匹配:
fentry类型的eBPF程序要求函数签名与内核目标函数的BTF定义严格一致。__user是内核空间专属宏,eBPF编译环境未定义该宏,直接添加会导致语法错误;即使内核函数参数带有__user标记,eBPF程序中只需使用普通指针类型即可,BTF会自动处理指针属性。辅助函数参数类型不兼容:
改用struct pt_regs *ctx后,辅助函数handle_event_tracing使用void *ctx作为参数,内核验证器会检查辅助函数的参数类型是否与调用上下文匹配,void*无法通过验证,导致加载失败。
解决方案
使用BTF自动匹配参数(推荐):
依赖内核BTF信息,用BPF_PROG宏自动生成与目标函数一致的签名,无需手动指定参数类型,需确保内核开启CONFIG_DEBUG_INFO_BTF。手动处理参数时保持类型一致:
若使用struct pt_regs *ctx获取参数,辅助函数的参数必须显式声明为struct pt_regs *ctx,避免类型不匹配。
完整可运行示例
eBPF代码(test.bpf.c)
#include <vmlinux.h> #include <bpf/bpf_tracing.h> #include <bpf/bpf_core_read.h> #define TASK_COMM_LEN 16 #define O_CREAT 0100 // 八进制,对应内核O_CREAT的0x40 struct event { __u64 ts; pid_t pid; __u32 tid; uid_t uid; char comm[TASK_COMM_LEN]; __u64 cgroup_id; }; struct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); __uint(key_size, sizeof(u32)); __uint(value_size, sizeof(u32)); } events SEC("maps"); // 辅助函数参数显式声明为struct pt_regs* static __always_inline int handle_event_tracing(struct pt_regs *ctx) { struct event event = {}; event.ts = bpf_ktime_get_ns(); // 补充时间戳 event.cgroup_id = bpf_get_current_cgroup_id(); u64 pid_tgid = bpf_get_current_pid_tgid(); event.pid = pid_tgid >> 32; event.tid = pid_tgid & 0xFFFFFFFF; event.uid = bpf_get_current_uid_gid() >> 32; bpf_get_current_comm(&event.comm, sizeof(event.comm)); // 输出perf事件 bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event)); return 0; } // 方案1:使用BTF自动匹配参数(推荐) SEC("fentry/__x64_sys_open") int BPF_PROG(trace_sys_open, const char *filename, int flags, umode_t mode) { if (flags & O_CREAT) { // 传递当前函数的ctx(由BPF_PROG自动注入) return handle_event_tracing(ctx); } return 0; } /* // 方案2:手动使用struct pt_regs*获取参数 SEC("fentry/__x64_sys_open") int trace_sys_open(struct pt_regs *ctx) { int flags = (int)PT_REGS_PARM2(ctx); if (flags & O_CREAT) { return handle_event_tracing(ctx); } return 0; } */ char LICENSE[] SEC("license") = "GPL";
编译与加载步骤
# 生成vmlinux.h(依赖内核BTF) bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h # 编译eBPF字节码 clang -O2 -g -I. -target bpf -D__TARGET_ARCH_x86 -c test.bpf.c -o test.bpf.o # 生成skeleton头文件(可选,用于用户态加载) bpftool gen skeleton test.bpf.o > test.skel.h # 加载eBPF程序 sudo bpftool prog load ./test.bpf.o /sys/fs/bpf/test_prog
验证方法
加载成功后,可通过perf工具监听事件:
sudo perf record -e bpf_output -a # 执行touch test.txt触发open(O_CREAT) sudo perf script
内容的提问来源于stack exchange,提问作者Polo
相关产品推荐
相关产品推荐

