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

为何BPF验证器检测到我的XDP代码存在无限循环?

BPF验证器判定循环为无限循环的原因及修复

问题背景

编写了用于统计数据包数量的XDP类型BPF代码,执行加载命令时被BPF验证器提示检测到无限循环,怀疑问题与helper函数相关,需要排查原因。

原BPF代码

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

struct
{
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __type(key, __u32);
    __type(value, __u64);
    __uint(max_entries, 4);
} pkt_count SEC(".maps");

// count_packets atomically increases a packet counter on every invocation.
SEC("xdp")
int count_packets()
{
    int max = 100;
    for (int i = 0; i < max; i++)
    {
        __u64 *value = bpf_map_lookup_elem(&pkt_count, &i);
        if (!value)
        {
            return 0;
        }
        bpf_printk("%p", value);
    }

    return XDP_PASS;
}

char __license[] SEC("license") = "Dual MIT/GPL";

加载命令

bpftool prog load counter_bpfel.o /sys/fs/bpf/my_prog

报错日志

libbpf: prog 'count_packets': BPF program load failed: Invalid argument
libbpf: prog 'count_packets': -- BEGIN PROG LOAD LOG --
; int count_packets()
0: (b7) r6 = 0
; 
1: (63) *(u32 *)(r10 -4) = r6
last_idx 1 first_idx 0
regs=40 stack=0 before 0: (b7) r6 = 0
2: (b7) r7 = 28709
3: (b7) r8 = 99
4: (bf) r2 = r10
5: (07) r2 += -4
; __u64 *value = bpf_map_lookup_elem(&pkt_count, &i);
6: (18) r1 = 0xffff906dfb349e00
8: (85) call bpf_map_lookup_elem#1
; if (!value)
9: (15) if r0 == 0x0 goto pc+16
 R0_w=map_value(id=0,off=0,ks=4,vs=8,imm=0) R6_w=inv0 R7_w=inv28709 R8_w=inv99 R10=fp0 fp-8=mmmm????
; bpf_printk("%p", value);
10: (73) *(u8 *)(r10 -6) = r6
last_idx 10 first_idx 0
regs=40 stack=0 before 9: (15) if r0 == 0x0 goto pc+16
regs=40 stack=0 before 8: (85) call bpf_map_lookup_elem#1
regs=40 stack=0 before 6: (18) r1 = 0xffff906dfb349e00
regs=40 stack=0 before 5: (07) r2 += -4
regs=40 stack=0 before 4: (bf) r2 = r10
regs=40 stack=0 before 3: (b7) r8 = 99
regs=40 stack=0 before 2: (b7) r7 = 28709
regs=40 stack=0 before 1: (63) *(u32 *)(r10 -4) = r6
regs=40 stack=0 before 0: (b7) r6 = 0
11: (6b) *(u16 *)(r10 -8) = r7
12: (bf) r1 = r10
; 
13: (07) r1 += -8
; bpf_printk("%p", value);
14: (b7) r2 = 3
15: (bf) r3 = r0
16: (85) call bpf_trace_printk#6
last_idx 16 first_idx 0
regs=4 stack=0 before 15: (bf) r3 = r0
regs=4 stack=0 before 14: (b7) r2 = 3
; for (int i = 0; i < max; i++)
17: (61) r1 = *(u32 *)(r10 -4)
18: (bf) r2 = r1
19: (07) r2 += 1
; 
20: (63) *(u32 *)(r10 -4) = r2
; for (int i = 0; i < max; i++)
21: (67) r1 <<= 32
22: (c7) r1 s>>= 32
; for (int i = 0; i < max; i++)
23: (6d) if r8 s> r1 goto pc-20

from 23 to 4: R0=inv(id=0) R1_w=inv(id=0,smin_value=-2147483648,smax_value=98) R2_w=inv(id=0,umin_value=1,umax_value=4294967296,var_off=(0x0; 0x1ffffffff)) R6=inv0 R7=inv28709 R8=inv99 R10=fp0 fp-8=mmmm?mmm
; 
4: (bf) r2 = r10
5: (07) r2 += -4
; __u64 *value = bpf_map_lookup_elem(&pkt_count, &i);
6: (18) r1 = 0xffff906dfb349e00
8: (85) call bpf_map_lookup_elem#1
; if (!value)
9: (15) if r0 == 0x0 goto pc+16
 R0_w=map_value(id=0,off=0,ks=4,vs=8,imm=0) R6=inv0 R7=inv28709 R8=inv99 R10=fp0 fp-8=mmmm?mmm
; bpf_printk("%p", value);
10: (73) *(u8 *)(r10 -6) = r6
last_idx 10 first_idx 17
regs=40 stack=0 before 9: (15) if r0 == 0x0 goto pc+16
regs=40 stack=0 before 8: (85) call bpf_map_lookup_elem#1
regs=40 stack=0 before 6: (18) r1 = 0xffff906dfb349e00
regs=40 stack=0 before 5: (07) r2 += -4
regs=40 stack=0 before 4: (bf) r2 = r10
regs=40 stack=0 before 23: (6d) if r8 s> r1 goto pc-20
regs=40 stack=0 before 22: (c7) r1 s>>= 32
regs=40 stack=0 before 21: (67) r1 <<= 32
regs=40 stack=0 before 20: (63) *(u32 *)(r10 -4) = r2
regs=40 stack=0 before 19: (07) r2 += 1
regs=40 stack=0 before 18: (bf) r2 = r1
regs=40 stack=0 before 17: (61) r1 = *(u32 *)(r10 -4)
 R0_w=inv(id=0) R6_rw=invP0 R7_w=inv28709 R8_rw=inv99 R10=fp0 fp-8_r=mmmm?mmm
