Linux uprobe: User-Level Dynamic Tracing

我一直期待在较新的 Linux 内核上的功能之一是uprobes:用户级动态跟踪,它被添加到Linux 3.5并在Linux 3.14中得到改进。它使您可以跟踪用户级功能;例如,从所有正在运行的 bash shell 中返回 readline() 函数,并返回字符串:

./uprobe ‘r:bash:readline +0($retval):string’

跟踪 uprobe readline (r:readline /bin/bash:0x8db60 +0($retval):string)。Ctrl-C 结束。
bash-11886 [003] d… 19601837.001935: readline: (0x41e876 ← 0x48db60) arg1=" ls -l "
bash-11886 [002] d… 19601851.008409: readline: (0x41e876 ← 0x48db60) arg1=" echo “hello world” "
bash-11886 [002] d… 19601854.099730: readline: (0x41e876 ← 0x48db60) arg1=" df -h "
bash-11886 [002] d… 19601858.805740: readline: (0x41e876 ← 0x48db60) arg1=" cd … "
bash-11886 [003] d… 19601898.378753: readline: (0x41e876 ← 0x48db60) arg1=" foo bar "
^C
结束追踪…
这是通过动态检测 bash 代码来窥探来自所有 shell 的所有输入命令。我不需要重新启动任何 bash shell 来执行此操作。
还可以跟踪库函数。这是对 libc sleep() 的所有调用,带有参数(秒数):

./uprobe ‘p:libc:sleep %di’

