ARM Trusted Firmware (ATF) 入门:安全启动与可信执行环境实战指南

发布时间:2026/5/21 1:59:08

ARM Trusted Firmware (ATF) 入门:安全启动与可信执行环境实战指南 1. 项目概述从零开始理解ARM安全世界的基石如果你正在接触基于ARM架构的嵌入式系统尤其是那些涉及安全启动、可信执行环境TEE或者系统安全加固的项目那么“ARM Trusted Firmware”简称ATF这个名字你一定绕不过去。我第一次接触ATF时面对其庞大的代码仓库和复杂的初始化流程也是一头雾水。它不像我们熟悉的U-Boot那样有大量现成的板级支持包BSP也不像简单的裸机程序那样直观。ATF更像是一个隐藏在系统深处的“安全管家”在操作系统和应用程序启动之前就默默地构建起了一道坚固的安全防线。简单来说ARM Trusted Firmware是ARM公司为基于ARMv8-A及后续架构包括部分ARMv7-A的处理器提供的一套开源参考实现用于实现系统的安全启动和运行时安全服务。它定义了从芯片上电复位到将控制权交给非安全世界如普通操作系统的整个可信启动链。这个项目标题“ARM ATF入门-安全固件软件介绍和代码运行”精准地指向了学习ATF的两个核心阶段首先是理解它是什么、为什么需要它其次是能够动手搭建环境让代码真正跑起来看到效果。这对于任何想深入ARM平台底层安全或者从事相关固件开发的工程师来说都是必须跨越的第一道门槛。2. ATF的核心定位与安全启动模型解析2.1 为什么需要ATF——ARM安全扩展的必然产物在传统的嵌入式系统或早期的ARM系统中启动流程相对简单Boot ROM - Bootloader如U-Boot- 操作系统。然而随着移动支付、数字版权保护、企业级安全等需求的爆炸式增长这种简单的启动链变得不堪一击。恶意代码可能在启动早期就被植入从而掌控整个系统。ARM公司从ARMv7-A架构开始引入了TrustZone技术这是一种硬件级别的安全方案它将处理器的工作状态划分为两个“世界”安全世界Secure World和非安全世界Normal World。你可以把它想象成一栋大楼里的“金库区”和“公共办公区”。操作系统和应用大部分时间运行在“公共办公区”非安全世界而涉及密钥、指纹、支付等敏感操作则在硬件隔离的“金库区”安全世界进行。但是光有硬件隔离金库还不够你必须确保从大楼通电的那一刻起通往金库的道路就是绝对安全、未被篡改的。这就是安全启动Secure Boot要解决的问题。ATF正是这套安全启动流程的软件实现框架。它定义了一个分阶段的启动模型通常被称为BL1, BL2, BL31, BL32, BL33。2.2 ATF启动阶段深度拆解理解这几个阶段是读懂ATF代码的关键。它们像一场精密的接力赛每一棒都有明确的职责和交接条件。BL1 - 信任根Root of Trust这是安全启动链的绝对起点通常是芯片内部ROM中的代码不可修改。它的核心职责只有两个验证BL2镜像的完整性和真实性通过数字签名然后跳转到BL2。BL1本身是硬件信任的延伸。BL2 - 可信引导加载程序Trusted Boot FirmwareBL2通常加载到芯片内部的SRAM中运行。它的任务更重一些初始化必要的基础硬件如更复杂的时钟、关键外设控制器。从外部存储如eMMC、SD卡加载后续阶段的镜像包括BL31、BL32可选、BL33通常是U-Boot。验证这些镜像的签名。将控制权交给BL31。BL31 - 运行时固件Runtime Firmware这是ATF的核心是一个常驻内存的“安全监视器”。它运行在EL3ARMv8-A的最高特权异常等级。它的核心功能是管理“世界切换”。当非安全世界的操作系统BL33需要访问安全世界的服务BL32时会产生一个“安全监控调用SMC”此时CPU会陷入EL3由BL31来处理这个请求并安全地切换到BL32。你可以把BL31看作是大楼里那个掌管“金库区”和“办公区”唯一通道的、绝对中立的保安。BL32 - 安全世界操作系统可选这就是运行在安全世界的软件比如OP-TEE一个开源的TEE实现。它提供具体的安全服务如加密、安全存储、指纹验证等。BL31负责在它和BL33之间安全地传递请求和结果。BL33 - 非安全世界软件通常就是我们所熟悉的引导加载程序如U-Boot。它运行在非安全世界负责最终加载Linux内核等操作系统。从ATF的角度看BL33已经是“不可信”的普通软件了。注意在实际项目中BL1和BL2有时会被芯片厂商的Boot ROM或第一级Bootloader替代。ATF代码通常从作为BL31开始编译和集成。但理解完整的链条对于调试和解决启动问题至关重要。3. 开发环境搭建与代码获取3.1 工具链的选择与配置要让ATF跑起来第一步是准备交叉编译工具链。ATF主要使用ARM架构的aarch64-none-elf-或aarch64-linux-gnu-工具链。我强烈建议使用Linaro官方发布或ARM官方提供的GCC工具链兼容性最好。以Ubuntu系统为例安装aarch64-linux-gnu工具链sudo apt update sudo apt install gcc-aarch64-linux-gnu安装后通过aarch64-linux-gnu-gcc -v验证是否安装成功。如果你需要编译用于模拟器如QEMU的、不带操作系统依赖的纯固件可能需要aarch64-none-elf-工具链。可以从ARM开发者网站或xPack项目页面下载预编译版本并添加到PATH环境变量中。3.2 获取ATF源代码与依赖ATF的源代码托管在GitHub上。使用Git克隆主分支git clone https://github.com/ARM-software/arm-trusted-firmware.git cd arm-trusted-firmwareATF本身依赖不多但为了构建一个完整的可启动系统你通常还需要其他组件U-Boot作为BL33。Linux Kernel最终要启动的系统。可选OP-TEE如果你想运行完整的TEE示例。一个高效的作法是为你的实验项目创建一个独立的工作目录用Git分别管理这些仓库。3.3 构建系统的理解Makefile与平台定义ATF使用Makefile作为构建系统。其核心编译命令格式如下make CROSS_COMPILEtoolchain_prefix PLATplatform targetCROSS_COMPILE: 指定交叉编译工具链前缀如aarch64-linux-gnu-。PLAT: 指定目标平台。这是关键参数。ATF支持众多平台如qemu用于QEMU虚拟化平台是学习和调试的最佳起点。fvp用于ARM Fixed Virtual Platform功能强大的官方仿真模型。rpi3、rpi4树莓派3/4是常见的实体硬件实验平台。sun50i_a64全志A64芯片平台。target构建目标最常用的是all编译所有和bl31仅编译BL31。平台相关的代码位于plat/目录下。每个平台子目录如plat/qemu/里都包含该平台特定的初始化代码、内存布局定义platform.mk和链接脚本。当你指定PLATqemu时构建系统就会去链接这个目录下的文件。4. 在QEMU上运行ATF首个“Hello World”4.1 编译用于QEMU的ATF镜像QEMU是一个功能强大的开源模拟器它完美模拟了一个ARMv8-A的“虚拟开发板”并且ATF官方对其支持非常完善。这是我们进行代码阅读、单步调试和功能验证的理想沙盒。首先确保你已安装QEMU的系统模拟组件sudo apt install qemu-system-arm进入ATF源码目录为QEMU平台编译一个完整的固件镜像包含BL1、BL2、BL31等make CROSS_COMPILEaarch64-linux-gnu- PLATqemu all编译成功后你会在build/qemu/release/目录下找到关键输出文件最重要的是bl1.bin和fip.bin。bl1.bin: 对应启动阶段的BL1镜像。fip.bin: 这是一个“Firmware Image Package”它由fiptool工具创建内部打包了BL2、BL31、BL33U-Boot等镜像。这是ATF启动后期加载的核心文件。4.2 整合U-Boot作为BL33ATF需要跳转到一个非安全世界的软件BL33。我们选择U-Boot作为这个BL33。获取并编译U-Boot:git clone https://github.com/u-boot/u-boot.git cd u-boot # 为QEMU的ARMv8Cortex-A57目标配置并编译 make qemu_arm64_defconfig make CROSS_COMPILEaarch64-linux-gnu- -j$(nproc)编译后得到u-boot.bin。创建FIP包 回到ATF目录使用其自带的tools/fiptool/fiptool来创建或更新FIP包将U-Boot集成进去。# 假设u-boot.bin在../u-boot目录下 tools/fiptool/fiptool create \ --tb-fw build/qemu/release/bl2.bin \ --soc-fw build/qemu/release/bl31.bin \ --nt-fw ../u-boot/u-boot.bin \ build/qemu/release/fip.bin这个命令创建了一个新的fip.bin其中包含了BL2 (bl2.bin)、BL31 (bl31.bin) 和非可信固件BL33 (u-boot.bin)。4.3 使用QEMU启动并观察日志现在我们可以用QEMU命令来启动这个完整的软件栈qemu-system-aarch64 \ -machine virt,virtualizationon,gic-version3 \ -cpu cortex-a57 \ -nographic \ -smp 1 \ -m 1024 \ -kernel ./build/qemu/release/bl1.bin \ -device loader,file./build/qemu/release/fip.bin,addr0x4000000 \ -d in_asm,unimp参数解析-machine virt指定使用QEMU的“virt”虚拟机器这是ARMv8的通用虚拟平台。-cpu cortex-a57指定CPU模型。-nographic无图形界面使用控制台。-kernel ./build/qemu/release/bl1.bin将BL1作为“内核”加载到QEMU模拟的ROM地址。-device loader...将FIP包加载到指定的物理内存地址0x4000000这个地址需要与ATF代码中plat/qemu/include/platform_def.h里定义的PLAT_QEMU_FIP_BASE一致。-d in_asm,unimp输出一些调试信息有助于观察执行流。如果一切顺利你将看到串口输出滚过ATF各个阶段的启动日志最终进入U-Boot的命令行界面。看到U-Boot的提示符就证明ATF成功完成了安全启动链并将控制权移交给了BL33。实操心得第一次运行时最常见的失败原因是内存地址不对齐或FIP包内容错误。务必确认PLAT_QEMU_FIP_BASE的地址与QEMU命令中的addr参数完全一致。另一个技巧是可以先不加-d参数只保留-nographic来观察纯净的启动日志定位问题阶段。5. ATF代码结构导读与关键流程分析5.1 源码目录结构剖析进入ATF源码根目录其结构清晰反映了其模块化设计bl1/,bl2/,bl31/各启动阶段的专属源代码。plat/平台移植层。这是将ATF适配到具体硬件芯片的关键目录。你需要关注的平台代码都在这里例如plat/qemu/。include/全局头文件定义公共API和数据结构。lib/可复用的库文件如加密库、标准C库替代品、CPU辅助函数等。drivers/通用设备驱动如控制台、定时器、中断控制器GIC的抽象层。services/运行时服务如电源状态控制PSCI、安全监控调用SMC处理框架。tools/构建和镜像处理工具如前面用到的fiptool。对于移植和深度定制plat/和include/drivers是你需要花费最多时间的地方。5.2 BL31初始化流程详解BL31作为常驻的运行时固件其初始化流程是ATF的核心。让我们跟踪bl31/aarch64/bl31_entrypoint.S这个汇编入口点冷启动路径Cold BootCPU从EL3开始执行BL31的入口函数bl31_entrypoint。设置异常向量表首先配置EL3的异常向量表以便处理来自低异常等级EL2, EL1的同步/异步异常、SMC调用等。CPU数据初始化初始化每个CPU核心的上下文数据为多核启动做准备。控制权移交C代码在完成最底层的汇编环境设置后跳转到C语言函数bl31_main位于bl31/bl31_main.c。bl31_main 核心工作平台早期初始化调用plat_early_platform_setup()让平台代码初始化最关键的硬件如串口用于打印调试信息。运行时服务初始化调用runtime_svc_init()这是BL31的“灵魂”。它遍历一个名为runtime_svc_descs的数组这个数组登记了所有在EL3提供的“服务”。每个服务都有一个唯一的SMC功能号Function ID和对应的处理函数。最重要的服务包括标准服务如PSCI电源管理、安全监控服务处理与TEE的通信等。架构和平台后期初始化进行更细致的硬件配置。准备进入下一阶段最终BL31会根据预定的启动计划决定是跳转到安全世界的BL32如OP-TEE还是直接跳转到非安全世界的BL33U-Boot。它通过bl31_prepare_next_image_entry()和bl31_plat_runtime_setup()来设置好目标世界的CPU上下文寄存器状态、异常等级等。世界切换BL31执行一条ERET异常返回指令。这条指令不会返回到调用点而是根据它设置好的CPU上下文让CPU“跳转”并切换到目标世界EL2或EL1安全或非安全状态去执行。至此BL31的初始化完成进入等待SMC调用的服务状态。5.3 SMC调用处理流程实战当非安全世界的Linux内核或U-Boot需要请求安全服务例如调用一个加密算法时它会执行一条SMC指令。这会触发一个异常CPU陷入EL3并跳转到BL31设置的异常向量表。异常向量表路由汇编代码保存当前CPU上下文寄存器后会路由到C处理函数smc_handler()通常在lib/aarch64/runtime_exceptions.S和runtime_svc.c中。服务查找smc_handler会解析SMC指令携带的功能号Function ID。这个ID是一个32位或64位的数其中包含了服务类型是快速调用还是标准调用、调用实体是安全世界还是非安全世界发起以及具体的服务编号。服务派发根据功能号在runtime_svc_descs数组中查找注册的处理函数。执行与返回调用对应的服务处理函数。函数执行完毕后BL31会恢复之前保存的非安全世界上下文再次执行ERET返回到非安全世界的调用点并携带返回值。这个过程就像应用程序通过操作系统API系统调用请求内核服务一样只不过这里是通过硬件指令SMC和固件BL31在安全世界和非安全世界之间进行切换和服务调用。6. 移植ATF到新硬件平台的关键步骤将ATF运行在QEMU上只是第一步。真正的挑战是将它移植到一块真实的、新的ARMv8-A开发板上。这通常涉及以下核心步骤6.1 创建平台目录与基础文件在plat/目录下为你平台创建一个新目录例如plat/my_company/my_platform/。你需要创建或移植以下关键文件platform.mk构建系统的核心。定义平台属性。# 示例片段 PLAT_MY_PLATFORM : 1 ENABLE_PIE : 0 # 是否支持位置无关代码 BL31_SOURCES plat/my_company/my_platform/my_platform_setup.c \ plat/my_company/my_platform/my_platform_pm.c这里需要指定本平台特有的源文件并覆盖一些全局编译选项。include/platform_def.h硬件抽象的核心。定义平台特定的内存布局和硬件常量。#define PLATFORM_STACK_SIZE 0x1000 // 栈大小 #define BL31_BASE 0x80000000 // BL31加载地址 #define BL31_LIMIT (BL31_BASE 0x20000) // BL31内存范围 #define PLAT_MY_PLATFORM_UART_BASE 0x12345000 // 串口寄存器基地址 #define PLAT_MY_PLATFORM_GICD_BASE 0x12346000 // GIC Distributor基地址 #define PLAT_MY_PLATFORM_GICR_BASE 0x12347000 // GIC Redistributor基地址这些地址必须严格参照你的芯片数据手册Datasheet或硬件参考手册。6.2 实现平台初始化函数ATF通过一组强制的平台接口函数来适配硬件。你需要在C文件中实现它们控制台初始化这是调试的命脉。实现plat_my_platform_console_init()初始化串口硬件并调用console_register()注册到ATF的通用控制台框架。void plat_my_platform_console_init(void) { /* 1. 配置串口引脚复用、时钟 */ /* 2. 设置串口波特率、数据格式 */ /* 3. 注册 console_t 结构体 */ static console_t my_console; int ret console_register(PLAT_MY_PLATFORM_UART_BASE, PLAT_MY_PLATFORM_UART_CLOCK, PLAT_MY_PLATFORM_UART_BAUDRATE, my_console); if (ret 0) { /* 注册成功 */ } }系统计数器初始化ATF的延时和调度依赖系统计数器。实现plat_my_platform_syscnt_freq()返回计数器频率如24MHz。中断控制器GIC初始化这是多核和SMC处理的基础。实现plat_my_platform_gic_init()配置GIC Distributor和CPU Interface。代码通常可参考其他平台但基地址必须修改正确。电源管理PSCI操作如果支持多核需要实现PSCI相关的平台函数如plat_my_platform_get_core_pos()通过MPIDR获取核心索引、plat_my_platform_pwr_domain_on()启动从核等。6.3 配置链接脚本与内存映射plat/my_company/my_platform/linker.ld.S文件定义了BL31等镜像在内存中的布局代码段.text、数据段.data、BSS段.bss的起始地址。这些地址必须与platform_def.h中的定义以及你的BootloaderBL2加载地址相匹配且不能与其他软件如U-Boot、Linux内核的内存区域冲突。6.4 集成与构建测试在ATF根目录的make_helpers/plat_helpers.mk中添加你的平台到ALL_PLATFORMS列表。尝试编译make CROSS_COMPILEaarch64-linux-gnu- PLATmy_platform bl31。将生成的bl31.bin与你为这块开发板定制的BL2和BL33U-Boot打包成FIP或符合你芯片启动格式的镜像。通过JTAG/SWD调试器或BootROM提供的下载工具将镜像烧录到开发板并通过串口观察输出。踩坑实录移植中最常见的问题是“死机”串口无任何输出。排查顺序应是1)串口引脚和时钟是否正确用示波器量一下TX引脚。2)内存地址是否正确BL31的加载地址是否在可执行的内存范围内如SRAM或初始化好的DDR3)栈指针(SP)在早期汇编代码中是否设置到了有效内存4)异常向量表地址是否正确可以通过在汇编入口点放置一个死循环并用调试器单步来确认代码是否被执行。7. 调试技巧与常见问题排查7.1 利用日志与断言ATF内置了多级日志系统ERROR,WARN,INFO,VERBOSE。在include/common/debug.h中可以通过修改LOG_LEVEL宏来调整输出级别。在开发初期建议设置为LOG_LEVEL_INFO甚至LOG_LEVEL_VERBOSE以获取更多信息。断言assert()在ATF中广泛使用。当条件为假时它会打印断言失败信息并挂起CPU。这是定位程序逻辑错误的利器。确保在调试版本中启用断言。7.2 使用调试器JTAG/SWD对于实体硬件调试器是必不可少的。以J-Link为例配合GDB进行调试编译时在Make命令中加入DEBUG1生成带调试信息的ELF文件如build/my_platform/debug/bl31/bl31.elf。在链接脚本中确保代码段从正确的内存地址开始。通过J-Link GDB Server连接开发板并用arm-none-eabi-gdb加载ELF文件。在关键函数如bl31_main,plat_my_platform_console_init设置断点单步执行查看寄存器值和内存内容。7.3 常见启动问题速查表现象可能原因排查思路串口无任何输出1. 串口硬件/引脚初始化错误2. BL1/BL2未正确加载或跳转3. 栈指针(SP)设置错误导致立即崩溃4. 代码未在预期地址执行1. 检查时钟、引脚复用配置用示波器测TX。2. 用调试器停在最早汇编代码检查PC和SP。3. 在汇编入口点写一个简单循环如点亮LED验证代码是否运行。打印若干条日志后死机1. 内存访问越界如访问未初始化的DDR2. 中断配置错误导致异常3. 数据或代码段地址与链接脚本不符1. 检查死机前最后一条日志附近的代码。2. 检查GIC初始化代码确认中断号、优先级配置。3. 对比map文件编译生成中的地址与实际加载地址。无法跳转到U-Boot (BL33)1. BL33加载地址或入口地址错误2. BL31为BL33设置的CPU上下文如SCTLR, ELR错误3. U-Boot镜像本身损坏或格式不对1. 检查plat_my_platform_get_bl33_mem_params()返回的参数。2. 在BL31跳转前bl31_prepare_next_image_entry打印或调试查看为BL33设置的上下文结构体entry_point_info_t。3. 单独验证U-Boot镜像是否能被之前的Bootloader直接启动。SMC调用无响应或返回错误1. SMC功能号未在BL31中注册2. SMC处理函数本身有bug3. 非安全世界调用SMC的姿势不对参数传递1. 在BL31的runtime_svc_init中检查服务描述符数组。2. 在SMC处理函数入口添加日志确认是否被调用。3. 核对ARM架构手册关于SMC指令和参数寄存器的约定X0-X7。7.4 模拟器调试进阶结合GDB与QEMU对于QEMU可以方便地使用GDB进行源码级调试这对理解ATF执行流有巨大帮助。在编译ATF和U-Boot时都加上DEBUG1选项。启动QEMU并添加-s -S参数。-S表示启动时暂停CPU-s是-gdb tcp::1234的简写表示在1234端口等待GDB连接。qemu-system-aarch64 -machine virt ... -s -S -nographic ...打开另一个终端启动GDB并连接到QEMUaarch64-linux-gnu-gdb ./build/qemu/debug/bl31/bl31.elf (gdb) target remote localhost:1234 (gdb) break bl31_main # 在BL31的C入口点设置断点 (gdb) continue # 让QEMU继续执行当执行到bl31_main时QEMU会暂停GDB会获得控制权此时你就可以单步执行、查看变量、回溯调用栈了。这个过程能让你清晰地看到BL1如何验证并跳转到BL2BL2如何加载FIP包BL31如何初始化并最终跳转到U-Boot。这种动态的、可视化的理解是单纯阅读代码无法比拟的。移植和调试ATF是一个系统工程需要耐心地对照手册、分析代码、利用工具。每一次成功启动都意味着你对ARM安全体系的理解又深入了一层。这个“安全管家”虽然隐藏在深处但正是它为上层丰富多彩的应用世界奠定了可信的基石。

相关新闻