有时当我们加载编译后的 eBPF 程序的时候,eBPF 验证器会提示程序中有类型错误的问题导致程序加载失败。 本文记录一下这种错误的一种解决方法。
错误示例¶
比如,当下面这段 eBPF 程序
SEC("iter/bpf_sk_storage_map")
int iter__bpf_sk_storage_map(struct bpf_iter__bpf_sk_storage_map *ctx)
{
if (ctx->sk)
bpf_sk_storage_delete(&sk_storage_map, ctx->sk);
return 0;
}
被加载到内核中时会提示如下类型错误:
libbpf: prog 'iter__bpf_sk_storage_map': BPF program load failed: Permission denied libbpf: prog 'iter__bpf_sk_storage_map': -- BEGIN PROG LOAD LOG -- R1 type=ctx expected=fp ; if (ctx->sk) 0: (79) r2 = *(u64 *)(r1 +16) ; if (ctx->sk) 1: (15) if r2 == 0x0 goto pc+4 R1=ctx(id=0,off=0,imm=0) R2_w=ptr_sock(id=0,off=0,imm=0) R10=fp0 ; bpf_sk_storage_delete(&sk_storage_map, ctx->sk); 2: (79) r2 = *(u64 *)(r1 +16) ; bpf_sk_storage_delete(&sk_storage_map, ctx->sk); 3: (18) r1 = 0xffffa0658305aa00 5: (85) call bpf_sk_storage_delete#108 R2 type=ptr_or_null_ expected=ptr_ processed 5 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0 -- END PROG LOAD LOG -- libbpf: prog 'iter__bpf_sk_storage_map': failed to load: -13 libbpf: failed to load object 'main.bpf.o' failed to load BPF object: permission denied
解决办法¶
这个错误信息有两个关键错误,一个错误是:
R1 type=ctx expected=fp ; if (ctx->sk) 0: (79) r2 = *(u64 *)(r1 +16) ; if (ctx->sk) 1: (15) if r2 == 0x0 goto pc+4
其中 R1 type=ctx expected=fp 说的是,验证器期望 R1 的类型是 fp 而不是 ctx 。 所谓的 fp 指的是栈上的指针类型,即期望 R1 是栈上的数据而不是 ctx 。
另一个错误是:
R1=ctx(id=0,off=0,imm=0) R2_w=ptr_sock(id=0,off=0,imm=0) R10=fp0 ; bpf_sk_storage_delete(&sk_storage_map, ctx->sk); 2: (79) r2 = *(u64 *)(r1 +16) ; bpf_sk_storage_delete(&sk_storage_map, ctx->sk); 3: (18) r1 = 0xffffa0658305aa00 5: (85) call bpf_sk_storage_delete#108 R2 type=ptr_or_null_ expected=ptr_
其中 R2 type=ptr_or_null_ expected=ptr_ 说的是,验证器期望 R2 的类型是 ptr 而不是 prt_or_null ,即,期望 R2 是一个指针而不是一个指针或 NULL 。 这里可能会有点疑惑,前面的判断 if (ctx->sk) 已经确保了不会为 NULL , 为啥这里还会认为它有可能为 NULL ,这是因为前面的 if 判断的不是栈变量, 存在 R1 type=ctx expected=fp 的问题也就无法保证它一定不是 NULL 了。
解决办法也很简单,就是用一个临时变量保存 ctx->sk 的值, 然后用这个栈上的临时变量做后续的操作:
SEC("iter/bpf_sk_storage_map")
int iter__bpf_sk_storage_map(struct bpf_iter__bpf_sk_storage_map *ctx)
{
- if (ctx->sk)
- bpf_sk_storage_delete(&sk_storage_map, ctx->sk);
+ struct sock *sk = ctx->sk;
+ if (sk)
+ bpf_sk_storage_delete(&sk_storage_map, sk);
return 0;
}
常见类型关键字的含义¶
这里记录一下类似前面 fp 这样的常见类型关键字具体的含义:
关键字 | 含义 |
---|---|
scalar | 标量类型(scalar type),不是一个有效的指针类型 |
ctx | bpf_context 指针 |
map_ptr | bpf_map 类型的指针 |
map_value | 指向 map 中的元素 value 的指针 |
map_value_or_null | 指向 map 中的元素 value 的指针或 NULL |
map_key | 指向 map 中的元素 key 的指针 |
fp | 栈上的指针(frame pointer) |
pkt | skb->data 指针 |
pkt_meta | skb->data - meta_len 位置的指针 |
pkt_end | skb->data + headlen 位置的指针 |
sock | bpf_sock 类型的指针 |
sock_or_null | bpf_sock 类型的指针或 NULL |
sock_common | sock_common 类型指针 |
sock_common_or_null | sock_common 类型指针或 NULL |
tcp_sock | tcp_sock 类型指针 |
tcp_sock_or_null | tcp_sock 类型指针或 NULL |
tp_buffer | 可写的 raw tracepoint buffer 指针 |
xdp_sock | xdp_sock 类型指针 |
ptr_ | 一个 BTF ID,非空指针 |
ptr_or_null_ | 一个 BTF ID 或 NULL,可能为空的指针 |
dynptr_ptr | 动态指针(dynptr 指针) |
mem | 指向一块有效内存区域的指针 |
mem_or_null | 指向一块有效内存区域的指针或 NULL |
buf | 指向一个读/写 buffer 的指针 |
func | BPF 程序函数指针 |
inv | 无效类型(invalid type),不是一个有效的指针类型 |
flow_keys | bpf_flow_keys 类型的指针 |
percpu_ptr_ | 指向一个 percpu 内核变量的指针 |
rdonly_buf | 指向一个只读 buffer 的指针 |
rdonly_buf_or_null | 指向一个只读 buffer 的指针或 NULL |
rdwr_buf | 指向一个读/写 buffer 的指针 |
rdwr_buf_or_null | 指向一个读/写 buffer 的指针或 NULL |
Comments