LinuxBPF学习笔记 - 调试技术[4]

Posted 宣之于口

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LinuxBPF学习笔记 - 调试技术[4]相关的知识,希望对你有一定的参考价值。

本学习笔记来自于阅读 Brendan Gregg的《BPF Performance Tools》

一、 KPROBES

kprobes调试技术是内核开发者们专门为了便于跟踪内核函数执行状态所设计的一种轻量级内核调试技术。它可以在生产环境中实时进行此操作,无需重新重启系统或以任何特殊模式运行内核。这意味着我们可以在内核的绝大多数指定函数中动态的插入探测点(动态插桩)来收集所需的调试状态信息。

kprobes技术还具有称为kretprobes的接口,用于检测函数何时返回及其返回值。 当kprobes和kretprobes具有相同的功能时,可以记录时间戳以计算功能的持续时间; 这可能是性能分析的重要指标

1. 工作流程

  1. 当用户注册一个探测点后,kprobe首先复制和保存目标地址的字节(足够的字节以使其可以用断点指令替换)

  2. 将目标地址字节替换为断点指令: x86_64 上的 int3

  3. 当指令流达到此断点时,断点处理程序将检查它是否由kprobes安装,如果是,则执行kprobe处理程序

  4. 然后执行原始指令,并恢复指令流程

  5. 当不再需要kprobe时,原始字节将被复制回目标地址,从而将指令恢复为原始状态

2. kprobe接口

最初的kprobes技术是通过编写一个内核模块来定义的,该模块定义了用C编写的 pre- and post-handlers并通过kprobe API调用注册它们: register_kprobe() 然后,您可以加载内核模块并通过调用printk()的系统消息发出自定义信息。 完成后,您需要调用unregister_kprobe(). 但几乎没有人使用这种方式.

如今,有三种使用kprobes的接口

  • kprobe APIregister_kprobe()
  • 基于Ftrace: 可以通过将配置字符串写入此文件(/sys/kernel/debug/tracing/kprobe_events)来启用和禁用kprobes
  • perf_event_open():由perf工具使用,最近由BPF跟踪提供支持

kprobes的最大用途是通过前端跟踪器,例如: perf, SystemTap, BPF 跟踪器 (BCC和bpftrace)

3. BPF和kprobes

kprobes为BCC和bpftrace提供内核动态插桩,并被众多工具使用。

例如,从任何内核函数中提取调用计数,可用于表示内核子系统的工作负载

a. BCC

利用 attach_kprobe()attach_kretprobe()

示例1: vfsstat工具检测对虚拟文件系统(VFS)接口的键调用,并显示每秒的摘要

vfsstat
# output
TIME         READ/s  WRITE/s CREATE/s   OPEN/s  FSYNC/s
07:48:16:       736     4209        0       24        0
07:48:17:       386     3141        0       14        0
07:48:18:       308     3394        0       34        0
[...]

可以在vfsstat的源代码中看到跟踪的探针(probes), 其利用了 attach_kprobe(),以在event =后看到内核功能

grep attach_ vfsstat.py
b.attach_kprobe(event="vfs_read", fn_name="do_read")
b.attach_kprobe(event="vfs_write", fn_name="do_write")
b.attach_kprobe(event="vfs_fsync", fn_name="do_fsync")
b.attach_kprobe(event="vfs_open", fn_name="do_open")
b.attach_kprobe(event="vfs_create", fn_name="do_create")
[...]

b. bpftrace

利用 kprobekretprobe probe 类型

示例1: 作为bpftrace的示例,此一类代码通过匹配vfs_来计算所有VFS函数的调用. 输出可以看出各个函数调用次数

bpftrace -e 'kprobe:vfs_*  @[probe] = count() '
Attaching 54 probes...
^C
# output
@[kprobe:vfs_unlink]: 2
@[kprobe:vfs_rename]: 2
@[kprobe:vfs_readlink]: 2
@[kprobe:vfs_statx]: 88
@[kprobe:vfs_statx_fd]: 91
[...]

二、UPROBES

uprobes提供了用户级别的动态插桩, 它的接口类似于 kprobes, 但用于用户空间过程。 uprobes可以检测用户级函数 enties 以及指令偏移量. 另外uretprobes可以检测功能返回值.

