为何无法将__sync_fetch_and_add返回值存入eBPF程序的Map?
eBPF程序加载失败:BPF_STX uses reserved fields错误分析与解决
问题现象
加载eBPF程序时出现如下错误:
libbpf: prog 'do_entry_point': BPF program load failed: Invalid argument libbpf: prog 'do_entry_point': -- BEGIN PROG LOAD LOG -- BPF_STX uses reserved fields processed 0 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0 -- END PROG LOAD LOG -- libbpf: prog 'do_entry_point': failed to load: -22 libbpf: failed to load object 'demo_bpf' libbpf: failed to load BPF skeleton 'demo_bpf': -22 Failed to open and load BPF skeleton
内核版本为5.10,排查确认问题出在将__sync_fetch_and_add的返回值存入event结构体并写入ringbuf的操作。
复现代码
demo.bpf.c
#include <vmlinux.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_tracing.h> #include <bpf/bpf_core_read.h> #include "demo.h" struct { __uint(type, BPF_MAP_TYPE_RINGBUF); __uint(max_entries, 1024 * 1024); } traceevents SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_ARRAY); __uint(max_entries, 1); __type(key, u32); __type(value, u64); } counters SEC(".maps"); static void fill_and_submit(struct pt_regs *ctx, u8 entry) { struct event *e = bpf_ringbuf_reserve(&traceevents, sizeof(struct event), 0); if (!e) { return; } u32 key = 0; u64 *count = bpf_map_lookup_elem(&counters, &key); e->id = 0; if (count) { e->id = __sync_fetch_and_add(count, 1); } e->timestamp = bpf_ktime_get_ns(); e->entry = entry; bpf_ringbuf_submit(e, 0); } SEC("uprobe") int do_entry_point(struct pt_regs *ctx) { fill_and_submit(ctx, 1); return 0; } char LICENSE[] SEC("license") = "GPL";
demo.c
#include <stdio.h> #include <unistd.h> #include <signal.h> #include <getopt.h> #include <bpf/libbpf.h> #include <bpf/bpf.h> #include <sys/mman.h> #include "demo.skel.h" #include "demo.h" #include <linux/limits.h> static volatile bool exiting = false; static void sig_handler(int sig) { exiting = true; } static int handle_event(void *ctx, void *data, size_t data_sz) { printf("Read event\n"); return 0; } int main(int argc, char **argv) { struct ring_buffer *rb_event = NULL; struct demo_bpf *skel; int err = 0; skel = demo_bpf__open_and_load(); if (!skel) { fprintf(stderr, "Failed to open and load BPF skeleton\n"); return 1; } LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts); uprobe_opts.func_name = "my_func"; uprobe_opts.retprobe = false; skel->links.do_entry_point = bpf_program__attach_uprobe_opts(skel->progs.do_entry_point, -1, argv[1], 0, &uprobe_opts); signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); rb_event = ring_buffer__new(bpf_map__fd(skel->maps.traceevents), handle_event, NULL, NULL); if (!rb_event) { err = -1; fprintf(stderr, "Failed to create trace_event ring buffer\n"); return -1; } printf("Successfully started! Tracing functions....\n"); while (!exiting) { err = ring_buffer__poll(rb_event, 100); if (err < 0 && err != -EINTR) { fprintf(stderr, "Error polling ring buffer: %d\n", err); break; } } demo_bpf__destroy(skel); return 0; }
demo.h
struct event { // identity __u64 id; __u64 timestamp; __u8 entry; };
编译命令
clang -g -O2 -target bpf -D__TARGET_ARCH_x86 -I. -c demo/demo.bpf.c -o demo/demo.bpf.o bpftool gen skeleton demo/demo.bpf.o > demo/demo.skel.h clang -Wchar-subscripts -Wcomment -Wformat -Winit-self -Wmain -Wmissing-braces -Wno-pragmas -Wparentheses -Wreturn-type -Wsequence-point -Wstrict-aliasing -Wswitch -Wtrigraphs -Wuninitialized -Wunknown-pragmas -Wunused-label -Wunused-variable -Wunused-value -Wpointer-sign -Wimplicit -pthread -fdiagnostics-color=auto -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -fuse-init-array -O3 -msse -mfpmath=sse -march=core2 -g -fPIC -I./demo -o demo demo/demo.o -pthread -Wl,-lpthread -Wl,--enable-new-dtags -Wl,--as-needed -g -lelf -lz -lbpf -ldw
问题原因
__sync_fetch_and_add是GCC提供的用户态原子操作内置函数,编译eBPF程序时,编译器会将其转换为x86架构的原子指令,但eBPF虚拟机不允许这类指令直接使用——它们会生成包含保留字段的BPF_STX指令,触发内核校验失败,也就是报错中的BPF_STX uses reserved fields。
eBPF有自己的原子操作机制,必须使用内核提供的eBPF helper函数来实现原子操作,而非用户态的原子操作函数。
解决方法
将__sync_fetch_and_add替换为eBPF原生的bpf_fetch_and_add helper函数,该函数专门用于对map元素执行原子递增操作,并返回递增前的旧值,完全符合原代码的需求。
修改demo.bpf.c中的fill_and_submit函数:
static void fill_and_submit(struct pt_regs *ctx, u8 entry) { struct event *e = bpf_ringbuf_reserve(&traceevents, sizeof(struct event), 0); if (!e) { return; } u32 key = 0; // 使用bpf_fetch_and_add原子操作替代__sync_fetch_and_add e->id = bpf_fetch_and_add(&counters, &key, 1, 0); e->timestamp = bpf_ktime_get_ns(); e->entry = entry; bpf_ringbuf_submit(e, 0); }
说明
bpf_fetch_and_add的参数分别为:map指针、key指针、增量值、flags(设为0即可)- 该函数会直接对map中指定key的元素执行原子递增,无需提前调用
bpf_map_lookup_elem,代码更简洁且安全 - 内核5.10完全支持这个helper函数
修改后重新编译运行,即可成功加载eBPF程序。
内容的提问来源于stack exchange,提问作者Gary Watson
相关产品推荐
相关产品推荐

