2. Boot ROM 的硬件基础:复位向量、异常向量表、CPU 启动模式

芯片上电那一刻,CPU 到底在干什么?

很多人觉得 Boot ROM 就是个黑盒子,上电后自动跑一段代码。其实没那么玄乎。CPU 能准确找到第一条指令,全靠硬件里几个固定的“锚点”。

这一节,我们就来拆开这些锚点。复位向量、异常向量表、CPU 启动模式,这三样东西构成了 Boot ROM 的硬件地基。地基不稳,后面的安全启动全是空中楼阁。

2.1 复位向量:CPU 的第一口气

CPU 上电或复位后,PC(程序计数器)会被硬件强制设为一个固定值。这个值指向的地址,就是复位向量。

说白了,复位向量就是 CPU 睁开眼看到的第一个地址。它通常存放在 Boot ROM 的最开头。

关键事实:复位向量里存的不是代码,而是一条跳转指令(如 B Reset_Handler)或一个地址值。CPU 拿到这个地址后,才会跳转到真正的启动代码。

我在项目中遇到过一个问题:某款芯片复位后 PC 指向 0x00000000,但 Boot ROM 实际映射在 0x00100000。硬件团队忘了做地址重映射,结果 CPU 读到的全是 0xFF,直接跑飞。嗯,这种坑,硬件和软件各打五十大板。

不同架构的复位向量位置不太一样:

架构 复位向量地址 存放内容
ARM Cortex-M 0x00000000 栈顶指针 + 复位向量(地址)
ARM Cortex-A 0x00000000 或 0xFFFF0000 跳转指令
RISC-V 0x80000000(典型) 跳转指令
x86 0xFFFFFFF0 跳转指令

避坑指南:我曾经在移植 U-Boot 时,发现复位向量指向的地址空间是只读的。硬件团队把 Boot ROM 放在了 Flash 里,但复位向量映射到了 RAM 区域。结果 CPU 一跳转就访问了空地址。所以,复位向量的物理映射必须在芯片设计阶段就确认好。

2.2 异常向量表:中断与异常的“电话本”

复位向量只是异常向量表里的第一个条目。完整的异常向量表,记录了所有异常和中断的处理入口地址。

你想想看,CPU 在运行过程中,随时可能遇到各种异常:

  • 硬件故障(比如访问了不存在的地址)
  • 软件触发(比如系统调用)
  • 外部中断(比如按键按下)

CPU 怎么知道该跳到哪里去处理?答案就是查表——异常向量表。

这张表通常放在 Boot ROM 的固定偏移位置。以 ARM Cortex-M 为例:

; 异常向量表示例(Cortex-M)
0x00000000: 栈顶地址
0x00000004: 复位向量
0x00000008: NMI(不可屏蔽中断)
0x0000000C: HardFault
0x00000010: MemManage
0x00000014: BusFault
0x00000018: UsageFault
...
0x00000040: SVCall
0x00000044: PendSV
0x00000048: SysTick
0x0000004C: 外部中断 0
0x00000050: 外部中断 1
...

我个人习惯把异常向量表叫做“CPU 的紧急联系人名单”。每个异常都有固定的优先级,硬件会自动判断先处理谁。

注意:异常向量表的大小和布局是架构强制的。你不能随意调整条目顺序,也不能跳过某些条目。否则 CPU 会找不到对应的处理函数。

我在调试一个安全启动方案时,遇到过一件怪事:芯片偶尔会在启动过程中触发 HardFault。查了三天,最后发现是异常向量表里 HardFault 的入口地址写错了,指向了一个未初始化的内存区域。CPU 一进异常就二次异常,直接死循环。嗯,这种问题,调试器都很难抓到。

2.3 CPU 启动模式:从哪来,到哪去

CPU 启动模式,决定了复位后从哪个物理介质加载第一条指令。常见的启动模式有:

  • Boot ROM 启动:从芯片内部固化的 ROM 启动,最安全,不可篡改。
  • Flash 启动:从外部或内部 Flash 启动,速度快,但容易被篡改。
  • RAM 启动:从 SRAM 或 DRAM 启动,通常用于调试或二次加载。
  • 串行接口启动:从 UART、SPI、I2C 等接口加载,常用于工厂烧录。

启动模式的选择,通常由芯片的启动引脚(Boot Pin)决定。芯片上电时,硬件会采样这些引脚的电平,然后决定从哪个地址开始执行。

举个例子:某款 SoC 有两个启动引脚 BOOT0 和 BOOT1。

BOOT1 BOOT0 启动模式
0 0 Boot ROM
0 1 Flash
1 0 RAM
1 1 串行接口

我曾经在一个物联网项目里,因为启动引脚虚焊,导致芯片每次上电都随机进入不同模式。有的板子能正常启动,有的直接卡死。排查了整整两天,最后用示波器量引脚电平才发现问题。嗯,硬件可靠性,往往就毁在这些小细节上。

2.4 三者关系:一张图说清楚

复位向量、异常向量表、CPU 启动模式,这三者不是孤立的。它们共同构成了 Boot ROM 的硬件执行环境。

下面这张图,展示了芯片上电后的完整流程:

Boot ROM 硬件基础流程图 芯片上电 → Boot ROM 启动流程 1. 芯片上电 / 复位 2. 采样启动引脚 (Boot Mode) 3. 读取复位向量地址 4. 跳转到 Boot ROM 代码 5. 初始化异常向量表 说明:异常向量表通常由 Boot ROM 代码在启动早期设置,确保后续异常能被正确捕获

这张图里,我特意把“采样启动引脚”放在了第二步。为什么?因为复位向量本身是固定的,但 CPU 从哪个物理地址读取复位向量,是由启动模式决定的。换句话说,启动模式决定了复位向量的“物理映射目标”。

个人经验:我在设计安全启动方案时,会把 Boot ROM 启动模式设为默认模式,并且硬件上把启动引脚拉死。这样即使攻击者想通过修改引脚电平来切换启动模式,也无法绕过 Boot ROM 的安全校验。

2.5 常见陷阱与避坑指南

做 Boot ROM 开发,有几个坑我几乎每次都会遇到:

  • 陷阱一:复位向量指向了未初始化的内存。我曾经在调试时,发现复位向量指向的地址空间被 Boot ROM 代码自己覆盖了。结果 CPU 跳转过去,读到的全是垃圾指令。解决方案:复位向量必须指向只读区域,或者在跳转前确保目标内存已初始化。
  • 陷阱二:异常向量表没有对齐。有些架构要求异常向量表必须按 256 字节或 1KB 对齐。如果没对齐,CPU 查表时会算错偏移,直接跳转到错误地址。嗯,这种问题,查起来非常痛苦。
  • 陷阱三:启动引脚电平不稳定。芯片上电瞬间,引脚电平可能还没稳定。如果硬件没有做去抖处理,CPU 可能采样到错误值,进入错误的启动模式。解决方案:在 Boot ROM 代码开头,重新读取启动引脚状态,并做多次采样确认。

重要提醒:Boot ROM 是芯片上电后第一个执行的代码,没有任何调试器或操作系统可以依赖。所以,复位向量和异常向量表的正确性,必须在芯片设计阶段通过仿真验证。不要等到流片回来再改,那代价太大了。

好了,这一节的内容就到这里。复位向量、异常向量表、CPU 启动模式,这三样东西是 Boot ROM 的硬件基石。理解它们,你才能明白 Boot ROM 代码为什么那样写,为什么有些地址不能动,为什么有些模式不能乱选。