Last Branch Record,简称 LBR,是 CPU 提供的一种硬件功能,可以记录程序执行过程中的分支跳转信息;目前,Intel CPU、AMD CPU 均已支持 LBR 功能;Linux 内核正在支持 ARM CPU 的分支跳转记录功能。

即使是个人电脑的 Intel CPU,也支持 LBR 功能:

1
2
$ dmesg | grep -i lbr
[    0.101727] Performance Events: XSAVE Architectural LBR, PEBS fmt4+-baseline,  AnyThread deprecated, Alderlake Hybrid events, 32-deep LBR, full-width counters, Intel PMU driver.

LBR 的资料请参考:

尽管不清楚 LBR 的工作原理,并不妨碍用来追踪内核函数;甚至可以用来追踪 bpf prog 的内部执行细节。

在 bpf 中,从 commit (“bpf: Introduce helper bpf_get_branch_snapshot”) v5.16 内核开始,引入了 bpf_get_branch_snapshot helper 函数,可以获取当前 CPU 的 LBR 记录。

bpflbr 便是使用 bpf_get_branch_snapshot helper 实现的 LBR 追踪工具,可以追踪内核函数的调用关系,以及 bpf prog 的执行细节。

bpflbr: 实现原理讲解

bpflbr 分 2 部分:

  1. bpflbr bpf prog,用于采集 LBR 记录。
  2. bpflbr 用户态程序,用于解析 LBR 记录。

因为 LBR 记录的是分支跳转记录,所以就需要在 bpflbr bpf prog 的最前面就调用 bpf_get_branch_snapshot helper 函数来获取 LBR 记录,避免 bpflbr bpf prog 本身的分支跳转记录。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
struct event {
    struct perf_branch_entry lbr[MAX_LBR_ENTRIES];
    // ...
} __attribute__((packed));

static __always_inline int
emit_lbr_event(void *ctx)
{
    struct event *event;
    // ...

    if (!cfg->suppress_lbr)
        event->nr_bytes = bpf_get_branch_snapshot(event->lbr, sizeof(event->lbr), 0); /* required 5.16 kernel. */

    // ...

    bpf_ringbuf_output(&events, event, sizeof(*event), 0);

    return BPF_OK;
}

然后,在用户态程序中,读取并解析 LBR 记录:

  1. 如果是内核函数:
    1. 通过 /proc/kallsyms 符号表解析函数名。
    2. 通过 vmlinux dbgsym 文件解析 line info。
  2. 如果是 bpf prog:
    1. 事先解析所有 bpf prog 的信息,包括函数名、line info。
  3. 通过 LBR 记录输出分支跳转信息。

更详细的源代码讲解:eBPF 项目源码分析-BPFLBR

bpflbr: 功能列表

bpflbr 已经实现了以下功能:

  1. 使用 fexit 动态追踪 bpf prog 的内部执行细节。
  2. 使用 fentry 动态追踪 bpf prog 执行前的内核函数分支跳转记录。
  3. 使用 fexit 动态追踪内核函数的内部执行细节。
  4. 使用 fentry 动态追踪内核函数执行前的分支跳转记录。
  5. 通过 --output-stack 输出函数调用栈。
  6. 默认输出函数参数、函数返回值的详细信息。
  7. 通过 capstone-engine 反汇编内核函数和 bpf prog。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$ ./bpflbr -h
Usage of bpflbr:
  -d, --disasm                disasm bpf prog or kernel function
  -B, --disasm-bytes uint     disasm bytes of kernel function, 0 to guess it automatically
      --disasm-intel-syntax   use Intel asm syntax for disasm, ATT asm syntax by default
      --filter-pid uint32     filter pid for tracing
  -k, --kfunc strings         filter kernel functions by shell wildcards way
      --kfunc-all-kmods       filter functions in all kernel modules
      --limit-events uint     limited number events to output, 0 to output all events
  -m, --mode string           mode of lbr tracing, exit or entry (default "exit")
  -o, --output string         output file for the result, default is stdout
      --output-stack          output function call stack
  -p, --prog strings          bpf prog info for bpflbr in format PROG[,PROG,..], PROG: PROGID[:<prog function name>], PROGID: <prog ID> or 'i/id:<prog ID>' or 'p/pinned:<pinned file>' or 't/tag:<prog tag>' or 'n/name:<prog full name>' or 'pid:<pid>'; all bpf progs will be traced if '*' is specified
      --suppress-lbr          suppress LBR perf event
  -v, --verbose               output verbose log

bpflbr: LBR 功能展示

使用 fexit 动态追踪 bpf prog 的内部执行细节:

bpflbr pwru - exit mode

使用 fentry 动态追踪 bpf prog 执行前的内核函数分支跳转记录:

bpflbr pwru - entry mode

bpflbr: 输出函数调用栈

bpflbr func stack

bpflbr: 反汇编 bpf prog

bpflbr disasm bpf prog

bpflbr: 反汇编内核函数

bpflbr disasm kfunc

bpflbr: 将具备更加强大的功能

计划将 eBPF Talk: 动态过滤函数参数 的能力整合进 bpflbr,使得可以动态过滤函数参数;甚至使用 pcap-filter(7) 的语法来过滤 skb/xdp 数据包。

并打算参考 bice 的原理,动态输出结构体/联合体指针函数参数的指定字段。

总结

bpflbr 是一个使用 LBR 功能追踪内核函数和 bpf prog 的工具,可以输出函数调用栈、函数参数、函数返回值的详细信息,还可以反汇编内核函数和 bpf prog。

bpflbr 的源代码在 bpflbr