3. QEMU模拟器搭建
好,咱们进入第三章。这一章我打算带大家亲手把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异常等级这些高级特性,我建议还是从源码编译,确保所有功能都打开了。
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);
}
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之间切换的。
handle SIGILL nostop noprint 忽略某些信号。嗯,这种问题在模拟器上挺常见的,别慌。
3.5 本章知识体系
说了这么多,咱们来张图总结一下QEMU搭建的核心逻辑:
这张图把咱们这章的核心流程串起来了。从安装到验证,每一步都有明确的输入输出。我个人建议你按照这个流程走一遍,别跳步。我在带团队的时候,经常看到有人跳过验证直接开始写代码,结果后面出了问题,回头排查才发现是环境没搭对。
好了,QEMU环境搭建就讲到这里。下一章咱们会基于这个环境,开始真正动手编译和集成ARM Trusted Firmware。到时候你会看到,今天搭的这个环境有多重要。
可可精品资料库