4. ATF源码编译

好,咱们进入实战环节。上一章我们把ATF的架构和启动流程捋了一遍,这一章就该动手了——把源码拉下来,编译出能跑的镜像。

说实话,ATF的编译比我想象中要简单。我第一次接触时,以为要配一堆交叉编译链、设置各种环境变量,结果发现它自带的构建系统做得挺完善。你只要把几个关键参数搞明白,基本就能一次通过。

4.1 源码获取

ATF的源码托管在GitHub上,官方仓库是 ARM-software/arm-trusted-firmware。我个人习惯用 git clone 拉取,然后切到稳定的release版本。

git clone https://github.com/ARM-software/arm-trusted-firmware.git
cd arm-trusted-firmware
git checkout v2.9

为什么推荐切到release版本?因为master分支可能包含一些实验性改动,不一定稳定。我在项目里吃过这个亏——用master编译出来的BL31在某个平台上死活跑不起来,后来发现是某个commit引入了bug。从那以后,我坚持用tag版本。

目前主流平台建议用v2.8或v2.9,这两个版本对ARMv8.2和v8.4的支持比较成熟。如果你用的是最新的ARMv9平台,那可能需要v2.10以上的版本。

4.2 编译选项解析

ATF的编译选项,说白了就是通过 make 命令传递的参数。你不需要全部记住,但有几个核心选项必须搞明白。

选项 说明 我的建议
PLAT 目标平台,比如 qemu、fvp、rk3399 刚开始用 qemu 练手最方便
ARCH 架构,aarch64 或 aarch32 现在主流都是64位,选 aarch64
DEBUG 是否开启调试模式,0 或 1 开发阶段开 1,发布时关掉
LOG_LEVEL 日志级别,0~50,越高越详细 调试时设 40,正常用 20
BL33 指定非安全侧镜像路径(通常是U-Boot) 如果只测ATF,可以不设
TRUSTED_BOARD_BOOT 是否启用可信启动 量产必须开,开发阶段可以先关

嗯,这里要注意 LOG_LEVEL 这个选项。我见过不少新手把日志级别开到50,结果终端刷屏刷到根本看不清关键信息。其实 LOG_LEVEL=40 已经能看到绝大部分调试信息了。

4.3 编译流程

ATF的编译流程,我画了一张图帮你理解:

make 命令解析 读取 PLAT、ARCH 等选项 Makefile 展开 加载平台配置、工具链 源码编译 编译 BL1/BL2/BL31 链接与打包 生成 fip.bin、bl31.bin 等 镜像签名(可选) 开启 TBB 时执行 输出产物 build/<plat>/release/ 关键文件 bl31.bin fip.bin

实际编译命令长这样:

make CROSS_COMPILE=aarch64-linux-gnu- \
     PLAT=qemu \
     ARCH=aarch64 \
     DEBUG=1 \
     LOG_LEVEL=40 \
     all

这里 CROSS_COMPILE 指定交叉编译工具链的前缀。如果你用的是ARM官方提供的 gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu,那前缀就是 aarch64-none-linux-gnu-。不同工具链前缀可能不一样,这个要留意。

我的小技巧: 把编译命令写成一个shell脚本,比如 build_atf.sh,这样每次编译不用重新敲参数。我项目里至少改过几十次编译选项,脚本化之后省了不少时间。

4.4 编译产物分析

编译完成后,产物默认放在 build/<PLAT>/<BUILD_TYPE>/ 目录下。比如 build/qemu/debug/。我们来看看里面都有什么:

$ ls -la build/qemu/debug/
total 1024
-rw-r--r--  bl1.bin        # BL1 镜像,约 32KB
-rw-r--r--  bl2.bin        # BL2 镜像,约 64KB
-rw-r--r--  bl31.bin       # BL31 镜像,约 128KB
-rw-r--r--  fip.bin        # FIP 打包镜像,包含 BL2 + BL31
-rw-r--r--  bl1.elf        # BL1 的 ELF 文件,带调试符号
-rw-r--r--  bl2.elf
-rw-r--r--  bl31.elf
-rw-r--r--  bl31.map       # 内存映射文件,分析布局用

这几个文件各自有什么用?我简单说一下:

  • bl1.bin:ROM里加载的第一个镜像,负责初始化最小系统,然后加载BL2。在QEMU平台上,这个文件通常不用我们自己烧录,QEMU内部已经模拟了ROM行为。
  • bl2.bin:可信启动的第二阶段,负责加载BL31和BL33。如果你开启了可信启动,它还会做镜像校验。
  • bl31.bin:这是最核心的——EL3运行时固件,提供SMC调用、PSCI电源管理等服务。我们后面做TEE集成,主要就是跟它打交道。
  • fip.bin:Firmware Image Package,把BL2、BL31、BL33(如果有)打包成一个文件。QEMU启动时直接加载fip.bin就行。
重点: 如果你只关心TEE集成,那 bl31.bin 是你最需要关注的文件。TEE的SMC入口、安全中断处理、上下文切换都在这里面。

另外,bl31.map 这个文件很多人忽略,但我建议你养成看的习惯。它记录了每个函数、每个变量的内存地址。我在调试一次安全中断处理时,发现某个函数被链接到了意想不到的位置,导致跳转出错。打开map文件一看,原来是链接脚本里某个section的地址对齐没设对。

4.5 常见编译问题与避坑

编译过程中可能会遇到一些问题,我把自己踩过的坑列出来:

问题1:找不到交叉编译工具链
错误信息类似 aarch64-linux-gnu-gcc: command not found。
解决办法:确保工具链已安装,并且 CROSS_COMPILE 路径正确。我习惯把工具链路径加到 PATH 环境变量里。
问题2:平台配置缺失
比如 PLAT=qemu 但没装QEMU相关的依赖。
解决办法:ATF对QEMU平台依赖很少,基本只需要标准C库。如果是FVP平台,需要安装FVP模型。
问题3:DEBUG=1 导致镜像过大
开启调试模式后,bl31.bin 可能从128KB膨胀到500KB以上。
解决办法:调试阶段无所谓,发布时记得关掉 DEBUG=1。

我曾经在一个项目里,因为忘记关 DEBUG=1,导致BL31镜像超过了平台ROM的限制,启动时直接卡死。查了半天才发现是镜像太大,装不下了。嗯,从那以后我每次发布前都会检查一下镜像大小。

4.6 验证编译结果

编译完了怎么验证能不能跑?最简单的方法是用QEMU:

qemu-system-aarch64 -machine virt \
  -cpu cortex-a57 \
  -bios bl1.bin \
  -semihosting \
  -nographic

如果一切正常,你会看到ATF的启动日志,类似这样:

NOTICE:  BL1: v2.9(release):v2.9
NOTICE:  BL1: Built : 14:48:00, Mar 15 2024
NOTICE:  BL2: v2.9(release):v2.9
NOTICE:  BL2: Built : 14:48:01, Mar 15 2024
NOTICE:  BL31: v2.9(release):v2.9
NOTICE:  BL31: Built : 14:48:02, Mar 15 2024

看到这三行 NOTICE,说明你的ATF已经成功跑起来了。接下来就可以开始集成TEE了。

好,这一章的内容就到这里。ATF的编译其实不复杂,关键是理解每个选项的作用,以及知道编译产物里哪个文件是你真正需要的。下一章我们会把这些镜像烧录到目标平台上,看看实际运行效果。

补充提示: 如果你在编译过程中遇到任何奇怪的问题,先试试 make clean 再重新编译。有时候缓存会导致一些莫名其妙的问题,clean一下就好了。