内核内存映射 I/O 跟踪¶
主页以及可选用户空间工具的链接
MMIO 跟踪最初由英特尔于 2003 年左右为其故障注入测试框架开发。在 2006 年 12 月至 2007 年 1 月期间,Jeff Muizelaar 利用英特尔的代码,结合 Nouveau 项目的需求,创建了一个用于跟踪 MMIO 访问的工具。从那时起,许多人都做出了贡献。
Mmiotrace 旨在对任何内存映射的 IO 设备进行逆向工程,Nouveau 项目是其第一个实际用户。仅支持 x86 和 x86_64 架构。
树外的 mmiotrace 最初由 Pekka Paalanen <pq@iki.fi> 修改以并入主线和 ftrace 框架。
准备工作¶
Mmiotrace 功能通过 CONFIG_MMIOTRACE 选项编译进内核。默认情况下跟踪是禁用的,因此将其设为 yes 是安全的。支持 SMP systems,但如果有多于一个 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 是如何工作的¶
对硬件 IO 内存的访问是通过调用某个 ioremap_*() 函数从 PCI 总线映射地址来获得的。Mmiotrace 被挂钩到 __ioremap() 函数中,并且每当创建映射时都会被调用。映射是一个记录在跟踪日志中的事件。请注意,ISA 范围的映射不会被捕获,因为该映射一直存在并被直接返回。
MMIO 访问是通过页错误(page fault)来记录的。就在 __ioremap() 返回之前,映射的页面被标记为不存在(not present)。对这些页面的任何访问都会导致错误。页错误处理程序调用 mmiotrace 来处理该错误。Mmiotrace 将页面标记为存在,设置 TF 标志以实现单步执行,并退出错误处理程序。引发错误的指令被执行并进入调试陷阱(debug trap)。在这里,mmiotrace 再次将页面标记为不存在。指令被解码以获取操作类型(读/写)、数据宽度以及读取或写入的值。这些都被存储到跟踪日志中。
在页错误处理程序中将页面设置为存在,在 SMP 机器上会存在竞争条件。在单步执行期间,其他 CPU 可能会在该页面上自由运行,从而可能会在没有任何通知的情况下丢失事件。不鼓励在跟踪期间重新启用其他 CPU。
跟踪日志格式¶
原始日志是文本,可以很容易地用例如 grep 和 awk 进行过滤。一条记录是日志中的一行。一条记录以关键字开头,后面跟着依赖于关键字的参数。参数由空格分隔,或者一直持续到行尾。20070824 版本的格式如下
解释 关键字 空格分隔的参数¶
读事件 R width, timestamp, map id, physical, value, PC, PID 写事件 W width, timestamp, map id, physical, value, PC, PID ioremap 事件 MAP timestamp, map id, physical, virtual, length, PC, PID iounmap 事件 UNMAP timestamp, map id, PC, PID 标记 MARK timestamp, text 版本 VERSION 字符串 “20070824” 读者信息 LSPCI 来自 lspci -v 的一行 PCI 地址映射 PCIDEV 空格分隔的 /proc/bus/pci/devices 数据未知操作码 UNKNOWN timestamp, map id, physical, data, PC, PID
时间戳是以秒为单位带小数的。Physical 是 PCI 总线地址,virtual 是内核虚拟地址。Width 是以字节为单位的数据宽度,value 是数据值。Map 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 日志,即重新执行记录的写操作