3、QEMU模拟器搭建:QEMU安装、ARM平台模拟、启动验证与基础调试

好,咱们进入第三章。这一章我打算带大家亲手把QEMU环境搭起来。说实话,QEMU这东西,做嵌入式开发的兄弟应该都不陌生。但咱们这次不一样——我们要用它来模拟ARM平台,并且为后续的TEE和ATF集成铺路。

我个人习惯,在开始任何复杂系统开发之前,先把模拟环境跑通。为什么?因为真机调试太痛苦了,尤其是早期阶段,你连串口输出都看不到,出了问题都不知道是硬件没焊好还是代码写崩了。QEMU能帮你把这个问题扼杀在摇篮里。

3.1 QEMU的安装与选型

先说说QEMU的版本选择。我建议直接用最新的稳定版,别用太老的。我在项目中遇到过因为QEMU版本太旧,导致某些ARMv8特性模拟不出来的情况,折腾了两天才发现是模拟器的问题。

安装方式很简单,主流Linux发行版都能直接装:

# Ubuntu/Debian
sudo apt-get install qemu-system-arm qemu-efi-aarch64

# CentOS/RHEL
sudo yum install qemu-system-arm

# 或者从源码编译(我推荐这种方式,能拿到最新特性)
git clone https://git.qemu.org/git/qemu.git
cd qemu
./configure --target-list=aarch64-softmmu,arm-softmmu
make -j$(nproc)
sudo make install

嗯,这里要注意。如果你只是做简单的ARM Linux内核调试,用apt装就够了。但咱们后面要搞ATF和TEE,涉及到TrustZone、EL3异常等级这些高级特性,我建议还是从源码编译,确保所有功能都打开了。

我的小技巧: 编译时加上 --enable-debug 选项,这样后面调试的时候能拿到更多信息。别问我怎么知道的——有一次我为了追一个SMC调用异常,硬是加了debug重编了一遍。

3.2 ARM平台模拟:从裸机到完整系统

QEMU能模拟的ARM平台很多。咱们重点关注两个:virt 和 vexpress-a9。

virt平台是QEMU自己定义的通用虚拟平台,支持ARMv8,灵活性最高。vexpress-a9是ARM官方的开发板模拟,更接近真实硬件。我个人建议初学者先用virt,因为它配置简单,而且对ATF支持最好。

来看看怎么启动一个最基本的ARMv8系统:

# 启动一个64位ARM虚拟机,带4个CPU核心,2GB内存
qemu-system-aarch64 \
  -machine virt \
  -cpu cortex-a57 \
  -smp 4 \
  -m 2G \
  -nographic \
  -kernel ./Image \
  -append "console=ttyAMA0 root=/dev/vda" \
  -drive file=./rootfs.img,if=virtio,format=raw

这里面的参数我解释一下:

  • -machine virt:选择virt平台
  • -cpu cortex-a57:模拟Cortex-A57处理器,这是ARMv8的经典核心
  • -nographic:不用图形界面,纯串口输出。做嵌入式的人应该都懂,图形界面是奢侈品
  • -kernel:指定内核镜像

你可能会问,为什么选cortex-a57而不是更新的核心?说白了,a57是ARMv8-A的基准实现,几乎所有TEE和ATF的参考实现都是基于它验证的。用它能少踩很多坑。

3.3 启动验证:确认你的模拟器在干活

启动之后,怎么知道系统跑起来了?看串口输出。如果一切正常,你会看到类似这样的信息:

[    0.000000] Booting Linux on physical CPU 0x0000000000 [0x410fd070]
[    0.000000] Linux version 5.10.0 ...
[    0.000000] CPU: AArch64 Processor [410fd070] revision 4
...
[    1.234567] VFS: Mounted root (ext4 filesystem) on device 254:0.
[    1.567890] Freeing unused kernel memory: 1024K
[    1.789012] Run /sbin/init as init process

看到这个,说明你的QEMU环境已经能正常启动ARM Linux了。但咱们的目标不止于此——我们要验证的是,QEMU能否正确模拟ARM的异常等级和TrustZone。

怎么验证?写一个简单的裸机程序,尝试访问EL3寄存器:

// 简单的EL3检测代码
#include <stdint.h>

void main(void) {
    uint64_t current_el;
    
    // 读取当前异常等级
    __asm__ volatile("mrs %0, CurrentEL" : "=r" (current_el));
    
    // CurrentEL的bit[3:2]表示EL等级
    uint64_t el = (current_el >> 2) & 0x3;
    
    if (el == 3) {
        // 我们在EL3!这意味着QEMU支持TrustZone
        uart_send_string("Running at EL3 - TrustZone supported!\n");
    } else {
        uart_send_string("Not at EL3 - check your QEMU configuration\n");
    }
    
    while(1);
}
关键点: 如果QEMU正确配置了EL3支持,这段代码应该输出"Running at EL3"。否则,说明你的QEMU可能没有开启TrustZone模拟。我曾经在这个问题上卡了半天——后来发现是编译QEMU时忘了加 --enable-trustzone 选项。

3.4 基础调试:GDB与QEMU的联姻

做系统级开发,调试是家常便饭。QEMU内置了GDB服务器支持,这玩意儿太好用了。你想想看,在模拟器上打断点、单步执行、查看寄存器,比在真机上方便一万倍。

启动带GDB服务器的QEMU:

qemu-system-aarch64 \
  -machine virt \
  -cpu cortex-a57 \
  -smp 1 \
  -m 1G \
  -nographic \
  -kernel ./my_tee.elf \
  -s -S

这里 -s 是 -gdb tcp::1234 的简写,-S 表示启动时暂停CPU,等待GDB连接。

然后在另一个终端启动GDB:

aarch64-linux-gnu-gdb ./my_tee.elf
(gdb) target remote :1234
(gdb) b main
(gdb) c

就这么简单,你已经可以在QEMU上调试你的TEE代码了。我个人习惯在关键函数入口都打上断点,比如 bl31_main、smc_handler 这些。这样能清晰地看到控制流是怎么在EL3和EL1之间切换的。

避坑指南: 我曾经在调试ATF时,发现单步执行到SMC指令后就卡死了。排查了半天,原来是QEMU的GDB stub在处理SMC指令时有bug。解决办法是升级QEMU版本,或者用 handle SIGILL nostop noprint 忽略某些信号。嗯,这种问题在模拟器上挺常见的,别慌。

3.5 本章知识体系

说了这么多,咱们来张图总结一下QEMU搭建的核心逻辑:

QEMU模拟器搭建核心流程 QEMU安装 apt/源码编译 ARM平台选择 virt / vexpress-a9 启动验证 串口输出检查 GDB调试配置 -s -S 参数 + aarch64-linux-gnu-gdb TEE/ATF集成验证 EL3寄存器访问 / SMC调用测试 注:每一步都建议验证通过后再进行下一步

这张图把咱们这章的核心流程串起来了。从安装到验证,每一步都有明确的输入输出。我个人建议你按照这个流程走一遍,别跳步。我在带团队的时候,经常看到有人跳过验证直接开始写代码,结果后面出了问题,回头排查才发现是环境没搭对。

好了,QEMU环境搭建就讲到这里。下一章咱们会基于这个环境,开始真正动手编译和集成ARM Trusted Firmware。到时候你会看到,今天搭的这个环境有多重要。

额外福利: 如果你在搭建过程中遇到任何问题,可以试试在QEMU的邮件列表里搜一下。很多坑别人早就踩过了,直接抄答案就行。我当年就是这么过来的。

可可精品资料库