对于 fentry 程序而言,在 arm64 上做 backtrace 不像 x86 上那么简单;第一步找到可用的 frame pointer 就很费劲了。

毕竟,找到 frame pointer 之后,就可以通过 frame pointer 逐级回溯,找到调用者的 IPFP

TL;DR 代码细节请看 pwru PR: Introduce two enhancements for func IP

bpf R10 寄存器

x86 上,bpf R10 寄存器就是 frame pointer,可以直接用来做 backtrace。但在 arm64 上,R10 寄存器是 R25 寄存器而不是 R29 寄存器。

因此,在 fentry 里,该如何找到 frame pointer 呢?

trampolinefentry 的栈分布

直接分析 trampolinefentry 的栈分布,如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    /* Stack layout on arm64:
     * |  r9  |
     * |  fp  | FP of tracee's caller
     * |  lr  | IP of tracee
     * |  fp  | FP of tracee
     * +------+ FP of trampoline  <-------+
     * |  ..  | padding                   |
     * |  ..  | callee saved regs         |
     * | retv | retval of tracee          |
     * | regs | regs of tracee            |
     * | nreg | number of regs            |
     * |  ip  | IP of tracee if needed    | possible range of
     * | rctx | bpf_tramp_run_ctx         | detection
     * |  lr  | IP of trampoline          |
     * |  fp  | FP of trampoline  <--------- detect it
     * +------+ FP of current prog        |
     * | regs | callee saved regs         |
     * +------+ R10 of bpf prog   <-------+
     * |  ..  |
     * +------+ SP of current prog
     */

可以看到,bpf R10 寄存器跟 fentryFP 之间隔着几个被调用者保存的寄存器。

因此,如果能够知道 fentry 里使用了几个被调用者保存的寄存器,就可以找到 FP

但是,在 commit 5d4fa9ec5643 (“bpf, arm64: Avoid blindly saving/restoring all callee-saved registers”) 之前,fentry 需要保存的寄存器数量是确定的,是 5 或者 6 个(取决于 commit 66ff4d61dc12 (“bpf, arm64: Fix tailcall hierarchy”))。而在这个 commit 之后,fentry 里保存的寄存器数量由 fentry 程序本身决定,即 fentry 使用了多少被调用者保存的寄存器,就保存多少个。

有没有简单可行的办法来找到 FP 呢?

猜测 tramplineFP

如上图,trampolineFP 保存于 trampoline 栈帧的底部,与 bpf prog 的 FP 相隔不远。

那么,假设 fentry FP 加上一段偏移后,就是 trampolineFP,该如何判断该 FP 就是 trampolineFP 呢?

一个简单一些的办法是,判断读取到的 fpfentryFPfentryFP 加上 256 字节之间;如果是,那么大概率就是 trampolineFP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
static __always_inline u64
detect_tramp_fp(void) {
    static const int range_of_detection = 256;
    u64 fp, r10;

    r10 = get_tracing_fp(); /* R10 of current bpf prog */
    for (int i = 6; i >= 0; i--) {
        bpf_probe_read_kernel(&fp, sizeof(fp), (void *) (r10 + i * 16));
        if (r10 < fp && fp < r10 + range_of_detection)
            return fp;
    }

    return r10;
}

不是很准确,但能用。

如果要拿到 tracee IP 呢?

如上图,在拿到 trampolineFP 之后,就可以直接读取 FP + 8 字节的位置,即可拿到 tracee 的 IP;该 IP 减去 12 字节,就是 tracee 的原始 IP

详情可以参考 commit b2ad54e1533e (“bpf, arm64: Implement bpf_arch_text_poke() for arm64”)。

总结

arm64 上,通过 fentry 实现 backtrace 是比较困难的,但是通过一些技巧,还是可以实现的。

详情请参考 pwru PR: Introduce two enhancements for func IP