跟踪 uprobe 睡眠(p:sleep /lib/x86_64-linux-gnu/libc-2.15.so:0xbf130 %di)。Ctrl-C 结束。
svscan-2134 [002] d … 19602517.962925:睡眠:(0x7f2dba562130)arg1 = 0x5
svscan-2134 [002] d … 19602522.963082:睡眠:(0x7f2dba562130)arg1 = 0x5
cron-923 [ 00 19602524.187733:睡眠:(0x7f3e26d9e130)arg1 = 0x3c
svscan-2134 [002] d … 19602527.963267:睡眠:(0x7f2dba562130)arg1 = 0x5
[…]
所以 svcscan 休眠 5 秒,而 cron 休眠 60 秒(0x3c = 60 十进制)。
uprobe是我为perf-tools集合编写的一个工具,用于通过 Linux ftrace(内置跟踪器)探索uprobes 。(uprobe 我的 kprobe 工具的用户级对应物,它跟踪内核函数。) uprobe 是一个实验性工具,仅适用于较新的内核(稍后会详细介绍)。它的真正价值——以及我写博客的原因——是展示 Linux 内核中内置了哪些功能。您可能只能通过另一个跟踪器(perf_events、SystemTap、LTTng、…)使用 uprobes,这很好。
uprobe One-Liners
以下是帮助消息 (-h) 和手册页中列出的单行代码,它们总结了 uprobe 特性:

在所有正在运行的“bash”可执行文件中跟踪 readline() 调用:

uprobe p:bash:readline

使用显式可执行路径跟踪 readline():

uprobe p:/bin/bash:readline

以字符串形式跟踪 readline() 的返回:

uprobe ‘r:bash:readline +0($retval):string’

在所有正在运行的 libc 共享库中跟踪 sleep() 调用:

uprobe p:libc:sleep

使用寄存器 %di (x86) 跟踪 sleep():

uprobe ‘p:libc:sleep %di’

跟踪这个地址(注意:必须是指令对齐的):

uprobe p:libc:0xbf130

仅跟踪 PID 1182 的 gettimeofday():

uprobe -p 1182 p:libc:gettimeofday

仅当 fopen() 返回 NULL 时才跟踪它的返回:

uprobe ‘r:libc:fopen file=$retval’ ‘file == 0’
入口函数的参数可以从它们的寄存器位置读取。如果这太麻烦了,请考虑使用带有 debuginfo的 Linux perf_events (或其他跟踪器,如 SystemTap),其中可以直接使用变量名。
警告
我希望在我现在正在调试的 Linux 3.13 内核上使用 uprobes,但经常遇到目标进程崩溃或进入无限自旋循环的问题。我并不感到惊讶,因为 uprobes 是相对较新的内核代码。如果我真的不走运,服务器需要重新启动,因为我已经楔入了多个进程。这些错误似乎已被 Linux 4.0(可能更早)修复。因此,uprobe 不会在早于 4.0 的内核上运行(没有 -F 强制)。也许那是悲观的,应该是3.18什么的。
你不太可能需要这个,但我也应该警告不要使用原始地址模式,例如“p:libc:0xbf130”,除非你知道你在做什么。 uprobe 很简单,不检查指令对齐。 如果您使用了错误的地址,例如,在多字节指令的中途,目标进程将崩溃或处于损坏状态。 其他工具,如 perf_events,使用 debuginfo 来检查指令对齐。
除此之外,还有关于动态跟踪的典型警告:您可以跟踪太多,从而减慢目标进程。 如果这是个问题,您可以尝试更高效的跟踪器:例如,Linux perf_events 和(即将推出的)eBPF。 您还可以尝试使用“-d duration”选项进行uprobe,该选项在指定的持续时间内使用内核缓冲(以牺牲实时更新为代价)。
如果要尝试uprobe,请先使用测试环境。
更多示例
请参阅我在 perf-tools 中包含的uprobe 示例文件和手册页。
uprobe 内部构件
编写 uprobe 的灵感来自阅读 Linux 内核中的文档uprobetracer.txt:
以下示例显示如何转储指令指针和 %ax 寄存器
在探测的文本地址。探测 /bin/zsh 中的 zfree 函数:

# cd /sys/kernel/debug/tracing/
# cat /proc/`pgrep zsh`/maps | grep /bin/zsh | 抓到r-xp
00400000-0048a000 r-xp 00000000 08:03 130904 /bin/zsh
# objdump -T /bin/zsh | grep -w zfree
0000000000446420 g DF .text 0000000000000012 基础 zfree

0x46420 是 zfree 在对象 /bin/zsh 中的偏移量,在
0x00400000。因此,uprobe 的命令是:

# echo 'p:zfree_entry /bin/zsh:0x46420 %ip %ax' > uprobe_events

与 uretprobe 相同的是:

# echo 'r:zfree_exit /bin/zsh:0x46420 %ip %ax' >> uprobe_events

(此示例使用 zsh 而不是 bash,这在尝试检测时可能比使用默认登录 shell 更安全!)
这些步骤很麻烦(并且还有其他步骤:探针也需要清理),并且需要自动化。幸运的是,它已经有了:除了我的 uprobe 工具,您还可以通过 Linux perf 命令(来自perf_events)使用这些工具。
使用性能
为了比较,这里使用标准 Linux 跟踪器 perf 进行跟踪:

perf probe -x /bin/bash ‘readline%return +0($retval):string’

添加了新事件:
probe_bash:readline(在 /bin/bash 中的 readline%return 中,带有 +0($retval):string)

您现在可以在所有性能工具中使用它,例如:

性能记录 -e probe_bash:readline -aR sleep 1

#性能记录 -e probe_bash:readline -a
^C[ 性能记录:唤醒 1 次写入数据 ]
[性能记录:捕获并写入 0.259 MB perf.data(2 个样本)]

#性能脚本
bash 26239 [003] 283194.152199: probe_bash:readline: (48db60 ← 41e876) arg1=“ls -l”
bash 26239 [003] 283195.016155: probe_bash:readline: (48db60 ← 41e876) arg1=“date”

perf probe --del probe_bash:readline

删除事件:probe_bash:readline
perf 命令提供更好的错误检查、更低的开销和其他功能;但使用起来比较麻烦。我写了 uprobe 来用 ftrace 测试 uprobes,并有一个更快速的工具来探索这种类型的跟踪。它还可以做一些性能无法做到的事情,但在另一篇文章中有更多内容…
更新:在我的下一篇文章中,我介绍了使用 ftrace对用户静态探针(例如,Node.js、MySQL 等中的那些)进行黑客攻击,并包含了更多 uprobe 示例。

转载翻译自: https://www.brendangregg.com