3. 启动代码与启动链认知

复杂启动故障之所以难定位,根本原因在于“启动”并不是一段连续执行的代码,而是一条由多个独立固件阶段接力构成的链。每一棒交接时,CPU 的执行环境、可用内存、时钟、串口、调试接口都可能发生跳变。如果调试者只把系统当成“上电后跑一个程序”,就会在阶段边界处丢失线索。因此,本节先建立启动链的完整认知:谁把控制权交给谁、每一阶段运行在哪里、有哪些可观测信号、TRACE32 在哪些窗口可以介入。

一、启动链的五个阶段与交接关系

以典型的 ARM64 SoC(如带 BootROM 的嵌入式平台)为例,从上电到内核大致经过五个阶段。第一阶段是上电复位与 BootROM:CPU 从复位向量取指,运行固化在芯片内部 ROM 中的代码。BootROM 的职责非常有限——初始化最小必要的时钟与存储接口,然后按启动模式(eMMC、SD、SPI NOR、UART 下载等)去固定偏移处读取下一级镜像。它通常不初始化 DDR,因此这一阶段可用的 RAM 只有芯片内部的 SRAM(常见 128KB~256KB 量级)。

第二阶段是 SPL(Secondary Program Loader)或 TPL/SPL 组合。BootROM 把 SPL 载入片内 SRAM 并跳转执行。SPL 的核心任务是初始化 DDR 控制器、完成内存训练,然后把完整的 U-Boot 搬到 DDR 中。第三阶段是 U-Boot 主程序(proper),运行在 DDR 中,负责更完整的外设初始化、环境变量处理、镜像校验与加载,最后把内核镜像和设备树搬到内存并跳转。第四阶段是内核解压与早期初始化,第五阶段是内核子系统与用户空间接管。

关键认知是:每一次交接都是一次“执行环境切换”。BootROM→SPL 时 PC 从 ROM 跳到 SRAM;SPL→U-Boot 时从 SRAM 跳到 DDR;U-Boot→内核时从 U-Boot 的地址空间跳到内核加载地址。故障往往就发生在这些跳转点附近——镜像没搬对、DDR 没训练成功、跳转地址算错、设备树不匹配。

启动链五阶段:执行位置、交接点与 TRACE32 介入窗口 ① BootROM 片内 ROM 固化代码 运行于 SRAM ② SPL / TPL DDR 初始化与训练 运行于 SRAM ③ U-Boot proper 外设/镜像/环境变量 运行于 DDR ④ 内核解压 自解压 + 早期初始化 运行于 DDR ⑤ 内核/用户态 子系统与 init 运行于 DDR 跳转 跳转 跳转 跳转 可观测信号 复位释放 / 时钟起振 启动模式引脚电平 ROM 串口打印(若有) DDR 训练日志 SPL 串口 banner DDR 控制器寄存器 U-Boot 版本与命令 环境变量 / bootargs 镜像加载地址与校验 解压进度输出 earlycon 打印 设备树解析结果 内核 loglevel init 进程启动 根文件系统挂载 TRACE32 介入窗口 复位向量断点 片内 SRAM 读写 启动模式寄存器查看 SPL 入口断点 DDR 寄存器单步 训练失败点定位 U-Boot 符号加载 bootm 流程断点 内存镜像比对 内核入口断点 解压前后内存对比 设备树地址检查 内核符号调试 异常向量捕获 panic 现场回溯 阶段边界是故障高发区: BootROM→SPL:镜像偏移/校验错误、SRAM 容量不足;SPL→U-Boot:DDR 训练失败、搬运地址越界; U-Boot→内核:bootargs 错误、设备树不匹配、加载地址冲突。TRACE32 应在每个边界前后各设一个观察点。

二、各阶段的可观测信号与判读方法

BootROM 阶段最容易被忽视,但它决定了“系统是否愿意启动”。可观测信号包括:复位释放时序、主时钟是否起振、启动模式引脚(BOOT_MODE[3:0] 之类)的电平组合、以及部分 SoC 在 ROM 中预留的串口输出。如果串口完全无输出,先确认启动模式引脚是否被误拉高/拉低,再确认 ROM 是否真的执行——此时 TRACE32 的复位向量断点是最直接的证据。

SPL 阶段的核心是 DDR 初始化。DDR 训练失败时,串口通常打印类似 “DDR training failed” 或直接卡死。可观测信号包括 DDR 控制器的训练状态寄存器、PHY 的延迟参数、以及训练日志中的眼图余量。TRACE32 可以在这里读取 DDR 控制器寄存器,判断是训练超时、还是某条 lane 的延迟校准失败。

U-Boot proper 阶段的可观测信号最丰富:版本 banner、环境变量、bootargs、镜像加载地址、校验结果。常见故障是 bootargs 中的 root= 指向错误设备,或设备树与内核版本不匹配。TRACE32 加载 U-Boot 符号后,可以在 bootm/booti 流程中设断点,检查镜像头、加载地址和跳转前的寄存器状态。

内核解压与早期初始化阶段,可观测信号是解压进度和 earlycon 输出。如果解压后无任何打印,可能是解压目标地址与 U-Boot 自身占用区域重叠,或设备树地址被覆盖。TRACE32 可以在内核入口设断点,对比解压前后的内存内容,确认镜像是否完整。

三、TRACE32 介入窗口的选取原则

TRACE32 的介入不是“越早越好”,而是“在阶段边界前后各设一个观察点”。具体原则如下:

  • 复位向量断点:确认 CPU 是否从正确地址取指,排除启动模式与复位电路问题。
  • SPL 入口断点:确认 BootROM 是否成功搬运并跳转,检查 SRAM 中的镜像完整性。
  • DDR 训练后断点:确认 DDR 是否可用,读取训练寄存器判断失败原因。
  • U-Boot 跳转内核前断点:检查加载地址、设备树地址、bootargs 和寄存器传参。
  • 内核入口断点:确认内核镜像完整、解压目标正确、设备树未被破坏。
在实操中,建议先用 TRACE32 的 SYStem.Mode Attach 或 SYStem.Up 连接目标,再通过 Break.Set 在关键地址设断点。对于 BootROM 阶段,由于符号不可用,通常使用物理地址断点;进入 U-Boot 后,加载 u-boot.elf 符号即可按函数名设断点。

四、一个典型的启动链观察示例

假设某 ARM64 平台在 U-Boot 后无内核输出。按启动链逐段排查:首先确认 BootROM 串口有输出,说明 ROM 已执行;其次确认 SPL 打印了 DDR 训练成功信息,说明 DDR 可用;然后在 U-Boot 中执行 bdinfo 查看加载地址,确认 kernel_addr_r 与 fdt_addr_r 不重叠;接着用 TRACE32 在 booti 跳转前设断点,读取 X0(设备树地址)、X1(内核入口)等寄存器;最后在内核入口设断点,若命中则说明跳转成功,问题在内核早期初始化;若未命中,则问题在 U-Boot 的镜像加载或跳转逻辑。

这个示例说明:启动链认知的价值在于把“系统不启动”这个大问题,拆解成“哪一棒没交接好”的小问题。每一阶段都有其专属的可观测信号和 TRACE32 介入方式,只有建立完整的链路视图,才能在复杂故障中快速缩小范围。