4. 工具架构设计

量产固件管理,说白了就是回答三个问题:用什么格式存、怎么保证安全、怎么管版本。这三个问题搞不定,产线就会乱成一锅粥。我见过太多团队,固件格式混用、签名缺失、版本号乱标,最后烧录出来的板子要么跑不起来,要么功能对不上。

这一章,咱们就把这些坑一个个填上。

4.1 镜像格式:Bin、Hex、ELF 怎么选

先看最基础的问题——固件该存成什么格式?

三种主流格式,各有各的用途。我直接给你一张对比表,看完就明白。

格式 包含内容 典型用途 产线适用性
Bin 纯二进制数据,无地址信息 Flash 直接烧录、OTA 升级包 ★★★★★ 最常用
Hex (Intel HEX) 二进制数据 + 地址 + 校验 烧录器直接使用、小批量调试 ★★★★☆ 适合分段烧录
ELF 代码 + 数据 + 符号表 + 调试信息 调试、反汇编、分析 ★★☆☆☆ 产线几乎不用

产线铁律:量产烧录只用 Bin 格式。Hex 虽然带地址,但解析效率低;ELF 体积大、信息冗余,产线工具处理起来容易出问题。

为什么我这么强调 Bin?

有一次,某团队用 Hex 格式做量产。烧录器每烧一行就要解析一次地址,速度慢不说,还因为某行校验码算错,导致整批板子烧录失败。后来换成 Bin 格式,速度提升 3 倍,问题再没出现过。

不过,Hex 也不是一无是处。如果你的固件需要烧录到多个不连续地址段(比如 Bootloader 在 0x08000000,App 在 0x08020000),Hex 天然支持分段,比 Bin 方便。但量产时,我建议还是把多个段合并成一个 Bin,用烧录工具指定偏移地址。

格式转换实战

你手头可能只有 ELF 文件,怎么转成 Bin?

# 从 ELF 提取 Bin(最常用)
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin

# 从 ELF 生成 Hex
arm-none-eabi-objcopy -O ihex firmware.elf firmware.hex

# 从 Bin 转 Hex(需要指定基地址)
arm-none-eabi-objcopy -I binary -O ihex --change-addresses 0x08000000 firmware.bin firmware.hex

我的习惯:在 Makefile 或 CMake 里同时生成 Bin 和 Hex。Bin 给产线用,Hex 给调试用。这样两边都不耽误。

4.2 镜像打包与签名:给固件上把锁

裸固件直接烧录?太危险了。

产线上,任何人都可能拿到你的固件文件。如果没有签名,别人改一个字节,烧进去就是一颗炸弹。更别说 OTA 升级时,中间人攻击可以轻松植入恶意固件。

所以,签名不是可选项,是必选项。

签名流程

我常用的签名方案分三步:

  1. 计算哈希:对固件 Bin 做 SHA-256 或 SHA-512
  2. 私钥签名:用 RSA 或 ECDSA 私钥对哈希加密
  3. 打包:将签名附加到固件尾部或头部

看一个实际例子:

# 1. 计算固件哈希
sha256sum firmware.bin > firmware.hash

# 2. 用私钥签名(假设使用 OpenSSL)
openssl dgst -sha256 -sign private.pem -out firmware.sig firmware.bin

# 3. 打包:将签名附加到固件末尾
cat firmware.bin firmware.sig > firmware_signed.bin

烧录时,Bootloader 会做反向操作:

  • 读取固件主体,计算哈希
  • 用公钥解密签名,得到原始哈希
  • 对比两个哈希,一致则通过

我曾经踩过的坑:签名打包时,把签名放在了固件头部。结果 Bootloader 解析时,要先跳过签名头才能读到固件入口地址。后来我改成把签名放在尾部,Bootloader 先校验固件主体,再读尾部签名,逻辑清晰多了。

打包格式设计

一个完整的固件包,不止是固件+签名。我建议包含以下字段:

