我运行了一个示例 ebpf 代码,以 void 作为返回类型,并在返回之前调用 bpf_printk。我原以为这段代码会被验证者拒绝。然而,我能够成功地将它加载到内核中,但它有点损坏了我的网络子系统,因为它与 xdp 相关,并且必须重新启动我的计算机才能使网络再次工作。这正常吗?
内核:Linux asus 6.5.0-28-generic #29~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Apr 4 14:39:20 UTC 2 x86_64 x86_64 x86_64 GNU/Linux 操作系统:Ubuntu 22.04.4 LTS
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
int counter = 0;
SEC("xdp")
void error_packet_count(void *ctx) {
bpf_printk("%d", counter);
counter++;
return;
}
char LICENSE[] SEC("license") = "Dual BSD/GPL";
TL;DR. 看起来编译器忽略了您的
void
返回值并添加了 exit
eBPF 指令。
我已经能够使用以下程序重现这一点,这里是在加载到内核后由 bpftool 转储的:
$ sudo bpftool prog dump xlated id 201
void error_packet_count(struct xdp_md * ctx):
; void error_packet_count(struct xdp_md *ctx) {
0: (b7) r1 = 0
; ({ char _fmt[] = "Hello, World!\n"; bpf_trace_printk_(_fmt, sizeof(_fmt)); });
1: (73) *(u8 *)(r10 -2) = r1
2: (b7) r1 = 2593
3: (6b) *(u16 *)(r10 -4) = r1
4: (b7) r1 = 1684828783
5: (63) *(u32 *)(r10 -8) = r1
6: (18) r1 = 0x57202c6f6c6c6548
8: (7b) *(u64 *)(r10 -16) = r1
9: (bf) r1 = r10
;
10: (07) r1 += -16
; ({ char _fmt[] = "Hello, World!\n"; bpf_trace_printk_(_fmt, sizeof(_fmt)); });
11: (b7) r2 = 15
12: (85) call bpf_trace_printk#-108752
; }
13: (95) exit
我们可以看到,尽管 C 程序有一个
void
返回值,但 BPF 程序以 exit
指令结束。看起来 编译器会自动添加 exit
指令。
有趣的是,如果您删除对
bpf_trace_printk
的调用,它将失败并显示:
0: (95) exit
R0 !read_ok
这里,验证者抱怨 R0 未初始化。 R0 包含程序的返回代码,因此在程序结束时不应未初始化。
调用
bpf_trace_printk
可以避免此验证器错误,因为它最终会将 bpf_trace_printk
的返回值放入 R0 中。因此,无论 bpf_trace_printk
给你什么返回值都将是你的 BPF 程序的返回代码。