有位 pwru 的用户在 GitHub 上提了一个 issue,说 --filter-trace-tc 不能正常工作。

1
2
3
4
5
6
7
8
9
......
        ; event.addr = bpf_get_func_ip(ctx);
        26: (bf) r1 = r6
        27: R0=invP1 R1_w=ctx(id=0,off=0,imm=0) R6=ctx(id=0,off=0,imm=0) R7=ptr_sk_buff(id=0,off=0,imm=0) R10=fp0 fp-8=????mmmm fp-16=00000000 fp-24=00000000 fp-32=0mm0mmmm fp-40=mmmmmmmm fp-48=mmmmmmmm fp-56=mmmmmmmm fp-64=mmmmmmmm fp-72=00000000 fp-80=00000000 fp-88=00000000 fp-96=00000000 fp-104=mmmmmmmm fp-112_w=ptr_ fp-120=00000000 fp-128=0000mmmm
        27: (85) call bpf_get_func_ip#173
        func bpf_get_func_ip#173 not supported for program type 3
        processed 437 insns (limit 1000000) max_states_per_insn 1 total_states 28 peak_states 28 mark_read 28

program fentry_tc: load program: operation not supported: func bpf_get_func_ip#173 not supported for program type 3 (1020 line(s) omitted)

这个 issue 的原因是 --filter-trace-tc 选项使用了 bpf_get_func_ip(),而 bpf_get_func_ip() 在 5.15 版本的内核中才支持。

噢,--filter-trace-tc 是我给 pwru 添加的一个功能。

所以,我得想个办法避免使用 bpf_get_func_ip() 的情况下去支持 --filter-trace-tc

bpf_get_func_ip() helper

bpf_get_func_ip() 是 5.15 内核中新增的一个 helper,它的作用是获取当前被跟踪函数/bpf prog 的地址。

1
2
3
4
5
        /* Implement bpf_get_func_ip inline. */
        if (prog_type == BPF_PROG_TYPE_TRACING &&
            insn->imm == BPF_FUNC_get_func_ip) {
            /* Load IP address from ctx - 8 */
            insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);

不过,先不深究该 helper 函数的实现,看下如何在不使用 bpf_get_func_ip() 的情况下实现 --filter-trace-tc

bpf prog 在 /proc/kallsyms 中的信息

/proc/kallsyms 中,bpf prog 的信息是这样子的:

1
2
3
4
5
# tail -n 4 /proc/kallsyms
ffffffffc0024020 t bpf_prog_a04f5eef06a7f555_dummy  [bpf]
ffffffffc0024094 t bpf_prog_a04f5eef06a7f555_dummy  [bpf]
ffffffffc002ac4c t bpf_prog_0b3a2ce7164b50f3_fentry_tc  [bpf]
ffffffffc002add0 t bpf_prog_0ca2b1941f84fe9b_fexit_tc   [bpf]

大概是这个格式:<addr> t bpf_prog_<tag>_<name>\t[bpf].

何以见得?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ${KERNEL}/kernel/bpf/core.c

static void
bpf_prog_ksym_set_name(struct bpf_prog *prog)
{
    // ...

    sym += snprintf(sym, KSYM_NAME_LEN, "bpf_prog_");
    sym  = bin2hex(sym, prog->tag, sizeof(prog->tag));

    /* prog->aux->name will be ignored if full btf name is available */
    if (prog->aux->func_info_cnt) {
        type = btf_type_by_id(prog->aux->btf,
                      prog->aux->func_info[prog->aux->func_idx].type_id);
        func_name = btf_name_by_offset(prog->aux->btf, type->name_off);
        snprintf(sym, (size_t)(end - sym), "_%s", func_name);
        return;
    }

    if (prog->aux->name[0])
        snprintf(sym, (size_t)(end - sym), "_%s", prog->aux->name);
    else
        *sym = 0;
}

所以,确定了 bpf prog 在 /proc/kallsyms 中的信息后,就可以通过 /proc/kallsyms 来获取 bpf prog 的地址了。

tag -> addr -> name

原先使用 bpf_get_func_ip() 的目的是获取当前被跟踪 bpf prog 的地址,然后通过该地址去获取被跟踪 bpf prog 的名字。

既然避免使用 bpf_get_func_ip(),那么就可以通过 /proc/kallsyms 来获取被跟踪 bpf prog 的 tag -> addr 信息。

然后,因为 bpf prog 的 ProgramInfo 中有 tag 信息,就可以通过 tag 来获取 bpf prog 的地址。

最终,将 bpf prog 的地址通过 spec.RewriteConstants() 注入到 bpf prog 中,从而替换了 bpf_get_func_ip() 的作用。

因为在打印事件信息的时候,已经有了 addr -> name 的信息,所以就不需要额外去获取被跟踪 bpf prog 的名字了。

小结

bpf_get_func_ip() 是 5.15 内核中新增的一个 helper,它的作用是获取当前被跟踪函数/bpf prog 的地址。

bpf_get_func_ip() 不可用时,可以通过 /proc/kallsyms 来获取被跟踪函数/bpf prog 的地址。