字段 长度 说明
Magic Number 4 字节 0xDEADBEEF 之类的标识
固件版本 4 字节 主版本+次版本+修订号
固件长度 4 字节 固件主体字节数
固件类型 1 字节 0x01=Bootloader, 0x02=App, 0x03=数据
校验算法 1 字节 0x01=SHA256, 0x02=SHA512
签名算法 1 字节 0x01=RSA2048, 0x02=ECDSA256
固件数据 可变 实际固件内容
签名值 可变 RSA 256 字节 / ECDSA 64 字节

建议:Magic Number 一定要选不常见的值。我见过有人用 0x12345678,结果和某些数据包冲突了。用 0xDEADBEEF 或 0xFEEDFACE 这种,基本不会撞车。

4.3 多版本固件管理策略

产线不是只烧一个版本。今天烧 V1.0,明天可能烧 V1.1,后天可能同时烧 V1.0 和 V2.0(不同产品线)。怎么管?

我总结了一套策略,核心就四个字:目录 + 清单。

目录结构

每个固件版本一个独立目录,命名规则统一:

firmware/
├── v1.0.0/
│   ├── bootloader_signed.bin
│   ├── app_signed.bin
│   ├── manifest.json
│   └── release_notes.txt
├── v1.1.0/
│   ├── bootloader_signed.bin
│   ├── app_signed.bin
│   ├── manifest.json
│   └── release_notes.txt
├── v2.0.0/
│   ├── bootloader_signed.bin
│   ├── app_signed.bin
│   ├── manifest.json
│   └── release_notes.txt
└── latest -> v2.0.0/

latest 是一个软链接,指向当前最新版本。产线工具默认读取 latest 目录,省去手动指定版本的麻烦。

清单文件 (Manifest)

每个版本目录下放一个 manifest.json,记录关键信息:

{
  "version": "1.1.0",
  "build_date": "2024-03-15",
  "git_commit": "a3f2b8c1",
  "files": [
    {
      "name": "bootloader_signed.bin",
      "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
      "target_address": "0x08000000"
    },
    {
      "name": "app_signed.bin",
      "sha256": "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a",
      "target_address": "0x08020000"
    }
  ],
  "signature": "base64_encoded_signature_here"
}

产线工具读取 Manifest,自动校验哈希、检查签名、按地址烧录。整个过程不需要人工干预。

关键点:Manifest 本身也要签名。否则别人改了 Manifest 里的哈希值,就能绕过校验。我习惯对整个 Manifest 做一次签名,和固件签名分开管理。

版本选择策略

产线怎么决定烧哪个版本?我见过三种方式:

  • 固定版本:产线工具写死版本号,适合单一产品线
  • 配置文件:在产线 PC 上放一个 config.ini,指定版本路径
  • 扫码匹配:扫描产品条码,从数据库查询对应固件版本

我个人最推荐第三种。扫码匹配可以做到一机一版本,比如某批次芯片有硬件缺陷,需要烧录特殊固件,扫码后自动匹配,不会出错。

我曾经踩过的坑:用配置文件管理版本,结果操作员不小心改了路径,烧了旧版本固件。后来我改成扫码匹配,条码和固件版本绑定,操作员想改都改不了。

4.4 固件管理知识体系

下面这张图,把本章的核心逻辑串起来了。你一看就明白。

固件镜像管理知识体系 镜像格式 镜像打包与签名 多版本管理 Bin 格式 Hex 格式 ELF 格式 哈希计算 私钥签名 目录结构 Manifest 产线烧录文件 OTA 升级包 核心原则 格式统一用 Bin,签名必须做,版本用目录+Manifest 管理 产线工具自动校验,扫码匹配版本,杜绝人为错误

嗯,到这里,固件镜像管理这块就讲完了。格式选 Bin,签名不能省,版本用目录+清单管起来。这三板斧砍下去,产线固件管理基本不会出大问题。