首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
Arm Trusted Firmware架构解析:从EL3安全固件到平台移植实践
📅 2026/9/6 10:57:06
✍️ 爱科研究院
👁 阅读 3,247
做 Arm 平台系统软件开发这几年有个东西基本上每周都会碰见可真正把它里里外外看懂的人说实话不多。这个东西就是 Arm Trusted Firmware现在 LF 项目里叫 TF-A。很多人把它当成一段“启动时要跑的固件”跑起来之后就没它什么事了。但实际上它是整个 Arm 安全世界的基石是你系统里所有安全决策、异常级别切换、可信启动链、甚至 TEE 和虚拟化方案的承重墙。我这次准备把 TF-A 从架构、源码审计到平台移植完整地复盘一遍。不是那种“照着 README 念一遍”的扫盲而是站在一个系统工程师的视角把每个模块为什么这样设计、移植时哪些地方最容易翻车、安全审计要重点盯哪些代码一条条拆开讲。适合已经在做 SoC 启动、固件开发、TEE 集成或者想往安全固件方向发展的人看。如果你只是听说过 ATF 想入个门这篇也能给你搭出完整骨架。1. ATF 架构全景先把这些模块和层级对齐1.1 从“启动引导”到“EL3 运行环境”的定位转变TF-A 官方定义是一组运行在 EL3 的参考安全固件实现。以前大家总把它和 U-Boot、UEFI 混在一起觉得都是 bootloader。但 U-Boot 主要跑在正常世界负责把内核加载起来TF-A 的核心使命是建立并维护“正常世界”和“安全世界”之间的边界。Armv8/Armv9 的异常模型里EL3 是最高特权级别。TF-A 长期驻扎在 EL3它不是“跑完就走”而是要一直活着处理来自非安全世界和安全世界的 SMC 请求、PSCI 电源管理请求、中断路由、安全 OS 的生命周期管理。理解这个定位非常关键你写的平台代码不只是跑个启动流程而是要构建一个能持续运行几十年的安全运行环境。很多人第一次接触 ATF 就被 BL1、BL2、BL31 这些术语绕晕。简单类比这就像盖房子BL1 是打地基固化在 ROM 或者非常可靠的一级存储里BL2 是搭主体框架把可信固件和安全配置加载好BL31 是房子的物业入住之后所有跟安全相关的管理事务都由它处理。这样想整个启动链就不难理解了。1.2 BL1/BL2/BL31/BL32/BL33各段职责与边界标准冷启动链是这样的BL1最早执行一般放在 BootROM 里。负责最小初始化建立安全栈和异常向量然后把 BL2 从后续存储加载到 SRAM并做认证。BL2运行在 EL1 的安全世界负责加载运行时固件 BL31、可选的 BL32比如 TEE OS以及正常世界的 BL33比如 U-Boot、UEFI。这些镜像会被打包成一个 FIP 文件BL2 负责解包、验签、分发。BL31运行时固件运行在 EL3。包含 SMC 分发器、PSCI 实现、中断管理、运行时服务框架以及 Secure Monitor 的核心逻辑。BL32可选的安全 OS运行在 S-EL1比如 OP-TEE、Trusty。BL31 通过标准接口与它交互。BL33正常世界镜像通常就是 U-Boot 或者 UEFI。从审计角度你要重点关注 BL1 和 BL31因为一个不可升级、一个常驻运行。BL1 出错基本只能用 JTAG 或者重新流片解决BL31 出错会导致运行时安全服务全线崩溃。BL2 虽然只跑在启动阶段但它持有加载和认证大权同样不能忽视。实际的启动链路不一定全走很多 SoC 平台使用 BL33 内部的加载器比如 U-Boot SPL直接加载 BL31而把 BL1/BL2 绕过。这种“非标准”路径能做但必须清楚后果你放弃的是标准化的可信引导链所以也必须自己确保加载过程的完整性。1.3 SMC、TrustZone、安全世界先把术语对齐TF-A 生态里几个高频术语建议先理清SMCSecure Monitor Call非安全世界通过 SMC 指令陷入 EL3向安全世界请求服务。TrustZoneArm 提供的硬件隔离技术把 SoC 的总线、内存、外设从物理层面划分为安全和非安全区域。PSCIPower State Coordination Interface标准电源管理接口操作系统内核通过它来请求 CPU 上下电、挂起、系统重启。SPD/SPMSecure Payload Dispatcher / Secure Partition Manager负责在 BL31 里管理 BL32 或安全分区的生命周期。这些概念不是并列关系而是层层嵌套SMC 是入口TrustZone 是硬件保障PSCI 是运行在 SMC 基础上的服务SPD/SPM 则是 BL31 扩展安全生态的胶水。1.4 源码目录速览拿到手不要瞎翻TF-A 源码包结构非常清晰第一次看建议按下面顺序扫bl1/、bl2/、bl31/各阶段主流程。plat/平台代码按厂商/板卡分目录是你移植主要动刀的地方。drivers/外设驱动比如 GIC、串口、TEE、认证相关。lib/通用库包括翻译表xlat_tables、栈保护stack protector、Libfdt、PSCI 基础库等。services/运行时服务包括 SPD、SPM、标准 SMC、PSCI 服务。tools/辅助工具比如fiptool和cert_create。强烈建议新人先从plat/arm/board/fvp和plat/qemu这两个参考平台读起。FVP 是 Arm 官方固定虚拟平台代码最完整QEMU 更轻量适合在本地快速跑通。直接啃真实板卡的平台目录容易被 vendor 定制代码带偏节奏。2. 安全固件工程审计源码层面要盯死的关键点2.1 信任根与安全启动链路重点审这几个文件安全启动不是“启动时验个签名”这么简单它是一条完整的信任链每个组件都要验证下一个组件的身份和完整性而整条链的根是芯片内部无法被篡改的信任根。在 TF-A 里这个根通常是 ROTPK也就是烧在 eFuse 里的根密钥哈希或公钥。审计时第一步要查plat/你的平台/board/include/platform_def.h里关于 ROT_KEY、TRUSTED_BOARD_BOOT 的配置。然后去drivers/auth/看认证模块怎么工作。TF-A 的认证框架是模块化的auth_mod负责调用不同的认证方法比如 CVE、非对称签名最后生成一个可信环境变量集合。我审计过几个团队的项目最典型的问题就是他们为了“先跑起来”在 BL1 返回AUTH_MOD_IGNORE或者直接不实现plat_get_rotpk_info。这样跑起来当然快但安全启动等于形同虚设。正确做法是至少要实现plat_get_rotpk_info并且在板级函数里返回从 eFuse 或 OTP 读到的 ROTPK 哈希。2.2 权限边界与内存映射让越权无处可藏ATF 对内存的控制依赖 Arm MMU 的翻译表和 TrustZone 配置。平台代码里通常会定义两种空间物理地址空间和安全虚拟地址空间。plat_get_mmap_entries会返回一组内存映射条目告诉 ATF 哪些内存是安全的、哪些是非安全的、哪些是设备内存。审计时我会重点看三处plat_get_mmap_entries里有没有把安全内存错误地映射成非安全可读。PLAT_PHY_ADDR_SPACE_SIZE和PLAT_VIRT_ADDR_SPACE_SIZE是否覆盖了实际物理内存范围如果配小了ATF 可能无法访问某些区域导致启动后随机异常。TrustZone 地址空间TZASC/TZPC的配置是否真的把安全 DDR 挡在了非安全 DMA 等设备之外。千万记住软件层的访问控制建立在这些硬件配置之上。你以为你在做权限管理其实你是在给硬件策略写配置文件。配置错了普通用户态程序就可能摸到安全世界的内存。2.3 密钥管理、固件打包与构建配置最容易忽略的工程问题很多人一大半安全漏洞都出在构建流程和密钥管理上而不是源码逻辑。TF-A 提供了cert_create工具来生成证书链证书类型里定义了用于校验 BL2、BL31、BL32、BL33 的各种密钥角色。建议用你熟悉的工具来生成密钥但不要让私钥出现在构建服务器上。一个我在实际项目中反复强调的规范所有签名操作放到离线签名机里构建产物只上传未签名的固件包由发布流水线去完成签名。源代码仓库里永远不应该出现密钥文件哪怕是测试密钥。测试密钥泄露一旦被逆向工程到攻击者就能伪造一整套固件包。还要检查Makefile里的TRUSTED_BOARD_BOOT、MEASURED_BOOT、GENERATE_COT这些选项。审计的时候别只看代码把 CI 构建脚本也拉出来看看编译参数是否稳定、证书生成步骤是否可重复。如果构建一次一个结果那整个安全发布流程就是不可控的。2.4 工具链与编译器编译参数同样决定安全等级TF-A 官方支持 GCC 和 Arm Compiler。实际使用中GCC 最常见命令类似make CROSS_COMPILEaarch64-none-elf- PLATqemu DEBUG1如果你是老项目迁移还可能在用 Arm Compiler 5。比如网上常搜的 ARM Compiler 5.06它已经是很老的版本了适合维护 legacy 代码但新版本 TF-A 并不推荐。Arm Compiler 6 和 GCC 对新架构特性和安全特性的支持更好。用旧编译器编译现代固件不仅可能有错误指令还可能丢失重要的 CPU 安全缓解措施。此外一定要检查DEBUG和日志等级。上线固件如果不关调试输出攻击者能通过 UART 拿到大量布局信息。我的习惯是 release 构建固定LOG_LEVEL20或更低并且关闭DEBUG1。3. 平台移植落地指南从 QEMU/FVP 到真实板卡3.1 移植前三个准备动作少一个都难受第一拿到 SoC 的参考手册重点看启动流程、内存映射、GIC 和串口地址。第二找一块和你的目标平台类似的参考平台比如 QEMU 虚拟平台或者你 SoC vendor 提供的参考设计。第三准备好工具链和调试器至少要有 UART 日志输出否则你几乎没法定位启动问题。平台移植本质上是“把 TF-A 抽象出来的接口填满”。TF-A 设计得已经很克制它把平台支持抽象成一群函数和宏只要你实现了这些接口BL1/BL2/BL31 就能跑起来。反过来你哪怕有一个接口实现错了启动就会很崩溃地卡住而且没有任何报错。3.2 最小平台支持必须先实现的接口一个能跑到 BL31 的最小平台至少要实现这么几组内容平台宏定义PLATFORM_LINKER_FORMAT、PLATFORM_LINKER_ARCH、PLATFORM_STACK_SIZE、PLAT_PHY_ADDR_SPACE_SIZE、PLAT_VIRT_ADDR_SPACE_SIZE、MAX_MMAP_REGIONS等。串口驱动相关plat_crash_console_init、plat_crash_console_putc没有它你连死在哪都不知道。平台设置函数bl31_early_platform_setup2、bl31_plat_arch_setup、bl31_platform_setup。内存映射plat_get_mmap_entries。PSCI 相关plat_my_core_pos、plat_core_pos_by_mpidr。GIC 配置plat_arm_gic_init或等价函数。听起来不少但每个函数职责都很单一。比如bl31_plat_arch_setup里要做的是初始化 MMU 和翻译表bl31_platform_setup则是初始化 GIC 和串口等外设。3.3 platform.mk 与链接脚本构建系统的工作流每个平台目录下都有一个platform.mk它会告诉构建系统你要编哪些源文件、定义哪些编译宏、使用哪些启动镜像。直接在plat/arm/board/xxx/platform.mk上改就行。最核心的是添加你的平台源文件列表PLAT_BL_COMMON_SOURCES \ plat/xxx/xxx/plat_topology.c \ plat/xxx/xxx/plat_helpers.S BL31_SOURCES \ plat/xxx/xxx/bl31_plat_setup.c \ plat/xxx/xxx/aarch64/platform_common.c \ drivers/arm/gic/v3/gicv3_main.c需要注意BL31_SOURCES和PLAT_BL_COMMON_SOURCES的区别后者是被多个 BL 镜像共用的代码前者只编进 BL31。如果你的启动阶段代码互相引用混乱很容易出现链接错误所以建议早期尽量参考 FVP 的目录结构不要自创一套。链接脚本platform.ld.S也很重要。BL31 通常被链接到安全内存的某一块地址这个地址必须和硬件中 BL31 实际被加载的位置一致。地址写错了跳转过去基本就是取指异常。3.4 在 QEMU 上跑通 ATF 的完整操作本地快速验证移植最推荐 QEMU 的 Arm 虚拟平台。下面是一套我常用的操作流程git clone https://github.com/ARM-software/arm-trusted-firmware.git cd arm-trusted-firmware make PLATqemu CROSS_COMPILEaarch64-linux-gnu- DEBUG1 LOG_LEVEL40 BL33/path/to/u-boot.bin all fip编译完你会得到build/qemu/debug/bl1.bin和build/qemu/debug/fip.bin。然后用 QEMU 启动qemu-system-aarch64 -machine virt -cpu cortex-a53 -smp 4 -m 1G \ -nographic \ -bios build/qemu/debug/bl1.bin \ -drive filebuild/qemu/debug/fip.bin,formatraw,ifpflash注意不同版本的 QEMU 对 flash 参数略有差异如果遇到无法加载先看 QEMU 的-machine帮助。跑通之后你应该能看到 ATF 的启动日志再接下来才会进入 BL33 的 U-Boot 启动输出。第一次看到完整日志说明最小移植链路已经通了。QEMU 跑通之后再用 FVPFixed Virtual Platform验证。FVP 比 QEMU 更接近真实硬件的时序和中断行为但需要许可证。Arm 提供了FVP_Base_AEMv8A-AEMv8A等模型通常用-C bp.flashloader0.fnamefip.bin这类参数去加载固件具体参数看 FVP 文档。3.5 从模拟器到真实板卡差异清单要提前列好模拟器和实际硬件之间的差异经常让人措手不及提前列个清单能省很多时间GIC 版本和初始化时序真实板卡的 GIC 可能有独立电源域和复杂的中断路由必须确认在启动阶段已经上电。串口地址模拟器通常把 UART 固定在某地址真实板卡可能通过 pinmux 复用需要在最早期 GPIO 配置里打开。DDR 初始化模拟器帮你跳过 DRAM 训练真实板卡必须把 DDR 初始化流程前置于 ATF 或者依赖 BootROM 完成。安全 IP 配置TZASC、TZPC、TrustZone 过滤器这些真实硬件保护单元模拟器不一定模拟完整。真板调试时手头准备一个硬件调试器比如 Arm DS 或者 OpenOCD 加 JTAG。软件卡死没有输出的时候调试器是唯一能看清“CPU 到底停在哪条指令”的途径。4. 启动失败排查与工程经验速查4.1 先看日志和异常向量别急着改代码TF-A 自带不错的多级日志把日志等级开到LOG_LEVEL40可以看到非常详细的流程输出。启动卡住后第一件事就是看日志最后一行它能告诉你当前执行到哪个模块。如果连串口输出都没有那就检查三件事串口驱动是否初始化成功、终端占位地址是否映射、时钟是否正常。还有一种可能是异常向量表本身没配对。AArch64 的异常向量比较复杂如果 TBZ 或 SP 选择有误CPU 会在异常时跳到完全错误的地方。早期移植时强烈建议在 BL31 的bl31_main前手动设置一个测试 SMC反复验证异常入口正常才开始做后面的功能。4.2 常见卡死位置与根因我整理了一个常见问题速查表按我遇到的频率排列现象最可能原因处理方向BL1 后无任何输出BL2 FIP 找不到或认证失败检查 FIP 内容、BL2 加载地址BL2 后卡死mmap 或 xlat table 配置错误检查翻译表项和虚拟地址空间BL31 起不来BL31 镜像地址或 PSCI 未初始化检查加载地址、PSCI 版本匹配GIC 相关异常GIC 寄存器基地址或权限配置错误核对 GIC 映射和时钟使能切到 BL33 后崩溃DT/BL33 地址错或参数传递错误核对plat_get_next_bl_params上电后串口只有乱码波特率、时钟源或 pinmux 配置错先查参考手册默认配置其中plat_get_next_bl_params是特别容易被忽略的坑。BL31 跳转 BL33 前要把启动参数、DTC 地址、CPU 上下文都传到约定的寄存器里。很多新人只把 BL33 地址填了忘了灌 DTB结果 U-Boot 一启动读不到设备树就挂。4.3 踩坑清单避免重复造轮子我踩过几个印象特别深的坑写出来供你留意修改了 BL1 但没刷 BootROMBL1 在很多平台其实固化在 ROM 里你改的只是源码但硬件启动时根本不读你编出来的 BL1。移植时一定要确认你们平台的 BL1 是从 pflash 加载还是真 ROM。安全启动开启后反复重启极大概率是 ROTPK 或证书链校验失败。先检查 eFuse 里的 ROTPK 跟cert_create生成的公钥是否一致。测量启动Measured Boot影响启动性能很多人开MEASURED_BOOT1后启动慢了几十毫秒就慌。这是正常代价关键是要把测量值记录到受信任的存储里别只展不存。直接抄 vendor 的 BL31 配置vendor 平台代码通常为特定板卡调过参换板子直接抄容易碰到 GIC 中断 ID 对不上、内存区域重叠的问题。最好在参考代码基础上减到最少功能再逐步加上去。4.4 调试时值得养成的好习惯最后分享几个我调试固件期一直坚持的做法比任何技巧都管用从最小配置开始。不要一上来就把 U-Boot、OP-TEE、安全分区全塞进去。先一个 BL31 跑空转确认 EL3 正常再加 BL33再加 BL32逐个引入“变量”问题范围一下子就小了。保留一个自动构建脚本。最好一个make命令能出 qemu 镜像、fvp 镜像、真实板卡镜像。频繁改动后没那么容易回归。把安全启动和普通启动分成两条链路。经常有人在开发时图省事关掉安全启动结果上线前开回来发现整条链重构了。开发阶段可以都开着至少每周定期跑一次带签名的完整流水线。把 TF-A 学明白不是一个周末能完成的事。但只要你从架构、审计、移植三条线并行前进先在模拟器上把最小平台跑起来再迁移到真板再逐步打开安全启动、TEE、测量启动这些高阶开关你会慢慢发现它已经变成你系统上最稳定、最可控的那块“安全压舱石”。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/9/6 10:57:06
深入解析ARM Trusted Firmware:从BL31启动到安全固件移植实战
2026/9/6 10:57:06
PI-Goi本地AI部署与批量任务处理实践指南
2026/9/6 10:57:06
RISC-V启动流程深度解析:从复位向量到内核加载
2026/9/6 11:32:07
FM合成器实时控制:SMC打击垫MIDI映射与CC参数设置全攻略
2026/9/6 11:32:07
自动驾驶场景车辆监控视角行人关注度检测数据集VOC+YOLO格式4904张3类别
2026/9/6 11:32:07
端侧AI算力选型:从标称TOPS到实际部署的避坑指南
2026/9/6 11:32:07
具身智能端侧算力选型避坑指南:从TOPS到实测的完整方法
2026/9/6 11:32:07
足式机器人足底多模态传感器阵列融合与感知系统解析
2026/9/6 11:27:07
PCB布线进阶指南:从信号完整性与电源路径到高速电路设计
2026/9/6 0:01:31
超人会飞不算本事:系统稳定依赖清晰规则与边界设计
2026/9/6 0:01:31
超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论
2026/9/6 0:01:31
基于CNN的调制信号识别:MATLAB实现时频图分类实战
2026/9/6 0:01:31
超人会飞不算本事:系统稳定依赖清晰规则与边界设计
2026/9/6 0:01:31
超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论
2026/9/6 0:01:31
基于CNN的调制信号识别:MATLAB实现时频图分类实战