fentry与kprobe的BPF验证器行为不一致问题排查
kprobe与fentry eBPF程序访问vfs_unlink参数行为差异的原因与解决方法
问题背景
- 内核版本:6.8.0-87-generic
- 两个kprobe程序(程序1、2)可正常编译加载,均能打印
vfs_unlink的第4个参数(struct inode **类型,对应代码中的arg3) - 同逻辑的fentry程序(程序3、4)尝试访问该参数时触发BPF验证器错误,仅访问前3个参数的fentry程序5可正常加载
- 验证器核心错误日志:
func 'vfs_unlink' arg3 type PTR is not a struct
根因分析
kprobe与fentry的参数校验机制存在本质区别:
- kprobe:通过
pt_regs直接读取寄存器原始值,验证器仅校验寄存器访问的合法性,不会对参数的深层类型(如二级指针)做限制——即使是struct inode **,也会被当作64位地址直接读取,因此不会触发错误。 - fentry:属于内核函数入口的精准追踪,会严格匹配内核暴露的目标函数参数类型定义。对于
vfs_unlink的第4个参数,内核给fentry的类型定义是指向struct的指针,而非指向指针的指针。当代码中声明为struct inode **时,验证器判定类型不匹配,直接拒绝加载程序。
内核中vfs_unlink的原型为:
int vfs_unlink(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, struct inode **delegated_inode);
其中delegated_inode是输出参数,但fentry框架不支持直接声明二级指针类型的参数,因此触发校验报错。
解决方案
对于fentry程序,需将该二级指针参数当作64位无符号整数(__u64)读取,再转换为目标类型,绕过验证器的类型校验,同时获取正确的地址值。
修改后的fentry程序示例
方式1:手动读取ctx数组
SEC("fentry/vfs_unlink") int vfs_unlink(__u64 *ctx) { struct mnt_idmap *arg0 = (struct mnt_idmap *)ctx[0]; struct inode *arg1 = (struct inode *)ctx[1]; struct dentry *arg2 = (struct dentry *)ctx[2]; // 将arg3以__u64读取后转换为二级指针 struct inode **arg3 = (struct inode **)(ctx[3]); bpf_printk("arg3: %p\n", arg3); return 0; }
方式2:使用BPF_PROG宏调整参数类型
SEC("fentry/vfs_unlink") int BPF_PROG(vfs_unlink, struct mnt_idmap *arg0, struct inode *arg1, struct dentry *arg2, __u64 arg3) { struct inode **delegated_inode = (struct inode **)arg3; bpf_printk("arg3: %p\n", delegated_inode); return 0; }
验证结果
修改后的程序可正常编译加载,打印的arg3地址与kprobe程序输出完全一致,功能符合预期。
内容的提问来源于stack exchange,提问作者fpes
相关产品推荐
相关产品推荐

