4、QEMU快速上手:安装、基本命令行用法、启动一个虚拟目标(如ARM、RISC-V)、与GDB联调
QEMU 是当前硬件仿真与驱动开发领域使用最广泛的开源模拟器之一。它支持多种指令集架构(如 x86、ARM、RISC-V、MIPS、PowerPC 等),既可以进行纯系统级仿真(System Mode),也可以进行用户态仿真(User Mode)。在驱动开发、BSP 移植、固件调试和嵌入式系统学习场景中,QEMU 提供了无需真实硬件即可运行目标代码的能力,配合 GDB 的远程调试接口,可以极大提升开发效率。本章将围绕 QEMU 的安装、命令行参数、启动 ARM/RISC-V 虚拟目标以及与 GDB 的联调方法展开,帮助学员建立一套可复用的 QEMU 驱动模拟工作流。
上图展示了 QEMU 驱动模拟与调试的整体链路。主机环境通过 QEMU 启动虚拟目标,虚拟目标内部包含 CPU 核、内存、中断控制器和各类外设模型。调试时,GDB 通过 QEMU 开放的远程调试端口(默认 1234)连接,实现断点、单步、寄存器查看等操作。串口控制台则通过 -nographic 或 -serial 参数映射到主机终端,用于观察驱动输出和交互输入。理解这条链路是后续所有操作的基础。
4.1 QEMU 的安装与版本选择
核心原理
QEMU 的安装方式取决于主机操作系统和使用场景。对于驱动开发人员,推荐使用发行版自带的 QEMU 包或从源码编译。发行版包安装简单,但版本可能较旧;源码编译可以获取最新特性,但需要额外处理依赖。QEMU 的版本直接影响所支持的机器类型(Machine Type)和 CPU 模型,例如 ARM 的 vexpress-a9、RISC-V 的 virt 机器在不同版本中行为可能略有差异。
安装操作流程
以下是在 Ubuntu 22.04 LTS 上安装 QEMU 的典型步骤:
# 更新软件源
sudo apt update
# 安装 QEMU 全功能包(包含系统模式与用户模式)
sudo apt install qemu-system-arm qemu-system-riscv64 qemu-user
# 验证安装
qemu-system-arm --version
qemu-system-riscv64 --version
# 如果需要图形界面支持(如 GTK 显示)
sudo apt install qemu-system-gui
对于 macOS,可以使用 Homebrew:
brew install qemu
对于 Windows,建议使用 MSYS2 或 WSL2 环境,因为 QEMU 在 Linux 环境下对串口、网络和调试的支持更完善。
版本选择建议
| 场景 | 推荐版本 | 理由 |
|---|---|---|
| 嵌入式驱动开发(ARM/RISC-V) | QEMU 7.0+ | 支持更多外设模型,修复了旧版中断控制器 bug |
| 生产环境长期维护 | 发行版自带版本 | 稳定性优先,经过充分测试 |
| 需要最新 CPU 特性 | 源码编译 8.0+ | 可自定义编译选项,启用 TCG 优化 |
4.2 基本命令行用法与启动 ARM 虚拟目标
核心原理
QEMU 的命令行参数遵循 qemu-system-<arch> [options] 的格式。关键参数包括 -M(指定机器类型)、-cpu(指定 CPU 模型)、-m(内存大小)、-kernel(加载内核镜像)、-nographic(无图形界面)、-s(开启 GDB 服务)、-S(启动时暂停 CPU)。理解这些参数是启动虚拟目标的前提。
启动 ARM 虚拟目标(vexpress-a9)
以 ARM Cortex-A9 开发板为例,使用 vexpress-a9 机器类型。该机器模拟了 RealView Express 开发板,包含 PL011 UART、PL181 SD 控制器、PL041 音频等外设。启动命令如下:
# 准备内核镜像(以 Linux 内核 zImage 为例)
# 假设已经编译好 zImage 和 dtb 文件
qemu-system-arm \
-M vexpress-a9 \
-cpu cortex-a9 \
-m 512M \
-kernel zImage \
-dtb vexpress-v2p-ca9.dtb \
-nographic \
-append "console=ttyAMA0,115200 root=/dev/mmcblk0 rw" \
-sd rootfs.ext4
参数说明:
-M vexpress-a9:指定机器类型为 vexpress-a9。-cpu cortex-a9:指定 CPU 为 Cortex-A9。-m 512M:分配 512MB 内存。-kernel zImage:加载内核镜像。-dtb:加载设备树文件。-nographic:将串口重定向到当前终端,不启动图形窗口。-append:传递内核启动参数。-sd rootfs.ext4:挂载 SD 卡镜像作为根文件系统。
启动 RISC-V 虚拟目标(virt)
RISC-V 的 virt 机器是 QEMU 提供的通用虚拟平台,支持 64 位 RISC-V 处理器。启动命令如下:
qemu-system-riscv64 \
-M virt \
-cpu rv64 \
-m 256M \
-kernel Image \
-nographic \
-append "console=ttyS0,115200 root=/dev/vda rw" \
-drive file=rootfs.img,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0
这里使用了 virtio-blk 设备作为磁盘,这是 RISC-V virt 机器常用的存储方案。
常见误区
-nographic 模式下,QEMU 的 monitor 控制台默认绑定到串口 0,如果内核串口参数配置错误,将看不到任何输出。建议先使用 -serial mon:stdio 分离 monitor 和串口输出,便于排查。4.3 与 GDB 联调:断点、单步与寄存器查看
核心原理
QEMU 内置了 GDB 远程调试服务器。通过 -s 参数在端口 1234 上监听 GDB 连接,-S 参数让 CPU 在启动时暂停,等待调试器连接。GDB 通过 target remote :1234 连接后,可以像调试本地程序一样设置断点、单步执行、查看寄存器和内存。对于驱动开发,这允许我们在驱动初始化代码处设置断点,逐步跟踪硬件寄存器操作。
联调操作流程
以下是一个完整的 GDB 联调示例,以 ARM vexpress-a9 为例:
# 终端 1:启动 QEMU 并等待 GDB
qemu-system-arm \
-M vexpress-a9 \
-cpu cortex-a9 \
-m 512M \
-kernel zImage \
-dtb vexpress-v2p-ca9.dtb \
-nographic \
-s -S \
-append "console=ttyAMA0,115200"
# 终端 2:启动 GDB 并连接
arm-none-eabi-gdb vmlinux # 使用带符号的内核镜像
(gdb) target remote :1234
(gdb) break start_kernel # 在内核入口设置断点
(gdb) continue
(gdb) info registers # 查看寄存器
(gdb) x/10x 0x10000000 # 查看内存内容
(gdb) stepi # 单步执行一条指令
案例拆解:调试一个 UART 驱动初始化
假设我们需要调试 PL011 UART 驱动的初始化过程。PL011 的基地址在 vexpress-a9 上为 0x10009000。我们可以在驱动初始化函数中设置断点,观察寄存器写入顺序。
| 步骤 | GDB 命令 | 预期结果 |
|---|---|---|
| 1. 设置断点 | break pl011_init | 断点命中在 pl011_init 函数入口 |
| 2. 查看当前 PC | info registers pc | PC 指向 pl011_init 首条指令 |
| 3. 单步执行 | stepi | 逐条执行汇编指令 |
| 4. 查看 UART 寄存器 | x/4x 0x10009000 | 观察 UARTDR、UARTFR 等寄存器值 |
| 5. 检查中断状态 | p/x *(unsigned int*)0x10009018 | 查看 UARTIMSC 中断屏蔽寄存器 |
通过这种方式,我们可以精确判断驱动是否按照预期顺序配置了波特率、数据位、停止位等参数。如果发现寄存器值异常,可以回溯到代码逻辑,检查是否存在位操作错误或地址偏移错误。
常见误区与总结
- 误区一:忘记使用
-S参数,导致 CPU 在 GDB 连接前就开始执行,错过早期初始化代码。 - 误区二:使用
-s时端口被占用,可以改用-gdb tcp::1235指定其他端口。 - 误区三:GDB 连接后无法设置硬件断点。QEMU 的 TCG 模式支持软件断点,但硬件断点数量有限(通常 4 个),如果断点过多,可以改用
hbreak命令。 - 误区四:在
-nographic模式下,GDB 的continue后串口输出会与 GDB 输出混在一起。建议使用set pagination off关闭分页,并使用set logging file保存调试日志。
总结本章内容,QEMU 的快速上手需要掌握三个核心能力:一是正确安装并选择合适版本;二是熟练使用命令行参数启动 ARM/RISC-V 虚拟目标;三是通过 GDB 远程调试实现驱动代码的断点跟踪和寄存器分析。这三项能力构成了驱动模拟开发的基础工作流。在实际项目中,建议将 QEMU 启动命令封装为脚本,将 GDB 调试命令写入初始化文件(如 .gdbinit),以提高开发效率。同时,要养成查看 QEMU 日志(-d 参数)的习惯,例如 -d int,cpu_reset 可以查看中断和 CPU 复位信息,这对于排查驱动中的中断处理问题非常有帮助。