btrace 是什么?btrace 是一款 bpf 时代的现代化内核函数动态追踪工具。

btrace 重命名自 bpflbr,参考 eBPF Talk: 使用 Last Branch Record 动态追踪内核函数,在 bpflbr v0.1.0 的基础上,新增了几个重要的功能:

  1. 使用简单的 C 表达式来过滤函数参数的属性。
  2. 根据函数参数来过滤需要动态追踪的内核函数列表。
  3. 使用 pcap-filter(7) 语法来过滤网络包。
  4. 支持 --output-pkt 输出网络包里的五元组信息。

使用简单的 C 表达式来过滤函数参数的属性

使用 eBPF Talk: 动态过滤函数参数 里介绍的 bice 库,btrace 可以使用简单的 C 表达式来过滤函数参数的属性。

btrace –filter-arg

基于 bice 库,该功能的实现原理:

  1. 匹配内核函数里的参数名称。
  2. 根据参数的索引,生成一段调用 bpf_get_func_arg() helper 的 bpf 字节码。
  3. 使用 bice 库将 C 表达式编译成一段 bpf 字节码。
  4. 将这 2 段字节码拼接到一起,然后注入到 btrace bpf prog 的 filter_fnarg() 桩函数里。

根据函数参数来过滤需要动态追踪的内核函数列表

pwru 项目里动态追踪的是名称为 skb 且类型为 struct sk_buff * 的参数的内核函数列表。

socketrace 项目里动态追踪的是名称为 sk 且类型为 struct sock * 的参数的内核函数列表。

btrace 也支持根据函数参数来过滤需要动态追踪的内核函数列表:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ ./btrace -k '*:(struct sk_buff *)skb' --show-func-proto
Kernel functions: (total 1543)
void kauditd_rehold_skb(struct sk_buff *skb, int error);
void kauditd_send_multicast_skb(struct sk_buff *skb);
int auditd_set(struct pid *pid, u32 portid, struct net *net, struct sk_buff *skb, bool *ack);
void kauditd_retry_skb(struct sk_buff *skb, int error);
void kauditd_hold_skb(struct sk_buff *skb, int error);
int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh, bool *ack);
void audit_receive(struct sk_buff *skb);
...

$ ./btrace -k '*:(struct sock *)sk' --show-func-proto
Kernel functions: (total 1146)
int kauditd_send_queue(struct sock *sk, u32 portid, struct sk_buff_head *queue, unsigned int retry_limit, func *skb_hook, func *err_hook);
int __cgroup_bpf_run_filter_skb(struct sock *sk, struct sk_buff *skb, enum cgroup_bpf_attach_type atype);
int __cgroup_bpf_run_filter_sock_ops(struct sock *sk, struct bpf_sock_ops_kern *sock_ops, enum cgroup_bpf_attach_type atype);
int __cgroup_bpf_run_filter_sk(struct sock *sk, enum cgroup_bpf_attach_type atype);
int __cgroup_bpf_run_filter_sock_addr(struct sock *sk, struct sockaddr *uaddr, int *uaddrlen, enum cgroup_bpf_attach_type atype, void *t_ctx, u32 *flags);
int __cgroup_bpf_run_filter_setsockopt(struct sock *sk, int *level, int *optname, sockptr_t optval, int *optlen, char **kernel_optval);
int __cgroup_bpf_run_filter_getsockopt_kern(struct sock *sk, int level, int optname, void *optval, int *optlen, int retval);
void bpf_sk_reuseport_detach(struct sock *sk);
...

使用 pcap-filter(7) 语法来过滤网络包

如果被动态追踪的函数带有 struct sk_buff *skbstruct __sk_buff *skbstruct xdp_buff *xdpstruct xdp_md *xdp 等参数,btrace 可以使用 pcap-filter(7) 语法来过滤网络包。

1
2
3
4
5
6
7
8
$ sudo ./btrace -k tcp_v4_rcv --filter-pkt 'host 1.1.1.1 and port 443'
2025/03/09 11:52:19 btrace is running..
tcp_v4_rcv args=((struct sk_buff *)skb=0xffff991f1ef51f00) retval=(int)0 cpu=6 process=(0:swapper/6)

tcp_v4_rcv args=((struct sk_buff *)skb=0xffff991f1ef50d00) retval=(int)0 cpu=6 process=(0:swapper/6)

tcp_v4_rcv args=((struct sk_buff *)skb=0xffff991f1ef51400) retval=(int)0 cpu=6 process=(0:swapper/6)
...

类似过滤函数参数的实现方式,该功能的实现原理:

  1. 匹配内核函数里的参数。
  2. 使用 elibpcap 库将 pcap-filter(7) 表达式注入到 btrace bpf prog 的桩函数里。
  3. 根据参数的索引,生成一段调用 bpf_get_func_arg() helper 的 bpf 字节码。
  4. 如果是 skb,则调用 filter_skb() 桩函数,如果是 xdp,则调用 filter_xdp() 桩函数。
  5. 将最终的 bpf 字节码注入到 filter_pkt() 桩函数里。

支持 --output-pkt 输出网络包里的五元组信息

既然支持过滤网络包,当然也可以支持输出网络包里的五元组信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ sudo ./btrace -k tcp_v4_rcv --filter-pkt 'host 1.1.1.1 and port 443' --output-pkt
2025/03/09 12:01:53 btrace is running..
tcp_v4_rcv args=((struct sk_buff *)skb=0xffff991f1ef51e00) retval=(int)0 cpu=6 process=(0:swapper/6)
Pkt tuple: 1.1.1.1:443 -> 192.168.241.133:39412 (TCP:SYN|ACK)

tcp_v4_rcv args=((struct sk_buff *)skb=0xffff991f1ef51400) retval=(int)0 cpu=6 process=(0:swapper/6)
Pkt tuple: 1.1.1.1:443 -> 192.168.241.133:39412 (TCP:ACK)

tcp_v4_rcv args=((struct sk_buff *)skb=0xffff991f1ef50c00) retval=(int)0 cpu=6 process=(0:swapper/6)
Pkt tuple: 1.1.1.1:443 -> 192.168.241.133:39412 (TCP:PSH|ACK)
...

类似于过滤网络包的实现方式,该功能的实现原理:

  1. 匹配内核函数里的参数。
  2. 根据参数的索引,生成一段调用 bpf_get_func_arg() helper 的 bpf 字节码。
  3. 如果是 skb,则调用 output_skb() 桩函数,如果是 xdp,则调用 output_xdp() 桩函数。
  4. 将最终的 bpf 字节码注入到 output_pkt() 桩函数里。

总结

btrace 是一款 bpf 时代的现代化内核函数动态追踪工具:

  1. 支持输出 LBR 记录。
  2. 支持反汇编内核函数和 bpf prog。
  3. 支持输出函数调用栈。
  4. 支持输出带类型信息的参数和带类型的返回值。
  5. 支持使用简单的 C 表达式来过滤函数参数的属性。
  6. 支持根据函数参数来过滤需要动态追踪的内核函数列表。
  7. 支持使用 pcap-filter(7) 语法来过滤网络包。
  8. 支持 --output-pkt 输出网络包里的五元组信息。

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

btrace 项目地址:btrace