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

为何无法将__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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 21:09:52