ebpf: BPF_FUNC_map_lookup_elem 调用约定

ebpf: BPF_FUNC_map_lookup_elem calling convention

查看内核的 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?根据 helpers code :

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是map fd。除非,我正在查找错误的代码?

在用户 space 中编写程序时的文件描述符,但后来由验证器替换为指向映射的指针。

编写 eBPF 程序时的文件描述符

您在用户 space 中编写您的 eBPF 程序,其中您没有任何指向地图的地址指针。因此,您使用文件描述符来引用该映射以执行您的程序可能 运行.

的各种操作(查找、更新、删除)

如果用 C 编写程序,而不是像您那样使用汇编指令,这通常是抽象的:程序使用 C 指针引用映射,但加载程序(通常依赖于 libbpf)执行一些重定位步骤以提取从对象文件的专用 ELF 部分获取关于映射的元数据,检索映射的文件描述符,并将其插入相关的字节码指令中。

内核验证器切换到指针

但你是对的:在内核中,BPF_FUNC_map_lookup_elem() 助手等使用指向映射的指针,而不是文件描述符。这是在加载时,在程序验证期间,验证器将文件描述符替换为指向与映射关联的内存区域的指针(请参阅 kernel/bpf/verifier.c 中的 resolve_pseudo_ldimm64())。此时可以获得指针:验证者确实可以访问那些映射的内核内存指针。

请注意,验证器实际上走得更远,对于某些地图类型(哈希、数组),它甚至完全取代了对地图查找助手的调用,而是使用指令直接从中的相关地址读取地图(搜索 map_gen_lookup 了解详情)。