uprobes也是基于文件的:跟踪可执行文件中的功能时,将检测使用该文件的所有进程,包括将来启动的进程。 这样就可以在系统范围内跟踪库调用。

1. 工作流程

在目标指令处插入一个断点,该断点将执行传递给uprobe处理程序。当不再需要uprobe时,目标指令将恢复为原始字节。

示例:readline()函数为例

原始指令

gdb -p 31817
[...]
(gdb) disas readline
Dump of assembler code for function readline:
   0x000055f7fa995610 <+0>:       cmpl   $0xffffffff,0x2656f9(%rip)        
# 0x55f7fabfad10
<rl_pending_input>
   0x000055f7fa995617 <+7>:       push   %rbx”

当被替换为断点后: 可以看到第一个地址被替换成了 int3 断点

gdb -p 31817
[...]
(gdb) disas readline
Dump of assembler code for function readline:
   0x000055f7fa995610 <+0>:       int3
   0x000055f7fa995611 <+1>:       cmp    $0x2656f9,%eax

2. uprobes 接口

如今,有两种使用uprobes的接口

  • 基于Ftrace: 可以通过将配置字符串写入此文件(/sys/kernel/debug/tracing/uprobes_events)来启用和禁用uprobes
  • perf_event_open():由perf工具使用,最近由BPF跟踪提供支持

3. BPF 和 uprobes

kprobes为BCC和bpftrace提供用户级别动态插桩,并被众多工具使用。BCC中的uprobe接口支持检测功能和任意地址的开头,而bpftrace当前仅支持功能的开头

开销和未来工作: uprobes可以附加到每秒触发数百万次的事件,例如用户级分配例程:malloc()free() 即使BPF在性能上进行了优化,但将微小的开销乘以每秒数百万次仍会加起来, 速度很慢

BCC

利用: attach_uprobe()attach_uretprobe()

示例: gethostlatency 工具通过解析程序库(getaddrinfo 和 gethostbyname)来检测主机解析调用(DNS)

gethostlatency
TIME      PID    COMM                  LATms HOST
01:42:15  19488  curl                  15.90 www.brendangregg.com
01:42:37  19476  curl                  17.40 www.netflix.com
01:42:40  19481  curl                  19.38 www.netflix.com
01:42:46  10111  DNS Res~er #659       28.70 www.google.com

在源代码中可以看到跟踪到的探针以获取主机等待时间,其利用了attach_uprobe()attach_uretprobe()调用。 在sym =分配后可以看到用户级功能

grep attach_ gethostlatency.py
# output
b.attach_uprobe(name="c", sym="getaddrinfo", fn_name="do_entry", pid=args.pid)
b.attach_uprobe(name="c", sym="gethostbyname", fn_name="do_entry",
b.attach_uprobe(name="c", sym="gethostbyname2", fn_name="do_entry",

bpftrace

利用了 uprobeuretprobe probe 类型

示例: 这些单行代码列出了libc系统库中所有gethost函数的调用,然后对其进行了计数

bpftrace -l 'uprobe:/lib/x86_64-linux-gnu/libc.so.6:gethost*'
# output
uprobe:/lib/x86_64-linux-gnu/libc.so.6:gethostbyname
uprobe:/lib/x86_64-linux-gnu/libc.so.6:gethostbyname2
uprobe:/lib/x86_64-linux-gnu/libc.so.6:gethostname
uprobe:/lib/x86_64-linux-gnu/libc.so.6:gethostid
[...]
bpftrace -e 'uprobe:/lib/x86_64-linux-gnu/libc.so.6:gethost*  @[probe] = count(); '
# output
Attaching 10 probes...
^C

@[uprobe:/lib/x86_64-linux-gnu/libc.so.6:gethostname]: 2

参考文章

Linux内核调试技术——kprobe使用与实现

以上是关于LinuxBPF学习笔记 - 调试技术[4]的主要内容,如果未能解决你的问题,请参考以下文章

LinuxBPF学习笔记 - 技术背景[2]

LinuxBPF学习笔记 - 技术背景[2]

LinuxBPF学习笔记 - 基本概念 [1]

LinuxBPF学习笔记 - 基本概念 [1]

LinuxBPF学习笔记 - BCC工具[6]

LinuxBPF学习笔记 - BCC工具[6]