bpfsnoop 是一款 bpf 时代的现代化内核函数、内核跟踪点和 bpf 程序的动态追踪工具。

bpfsnoop 发布 v0.5.0 版本,主要更新如下:

  1. 使用 fentryfexit 实现 bpf 版本的 funcgraph 功能。
  2. --output-arg 新增大量的内置函数。
  3. --filter-pkt--output-pkt 支持 xdp_frame 参数。
  4. 为每个 tracee 引入 insn/fgraph/stack/lbr/both/pkt 等模式,各不干扰。

bpfsnoop 官网 bpfsnoop.com

1. 使用 fentryfexit 实现 bpf 版本的 funcgraph 功能

先看效果,比如内核协议栈发包时的部分函数调用路径:

bpfsnoop funcgraph

P.S. 这是在 v5.15 内核上运行命令 ./bpfsnoop -k '(g)tcp_connect' --fgraph-max-depth 20 --fgraph-include '*:skb' 的结果;在不同版本的内核上运行该命令会有不同的结果。

为 funcgraph 功能新增如下命令行选项:

  1. --output-fgraph:启用 funcgraph 功能。
  2. --fgraph-max-depth:设置 funcgraph 的最大深度,默认为 5。
  3. --fgraph-exclude:设置 funcgraph 的路径排除规则,查找时会忽略匹配的函数。
  4. --fgraph-include:设置 funcgraph 的路径包含规则,在排除规则之外,查找时只包含匹配的函数。
  5. --fgraph-extra:设置 funcgraph 的额外匹配规则,同时会应用以上的包含和排除规则。
  6. --fgraph-proto: 类似 --show-func-proto,输出 funcgraph 的函数调用路径。

因为 funcgraph 功能无法跟踪间接调用的函数,所以在使用时需要注意:

  1. 该功能有可能导致内核崩溃。
  2. 没有间接调用的函数的输出结果。

因此,可以使用 --fgraph-extra 选项来添加额外的匹配规则,来跟踪可能被间接调用的函数。

特别:

  1. --fgraph-max-depth 选项的最大值为 500,足以覆盖内核的最大函数调用深度。
  2. 巧妙地规避 funcgraph 带来的观察者效应。
  3. 尽力降低 funcgraph 功能导致内核崩溃的可能性。

推荐:在虚拟机里用来研究内核。

警告:因内核缺陷,使用该功能可能会导致内核挂起/崩溃。

2. 给 --output-arg 新增大量的内置函数

新增以下内置函数,供 --output-arg 选项使用:

  1. pkt: 将一段内核内存使用 gopacket 解析成网络包。
  2. eth/ip4/ip6: 将一段内核内存解析成一个以太网/IPv4/IPv6 地址。
  3. eth2/ip42/ip62: 将一段内核内存解析成两个连续的以太网/IPv4/IPv6 地址。
  4. port: 将一段内核内存解析成一个端口号。
  5. port2: 将一段内核内存解析成两个连续的端口号。
  6. slice: 将一段内核内存解析成一个 BTF 类型切片。
  7. hex: 将一段内核内存解析成一个十六进制字符串。
  8. u8/u16/u32/u64: 将一段内核内存解析成一个无符号整数。
  9. s8/s16/s32/s64: 将一段内核内存解析成一个有符号整数。
  10. le16/le32/le64: 将一段内核内存解析成一个小端无符号整数。
  11. be16/be32/be64: 将一段内核内存解析成一个大端无符号整数。

新增这些内置函数后,--output-arg 选项的使用更加灵活和强大。

使用范例:eBPF Talk: 使用 bpfsnoop 调试 i40e 驱动丢包问题

3. --filter-pkt--output-pkt 支持 xdp_frame 参数

对于网卡驱动,在运行 driver 模式的 XDP 程序之后,可能会将 xdp_buff 转换成 xdp_frame:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// include/linux/xdp.h

static inline
int xdp_update_frame_from_buff(const struct xdp_buff *xdp,
                               struct xdp_frame *xdp_frame)
{
    int metasize, headroom;

    /* Assure headroom is available for storing info */
    headroom = xdp->data - xdp->data_hard_start;
    metasize = xdp->data - xdp->data_meta;
    metasize = metasize > 0 ? metasize : 0;
    if (unlikely((headroom - metasize) < sizeof(*xdp_frame)))
        return -ENOSPC;

