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

Linux 4.14内核EBPF技术问询:如何实现将变长用户态数据复制到BPF Map中

适配Linux 4.14内核的eBPF read()追踪解决方案

我碰到过不少4.14内核BPF验证器的“奇葩”限制,你的问题核心是4.14的验证器无法正确推断三元运算符(MIN宏展开的结果)的变量范围,再加上多余的位操作干扰了验证器的分析,导致它认为bpf_probe_read_user的size参数可能超过heap buffer的大小,从而拒绝加载程序。

下面是具体的修复方案,附修改后的完整代码:

关键修改点

  • 把MIN宏替换为显式的if-else条件判断:4.14验证器对三元运算符的范围推导支持不足,显式的分支判断能让验证器清晰跟踪到min的上限不会超过HEAP_BUFFER_SIZE。
  • 移除不必要的min &= 0xffff操作:HEAP_BUFFER_SIZE是2048,远小于65535,这个操作不会改变min的值,反而会让验证器误以为min的范围是0-65535,触发越界检查错误。
  • 强化变量范围的显式约束:确保验证器能明确min的取值范围是1到2048之间。

修改后的完整代码

#include <stddef.h>
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

struct read_enter_ctx {
    unsigned long long unused;
    int __syscall_nr;
    unsigned long fd;
    char* buf;
    size_t count;
};

struct read_exit_ctx {
    unsigned long long unused;
    int __syscall_nr;
    long ret;
};

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __uint(max_entries, 1);
    __type(key, int);
    __type(value, void*);
} saved_read_ctx SEC(".maps");

#define HEAP_BUFFER_SIZE (2 * 1024)
struct heap_buffer {
    char buf[HEAP_BUFFER_SIZE];
};

struct {
    __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
    __uint(max_entries, 1);
    __type(key, int);
    __type(value, struct heap_buffer);
} heap SEC(".maps");

SEC("tracepoint/syscalls/sys_enter_read")
int trace_read_enter(struct read_enter_ctx *ctx) {
    int zero = 0;
    void *p = ctx->buf;
    bpf_map_update_elem(&saved_read_ctx, &zero, &p, BPF_ANY);
    return 0;
}

SEC("tracepoint/syscalls/sys_exit_read")
int trace_read_exit(struct read_exit_ctx *ctx) {
    char *map_buf;
    void **ubuf;
    int zero = 0;
    unsigned long min;

    ubuf = bpf_map_lookup_elem(&saved_read_ctx, &zero);
    if (!ubuf)
        return 0;
    if (ctx->ret <= 0)
        return 0;

    map_buf = bpf_map_lookup_elem(&heap, &zero);
    if (!map_buf) {
        return 0;
    }

    // 替换MIN宏为显式if判断,让4.14验证器能跟踪变量范围
    min = ctx->ret;
    if (min > HEAP_BUFFER_SIZE) {
        min = HEAP_BUFFER_SIZE;
    }

    // 移除多余的位操作,避免干扰验证器
    // min &= 0xffff;

    if (bpf_probe_read_user(map_buf, min, *ubuf)) {
        return 0;
    }

    return 0;
}

char _license[] SEC("license") = "GPL";

额外优化建议(可选)

  • 把saved_read_ctx改成BPF_MAP_TYPE_PERCPU_ARRAY:全局数组在多CPU并发场景下会有竞争问题,percpu数组能避免不同CPU的tracepoint程序互相覆盖缓冲区指针,同时也能让验证器更“放心”(因为percpu变量的访问不会有跨CPU的竞态)。

修改后的saved_read_ctx定义:

struct {
    __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
    __uint(max_entries, 1);
    __type(key, int);
    __type(value, void*);
} saved_read_ctx SEC(".maps");

这样修改后,程序应该能顺利通过4.14内核的BPF验证器,同时保留原有功能。

内容的提问来源于stack exchange,提问作者joz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 23:52:33