仅用系统调用加载并附着eBPF至sys_enter_execve失败原因排查
问题
我正尝试对eBPF字节码引入变异,因此希望从简单目标文件而非ELF加载eBPF字节码(据我所知libbpf要求使用ELF)。
我编写了一个向跟踪管道输出'hello world'的简单eBPF程序:
#include "vmlinux.h" #include <bpf/bpf_helpers.h> int hello_world(void *ctx) { char msg[12] = "Hello World\n"; bpf_trace_printk(msg, sizeof(msg)); return 0; }
我按以下步骤加载并将该程序附着到sys_enter_execve:
- 将程序从目标文件加载到缓冲区
- 调用
bpf(BPF_PROG_LOAD) - 调用
perf_event_open(PERF_TYPE_TRACEPOINT, sys_enter_execve_id) - 调用
bpf(BPF_LINK_CREATE, prog_fd, perf_fd) - 调用
ioctl(perf_fd, PERF_EVENT_IOC_ENABLE, 0)
这与libbpf等效程序执行的系统调用序列一致,且所有系统调用均返回成功。但使用bpftool prog show查看eBPF程序时,显示程序未附着:
46: tracepoint tag 57b707c2f01b5190 gpl loaded_at 2023-09-05T16:55:39+0100 uid 0 xlated 88B jited 61B memlock 4096B
而libbpf等效程序的bpftool输出显示程序已附着:
51: tracepoint name tracepoint__syscalls__sys_enter_execve tag 327022fb55d6e9d0 gpl loaded_at 2023-09-05T16:59:45+0100 uid 0 xlated 48B jited 35B memlock 4096B map_ids 27 btf_id 69
显然我的系统调用序列无法将eBPF程序附着到sys_enter_execve,而libbpf的序列可以。这是为什么?
以下是我传递给系统调用的具体参数:
//open performance event int perf_fd; struct perf_event_attr pe = { .type = PERF_TYPE_TRACEPOINT, .size = sizeof(struct perf_event_attr), .config = EXECVE_ID .sample_period = 1, .sample_type = PERF_SAMPLE_RAW, .wakeup_events = 1, .disabled = 0, .bpf_event = 1 }; perf_fd = syscall(__NR_perf_event_open, &pe, -1, 0, -1, PERF_FLAG_FD_CLOEXEC); //load the bpf program int prog_fd; union bpf_attr load_attr; memset(&load_attr, 0, sizeof(load_attr)); load_attr.prog_type = BPF_PROG_TYPE_TRACEPOINT; load_attr.insn_cnt = INSTRUCTION_COUNT; load_attr.insns = (__aligned_u64) insns_buf; load_attr.license = "GPL"; prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &load_attr, sizeof(load_attr)); //create bpf link(?) int link_fd; union bpf_attr link_attr; link_attr.link_create.prog_fd = prog_fd; link_attr.link_create.target_fd = perf_fd; link_attr.link_create.attach_type = BPF_PERF_EVENT; link_attr.link_create.flags = 0; int link_fd = syscall(__NR_bpf, BPF_LINK_CREATE, &link_attr, sizeof(link_attr)); ret = ioctl(perf_fd, PERF_EVENT_IOC_ENABLE, 0);
libbpf版本的strace片段:
openat(AT_FDCWD, "/sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/id", O_RDONLY) = 6 newfstatat(6, "", {st_mode=S_IFREG|0440, st_size=0, ...}, AT_EMPTY_PATH) = 0 read(6, "807\n", 4096) = 4 read(6, "", 4096) = 0 close(6) = 0 perf_event_open({type=PERF_TYPE_TRACEPOINT, size=0x88 /* PERF_ATTR_SIZE_??? */, config=807, sample_period=0, sample_type=0, read_format=0, precise_ip=0 /* arbitrary skid */, ...}, -1, 0, -1, PERF_FLAG_FD_CLOEXEC) = 6 bpf(BPF_LINK_CREATE, {link_create={prog_fd=5, target_fd=6, attach_type=BPF_PERF_EVENT, flags=0, perf_event={bpf_cookie=0}}}, 48) = 7 ioctl(6, PERF_EVENT_IOC_ENABLE, 0) = 0
分析与解决
对比你的代码和libbpf的系统调用参数,核心问题出在**perf_event_attr配置、BPF链接初始化细节**上:
1. perf_event_attr的错误配置
你的perf_event_attr存在两个关键问题:
- 语法错误:
.config = EXECVE_ID后缺少逗号,会导致编译异常(即使系统调用返回成功,实际运行代码可能存在隐式问题) - 冗余采样参数:libbpf的配置中
sample_period和sample_type均为0,而你设置了sample_period=1、sample_type=PERF_SAMPLE_RAW。仅用于附着BPF程序的tracepoint事件不需要这些采样参数,冗余配置可能干扰内核的附着逻辑 disabled字段:你设置为0,而libbpf默认设为1,再通过ioctl启用。虽然不是核心问题,但保持一致更稳妥
2. BPF链接创建时的未初始化问题
你没有对union bpf_attr link_attr做清零初始化(比如memset),可能导致无关字段残留垃圾值,干扰内核的链接创建流程。libbpf会确保所有无关字段被清零。
3. 程序元数据缺失(非功能影响)
libbpf会自动为BPF程序设置名称并关联BTF信息,这是bpftool能显示完整附着状态的原因。你的手动加载方式未设置这些元数据,但这不是程序未附着的直接原因,仅会导致bpftool显示信息不全。
修正后的代码示例
//open performance event int perf_fd; struct perf_event_attr pe = { .type = PERF_TYPE_TRACEPOINT, .size = sizeof(struct perf_event_attr), .config = EXECVE_ID, // 补上缺失的逗号 .disabled = 1, // 先禁用,后续通过ioctl启用 .bpf_event = 1 }; // 采样相关字段保持默认0即可,无需额外配置 perf_fd = syscall(__NR_perf_event_open, &pe, -1, 0, -1, PERF_FLAG_FD_CLOEXEC); //load the bpf program int prog_fd; union bpf_attr load_attr; memset(&load_attr, 0, sizeof(load_attr)); load_attr.prog_type = BPF_PROG_TYPE_TRACEPOINT; load_attr.insn_cnt = INSTRUCTION_COUNT; load_attr.insns = (__aligned_u64) insns_buf; load_attr.license = "GPL"; load_attr.prog_name = (__aligned_u64)"hello_world"; // 可选:设置程序名称,方便bpftool识别 prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &load_attr, sizeof(load_attr)); //create bpf link int link_fd; union bpf_attr link_attr; memset(&link_attr, 0, sizeof(link_attr)); // 关键:初始化整个union,避免垃圾值干扰 link_attr.link_create.prog_fd = prog_fd; link_attr.link_create.target_fd = perf_fd; link_attr.link_create.attach_type = BPF_PERF_EVENT; link_attr.link_create.flags = 0; link_fd = syscall(__NR_bpf, BPF_LINK_CREATE, &link_attr, sizeof(link_attr)); ret = ioctl(perf_fd, PERF_EVENT_IOC_ENABLE, 0);
额外注意事项
- 确保
EXECVE_ID是从/sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/id文件中正确读取的值 - 不要提前关闭
link_fd:内核通过该文件描述符维护BPF链接的生命周期,关闭后链接会被销毁,程序将不再附着 - 如果需要
bpftool显示完整的附着信息,可手动关联BTF,但这对程序功能无影响,仅用于调试
内容的提问来源于stack exchange,提问作者vykt
相关产品推荐
相关产品推荐

