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

eBPF验证器报错‘R1 is not a scalar’,求问题排查思路

eBPF验证器报错原因分析及解决办法

问题描述

编写的eBPF代码

struct sock_info {
    struct sockaddr addr;
};

SEC("tracepoint/syscalls/sys_enter_accept4")
int sys_enter_accept4(int fd, struct sockaddr *upeer_sockaddr, int *upeer_addrlen, int flags) {
    struct sock_info *iad = bpf_ringbuf_reserve(&connections, sizeof(struct sock_info), 0);
    if (!iad) {
        bpf_printk("can't reserve ringbuf space");
        return 0;
    }
    // 尝试读取用户态socket地址
    bpf_probe_read(&iad->addr, sizeof(struct sockaddr), upeer_sockaddr);
    bpf_ringbuf_submit(iad, 0);
    return 0;
}

加载时的验证错误

permission denied
R1 is not a scalar
; int sys_enter_accept4(int fd, struct sockaddr *upeer_sockaddr, int *upeer_addrlen, int flags) {
0: (bf) r6 = r2
R2 !read_ok
processed 1 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0

llvm-objdump输出

llvm-objdump -S --no-show-raw-insn pkg/ebpf/bpf_bpfel.o

pkg/ebpf/bpf_bpfel.o:   file format elf64-bpf

Disassembly of section tracepoint/syscalls/sys_enter_accept4:

0000000000000000 <sys_enter_accept4>:
       0:   r6 = r2
       1:   r1 = 0 ll
       3:   r2 = 16
       4:   r3 = 0
       5:   call 131
       6:   r7 = r0
       7:   if r7 != 0 goto +5 <LBB0_2>
       8:   r1 = 0 ll
      10:   r2 = 28
      11:   call 6
      12:   goto +7 <LBB0_3>

0000000000000068 <LBB0_2>:
      13:   r1 = r7
      14:   r2 = 16
      15:   r3 = r6
      16:   call 4
      17:   r1 = r7
      18:   r2 = 0
      19:   call 132

00000000000000a0 <LBB0_3>:
      20:   r0 = 0
      21:   exit

疑问

移除bpf_probe_read后代码可正常加载,但尝试多种读取*upeer_sockaddr内容的方法均失败,请问验证器报错的原因是什么?


报错原因及解决办法

核心原因

报错里的R2 !read_ok是关键——eBPF验证器判定寄存器R2(对应函数参数upeer_sockaddr)指向的内存不具备可读权限。

这是因为sys_enter_accept4 tracepoint触发时,内核还没执行完accept4系统调用:用户态传入的upeer_sockaddr此时可能是空指针,或者内核还未向该地址填充客户端数据。eBPF验证器出于安全防护,会直接拒绝访问这种未被内核标记为“安全可用”的用户态指针。

另外补充:bpf_probe_read是旧版辅助函数,现在推荐用更安全的bpf_probe_read_user,但这不是当前报错的直接原因——哪怕换用新函数,只要指针本身不被验证器认可,还是会报错。

解决思路

要拿到accept4返回的客户端地址,必须跟踪sys_exit_accept4而非sys_enter_accept4:

  • 当sys_exit_accept4触发时,内核已经完成了地址填充操作,此时upeer_sockaddr指向的内存是有效的,验证器会允许访问。
  • 需要注意,sys_exit_accept4的参数结构和sys_enter_accept4不同,要参考对应tracepoint的格式定义获取正确参数。

修正后的代码示例

struct sock_info {
    struct sockaddr addr;
};

// 跟踪sys_exit_accept4而非sys_enter_accept4
SEC("tracepoint/syscalls/sys_exit_accept4")
int sys_exit_accept4(long ret, int fd, struct sockaddr *upeer_sockaddr, int *upeer_addrlen, int flags) {
    // 先检查accept4是否调用成功(ret >=0表示成功)
    if (ret < 0) {
        return 0;
    }

    struct sock_info *iad = bpf_ringbuf_reserve(&connections, sizeof(struct sock_info), 0);
    if (!iad) {
        bpf_printk("can't reserve ringbuf space");
        return 0;
    }

    // 使用新版的bpf_probe_read_user读取用户态内存
    bpf_probe_read_user(&iad->addr, sizeof(struct sockaddr), upeer_sockaddr);
    bpf_ringbuf_submit(iad, 0);
    return 0;
}

额外说明

  • 不同内核版本的tracepoint参数可能有差异,可通过/sys/kernel/debug/tracing/events/syscalls/sys_exit_accept4/format文件查看当前系统的参数定义。
  • 确保用户态程序加载eBPF程序时,正确映射了connections环形缓冲区。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 02:10:16