为何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
原因分析
循环终止条件的验证器跟踪限制:BPF验证器需要明确推断出循环的最大迭代次数才能确认不会无限循环。代码中
max=100是局部变量,验证器无法确定其为常量;加上循环内调用了bpf_map_lookup_elem和bpf_printk等helper函数,进一步干扰了验证器对循环变量i范围的跟踪能力。数组越界导致的提前退出干扰:数组map仅配置了4个条目(
max_entries=4),但循环尝试遍历0到99的索引。当i>=4时,bpf_map_lookup_elem会返回NULL,触发return 0直接退出程序。但验证器在分析路径时,没有正确识别这个提前退出的逻辑,反而认为循环会持续迭代,最终误判为无限循环。缺少实际计数逻辑:原代码仅打印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
相关产品推荐
相关产品推荐

