Linux Trace 工具使用介绍

2023-05-21 language

这部分内容是内核 hacker 的最爱,能够支持 tracepointskprobesuprobes 等实现,提供了跟踪常见的一些能力,包括事件追踪, 可选择过滤器和参数; 事件计数和时间采样,内核概览;基于函数的路径追踪等。

简介

大部分工具的实现策略是通过定时器采集当前 PC 所执行的函数地址(甚至是调用栈),然后以方便分析的方式进行展示。

动态跟踪主要包含三部分:

  • 数据源 Event Sources,一般是内核、硬件等提供的事件。
  • 跟踪框架 Tracing Framework,运行在内核中做数据采集、统计,也包括类似 eBPF 的动态编程。
  • 前端 FrontEnd,负责与跟踪框架进行通讯,采集数据并进行展示。

在具体实现时,除了要实现具体的数据采集功能之外,还需要考虑如何降低指标采集对正常性能影响,这样就允许线上直接使用。

这里会介绍大部分的实现。

ftrace

function tracer, ftracer 是一个内核的跟踪器,通常用来调试、分析性能问题,包含了很多静态事件点,可以看到内核在哪些部分运行。通过不同的工具集,可以延迟跟踪检查、何时发生中断、任务的启用、禁用及抢占等。

grep CONFIG_DYNAMIC_FTRACE /boot/config-`uname -r`

其基本原理是基于编译器的 profiler 机制,例如 gcc 可以通过 -pg -mfentry 指定,此时会增加特殊的 mcount 代码调用,所有函数都会调用改函数,相当于一个跳转。

但是,如果直接增加 mcount 的话,会导致性能下降 13%,所以,实现是支持动态调试的,也就是:

  1. 编译阶段,记录被编译器添加了跳转指令的函数,也就是通过 scripts/recordmcount.pl 扫描所有 mcount 调用点,保存在 __mcount_loc 段中。
  2. 启动阶段,调用 ftrace_process_locs 函数将所有被编译器添加的跳转指令替换成 nop 指令,实现非调试状态性能零损失。
  3. 跟踪阶段,根据设置,通过 ftrace_run_update_code 动态将被调试函数的 nop 指令,替换成跳转指令。

一般跳转的目标函数是 ftrace_caller(),在该函数中实现跟踪功能。

tracepoints

在内核代码中静态维护,其入口函数一般为 trace_xxx(),也就是在内核代码中提供了一个 hook 用来调用探针函数,默认关闭,内核经过优化后基本没有开销。

event 作为操作系统提供的 tracepoint 点入口,通过挂载的 debugfs 文件系统可以实现用户态和内核态的交互,在 tracing/events 目录下包含了可以使用的节点,每个目录包含 enable 控制开关。

当代码执行到对应 hook 后就会调用 probe 函数,然后将相关信息输出到 ringbuffer 中,在用户态就可以通过 tracing/trace 读取。

----- 系统支持的TracePoints,其中的sys_enter_open函数
# ls /sys/kernel/debug/tracing/events/
# ls /sys/kernel/debug/tracing/events/syscalls/sys_enter_open/
----- 可以直接过滤所支持的事件
# grep '^syscalls:' /sys/kernel/debug/tracing/available_events
----- 包括一些常用的工具
# perf list tracepoint

kprobes

如果调试 Linux 内核需要知道某个变量的值,比较简单的做法是在内核添加日志信息,但是这就意味着需要重新编译内核,然后重启设备,而通过 Kernel Probes, KProbes 这种动态探测技术可以弥补上述的缺点。

通过 kprobes 可以允许用户指定探测点以及回调函数,当代码执行到探测点后,就会触发回调函数,然后在回调函数中获取相关的信息,例如 CPU 寄存器的值,最后返回原代码流程继续执行。

提供了三种回调方式:A) pre_handler 调用前;B) post_handler 调用后;C) fault_handler 内存访问出错。

简单来说,可以通过如下方式使用 kprobes,详细可以参考 an introduction to kprobes 中的介绍。

使用简介

先确定内核是否支持。

# grep CONFIG_KPROBES /boot/config-`uname -r`
CONFIG_KPROBES=y
CONFIG_KPROBES_ON_FTRACE=y

此时会在 debugfs 目录下生成 tracing/{kprobe_events,kprobe_profile} 文件。

uprobes

UserSpace Probes, UProbes 用户空间的动态跟踪,提供了与 kprobes 类似的特性,在 3.5 新增,并 3.14 完善,可以通过如下命令查看内核是否支持。

# grep CONFIG_UPROBE_EVENT /boot/config-`uname -r`
CONFIG_UPROBE_EVENTS=y

User-Level Dynamic Tracing 提供了一个很方便使用的脚本。

----- 获取可执行文件的符号地址
# grep 'r-xp.*/usr/bin/bash' /proc/$$/maps
00400000-004dd000 r-xp 00000000 fd:00 133328                             /usr/bin/bash
# objdump -T /usr/bin/bash | grep -w readline
000000000048a7c0 g    DF .text  00000000000001c5  Base        readline

其中 0x48a7c0/usr/bin/bashreadline 函数的偏移,而实际加载地址是 0x400000

echo 'p:readline /usr/bin/bash:0x48a7c0' > uprobe_events
----- 当上述命令执行成功后会出现如下目录
echo 1 > events/uprobes/enable
cat trace

其它

debugfs

procfs 以及 sysfs 文件系统类似,这是一个 虚拟文件系统,便于用户导出内核空间数据,大部分发行版本都支持,可以通过如下命令简单确认。

# grep CONFIG_DEBUG_FS /boot/config-`uname -r`
CONFIG_DEBUG_FS=y
CONFIG_DEBUG_FS_ALLOW_ALL=y

默认会挂载到 /sys/kernel/debug 目录,可以通过 mount | grep debugfs 命令确认是否默认挂载,如果没有挂载,可以通过 mount -t debugfs none /sys/kernel/debug 手动挂载。

参考

  • Trace 内核中与动态跟踪的相关文档。
  • uftrace 一个 trace 框架,可以记录入参、返回值、执行耗时等。
  • Brendan GreggOpenresty 两篇不错的文章介绍动态跟踪技术。
bpftrace和uprobe使用方法
https://zhuanlan.zhihu.com/p/651992788

在外部没有办法直接使用,需要以内核模块的方式编写代码
http://tinylab.org/linux-kprobes/