You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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...

错误原因分析

  1. 函数签名不匹配:
    fentry类型的eBPF程序要求函数签名与内核目标函数的BTF定义严格一致。__user是内核空间专属宏,eBPF编译环境未定义该宏,直接添加会导致语法错误;即使内核函数参数带有__user标记,eBPF程序中只需使用普通指针类型即可,BTF会自动处理指针属性。

  2. 辅助函数参数类型不兼容:
    改用struct pt_regs *ctx后,辅助函数handle_event_tracing使用void *ctx作为参数,内核验证器会检查辅助函数的参数类型是否与调用上下文匹配,void*无法通过验证,导致加载失败。

解决方案

  1. 使用BTF自动匹配参数(推荐):
    依赖内核BTF信息,用BPF_PROG宏自动生成与目标函数一致的签名,无需手动指定参数类型,需确保内核开启CONFIG_DEBUG_INFO_BTF。

  2. 手动处理参数时保持类型一致:
    若使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 03:24:54