    /* Catch if driver didn't reserve tailroom for skb_shared_info */
    if (unlikely(xdp->data_end > xdp_data_hard_end(xdp))) {
        XDP_WARN("Driver BUG: missing reserved tailroom");
        return -ENOSPC;
    }

    xdp_frame->data = xdp->data;
    xdp_frame->len  = xdp->data_end - xdp->data;
    xdp_frame->headroom = headroom - sizeof(*xdp_frame);
    xdp_frame->metasize = metasize;
    xdp_frame->frame_sz = xdp->frame_sz;
    xdp_frame->flags = xdp->flags;

    return 0;
}

/* Convert xdp_buff to xdp_frame */
static inline
struct xdp_frame *xdp_convert_buff_to_frame(struct xdp_buff *xdp)
{
    struct xdp_frame *xdp_frame;

    if (xdp->rxq->mem.type == MEM_TYPE_XSK_BUFF_POOL)
        return xdp_convert_zc_to_xdp_frame(xdp);

    /* Store info in top of packet */
    xdp_frame = xdp->data_hard_start;
    if (unlikely(xdp_update_frame_from_buff(xdp, xdp_frame) < 0))
        return NULL;

    /* rxq only valid until napi_schedule ends, convert to xdp_mem_type */
    xdp_frame->mem_type = xdp->rxq->mem.type;

    return xdp_frame;
}

因此,在对网络驱动进行调试时,可以使用 xdp_frame 作为 --filter-pkt--output-pkt 的对象。

4. 为每个 tracee 引入 insn/fgraph/stack/lbr/both/pkt 等模式,各不干扰

-k 选项支持:

  1. (i): insn 模式,动态追踪内核函数的所有指令。
  2. (g): fgraph 模式,动态追踪内核函数的函数调用路径。
  3. (s): stack 模式,动态追踪内核函数的调用栈。
  4. (l): lbr 模式,动态追踪内核函数的 LBR 记录。
  5. (b): both 模式,同时使用 fentryfexit 动态追踪内核函数。
  6. (p): pkt 模式,即 --output-pkt 选项,输出网络包的五元组信息。

-p 选项支持:

  1. (g): fgraph 模式,动态追踪 bpf 程序的函数调用路径。
  2. (s): stack 模式,动态追踪 bpf 程序的调用栈。
  3. (l): lbr 模式,动态追踪 bpf 程序的 LBR 记录。
  4. (b): both 模式,同时使用 fentryfexit 动态追踪 bpf 程序。
  5. (p): pkt 模式,即 --output-pkt 选项,输出 bpf 程序处理的网络包信息。

-t 选项支持:

  1. (s): stack 模式,动态追踪内核跟踪点的调用栈。
  2. (l): lbr 模式,动态追踪内核跟踪点的 LBR 记录。
  3. (p): pkt 模式,即 --output-pkt 选项,输出内核跟踪点处理的网络包信息。

当 bpfsnoop 用来调试比较复杂的内核问题时,就需要针对不同的 tracee 使用不同的模式。

总结

bpfsnoop v0.5.0 版本实现了 bpf 版本的 funcgraph 功能,并新增了大量的内置函数供 --output-arg 使用。

bpfsnoop 是一款 bpf 时代的现代化内核函数、内核跟踪点和 bpf 程序的动态追踪工具:

  1. 支持输出 LBR 记录。
  2. 支持反汇编内核函数和 bpf prog。
  3. 支持输出函数调用栈。
  4. 支持输出带类型信息的参数和带类型的返回值。
  5. 支持使用复杂的 C 表达式来过滤函数参数的属性。
  6. 支持根据函数参数来过滤需要动态追踪的内核函数列表。
  7. 支持使用 pcap-filter(7) 语法来过滤网络包。
  8. 支持 --output-pkt 输出网络包里的五元组信息。
  9. 支持动态追踪多达 12 个参数的内核函数。
  10. 支持 --output-arg 来输出函数参数的属性,类似于 --filter-arg
  11. 支持 --output-flamegraph 来输出火焰图的折叠后的数据。
  12. 支持 -t 来动态追踪内核跟踪点。
  13. -k 里支持 ‘(i)’ 前缀来动态追踪内核函数的所有指令。
  14. 支持 --output-arg 使用大量内置函数来解析内核内存。
  15. 支持 --output-fgraph 来动态追踪内核函数的调用路径。

未来将支持更多高级功能,敬请期待!

bpfsnoop 项目地址:bpfsnoop