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

为何不使用bpf_map_update_element时eBPF编译器行为异常?如何规避冗余更新?

如何避免eBPF中冗余的map更新操作?

我编写了一段eBPF代码,通过bpf_map_lookup_element()获取map内结构体的指针,直接访问并修改其内部字段。按逻辑来说,指针操作应该直接修改map中的数据,不需要额外调用bpf_map_update_element()同步,但注释掉这个更新操作后,验证器抛出异常,代码无法加载;只有保留该调用时,字节码才能正常通过验证。

保留bpf_map_update_element()时的字节码

int find(struct __sk_buff *skb){
91: 7b 1a e8 ff 00 00 00 00 *(u64 *)(r10 - 24) = r1
;     if(skb == NULL)
      92:   15 01 92 00 00 00 00 00 if r1 == 0 goto +146 <LBB1_23>
      93:   b7 01 00 00 00 00 00 00 r1 = 0
;     context_key_t key = CONTEXT_KEY;
      94:   63 1a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r1
      95:   bf a2 00 00 00 00 00 00 r2 = r10
      96:   07 02 00 00 fc ff ff ff r2 += -4
;     context_data_t * ctx = bpf_map_lookup_elem(&context_map,&key);    
      97:   18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll
      99:   85 00 00 00 01 00 00 00 call 1
     100:   7b 0a f0 ff 00 00 00 00 *(u64 *)(r10 - 16) = r0
;     if(ctx==NULL)
     101:   15 00 89 00 00 00 00 00 if r0 == 0 goto +137 <LBB1_23>
;     if(ctx->action_index<0 || ctx->action_index >= MAX_ACTION_LIST) 
     102:   79 a1 f0 ff 00 00 00 00 r1 = *(u64 *)(r10 - 16)
     103:   69 11 06 00 00 00 00 00 r1 = *(u16 *)(r1 + 6)
     104:   25 01 86 00 1f 00 00 00 if r1 > 31 goto +134 <LBB1_23>
;     unsigned short offset= args->offset;
     105:   27 01 00 00 90 00 00 00 r1 *= 144
;     find_t *args = &(ctx->action_argument[ctx->action_index].find_args);
     106:   79 a2 f0 ff 00 00 00 00 r2 = *(u64 *)(r10 - 16)
     107:   07 02 00 00 10 01 00 00 r2 += 272
;     unsigned short offset= args->offset;
     108:   bf 24 00 00 00 00 00 00 r4 = r2
     109:   0f 14 00 00 00 00 00 00 r4 += r1
     110:   79 a3 e8 ff 00 00 00 00 r3 = *(u64 *)(r10 - 24)
;     void *data = (void *)(__u64)skb->data;
     111:   61 30 4c 00 00 00 00 00 r0 = *(u32 *)(r3 + 76)
;     void *data_end = (void*)(__u64)skb->data_end;
     112:   61 33 50 00 00 00 00 00 r3 = *(u32 *)(r3 + 80)
;     unsigned short offset= args->offset;
     113:   7b 3a e0 ff 00 00 00 00 *(u64 *)(r10 - 32) = r3
     114:   69 43 00 00 00 00 00 00 r3 = *(u16 *)(r4 + 0)

Update ctx structure
Use bpf_map_update_element() to update ctx structure in map

注释掉bpf_map_update_element()后的异常

验证器抛出错误:

invalid access to map value, value_size=5016 off=9437312 size=2 R5 max value is outside of the allowed memory range

对应的字节码:

int find(struct __sk_buff *skb){
      91:   bf 16 00 00 00 00 00 00 r6 = r1
;     if(skb == NULL)
      92:   15 06 8b 00 00 00 00 00 if r6 == 0 goto +139 <LBB1_22>
      93:   b7 01 00 00 00 00 00 00 r1 = 0
;     context_key_t key = CONTEXT_KEY;
      94:   63 1a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r1
      95:   bf a2 00 00 00 00 00 00 r2 = r10
      96:   07 02 00 00 fc ff ff ff r2 += -4
;     context_data_t * ctx = bpf_map_lookup_elem(&context_map,&key);    
      97:   18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll
      99:   85 00 00 00 01 00 00 00 call 1
     100:   7b 0a f0 ff 00 00 00 00 *(u64 *)(r10 - 16) = r0
;     if(ctx==NULL)
     101:   15 00 82 00 00 00 00 00 if r0 == 0 goto +130 <LBB1_22>
;     if(ctx->action_index<0 || ctx->action_index >= MAX_ACTION_LIST) 
     102:   79 a1 f0 ff 00 00 00 00 r1 = *(u64 *)(r10 - 16)
     103:   69 11 06 00 00 00 00 00 r1 = *(u16 *)(r1 + 6)
     104:   7b 1a d0 ff 00 00 00 00 *(u64 *)(r10 - 48) = r1
     105:   25 01 7e 00 1f 00 00 00 if r1 > 31 goto +126 <LBB1_22>
;     unsigned short offset= args->offset;
     106:   79 a2 d0 ff 00 00 00 00 r2 = *(u64 *)(r10 - 48)
     107:   27 02 00 00 90 00 00 00 r2 *= 144
;     find_t *args = &(ctx->action_argument[ctx->action_index].find_args);
     108:   79 a5 f0 ff 00 00 00 00 r5 = *(u64 *)(r10 - 16)
     109:   07 05 00 00 10 01 00 00 r5 += 272
     110:   7b 5a e8 ff 00 00 00 00 *(u64 *)(r10 - 24) = r5
;     unsigned short offset= args->offset;
     111:   0f 25 00 00 00 00 00 00 r5 += r2
;     void *data = (void *)(__u64)skb->data;
     112:   61 69 4c 00 00 00 00 00 r9 = *(u32 *)(r6 + 76)
;     void *data_end = (void*)(__u64)skb->data_end;
     113:   61 61 50 00 00 00 00 00 r1 = *(u32 *)(r6 + 80)
;     unsigned short offset= args->offset;
     114:   7b 1a e0 ff 00 00 00 00 *(u64 *)(r10 - 32) = r1
     115:   69 54 00 00 00 00 00 00 r4 = *(u16 *)(r5 + 0)

核心差异分析

保留bpf_map_update_element()时,对action_index的边界校验完成后,直接用同一个寄存器(r1)进行后续的乘法和偏移计算,eBPF验证器能跟踪到该寄存器的边界约束,认可数组访问的合法性。

注释掉更新操作后,编译器将校验后的action_index值存入栈,再读取到另一个寄存器(r2)中使用,验证器无法关联r2的边界约束,认为其可能超出map值的内存范围,从而抛出错误。

解决方案

1. 复用校验后的变量/寄存器,避免中间存储

不要将校验后的action_index存入中间变量或栈,直接使用原始的字段访问完成后续计算,让编译器复用同一个寄存器,帮助验证器跟踪边界约束。

示例代码调整:

// 原代码(会生成中间存储)
unsigned short idx = ctx->action_index;
if (idx >= MAX_ACTION_LIST)
    return TC_ACT_OK;
find_t *args = &ctx->action_argument[idx].find_args;

// 修改后(直接复用字段访问)
if (ctx->action_index >= MAX_ACTION_LIST)
    return TC_ACT_OK;
find_t *args = &ctx->action_argument[ctx->action_index].find_args;

2. 使用内置函数保留访问索引信息

如果必须使用中间变量,可以通过__builtin_bpf_preserve_access_index()告知验证器保留变量的边界约束信息:

unsigned short idx = ctx->action_index;
__builtin_bpf_preserve_access_index(idx); // 帮助验证器跟踪idx的来源和边界
if (idx >= MAX_ACTION_LIST)
    return TC_ACT_OK;
find_t *args = &ctx->action_argument[idx].find_args;

3. 开启编译器优化

确保编译时开启eBPF相关的优化选项(如-O2),优化后的编译器会减少不必要的寄存器/栈存储,更可能保留验证器需要的边界跟踪信息。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 14:23:10