ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

ATF源码深度评测:Arm Trusted Firmware架构、安全设计与平台移植实战

ATF源码深度评测:Arm Trusted Firmware架构、安全设计与平台移植实战 我最早接触 Arm Trusted FirmwareATF是在做一款带安全启动要求的服务器 SoC 适配那时候对这种 EL3 固件还停留在“跑个 bl31.bin 给 U-Boot 用”的阶段直到被现场一个 PSCI 调用崩溃拖了两天才开始认真把这套代码当工程项目而不是启动补丁来看。后来做过的平台移植越多越发现 ATF 的价值远不止“引导下一个阶段”这么简单安全启动信任根、运行时安全服务、电源状态协调、可信执行环境的隔离边界全都在这一层固件上落地。这篇文章我会以源码评测结合工程审计的视角把 ATF-A 的架构全景、安全设计逻辑和平台移植的完整流程梳理一遍适合正在做 ARM64 平台 BSP、想让自己的固件启动链和 TEE 方案更靠谱的工程师参考。我尽量把每一层都讲透包括为什么要这么分、源码里对应哪些目录和函数、你在自己的平台上动手时需要改哪些文件。读完之后你至少能回答三个问题EL3 固件在整个系统中到底管什么ATF 的代码结构怎么读才高效拿到一个新的开发板或自研芯片从零开始做一个 ATF port 的落地路径是什么1. ATF 在 ARM 安全体系中的生态定位很多刚接触 ATF 的人会把它和 U-Boot 归为一类觉得都是“启动用的裸机程序”这个理解不算全错但会严重低估它的职责。要理清 ATF 的定位得先从 ARM64 的异常模型和安全世界说起。1.1 EL3 到底是什么为什么必须有个 ATFARMv8-A 架构把处理器运行特权分成了四个 Exception LevelEL0 跑应用EL1 跑操作系统内核EL2 跑虚拟化 HypervisorEL3 则是最高特权等级负责安全世界和普通世界的切换与管理。一个简单类比如果 Linux 内核是公司里的总经理EL3 固件就是董事会的常设代表——平时不怎么露面但涉及安全审计、公司印章信任根和突发断电电源管理时必须由它说了算。普通世界里跑的 U-Boot、Linux 内核、用户态程序全都无法直接访问 EL3 的资源。你要切换安全状态、要触发系统休眠、要调用可信固件提供服务只能通过 SMCSecure Monitor Call指令陷入 EL3由运行在 EL3 的固件统一处理。这个“运行在 EL3 的固件”在 ARM 生态里最通用的开源实现就是 ATF-AArm Trusted Firmware-A官方叫 TF-A社区里大家习惯还叫 ATF。ATF 不是可选的调味品。哪怕你做的是一个纯 Linux 系统、完全不打算跑 TEE只要 SoC 是多核 ARM64电源管理的 PSCI 功能基本都要依赖 EL3 来实现。现在很多 U-Boot 也支持直接引导 Linux但到了 CPU 热插拔、系统的 suspend/resume 这种需要操作处理器电源状态的场景U-Boot 没有权限也不是合适的位置标准做法还是交给 ATF 里的 PSCI 实现。1.2 ATF 与 U-Boot、OP-TEE、Linux 内核的分工边界一套典型的 ARM64 启动链是这样的芯片上电后 CPU 从 ROM 里的固化代码开始执行ROM 加载 BL1。BL1 完成最小初始化加载并验证 BL2。BL2 负责加载后续所有镜像BL31EL3 运行时固件、BL32可选的可信执行环境比如 OP-TEE、BL33通常是 U-Boot。BL31 留在 EL3 常驻提供运行时服务。BL33 跳到 Linux 内核系统进入正常世界。在这条链里BL31 和 BL32 都是 ATF 工程内的概念BL33 一般是 U-Boot 等非安全 bootloader。这里有个容易混淆的点ATF 并不是只产出一个二进制。你编译完 ATF 后通常能拿到 bl1.bin、bl2.bin、bl31.bin 这几个文件如果开了 TEE 支持还会有 bl32.bin实际是 OP-TEE 的镜像通过 ATF 的构建流程一起打包。这几个文件分别承担不同阶段的任务不是同一个程序复制三份。BL1 和 BL2 是安全启动的关键环节负责建立信任链BL31 是业务最复杂的部分它集成了运行时 SMC 分发器接收普通世界发来的 SMC 请求。PSCI 实现处理 CPU 开启/关闭、系统重启、挂起等电源管理请求。安全中断处理管理与安全世界相关的中断路由。可信启动的运行时验证接口。平台相关服务比如部分 SoC 的厂商自定义 SMC 服务。1.3 为什么平台移植绕不开 ATF只要你的目标平台是 ARM64无论用不用 TEEATF 的移植工作几乎是必做的。可能有人会问我的板子只用 U-Boot 引导 Linux不接 OP-TEE是不是就不用管 ATF 了答案是你至少需要一个能提供 PSCI 的 EL3 固件。U-Boot 里虽然也有简单的 PSCI 实现在一些旧的 32 位 ARM 平台常见但主流 ARM64 平台的标准组合已经是 ATF U-Boot。原因有两个一是安全启动的要求。现代 SoC 的 BootROM 多半只认自己定义的安全引导流程ATF 的 BL1/BL2 是这套流程里的核心环节。如果你不用 ATF就必须用 SoC 原厂或第三方提供的另一套 EL3 固件这些方案通常不开源或文档稀少踩坑成本更高。二是生态一致性和可维护性。ATF 代码在 Arm 和各大 SoC 厂商的共同维护下对 GIC、CCI/CMN 总线、PSCI 等都有标准的驱动和抽象层。你换一个平台时只需要实现平台接口不需要把整个安全监控逻辑重写。对于团队来说基于 ATF 做二开比维护私有 EL3 方案要可控得多。2. 深度源码评测ATF 的架构全景与关键模块这次源码评测我基于的是最新的 LTS 分支TF-A v2.10 左右的代码结构大版本之间目录结构基本稳定只要你把下面的要点抓住了看其他版本也能快速上手。2.1 代码仓库第一眼目录结构其实在讲一个完整故事把 atf 仓库克隆下来之后顶层目录看一遍就能猜出整个工程的骨架bl1/BL1 阶段源码安全启动的第一棒。bl2/BL2 阶段源码负责加载所有镜像并做验证。bl31/BL31 运行时固件源码ATF 里代码量最大、逻辑最复杂的部分。bl32/BL32 Secure Payload 的接入代码。它本身不实现 TEE而是定义了一套接口去加载 OP-TEE 之类的 TEE OS。common/bl_common 等公共逻辑比如镜像描述符、启动状态跟踪。lib/核心库包括 el3_runtimeEL3 运行时环境、psciPSCI 实现、smcccSMC 调用约定、stack_protector、xlat_tables页表转换、extensionsPAuth、MTE 等架构扩展支持。drivers/各种外设驱动最常见的是arm/gic、ti/uart、st、marvell以及可信板卡相关的auth驱动。plat/平台相关代码这里是最需要关注和修改的地方。plat/arm/是 Arm 官方参考实现FVP、Juno 等plat/下还有各大 SoC 厂商的实现比如plat/hisilicon、plat/xilinx、plat/nvidia等。自己移植时一般是在plat/下新建目录。tools/构建辅助工具包括fiptool打包 FIP 镜像、cert_create创建安全启动证书、sptool生成 secure partition 描述符等。docs/官方文档设计文档都在里面读源码前先翻docs/design/下的文档尤其是firmware-design.rst强烈建议通读一遍。如果你要读这套代码我建议的顺序是先读docs/firmware-design.rst然后读bl31/main.c再读lib/psci/psci_main.c最后回到plat/arm/board/fvp/看平台代码。这样从设计到运行时再到平台接入是一条完整的主线。2.2 启动流程源码级拆解从 BL1 到 BL33 每一步在干什么上电后 CPU 首先执行的是 BL1入口在bl1/bl1_main.c里的bl1_main()函数。BL1 的任务非常精简初始化最小环境串口、内存、异常向量表然后调用bl1_load_bl2()把 BL2 从存储介质里读出来。这里有个关键设计BL1 默认运行在安全世界的最高特权但会把代码放在 SRAM 或 ROM 里因为 BL1 是信任根的一部分它不能被篡改。BL1 在跳转前会调用bl1_plat_prepare_exception()之类的平台钩子处理 cache、MMU 的状态保证 BL2 运行在一个干净的环境里。BL2 的入口在bl2/bl2_main.c。它做的事情用一个词概括就是“加载器”。BL2 运行在安全世界通常放在 DRAM 里负责把 BL31、BL32、BL33 等镜像从 flash 加载到内存指定位置。这一步的关键函数是load_bl30()、load_bl31()、load_bl32()、load_bl33()它们共同维护一个bl_mem_params_desc_t数组描述每个镜像的加载地址、验证方法、执行权限等信息。BL2 执行完后会调用bl2_run_next_image()按顺序进入 BL31。这里有个“退出安全世界”的动作BL2 会配置好下一个阶段的异常向量然后发一条SMC指令让 BL1 切到 EL3 并跳转。只有 BL31 是“常驻型”的运行时固件BL32 和 BL33 都是由 BL31 在适当时机加载或切换过去的。BL31 的启动流程在bl31/bl31_main.c的bl31_main()里。核心动作是初始化运行时服务bl31_lib_init()设置 PSCI、SMC 分发器、标准服务等。然后它会执行bl31_plat_setup()做平台后初始化再通过bl31_prepare_next_image_entry()进入 BL33U-Boot。之后 BL31 就留在 EL3 等待各种 SMC 请求不再主动执行引导动作。2.3 EL3 运行时服务的引擎SMC 分发器与 PSCI 实现逻辑BL31 起来以后整个系统就是“事件驱动”的所谓的运行时服务就是一个大的分发器。普通世界的软件内核、U-Boot通过smc指令带上一个函数 ID 陷入 EL3BL31 的异常向量表捕获后交给dispatch()逻辑。这个分发机制的实现在bl31/ehf.c、services/目录下各种服务通过DECLARE_RT_SVC()宏注册自己的rt_svc_desc_t。每个服务描述符包含服务名。SMC 函数 ID 的范围OENOwner ID。init、handle、reset等回调函数指针。PSCI 就是这些运行时服务里最核心的一个代码在lib/psci/下。PSCI 服务处理 CPU 的开启、关闭、系统挂起、重启等电源管理请求它内部维护着一个 CPU 电源状态机。Linux 内核的pscidriver 在启动时通过 SMC 查询并调用这些服务CPU hotplug 和 cpuidle 的底层动作都是走这条路。ATF 的 PSCI 实现不是简简单单的cpu_on然后跳转。它针对不同平台抽象出了plat_psci_ops结构体里面有cpu_on_start、cpu_on_finish、pwr_domain_on、pwr_domain_off等回调。你做平台移植时重点就是实现这一组 ops把你的 SoC 的电源控制逻辑挂进去。2.4 源码里的安全固件工程审计点从安全审计角度看ATF 源码里有几个值得重点关注的地方信任根与代码签名。ATF 支持通过证书链完成镜像验证相关逻辑在drivers/auth/下。默认使用mbedTLS做签名验签通过TRUSTED_BOARD_BOOT配置项打开。审计时要关注公钥存放在哪里是否被硬件保护、证书格式与过期机制、以及验签失败的 panic 路径是否干净。SMC 接口的参数校验。BL31 收到来自普通世界的 SMC 请求后必须先做参数校验再处理。如果某个服务忘记检查长度或权限就有可能导致内存越界或提权。审计时重点检查handle函数里是否有健全的 size 和 permission 校验。安全中断处理。ATF 在interrupt_management里有看门狗、GIC 配置、安全中断的注册机制。如果配置不当普通世界可能通过中断干扰安全世界或者反过来被安全中断打断得太频繁造成性能问题。最小化 EL3 代码。EL3 代码越多攻击面越大。审计时最基础的原则是检查是否只把必要代码编译进了 BL31。默认构建如果开了很多调试功能比如ENABLE_DEBUG生成的二进制里会带很多打印和缓冲区这在正式产品上要严格关闭。3. 平台移植落地手把手从零建立一个新的 ATF Port平台移植是 ATF 使用中最常见也最细节的活儿。这节我以一块假想的“自研 ARM64 SoC”为例把完整流程过一遍——从拿到硬件手册到最终产出能跑 U-Boot 的 FIP 镜像。实际工作中你会拿着真实 TRM 替换我这里的假设参数。3.1 移植前要准备什么物料清单不只是编译链动手写代码之前先把下面这些信息摸清楚否则移植过程会不断返工SoC 的内存映射哪段地址给 SRAM、哪段给 DRAM、哪段是外设寄存器空间BL31 要放哪段 SRAM 或 DRAMBL2 放在哪里。串口控制器型号和地址ATF 早期串口打印基本就是console_pl011或console_16550两种你的 SoC 用哪个、基地址是什么。GIC 版本和中断路由GICv2 还是 GICv3安全中断的 SPI/PPI 分配。电源管理需求系统要不要 CPU hotplug要不要 suspend如果只需要最简启动链PSCI 只接system_off和system_reset也行。启动源BL2 从 SPI NOR flash 加载镜像还是从 eMMC、SD 卡加载这决定了 BL2 要考虑驱动哪些外设。交叉编译器ATF 构建推荐aarch64-none-elf-或aarch64-linux-gnu-。用 ARM 自家的 Arm Compiler 6 也可以但社区兼容性最好的还是 GCC 交叉工具链。实际踩过坑后我的建议是先拿 GCC 跑通再验证 Arm Compiler 6PR 提交如果用 AC6 会遇到一些内置函数差异和链接脚本兼容问题。aarch64-none-elf-gcc --version # 推荐 12.2 之后或最新的 linaro 工具链3.2 新建平台目录和 platform_def.h 的坑ATF 平台代码的惯例是plat/vendor/platform/。最小移植至少要有这些文件platform_def.h所有平台宏定义。plat_common.c/plat_setup.c平台初始化。plat_topology.cCPU 拓扑结构。plat_psci.cPSCI 电源管理回调。plat_sip_svc.c可选自定义 SMC 服务。plat_bl31_setup.cBL31 运行环境设置。最核心的platform_def.h里你需要定义的宏至少有这些#define PLATFORM_LINKER_FORMAT elf64-littleaarch64 #define PLATFORM_LINKER_ARCH aarch64 #define PLAT_PHY_ADDR_SPACE_SIZE (1ull 40) #define PLAT_VIRT_ADDR_SPACE_SIZE (1ull 40) #define PLAT_MAX_PWR_LVL 2 #define PLATFORM_CORE_COUNT 8 #define PLATFORM_CLUSTER_COUNT 2 #define BL31_BASE 0x04000000 #define BL31_LIMIT 0x04020000 #define PLAT_MMAP_ENTRIES 8 #define PLAT_XLAT_TABLES_ENTRIES 16 #define MAX_XLAT_TABLES 16 #define PLAT_ARM_MMAP_ENTRIES 6这里的BL31_BASE到BL31_LIMIT是 BL31 镜像的运行地址范围必须和链接脚本、内存映射一致否则编出来的镜像一启动就崩。PLAT_MAX_PWR_LVL表示电源域的层级一般集群级是 2如果只有单簇简单 SoC设成 1 就够了这个值直接影响 PSCI 的状态机复杂度。3.3 从串口打印到 PSCI最小可启动移植的实现顺序我的移植顺序一般分四步每一步都跑通验证再进下一步第一步串口和平台初始化。先实现plat_get_console()和plat_crash_console_putc()保证 BL1、BL2 能打日志。如果串口不亮其他都白搭。ATF 里驱动抽象在drivers/arm/pl011和drivers/ti/uart/16550你只需要在平台代码里调用并传入基地址即可。第二步BL2 加载镜像。先不管签名验证把TRUSTED_BOARD_BOOT关掉让 BL2 直接从 flash 读取镜像并放到对应地址。这一步的目标是能看到 BL31 跑起来、U-Boot 被引导。第三步PSCI 和电源管理。实现最简单的plat_psci_ops至少包括system_reset和system_off。然后逐步加上pwr_domain_on、pwr_domain_off。注意 CPU 的启动地址要用plat_secondary_cold_boot_setup()告诉硬件让被唤醒的 CPU 跳到一个安全地址执行而不是直接跳到 Linux 的 entry。第四步安全启动和 TEE 集成。这一步视产品需求决定是否做。要打开TRUSTED_BOARD_BOOT先生成密钥和证书然后用cert_create工具和fiptool打包 FIP 镜像。# 一个典型的最小移植构建命令 make CROSS_COMPILEaarch64-none-elf- PLATmyplatform DEBUG1 \ BL33/path/to/u-boot.bin \ BL32/path/to/optee.bin \ all fip这条命令编译完之后在 build/myplatform/debug/ 下会生成 bl1.bin、bl2.bin、bl31.bin 和 fip.bin。U-Boot 和 OP-TEE 的镜像会被打包进 fip.bin烧录时只需要烧 BL1、BL2 和 fip.bin 即可。3.4 编译链接中的常见坑和选参经验移植中踩过的坑我列几个高频的链接脚本报 region SRAM overflowed。原因是BL31_BASE到BL31_LIMIT的范围太小装的代码超过空间。常见做法是扩大这段空间或者把不需要的调试功能关掉。但要注意这段内存必须是 EL3 可访问且不能被普通世界改写的地方。编译器版本问题。ATF 对现代 GCC 比较友好但如果你用老的 Linaro GCC 5.x 编新版本 ATF会遇到内嵌汇编语法错误。基本规律ATF 版本越新要求的 GCC 版本越新。遇到这类问题优先升级工具链而不是改源码。打开ENABLE_DEBUG后打印大量日志但运行缓慢。这属于正常的release 构建DEBUG0会把大部分 log 编译掉调试时不要因此怀疑是死循环。把ARM_ARCH_MAJOR8编译时如果 SoC 是 ARMv8.2 以上特性比如有 FEAT_PAuth可以打开ARM_ARCH_MAJOR8、ARM_ARCH_MINOR5让 BL31 支持 pointer authentication但这需要内核和整个工具链都支持否则不要开。电源域设置错误导致psci初始化失败。PLAT_MAX_PWR_LVL和plat_core_pos_by_mpidr()必须保持一致。这个 macro 影响 PSCI 的拓扑遍历逻辑设大了会初始化一堆不存在的电源域设小了又可能漏掉真正的电源域。提示做平台移植时先拿 Arm 官方的 FVP 平台跑一遍代码确认你对 ATF 启动流程的理解没有偏差再动手改自己的平台。很多“我的平台起不来”的问题其实是基础流程没走通。4. 安全固件工程审计ATF 里的可信启动与攻击面标题里“工程审计”这个词不是虚的。当你的产品要过安全认证比如 PSA、CC时审计 ATF 的代码和安全设计是绕不开的一步。这里从工程实践角度分享我在审 ATF 时关注的核心点和一些工具方法。4.1 可信启动链路审计信任根到 BL33 的验签路径ATF 的可信启动流程可以用八个字概括逐级验证、先验证后使用。BL1 里藏的 Root-of-Trust通常是 SoC 内部 OTP 里的公钥验证 BL2 的签名BL2 再验证 BL31、BL32、BL33 的签名。整个过程的核心数据结构是auth_img_desc_t定义在drivers/auth/auth_common.c和各个平台提供的认证方法里。审计时我一般这样拉链路确认 BL1 里的 Root-of-Trust 公钥是否来自不可变的存储OTP/eFuse 或 ROM。确认 BL2 对 BL31 的校验是否覆盖整个镜像区间有没有只验头不验体的疏漏。确认 BL31 加载 Image 时使用的内存是否干净。比如镜像会被加载到一块可写的 DRAM 区域普通世界理论上可以通过 DMA 改写这块区域内容即使验签通过也要确认后续执行前有没有锁定或隔离。看fiptool打包时是否给每个镜像设置了正确的 UUID 和类型信息防止混淆镜像比如把 BL32 的镜像当成 BL33 加载。这部分的审计结论通常以“安全启动链路径图”方式输出每个镜像的哈希算法、签名算法、验签发生在哪个阶段、公钥来源、失败时的处理动作。ATF 默认的 failure action 是 panic 停机这在安全加固时是合理的但产品设计时要确认这种死机是否可接受——比如 OTA 升级时如果验签失败直接死机用户可能连版本回退的机会都没有。4.2 SMC 接口攻击面和运行时服务审计要点BL31 是长期在运行的 EL3 代码攻击者一旦能找到普通世界到 EL3 的漏洞就能拿到最高权限所以 SMC 接口的审计优先级极高。我有一套固定 checklist每个 SMC handler 是否在进入时校验 function ID 的合法性ATF 的smc会检查 OENOwner ID和 service但自定义服务里经常出现“fid 大于某个阈值就允许”的宽松判断这种要重点找。参数个数和大小是否匹配比如一个处理 buffer 的 SMC 服务只传了 buffer 地址没传长度或者长度可以从普通世界任意指定这就有越界读写风险。普通世界是否可以调用安全世界的 handlerATF 在SMC_RET1返回时会根据调用方的 safety state 做判断但如果你在自定义服务里手动解析参数并触发了 TEE 的调用要确认这个路径不能从 NS 侧直接构造。是否有用户态可访问的 debug 后门ENABLE_DEBUG开着的时候BL31 会开额外的 SMC 服务比如安全调试接口。产品发布时这类接口必须关掉。4.3 常见漏洞模式与缓解机制ATF 本身在持续修复 CVE我基于公开的漏洞报告总结了几类常见问题漏洞类型典型场景审计关注点整数溢出镜像大小计算时size offset溢出导致越界拷贝检查所有涉及 size/offset 的计算是否用 64 位安全数学参数类型混淆SMC 入参从 32 位/64 位寄存器里取数据类型转换不当严格按调用约定解析x1~x7不要用通用 void *不安全的内存映射把安全内存映射成可写且在 NS 世界可见检查 xlat table 的映射属性和 PSCI 下的内存隔离未定义行为对敏感内存的缓存维护策略错误导致脏数据泄漏检查clean/invalidate顺序特别是 DMA buffer 是否 in-place 执行缓解机制方面ATF 支持很多硬件安全特性ARMv8.3 的 PAuth 防止 ROP/JOP 攻击MTE 可以做内存标记以及 SVE/SME 的上下文切换在某些场景也需要 EL3 参与。ATF 还支持 secure partitionsFF-A这块为未来更多安全组件入住 EL3 提供了标准接口。做安全审计时不要只看能不能跑通还要看 SoC 支持哪些架构扩展把能打开的硬件缓解机制尽量打开。4.4 安全审计工具推荐和使用姿势看 ATF 的源码审计我用下来比较顺手的组合是静态分析cppcheck和clang-tidy全量扫一遍重点看数组越界、未初始化变量、资源未释放。ATF 里有些历史代码风格老静态分析会出一堆 warning你需要写个白名单把已知安全的部分过滤掉集中看高危 warning。编译时 sanitizermake ENABLE_STACK_PROTECTORstrong和ENABLE_ASSERTIONS1打开跑一下功能测试可以快速暴露栈溢出和状态机问题。动态调试用 FVP 跑 EL3 代码并接入 GDB可以打断点在bl31_main、psci_cpu_on等关键函数逐步跟踪 SMC 调用路径。真机调试如果遇到死机优先看crash_reporting打印的 panic 信息和当前 PSTATE、LR 寄存器值。5. 常见问题与排查技巧实录最后这部分是我在实际移植和跑 ATF 过程中积累的排障记录按出现频率排序。5.1 编译阶段的高频问题问题1编译报 no rule to make target bl31/bl31.elf。这通常是你没有进入正确的构建目录或者指定的 PLAT 名称在plat/下找不到对应目录。检查是否有plat/vendor/platform/platform.mk文件且make PLATxxx里的 xxx 和目录名完全一致。**问题2链接时报 undefined reference toplat_get_console**。这是因为平台代码缺少必须实现的外部函数。基本法则是ATF 编译时每个平台要满足编译框架的接口如果你实现了plat/common/plat_common.c但遗漏了某个函数链接阶段就会暴露出来。遇到 undefined reference优先去对应平台的参考实现里核对一下接口列表。问题3使用 Arm Compiler 6 (AC6) 编译时出现内嵌汇编报错。AC6 和 GCC 的汇编语法有差异ATF 虽然支持 AC6但有些老代码只适配了 GCC 风格。一个稳妥的做法是全部用 C 封装替换内嵌汇编或者暂时切换 GCC 工具链跑通PR 给社区时再适配 AC6。5.2 运行阶段的常见问题问题4BL1 能打印但 BL2 没有任何输出。这通常是 BL2 镜像加载失败或者 BL2 的串口重新初始化失败。优先检查 BL2 的加载地址是否正确flash 读取函数有没有从正确偏移读到数据以及 BL2 的.bss段是否被清零。清 BSS 是启动代码的必备动作你可以在el3_entrypoint_common前后打断点确认。问题5BL31 死机但没有任何 panic 信息。这是最让人抓狂的情况。如果连 panic 打印都没有说明 BL31 在执行早期代码初始化异常向量表、MMU 之前就崩溃了。排查方向有BL31 的加载地址是否超过了它的运行地址范围链接脚本里的 RAM 布局是否正确plat_setup里有没有关掉中断或者非法访问外设我做过一个最快的定位技巧在 BL31 的bl31_main()入口处加一个 GPIO 翻转或串口打印确认 BL31 有没有执行到主函数。如果入口都没到预判是 BL31 镜像本身没有正确跳转进来检查启动地址和 BL2 loaded image 的描述符如果入口到了但后续死机可以用二分注释代码的方法缩小范围先注释掉 PSCI 相关初始化再注释掉中断初始化直到定位到具体某个bl31_plat_setup里的外设配置。问题6每次system_suspend恢复失败。这类问题十有八九是 PSCI 的电源域状态描述和 SoC 实际电源域不一致。比如你把两个 CPU core 放在同一个电源域里但是代码里把它们当成独立域管理挂起时关了共享电源恢复时少唤醒了一个核。建议先用简单的psci命令cat /sys/devices/system/cpu/cpu0/hotplug/state之类逐个触发 on/off观察返回值和 ATF 日志。5.3 调试技巧ATF 日志和崩溃报告到底怎么读ATF 的日志分为 INFO、NOTICE、WARNING、ERROR 和 VERBOSE 几级编译时用LOG_LEVEL40可以带出详细日志默认的LOG_LEVEL20在 release 里是几乎不输出。调试时用DEBUG1 LOG_LEVEL50崩溃时可以打印出非常详细的 CPU 状态包括当前 EL、SCTLR 值、远跳转地址等。崩溃报告里最关键的是PANIC at PC : 0x...和Current EL3 PC 0x...这两行。PC 地址配合bl31.elf用aarch64-none-elf-addr2line -e bl31.elf 0x...转成源码行号十秒定位。这个方法比看着 GDB 调试省很多时间。5.4 避坑技巧我移植这么多平台总结的“三板斧”第一复制一个参考平台而不是从零开始。Arm 官方 FVP 平台和某厂商真实平台比如plat/xilinx/zynqmp、plat/nvidia/tegra的实现都很规整找和你 SoC 架构最接近的参考实现copy 一份再改比空手开写快得多而且不容易漏掉 ATF 强制要求的接口。第二每次只改一个变量。移植时经常出现“改了一堆内存映射又加了 GIC 配置又改了时钟”然后一步到位测试的场景这样一旦失败根本不知道是哪个改动导致的。正确姿势是每次只改一个平台参数编译烧录验证通过之后再做下一个。虽然流程慢一点但总时间一定更短。第三先关闭安全启动再开安全启动。先TRUSTED_BOARD_BOOT0跑通启动链再逐步加证书和验签。这能帮你把“启动流程问题”和“证书链问题”彻底隔离开否则两个问题叠在一起非常难排查。同理OP-TEE 也是可以先不接先跑纯 ATF U-Boot Linux再接 TEE。收尾一点个人体会最后分享一个经验。ATF 的平台移植真正花时间的不是 C 代码本身而是对 SoC 的深入了解内存映射里哪些地址是 secure 的电源域怎么划分GIC 路由表怎么配这些完全依赖 TRM。代码层面 ATF 已经把架构框架搭好了你需要做的只是往里面填参数、填回调。也因此我会建议每一个做 ARM64 平台的人都至少完整做一个 ATF 移植哪怕只是基于 FVP 模拟平台跑一遍这个过程能让你对整个系统的安全边界和启动时序有完全不一样的认识。等你以后再碰到“系统起来以后莫名其妙死机”或者“OP-TEE 起不来”的问题你会第一时间想到去 EL3 里找答案而不是在内核日志里瞎转圈。
返回列表