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

bpfsnoop 重命名自 btrace,为了避免与其它 btrace 项目重名,使用 bpfsnoop 作为新名称;并上线专属网站:bpfsnoop.com

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

  1. 支持动态追踪多达 12 个参数的内核函数。
  2. 新增 --output-arg 来输出函数参数的属性,类似于 --filter-arg
  3. 新增 --output-flamegraph 来输出火焰图的折叠后的数据。
  4. 新增 -t 来动态追踪内核跟踪点。
  5. 许多改进。
  6. 2 次重构。

支持动态追踪多达 12 个参数的内核函数

Menglong Dong 大佬在 commit bpf, x86: allow function arguments up to 12 for TRACING 中,将 x86 架构上 fentry/fexit 目标内核函数的参数个数限制从 6 个增加到了 12 个。

因此,bpfsnoop 会检测内核是否支持动态追踪超过 6 个参数的内核函数。

效果如下:

1
2
3
$ sudo ./bpfsnoop -k tcp_select_initial_window --show-func-proto
Kernel functions: (total 1)
void tcp_select_initial_window(const struct sock *sk, int __space, __u32 mss, __u32 *rcv_wnd, __u32 *window_clamp, int wscale_ok, __u8 *rcv_wscale, __u32 init_rcv_wnd);

tcp_select_initial_window 函数的参数个数为 8 个。

1
2
3
$ sudo ./bpfsnoop -k tcp_select_initial_window
2025/04/06 12:33:54 bpfsnoop is running..
tcp_select_initial_window args=((const struct sock *)sk=0xffff991e5dd14600, (int)__space=33280, (__u32)mss=0x5a8/1448, (__u32 *)rcv_wnd=0xffff991e45cc98dc(0x7c70/31856), (__u32 *)window_clamp=0xffff991e45cc98e4(0x7fff80/8388480), (int)wscale_ok=1, (__u8 *)rcv_wscale=0xffffa64a8060086f(0x7/7), (__u32)init_rcv_wnd=0x0/0) retval=(void) cpu=6 process=(0:swapper/6)

新增 --output-arg 来输出函数参数的属性,类似于 --filter-arg

参考 --filter-arg 的实现方式,新增 --output-arg 来输出函数参数的属性。

访问参数属性的实现:bice Access;实现过程请参考 btrace v0.2.0 的 “使用简单的 C 表达式过来函数参数的属性”。

效果如下:

1
2
3
4
5
$ sudo ./bpfsnoop -k tcp_select_initial_window --output-arg 'sk->__sk_common.skc_daddr' --output-arg 'sk->__sk_common.
skc_rcv_saddr' --output-arg 'sk->__sk_common.skc_dport' --output-arg 'sk->__sk_common.skc_num'
2025/04/06 12:42:03 bpfsnoop is running..
tcp_select_initial_window args=((const struct sock *)sk=0xffff991e64d69bc0, (int)__space=33280, (__u32)mss=0x5b4/1460, (__u32 *)rcv_wnd=0xffff991e64d6a260(0x7d78/32120), (__u32 *)window_clamp=0xffff991e64d6a244(0x7fff80/8388480), (int)wscale_ok=1, (__u8 *)rcv_wscale=0xffffa64a80de7c2f(0x7/7), (__u32)init_rcv_wnd=0x0/0) retval=(void) cpu=1 process=(735963:curl)
Arg attrs: (__be32)skc_daddr=0x1010101/16843009, (__be32)skc_rcv_saddr=0x85f1a8c0/2247207104, (__be16)skc_dport=0xbb01/47873, (__u16)skc_num=0xa5fe/42494

--output-arg 最多输出 4 个非字符串类型的参数属性和 1 个字符串类型的参数属性。

新增 --output-flamegraph 来输出火焰图的折叠后的数据

--output-flamegraph 是个隐藏的选项,因为它只是输出了火焰图的折叠后的数据,并没有输出火焰图的 SVG 文件。

用法如下:

1
2
3
4
$ sudo ./bpfsnoop -k 'ip*_rcv' --output-stack --output-flamegraph output.fold --limit-events 1000 >/dev/null
2025/04/06 12:47:31 bpfsnoop is running..
2025/04/06 12:47:38 bpfsnoop is exiting..
$ /path/to/FlameGraph/flamegraph.pl output.fold > output.svg

bpfsnoop –output-flamegraph

新增 -t 来动态追踪内核跟踪点

参考 eBPF Talk: tp_btf 经验分享bpfsnoop 想要动态追踪内核跟踪点时,就需要:

  1. 列出所有的内核跟踪点,包括内核模块的跟踪点。
  2. 根据内核跟踪点的名称、参数类型和参数名称来过滤需要动态追踪的内核跟踪点。
  3. 使用 tp_btf 来动态追踪内核跟踪点。

--output-XXX 选项的实现完成复用 -k 选项的实现。

效果如下:

1
2
3
4
5
$ sudo ./bpfsnoop -t 'netif_receive_skb' --filter-pkt 'host 1.1.1.1' --output-pkt --output-arg 'skb->dev->ifindex' --output-arg 'skb->dev->name'
2025/04/06 12:55:15 bpfsnoop is running..
netif_receive_skb[tp] args=((struct sk_buff *)skb=0xffff991e4b8d4000) cpu=6 process=(0:swapper/6)
Pkt tuple: 1.1.1.1:53 -> 192.168.241.133:53768 (UDP)
Arg attrs: (int)ifindex=2, (array(char[16]))name="ens33"

重要改进:支持 v5.15 内核

由于 bpf_get_func_{arg,ret} helper 的使用,bpfsnoop 只能在 v5.17 及以上内核上运行。

bpfsnoop v0.3.0 支持 v5.15 内核,主要是将 bpf_get_func_{arg,ret} helper 的使用改为 bpf_probe_read_kernel();因为 fentry/fexit/tp_btf 的目标内核函数/跟踪点的函数参数和返回值是放在一段连续的内存里,并由 ctx 指向该段内存,所以可以使用 bpf_probe_read_kernel() 来一次性读取全部函数参数和返回值。

因此,理论上,bpfsnoop 可以在 v5.8 及以上内核上运行,依赖于 bpf link 机制和 ringbuf

效果如下:

1
2
3
4
5
6
7
$ uname -r
5.15.0-126-generic

$ sudo ./bpfsnoop -k tcp_conn_request --output-arg 'sk->__sk_common.skc_daddr' --output-arg 'sk->__sk_common.skc_rcv_saddr' --output-arg 'sk->__sk_common.skc_dport' --output-arg 'sk->__sk_common.skc_num'
2025/04/06 13:03:27 bpfsnoop is running..
tcp_conn_request args=((struct request_sock_ops *)rsk_ops=0xffffffff99849b00, (const struct tcp_request_sock_ops *)af_ops=0xffffffff98944420, (struct sock *)sk=0xffff900816392bc0, (struct sk_buff *)skb=0xffff90080316f100) retval=(int)0 cpu=3 process=(0:swapper/3)
Arg attrs: (__be32)skc_daddr=0x0/0, (__be32)skc_rcv_saddr=0x0/0, (__be16)skc_dport=0x0/0, (__u16)skc_num=0x16/22

总结

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 来动态追踪内核跟踪点。

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

bpfsnoop 项目地址:bpfsnoop