查看内核的sample/bpf/sock_example.c
:
struct bpf_insn prog[] = {
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
BPF_LD_ABS(BPF_B, ETH_HLEN + offsetof(struct iphdr, protocol) /* R0 = ip->proto */),
BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4), /* *(u32 *)(fp - 4) = r0 */
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), /* r2 = fp - 4 */
BPF_LD_MAP_FD(BPF_REG_1, map_fd),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2),
BPF_MOV64_IMM(BPF_REG_1, 1), /* r1 = 1 */
BPF_ATOMIC_OP(BPF_DW, BPF_ADD, BPF_REG_0, BPF_REG_1, 0),
BPF_MOV64_IMM(BPF_REG_0, 0), /* r0 = 0 */
BPF_EXIT_INSN(),
};
我理解eBPF
将寄存器r1-r5设置为保存BPF助手的参数。我不明白的是,为什么要将映射fd
传递给BPF_FUNC_map_lookup_elem
?根据助手代码:
const struct bpf_func_proto bpf_map_lookup_elem_proto = {
.func = bpf_map_lookup_elem,
.gpl_only = false,
.pkt_access = true,
.ret_type = RET_PTR_TO_MAP_VALUE_OR_NULL,
.arg1_type = ARG_CONST_MAP_PTR,
.arg2_type = ARG_PTR_TO_MAP_KEY,
};
这意味着两个参数都是指针,而none是映射fd。除非,我查错了代码?
在用户空间中编写程序时使用文件描述符,但后来被验证器指向映射的指针所取代。
编写eBPF程序时的文件描述符
您在用户空间中编写eBPF程序,在那里您没有任何指向地图的地址指针。因此,您可以使用文件描述符来引用程序可能运行的各种操作(查找、更新、删除(的映射。
如果用C编写程序,而不是像你那样用汇编指令,这通常是抽象的:程序用C指针引用映射,但加载器(通常依赖于libbpf(执行一些重新定位步骤,从对象文件的专用ELF部分提取关于映射的元数据,检索映射的文件描述符,并将其插入到相关的字节码指令中。
内核验证程序切换到指针
但您是正确的:在内核中,BPF_FUNC_map_lookup_elem()
助手等使用指向映射的指针,而不是文件描述符。这是在加载时,在程序验证期间,验证器用指向与映射相关联的内存区域的指针替换文件描述符(请参阅kernel/bpf/verifier.c中的resolve_pseudo_ldimm64()
(。此时可以获得指针:验证器确实有权访问这些映射的内核内存指针。
请注意,验证器实际上走得更远,对于某些映射类型(哈希、数组(,它甚至完全替换了对映射查找助手的调用,而是使用直接从映射中的相关地址读取的指令(搜索map_gen_lookup
以获取详细信息(。