内核内存映射I/O追踪

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

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

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 激活时 ioremapped 的区域的 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 数据 未知 opcode UNKNOWN 时间戳、映射 ID、物理地址、数据、PC、PID

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

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

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

开发人员工具

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

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