2. QEMU 日志系统初探:日志级别详解与输出重定向

调试 RISC-V 模拟环境,第一件事就是学会跟 QEMU “对话”。

QEMU 的日志系统,说白了就是它内部运行状态的“黑匣子”。你告诉它记录什么,它就记什么。我个人习惯,拿到一个新镜像,第一件事就是打开日志,看看 CPU 到底在干什么。

2.1 -d 参数:日志级别的“开关”

QEMU 的日志控制,核心就是 -d 参数。它的语法很简单:

qemu-system-riscv64 -d [日志项1],[日志项2],... -D [输出文件]

这里 -d 后面跟的是日志类别,多个类别用逗号隔开。-D 则是把日志写到文件里。如果不加 -D,日志默认会打印到终端(stderr)。

我刚开始用的时候,经常忘了加 -D,结果终端被日志刷屏,连命令都输不了。嗯,这是个坑,后面会讲怎么避。

2.2 核心日志级别一览

QEMU 支持的日志级别很多,但常用的就那么几个。我把它们整理成了表格,方便你查阅:

日志项 含义 我的使用场景
cpu CPU 执行状态(PC、指令、寄存器) 跟踪程序执行流,看它跑到了哪里
int 中断和异常 排查中断没响应、异常向量表配置错误
mmu 内存管理单元(页表、地址翻译) 调试页表配置、虚拟地址映射问题
guest_errors 客户机错误(非法指令、对齐错误等) 快速定位 Guest OS 或裸机程序的 bug
page 内存页分配与释放 检查内存泄漏或内存布局异常
trace QEMU 内部事件跟踪(需编译时开启) 性能分析、设备模型行为追踪
all 开启所有日志(慎用!) 只有万不得已才用,输出量巨大
我的小技巧: 调试初期,我通常只开 cpu,guest_errors。这两个组合能覆盖 80% 的启动问题。等定位到具体模块,再针对性加 int 或 mmu。

2.3 实战:启动一个 RISC-V 内核并查看日志

光说不练假把式。我们直接上手,用 QEMU 启动一个 RISC-V 64 位 Linux 内核,看看日志长什么样。

假设你已经有编译好的内核镜像 Image 和设备树 fw_dynamic.elf。执行:

qemu-system-riscv64 \
  -machine virt \
  -cpu rv64 \
  -kernel Image \
  -append "console=ttyS0" \
  -serial stdio \
  -d cpu,guest_errors \
  -D qemu_trace.log

这条命令做了几件事:

  • 启动 RISC-V 64 位模拟器,机器模型是 virt。
  • 加载内核镜像 Image。
  • 把串口输出重定向到当前终端(-serial stdio)。
  • 开启 cpu 和 guest_errors 日志。
  • 把日志写入 qemu_trace.log 文件。

运行后,你会看到终端里内核的启动信息。同时,当前目录下会生成一个 qemu_trace.log 文件。打开它,你会看到类似这样的内容:

CPU #0:
  pc       0x0000000080000000
  insn     0x00000097
  priv     1

Guest errors:
  - Misaligned load at 0x0000000080001000

每一行都记录了 CPU 的当前状态。PC 值、指令编码、特权级……这些信息在排查“程序跑飞”或“死循环”时,就是救命稻草。

注意: 如果日志文件太大(比如开了 all),QEMU 的性能会急剧下降。我曾经有一次忘了关日志,结果模拟器跑得比乌龟还慢,排查了半天才发现是日志在疯狂写盘。所以,调试完记得关掉不必要的日志项。

2.4 日志输出重定向:从终端到文件

除了用 -D 指定文件,QEMU 还支持其他重定向方式。我整理了一下:

方式 命令示例 说明
默认输出到 stderr qemu-system-riscv64 -d cpu 日志和错误信息混在一起,适合快速查看
重定向到文件 qemu-system-riscv64 -d cpu -D log.txt 最常用,日志单独保存,方便后续分析
Shell 重定向 qemu-system-riscv64 -d cpu 2>log.txt 把 stderr 重定向到文件,但会混入其他错误信息
同时输出到终端和文件 qemu-system-riscv64 -d cpu -D log.txt 2>&1 | tee log.txt 实时查看 + 持久化保存,调试时很方便

我个人最推荐第二种:-D 指定文件。它干净、隔离,不会跟终端输出打架。

2.5 避坑指南:日志文件太大怎么办?

这个问题我遇到过好几次。尤其是调试 MMU 或中断时,日志量会爆炸式增长。一个不小心,硬盘就满了。

我的经验是:

  • 不要开 all,除非你确定需要所有信息。
  • 用 guest_errors 代替 cpu,前者只记录错误,后者记录每条指令。
  • 配合 -singlestep 使用,让 QEMU 单步执行,这样日志量可控。
  • 写个脚本定期清理,或者用 logrotate 管理日志文件。
我曾经踩过的坑: 有一次调试 RISC-V 的 S 模式页表,开了 mmu 日志,结果 10 秒钟生成了 2GB 的日志文件。当时我以为是 QEMU 死循环了,后来才发现是日志在疯狂输出。从那以后,我每次开日志前都会先估算一下输出量。

2.6 知识体系:日志系统核心逻辑

为了让你更直观地理解 QEMU 日志系统的工作流程,我画了一张图:

QEMU 日志系统核心流程 QEMU 日志系统核心流程 -d 参数指定日志项 日志分类(cpu/int/mmu/guest_errors...) 每个分类独立开关,可组合使用 输出方式(-D 或 stderr) -D 指定文件 / 默认 stderr 日志文件 / 终端输出 注意事项 1. 不要开 all 2. 优先用 guest_errors 3. 配合 -singlestep 使用 调试建议 先开 cpu+guest_errors 定位后再加其他项 日志文件定期清理

这张图展示了从输入参数到最终输出的完整链路。你只需要记住:先选类别,再定输出。就这么简单。

2.7 小结

这一章我们聊了 QEMU 日志系统的核心:-d 参数和 -D 输出重定向。你学会了:

  • 如何用 -d 开启不同级别的日志。
  • 如何用 -D 把日志保存到文件。
  • 如何避免日志文件过大导致性能问题。