1. 固件镜像基础:它到底是什么?

做嵌入式这么多年,我经常被刚入行的同事问一个问题:「固件镜像不就是一堆二进制数据吗?」
嗯,这么说也没错。但如果你只把它当成一堆数据,那后面遇到校验失败、启动卡死、OTA 变砖的时候,你连排查的方向都没有。

说白了,固件镜像就是嵌入式系统的「灵魂载体」。硬件是躯壳,固件是意识。没有镜像,芯片就是一块死硅片。

1.1 固件在嵌入式系统里的角色

你想想看,一个嵌入式设备从按下电源键到正常运行,中间发生了什么?

  • BootROM 先上电,它是个固化在芯片内部的小程序
  • BootROM 去指定的位置(比如 SPI Flash 的 0x0 地址)加载固件镜像
  • 校验通过后,把控制权交给镜像里的启动代码
  • 启动代码初始化硬件、配置时钟、搬运数据段……最终跳转到 main()

整个过程里,固件镜像就是那个被加载、被校验、被执行的对象。我在项目中遇到过好几次,因为镜像头部校验字段被意外修改,导致 BootROM 直接拒绝加载——设备变砖,连串口都打印不出任何信息。

核心认知:固件镜像不是「文件」,它是芯片上电后第一条指令的源头。镜像坏了,系统连「报错」的能力都没有。

1.2 镜像文件的常见格式

实际工作中,我们主要跟三种格式打交道:bin、hex、elf。它们的区别,我习惯用一个表格来对比:

格式 全称 包含信息 是否可执行 典型用途
bin Binary 纯二进制数据,无地址信息 是(烧录后) 量产烧录、OTA 升级包
hex Intel HEX 二进制数据 + 地址 + 校验和 否(需解析) 烧录器输入、调试阶段
elf Executable and Linkable Format 代码 + 数据 + 符号表 + 调试信息 否(需加载器) 编译输出、调试器输入

我个人习惯这样区分:elf 是给调试器看的,hex 是给烧录器看的,bin 是给芯片直接吃的。

1.3 深入看看每种格式

bin 格式

最纯粹的格式。没有头部、没有元数据、没有校验——就是你要烧录到 Flash 里的原始字节流。
我记得第一次做量产工具时,踩过一个坑:bin 文件没有地址信息,烧录器必须事先知道目标地址。如果你把 bin 烧到了错误的位置,芯片启动时读到的全是乱码,根本跑不起来。

// 一个典型的 bin 镜像布局(STM32 为例)
地址 0x08000000: 栈顶指针 (MSP)
地址 0x08000004: 复位向量 (Reset_Handler)
地址 0x08000008: NMI 向量
...
地址 0x08001000: .text 代码段
地址 0x08005000: .rodata 只读数据段
地址 0x08006000: .data 已初始化数据段

小技巧:用 hexdump 或 xxd 查看 bin 文件时,注意前 4 个字节。如果是 ARM Cortex-M 系列,前 4 字节应该是栈顶地址,通常接近 RAM 的顶端。如果看到 0xFFFFFFFF 或 0x00000000,那这个镜像大概率是空的或损坏的。

hex 格式

Intel HEX 是文本格式,每行以冒号开头,包含长度、地址、类型、数据和校验和。它比 bin 多了一层保护——每行都有校验和,烧录器可以逐行验证数据完整性。

:1000000000000020A5010008B1010008C1010008F0
:10001000C1010008C1010008C1010008C1010008C0
:10002000C1010008C1010008C1010008C1010008B0
:0400000508000000F7
:00000001FF

上面这个例子,第一行表示从地址 0x0000 开始,16 个字节的数据。最后一行是文件结束记录。
我曾经在产线上遇到过烧录器报「校验和错误」,排查了半天,发现是 hex 文件里某一行被文本编辑器多插了一个空格——烧录器解析时把数据读歪了。

注意:hex 文件是纯文本,不要用 Word 或带格式的编辑器打开修改。用 Notepad++、VS Code、Sublime 这类纯文本工具。我曾经见过有人用 Word 打开 hex 文件保存后,文件体积膨胀了 3 倍——烧录器直接不认。

elf 格式

elf 是编译器的最终输出,结构最复杂。它包含多个 section(.text、.data、.bss、.rodata……),还有符号表、重定位信息、调试信息。
调试器(比如 J-Link、ST-Link)通过 elf 文件知道「变量 x 在哪个地址」「函数 foo 的代码在哪里」。

但注意:elf 不能直接烧录到 Flash。你需要用 objcopy 把它转换成 bin 或 hex:

# 将 elf 转换为 bin
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin

# 将 elf 转换为 hex
arm-none-eabi-objcopy -O ihex firmware.elf firmware.hex

我见过不少新手直接拿 elf 文件去烧录,烧录器要么报错,要么只烧了部分数据——因为 elf 里有很多 Flash 里不需要的调试信息。

1.4 三种格式的关系——一张图说清楚

下面这张图展示了从源码到最终烧录的完整链路:

固件镜像生成与使用流程 C / 汇编 源码 编译器 (arm-none-eabi-gcc) firmware.elf 调试器 (GDB / J-Link) objcopy 转换 firmware.bin firmware.hex 烧录到 Flash 烧录到 Flash 说明 elf 用于调试,不可直接烧录 bin/hex 用于烧录,不含调试信息

从这张图你可以看到:elf 是「母版」,bin 和 hex 是「分发格式」。调试时用 elf,量产时用 bin 或 hex。

1.5 为什么理解这些对「校验异常排查」很重要?

好,回到我们的课程主题。为什么第一章要先讲这些基础?

  • 校验算法作用在 bin 上:CRC、SHA256 这些校验值,是对纯二进制数据计算的。如果你拿 hex 文件去算校验,结果会包含地址信息和冒号——跟芯片端算出来的完全对不上。
  • 镜像格式决定了校验位置:有些方案把校验值放在镜像头部(前 32 字节),有些放在尾部。如果你不清楚镜像结构,根本不知道去哪里读校验值。
  • elf 里的段信息能帮你定位问题:当校验失败时,通过 elf 的符号表可以快速定位到出错的函数或数据段,而不是在二进制流里大海捞针。

一句话总结:不懂镜像格式,校验异常排查就是盲人摸象。这一章打下的基础,后面每一章都会用到。