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 整体架构一览

到这里,我们的开发环境已经搭好了。来,用一张图看看整个架构:

UEFI与ATF协同开发环境架构图 开发环境架构总览 主机开发环境(x86_64 Ubuntu 22.04) 交叉编译工具链:aarch64-none-elf-gcc 13.2 EDK2 源码 edk2-stable202311 TF-A 源码 v2.10 编译产物 UEFI FD 文件 + TF-A BL1/BL2/BL31 镜像 QEMU 仿真环境 qemu-system-aarch64 -machine virt -cpu cortex-a72 说明:EDK2 编译生成 UEFI 固件(BL33),TF-A 编译生成 BL1/BL2/BL31 两者通过 FIP 工具打包后,由 QEMU 加载运行

这张图把整个流程串起来了。你在主机上开发,用交叉工具链编译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