内核内存映射 I/O 跟踪

主页和可选用户空间工具的链接

MMIO 跟踪最初由 Intel 在 2003 年左右为他们的故障注入测试工具开发。在 2006 年 12 月 - 2007 年 1 月,Jeff Muizelaar 使用 Intel 的代码,创建了一个用于跟踪 MMIO 访问的工具,考虑到 Nouveau 项目。从那时起,许多人为此做出了贡献。

Mmiotrace 是为反向工程任何内存映射的 IO 设备而构建的,Nouveau 项目是第一个真正的用户。仅支持 x86 和 x86_64 架构。

树外 mmiotrace 最初由 Pekka Paalanen <pq@iki.fi> 修改,以便包含在主线中并使用 ftrace 框架。

准备工作

Mmiotrace 功能通过 CONFIG_MMIOTRACE 选项编译进来。默认情况下禁用跟踪,因此将其设置为 yes 是安全的。支持 SMP 系统,但如果多个 CPU 在线,则跟踪不可靠,可能会丢失事件,因此 mmiotrace 在运行时激活期间会使除一个 CPU 之外的所有 CPU 脱机。您可以手动重新启用 CPU,但请注意,没有办法自动检测您是否由于 CPU 竞争而丢失事件。

用法快速参考

$ mount -t debugfs debugfs /sys/kernel/debug
$ echo mmiotrace > /sys/kernel/tracing/current_tracer
$ cat /sys/kernel/tracing/trace_pipe > mydump.txt &
Start X or whatever.
$ echo "X is up" > /sys/kernel/tracing/trace_marker
$ echo nop > /sys/kernel/tracing/current_tracer
Check for lost events.

用法

确保 debugfs 已挂载到 /sys/kernel/debug。如果没有(需要 root 权限)

$ mount -t debugfs debugfs /sys/kernel/debug

检查您要跟踪的驱动程序是否已加载。

激活 mmiotrace(需要 root 权限)

$ echo mmiotrace > /sys/kernel/tracing/current_tracer

开始存储跟踪

$ cat /sys/kernel/tracing/trace_pipe > mydump.txt &

“cat”进程应在后台保持运行(睡眠)。

加载要跟踪的驱动程序并使用它。Mmiotrace 仅捕获在 mmiotrace 处于活动状态时重新映射的区域的 MMIO 访问。

在跟踪期间,您可以通过 $ echo “X is up” > /sys/kernel/tracing/trace_marker 在跟踪中放置注释(标记)。这使得更容易查看(巨大的)跟踪的哪个部分对应于哪个操作。建议放置关于您所做操作的描述性标记。

关闭 mmiotrace(需要 root 权限)

$ echo nop > /sys/kernel/tracing/current_tracer

“cat”进程退出。如果它没有退出,请通过发出“fg”命令并按 ctrl+c 来终止它。

检查 mmiotrace 是否由于缓冲区已满而丢失事件。 要么

$ grep -i lost mydump.txt

这会告诉您确切丢失了多少事件,或者使用

$ dmesg

查看您的内核日志并查找“mmiotrace has lost events”警告。如果事件丢失,则跟踪不完整。您应该扩大缓冲区并重试。首先查看当前缓冲区有多大来扩大缓冲区

$ cat /sys/kernel/tracing/buffer_size_kb

会给您一个数字。大约将此数字加倍并写回,例如

$ echo 128000 > /sys/kernel/tracing/buffer_size_kb

然后从头开始。

如果您正在为驱动程序项目(例如 Nouveau)进行跟踪,您还应该在发送结果之前执行以下操作

$ lspci -vvv > lspci.txt
$ dmesg > dmesg.txt
$ tar zcf pciid-nick-mmiotrace.tar.gz mydump.txt lspci.txt dmesg.txt

然后发送 .tar.gz 文件。跟踪会大大压缩。将“pciid”和“nick”替换为您正在调查的硬件的 PCI ID 或型号名称以及您的昵称。

Mmiotrace 的工作原理

通过调用 ioremap_*() 函数之一来映射来自 PCI 总线的地址,从而获得对硬件 IO 内存的访问。Mmiotrace 被挂钩到 __ioremap() 函数中,并且在每次创建映射时都会被调用。映射是被记录到跟踪日志中的一个事件。请注意,ISA 范围映射不会被捕获,因为映射始终存在并直接返回。

MMIO 访问通过页面错误记录。就在 __ioremap() 返回之前,映射的页面被标记为不存在。对页面的任何访问都会导致错误。页面错误处理程序调用 mmiotrace 来处理错误。Mmiotrace 将页面标记为存在,设置 TF 标志以实现单步执行,并退出错误处理程序。导致错误的指令被执行,并进入调试陷阱。在这里,mmiotrace 再次将页面标记为不存在。对指令进行解码以获取操作类型(读取/写入)、数据宽度以及读取或写入的值。这些存储到跟踪日志中。

在页面错误处理程序中设置页面存在在 SMP 机器上存在竞争条件。在单步执行期间,其他 CPU 可能会在该页面上自由运行,并且事件可能会在没有通知的情况下丢失。不鼓励在跟踪期间重新启用其他 CPU。

跟踪日志格式

原始日志是文本,可以使用例如 grep 和 awk 轻松过滤。一条记录是日志中的一行。一条记录以关键字开头,后跟依赖于关键字的参数。参数之间用空格分隔,或继续到行尾。20070824 版本的格式如下

解释 关键字 空格分隔的参数

读取事件 R 宽度、时间戳、映射 ID、物理地址、值、PC、PID 写入事件 W 宽度、时间戳、映射 ID、物理地址、值、PC、PID ioremap 事件 MAP 时间戳、映射 ID、物理地址、虚拟地址、长度、PC、PID iounmap 事件 UNMAP 时间戳、映射 ID、PC、PID 标记 MARK 时间戳、文本 版本 VERSION 字符串 “20070824” 读取器信息 LSPCI 来自 lspci -v 的一行 PCI 地址映射 PCIDEV 空格分隔的 /proc/bus/pci/devices 数据 未知。操作码 UNKNOWN 时间戳、映射 ID、物理地址、数据、PC、PID

时间戳以秒为单位,带小数。物理地址是 PCI 总线地址,虚拟地址是内核虚拟地址。宽度是数据宽度(以字节为单位),值是数据值。映射 ID 是一个任意 ID 号,用于标识在操作中使用的映射。PC 是程序计数器,PID 是进程 ID。如果未记录,则 PC 为零。由于尚不支持跟踪用户空间内存中发起的 MMIO 访问,因此 PID 始终为零。

例如,以下 awk 过滤器将传递所有目标物理地址范围在 [0xfb73ce40, 0xfb800000] 的 32 位写入

$ awk '/W 4 / { adr=strtonum($5); if (adr >= 0xfb73ce40 &&
adr < 0xfb800000) print; }'

开发者工具

用户空间工具包括用于以下功能的实用程序
  • 使用硬件寄存器名称替换数字地址和值

  • 重放 MMIO 日志,即重新执行记录的写入