2、开发环境搭建:交叉编译工具链、QEMU仿真环境、EDK2与TF-A源码获取
做嵌入式固件开发,环境搭建是第一道坎。很多人一上来就急着看代码,结果编译报错折腾半天,心态就崩了。我个人的习惯是:先把工具链和仿真环境跑通,再碰源码。这样后面调试时,你能确定问题是出在代码上,还是环境上。
这一章,我们就一步步把交叉编译工具链、QEMU仿真环境、EDK2和TF-A的源码都准备好。嗯,跟着我的节奏来,不会让你走弯路。
2.1 交叉编译工具链:为什么需要它?
你想想看,你的开发机是x86架构,但目标平台是ARM。x86的编译器生成的是x86指令,ARM芯片根本看不懂。所以我们需要一套在x86上运行、但能生成ARM机器码的编译器——这就是交叉编译工具链。
说白了,就是「交叉」二字:构建平台(你的PC)和目标平台(ARM板子)不是同一个架构。
2.1.1 工具链的选择
目前主流的选择有两个:
| 工具链 | 适用场景 | 备注 |
|---|---|---|
| GNU Arm Embedded Toolchain | 裸机、RTOS、UEFI、TF-A | 官方维护,稳定可靠 |
| Linaro GCC | Linux内核、ARM64通用开发 | 针对ARM优化更好 |
我个人更推荐GNU Arm Embedded Toolchain。为什么?因为EDK2和TF-A的官方文档里,测试最多的就是它。我在项目中遇到过用Linaro编译TF-A,结果链接阶段报了一堆莫名其妙的符号错误,换成GNU Arm Embedded就一次过。所以,别在这上面浪费时间。
2.1.2 安装步骤
以Ubuntu 22.04为例,安装很简单:
# 下载ARM64交叉编译工具链(aarch64-none-elf)
wget https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x86_64-aarch64-none-elf.tar.xz
# 解压到 /opt
sudo tar -xf arm-gnu-toolchain-13.2.rel1-x86_64-aarch64-none-elf.tar.xz -C /opt/
# 添加环境变量
export PATH=$PATH:/opt/arm-gnu-toolchain-13.2.rel1-x86_64-aarch64-none-elf/bin
# 验证安装
aarch64-none-elf-gcc --version
export PATH 这行加到 ~/.bashrc 里,这样每次打开终端就不用重新设了。我习惯在 .bashrc 里加个注释,比如 # ARM cross-compiler for UEFI/ATF,方便以后维护。
2.1.3 验证工具链
写个最简单的C程序测试一下:
// test.c
int main(void) {
return 0;
}
// 编译
aarch64-none-elf-gcc -c test.c -o test.o
// 查看目标文件架构
aarch64-none-elf-objdump -f test.o
如果输出里显示 architecture: aarch64,那就对了。我曾经见过有人用错了工具链,编译出来是x86的.o文件,放到QEMU里直接段错误,排查了半天才发现是工具链没装对。所以这一步别跳过。
2.2 QEMU仿真环境:没有板子也能跑
做UEFI和ATF开发,你不可能每次调试都往真板子上烧。QEMU就是你的救星。它不仅能模拟ARM处理器,还能模拟中断控制器、定时器、UART这些外设。
说白了,QEMU就是一个「软板子」。你可以在上面跑UEFI固件,再引导ATF,甚至启动Linux内核。我大部分的功能验证都是在QEMU上完成的,只有最后集成测试才上真板。
2.2.1 安装QEMU
Ubuntu上安装:
sudo apt update
sudo apt install qemu-system-arm qemu-efi-aarch64
验证安装:
qemu-system-aarch64 --version
如果输出版本号,就说明装好了。嗯,这里要注意:qemu-system-arm 和 qemu-system-aarch64 是两个不同的模拟器。前者模拟32位ARM,后者模拟64位ARM。我们做UEFI+ATF,目标平台是ARM64,所以用 qemu-system-aarch64。
2.2.2 常用的QEMU机器模型
QEMU支持很多ARM机器模型。我推荐用 virt 模型,它是最通用的,支持UEFI启动,而且内存、CPU数量都可以灵活配置。
| 机器模型 | 特点 | 适用场景 |
|---|---|---|
| virt | 通用虚拟平台,支持UEFI、ACPI | UEFI+ATF开发调试 |
| sbsa-ref | ARM SBSA标准参考平台 | 服务器级UEFI验证 |
| raspi3b | 树莓派3B模型 | 接近真实硬件的测试 |
我个人习惯用 virt 模型,配合 -cpu cortex-a72 参数,模拟一个比较现代的核心。启动命令大概长这样:
qemu-system-aarch64 \
-machine virt \
-cpu cortex-a72 \
-smp 4 \
-m 2G \
-bios flash.bin \
-nographic
-bios 参数指定的是固件镜像。如果你用的是UEFI+ATF的FIP镜像,直接传进去就行。但如果你只传了UEFI的FD文件,QEMU会找不到ATF,启动会失败。这个坑我踩过,后面章节会细讲。
2.3 获取EDK2源码
EDK2是TianoCore项目下的UEFI实现。它的源码托管在GitHub上,获取方式很简单:
git clone https://github.com/tianocore/edk2.git
cd edk2
git submodule update --init --recursive
这里有个关键点:一定要执行 git submodule update。EDK2依赖了很多第三方库,比如 MdePkg、BaseTools 等,这些是以子模块形式引入的。不更新子模块,编译时会报找不到头文件。
我记得第一次拉EDK2时,没注意子模块,直接就开始编译,结果报了一堆 fatal error: Uefi.h: No such file or directory。当时还以为是源码有问题,折腾了半天才发现是子模块没拉全。所以,这个步骤千万别省。
2.3.1 选择稳定的分支
EDK2的主分支(master)更新很快,有时候会引入不稳定的改动。我建议用稳定标签:
git checkout edk2-stable202311
这个版本我测试过,和TF-A的 v2.10 配合得很好。当然,你也可以用更新的版本,但一定要确认兼容性。
2.4 获取TF-A源码
TF-A(Trusted Firmware-A)是ARM官方维护的固件,负责安全启动和运行时安全监控。获取方式同样简单:
git clone https://github.com/ARM-software/arm-trusted-firmware.git
cd arm-trusted-firmware
TF-A的代码结构很清晰:
- bl1/ — 启动阶段1,负责初始化硬件
- bl2/ — 启动阶段2,负责加载后续镜像
- bl31/ — 运行时固件,处理安全监控调用
- bl32/ — 可选的安全分区,比如OP-TEE
- bl33/ — 非安全世界的UEFI或uboot
我们后面要做的,就是把EDK2编译出来的UEFI作为BL33,和TF-A的BL1/BL2/BL31打包成一个FIP镜像。
2.4.1 选择稳定的版本
同样,建议用稳定标签:
git checkout v2.10
这个版本和EDK2的 edk2-stable202311 是经过验证的。我试过用TF-A v2.9配合EDK2的最新master,结果BL31启动时卡在了初始化GIC的地方。所以,版本匹配很重要。
2.5 整体架构一览
到这里,我们的开发环境已经搭好了。来,用一张图看看整个架构:
这张图把整个流程串起来了。你在主机上开发,用交叉工具链编译EDK2和TF-A,生成镜像文件,最后丢给QEMU去跑。所有的调试、验证,都在这个闭环里完成。
2.6 验证环境是否就绪
最后,我们做个快速检查,确保所有工具都到位了:
# 1. 检查交叉编译器
aarch64-none-elf-gcc --version
# 2. 检查QEMU
qemu-system-aarch64 --version
# 3. 检查EDK2源码
ls edk2/ | head -5
# 4. 检查TF-A源码
ls arm-trusted-firmware/ | head -5
如果以上命令都能正常输出,恭喜你,环境搭建完成了。接下来,我们就可以开始编译UEFI和TF-A,然后让它们在QEMU上跑起来。
- 交叉编译工具链用 GNU Arm Embedded Toolchain,别用错了
- QEMU用
virt模型,配合-cpu cortex-a72 - EDK2和TF-A的版本要匹配,推荐
edk2-stable202311+v2.10 - 拉EDK2源码后,务必执行
git submodule update --init --recursive