parent didn't have regs=40 stack=0 marks
last_idx 16 first_idx 0
regs=40 stack=0 before 16: (85) call bpf_trace_printk#6
regs=40 stack=0 before 15: (bf) r3 = r0
regs=40 stack=0 before 14: (b7) r2 = 3
regs=40 stack=0 before 13: (07) r1 += -8
regs=40 stack=0 before 12: (bf) r1 = r10
regs=40 stack=0 before 11: (6b) *(u16 *)(r10 -8) = r7
regs=40 stack=0 before 10: (73) *(u8 *)(r10 -6) = r6
regs=40 stack=0 before 9: (15) if r0 == 0x0 goto pc+16
regs=40 stack=0 before 8: (85) call bpf_map_lookup_elem#1
regs=40 stack=0 before 6: (18) r1 = 0xffff906dfb349e00
regs=40 stack=0 before 5: (07) r2 += -4
regs=40 stack=0 before 4: (bf) r2 = r10
regs=40 stack=0 before 3: (b7) r8 = 99
regs=40 stack=0 before 2: (b7) r7 = 28709
regs=40 stack=0 before 1: (63) *(u32 *)(r10 -4) = r6
regs=40 stack=0 before 0: (b7) r6 = 0
11: (6b) *(u16 *)(r10 -8) = r7
12: (bf) r1 = r10
; 
13: (07) r1 += -8
; bpf_printk("%p", value);
14: (b7) r2 = 3
15: (bf) r3 = r0
16: (85) call bpf_trace_printk#6
last_idx 16 first_idx 17
regs=4 stack=0 before 15: (bf) r3 = r0
regs=4 stack=0 before 14: (b7) r2 = 3
; for (int i = 0; i < max; i++)
infinite loop detected at insn 17
processed 39 insns (limit 1000000) max_states_per_insn 0 total_states 2 peak_states 2 mark_read 1
-- END PROG LOAD LOG --
libbpf: prog 'count_packets': failed to load: -22
libbpf: failed to load object 'counter_bpfel.o'
Error: failed to load object file

原因分析

  1. 循环终止条件的验证器跟踪限制:BPF验证器需要明确推断出循环的最大迭代次数才能确认不会无限循环。代码中max=100是局部变量,验证器无法确定其为常量;加上循环内调用了bpf_map_lookup_elem和bpf_printk等helper函数,进一步干扰了验证器对循环变量i范围的跟踪能力。

  2. 数组越界导致的提前退出干扰:数组map仅配置了4个条目(max_entries=4),但循环尝试遍历0到99的索引。当i>=4时,bpf_map_lookup_elem会返回NULL,触发return 0直接退出程序。但验证器在分析路径时,没有正确识别这个提前退出的逻辑,反而认为循环会持续迭代,最终误判为无限循环。

  3. 缺少实际计数逻辑:原代码仅打印map值的地址,没有实现数据包统计的核心逻辑(原子递增计数器),这虽然不是验证报错的直接原因,但属于功能缺失。

修复方案

方案一:匹配循环次数与map条目数(推荐)

将循环次数限制为map的实际条目数,使用常量让验证器明确迭代上限,同时补充计数逻辑:

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

#define MAX_MAP_ENTRIES 4  // 和map的max_entries保持一致

struct
{
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __type(key, __u32);
    __type(value, __u64);
    __uint(max_entries, MAX_MAP_ENTRIES);
} pkt_count SEC(".maps");

SEC("xdp")
int count_packets()
{
    for (int i = 0; i < MAX_MAP_ENTRIES; i++)
    {
        __u64 *value = bpf_map_lookup_elem(&pkt_count, &i);
        if (!value)
        {
            continue;  // 用continue替代return,避免验证器路径跟踪混乱
        }
        __sync_fetch_and_add(value, 1);  // 原子递增计数器,实现统计功能
        // bpf_printk("Counter %d: %llu", i, *value);  // 按需打印,避免频繁输出
    }

    return XDP_PASS;
}

char __license[] SEC("license") = "Dual MIT/GPL";

方案二:使用常量标记让验证器识别固定循环次数

如果需要遍历更多条目,先调整map的max_entries,再用__builtin_constant_p告知验证器max是常量:

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

#define MAX_MAP_ENTRIES 100  // 调整为需要的条目数

struct
{
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __type(key, __u32);
    __type(value, __u64);
    __uint(max_entries, MAX_MAP_ENTRIES);
} pkt_count SEC(".maps");

SEC("xdp")
int count_packets()
{
    int max = MAX_MAP_ENTRIES;
    // 告知验证器max是常量,帮助其分析循环终止条件
    if (!__builtin_constant_p(max)) {
        return XDP_PASS;
    }

    for (int i = 0; i < max; i++)
    {
        __u64 *value = bpf_map_lookup_elem(&pkt_count, &i);
        if (!value)
        {
            continue;
        }
        __sync_fetch_and_add(value, 1);
    }

    return XDP_PASS;
}

char __license[] SEC("license") = "Dual MIT/GPL";

修复关键点

  • 明确循环迭代上限:使用常量定义循环次数,让验证器能准确推断循环会终止。
  • 避免越界触发提前return:要么限制循环次数不超过map条目数,要么用continue替代return,减少验证器的路径分析复杂度。
  • 补充核心统计逻辑:通过__sync_fetch_and_add实现原子计数,确保多数据包并发时的统计准确性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 07:29:51