这部分内容是内核 hacker 的最爱,能够支持 tracepoints
、kprobes
、uprobes
等实现,提供了跟踪常见的一些能力,包括事件追踪, 可选择过滤器和参数; 事件计数和时间采样,内核概览;基于函数的路径追踪等。
简介
大部分工具的实现策略是通过定时器采集当前 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%
,所以,实现是支持动态调试的,也就是:
- 编译阶段,记录被编译器添加了跳转指令的函数,也就是通过
scripts/recordmcount.pl
扫描所有mcount
调用点,保存在__mcount_loc
段中。 - 启动阶段,调用
ftrace_process_locs
函数将所有被编译器添加的跳转指令替换成nop
指令,实现非调试状态性能零损失。 - 跟踪阶段,根据设置,通过
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/bash
中 readline
函数的偏移,而实际加载地址是 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 Gregg、Openresty 两篇不错的文章介绍动态跟踪技术。
bpftrace和uprobe使用方法
https://zhuanlan.zhihu.com/p/651992788
在外部没有办法直接使用,需要以内核模块的方式编写代码
http://tinylab.org/linux-kprobes/