☰
嵌入式固件处理利器:Srecord 核心命令与 CRC 校验实战
2026/9/28 1:43:35 网站建设 项目流程

做嵌入式开发的老哥们,估计都有过这种经历:编译完固件,拿到一个.hex或者.s19文件,然后就得想办法处理它——有的要合并两个固件,有的要裁剪出某个地址段,有的要补上空白区域,还有的要往固定偏移塞一个 CRC 校验值。以前我都是拿 Python 手撸脚本,Hex 文件简单还好说,一旦遇到 S19 这种带地址长度分级的格式,再赶上换行符、校验和的问题,脚本改到天亮是常有的事。后来换了 Srecord 这套命令行工具,才算是把这块彻底理顺了。Srecord 是个专门处理 SREC、Intel HEX、二进制等格式文件的工具集,核心命令就三个——srec_cat、srec_info、srec_cmp,但组合起来能干的活非常多,从裁剪、拼接、填充,到生成 CRC、转格式、比对差异,全都能用一条命令解决。

这篇东西不打算照着官方手册念,我按自己从零上手到写进构建脚本的过程来写,把最常用的操作、容易踩的坑、还有命令背后为什么要这么写,都一并讲清楚。无论你用的是 STM32、NXP 还是瑞萨的平台,只要你的编译产物需要被做“二次加工”,这篇文章就能帮上忙。

1. 为什么嵌入式项目需要 Srecord 这类文件处理工具

1.1 从编译产物到烧录镜像,中间缺一环

先说个扎心的事实:Keil、IAR 或者 GCC 工具链生成的.hex、.s19文件,绝大多数情况下不能直接拿去烧录。不是格式坏了,而是它跟你产品量产时的需求之间,往往隔着一堆“加工步骤”。

举几个我实际遇到过的场景:

  • 多区域合并:Bootloader 和 App 是分开编译的,产出两个独立 hex,量产时要么用烧录软件去拼接、要么先手动合并成一个镜像。用 Srecord 一句话就完成,还不会出现地址重叠的问题。
  • 空白区域填充:Flash 中未被代码覆盖的区域,如果随机值,读回来的数据既不美观、也影响校验。量产固件一般会统一填成0xFF或者0x00,这活儿让 Srecord 干最顺手。
  • 附加校验信息:很多 Bootloader 会要求 App 末尾或者头部某固定地址放一个 CRC 值,用来做完整性校验。编译阶段拿不到这个值,只能在拿到链接产物后追加,然后重新生成 hex。
  • 格式转换:调试器、烧录器、产测工具支持的格式不一样,有的要.bin、有的要.hex、有的偏要.s19,不可能每次让同事重编一遍工程。

这一类需求,本质上是“构建后处理”。Srecord 就是专门干这个的,它不参与编译、不参与链接,只做输出文件的加工。理解了这层定位,你就能明白为什么我建议把它放到每个嵌入式项目的构建流程里,而不是等到要发版了,再临时找个工具手动点来点去。

1.2 为什么选 Srecord 而不是写脚本

有人可能会说,这种活用 Python 写脚本也就几十行,为什么非要引入一个额外工具?

我这里说句公道话:脚本当然能做,但做不“全”。SREC 格式远比很多人想得复杂,它分为 S0/S1/S2/S3 等多条记录,地址长度不一样;数据的校验和算法虽然简单,但不同工具对格式的容忍度不同。你自己写脚本,处理自己工程里的文件没问题,一旦要跟别的部门、别的厂商、别的烧录器对接,各种兼容性问题会消耗掉你大量的时间。

Srecord 是一个有十多年历史的开源工具,从 SourceForge 时代活到了现在。它内置了对 SREC、Intel HEX、Motorola S-record、二进制、Verilog、Tektronix 等格式的读写支持,地址范围计算、重叠检测、CRC 生成都是现成的。而且它的命令行参数设计非常克制,没有花哨交互,纯文本,天然适合集成到 Makefile、CMake 和 CI 脚本里。

另外,用命令处理还有一个隐藏好处:可审计、可复现。今天你手动点了三下鼠标完成拼接,明天同事问你镜像里 CRC 是怎么算的,你可能要想半天;但如果是 Makefile 里写的一条srec_cat命令,任何人 checkout 代码之后,一条make就能重新生成完全一致的固件。

2. 环境准备与一条命令入门

2.1 安装与基础救援

Srecord 的安装非常简单,Linux 各发行版基本都有现成包,macOS 用 Homebrew 也能直接装。Windows 下建议在 MSYS2 或 WSL 里用,原生 Windows 版本也有,但我个人觉得维护价值不高。

几个常见平台的安装方式我列一下:

# Debian / Ubuntu sudo apt-get install srecord # CentOS / RHEL sudo yum install srecord # macOS brew install srecord # MSYS2 (Windows) pacman -S mingw-w64-x86_64-srecord

装完之后,验证一下三个核心命令是否可用:

srec_info --version srec_cat --version srec_cmp --version

看到版本号就说明环境没问题。

2.2 第一次处理:查看文件信息

拿到一个 hex 文件,第一件事永远是“看看里面有什么”。我一般直接用srec_info这个命令:

srec_info firmware.hex

输出大概是这样的:

Motorola S-Record format: header: "MY_PROJECT" data: 0x08000000 - 0x0801FFFC address: 0x08000000 - 0x0801FFFC execution start address: 0x08000185 data: 0x08020000 - 0x0802FFFF address: 0x08020000 - 0x0802FFFF execution start address: 0x08020000

这几行信息能告诉你很多东西:

  • 文件格式:是 S-record(S19/S28/S37)还是 Intel HEX。
  • 地址范围:数据分布在哪些地址区间,有没有空洞。
  • 执行起始地址:有些链接器会把入口地址写进文件,这信息可以用于反汇编或调试器加载。

我拿到任何固件,都会先跑一遍srec_info,确认地址范围和分段情况对不对,再决定后续怎么做。这就像拆快递先看清单,省的后面操作时“货不对板”。

2.3 第一个常用操作:格式转换

以前需要把 hex 转 bin,我第一时间想到的是objcopy。但 objcopy 的默认行为是直接把数据连续铺开,如果 S-record 文件里有地址洞,它会帮你全部补零,这在某些场景下非常危险——你原本只写了 32KB 代码,但地址跨度是 128KB,转出来的 bin 文件就有 128KB,里面塞满了0x00,烧录或者分析时完全被误导。

用 Srecord 转换格式,可以先用-crop裁剪出实际范围,再转:

srec_cat firmware.hex -crop 0x08000000 0x08020000 -o firmware.bin -binary

这样转出来的.bin就是连续的、只包含实际数据区间的镜像。如果不加-crop直接转,Srecord 也会忠实地把空洞填成空白(默认 0xFF 或 0x00),但关键是你得心里有数。更多关于-crop和填充的细节,下一节展开。

3. 核心命令解析:srec_cat 里最容易搞混的三类操作

3.1 裁剪、填充与拼装的基本逻辑

Srecord 整套工具的设计哲学,是把“输入”和“输出”变成一条流水线。srec_cat这个名字看着像个“拼接命令”,但它实际上是那个最全能的“处理器”——它是根据你给的参数,把输入文件的内容按顺序处理后,再输出到目标文件。

我个人把它拆成三类核心操作来记,这样就不会乱:

操作类型命令参数作用常见场景
裁剪-crop 起始地址 结束地址只保留指定地址区间内的数据从整个 Flash 镜像中提取 Bootloader 段
填充-fill 填充值 起始地址 结束地址把指定地址区间内的空洞填上Flash 空白区统一填0xFF
偏移-offset 偏移值把数据整体移动地址把 App 从加载地址挪到运行时地址

这三个操作经常组合使用,顺序不同结果也可能完全不同。下面用一个实战例子把所有操作串起来。

假设我手里有一个app.hex,它的真实代码分布在0x08010000 - 0x0801F000,现在要做三件事:

  1. 把范围裁剪成0x08010000 - 0x0801F000,排除掉头部的一些配置信息;
  2. 把这段范围里未被占用的空隙全部填充为0xFF;
  3. 把整段数据搬移到0x08020000开始的地址上。

对应的命令是:

srec_cat app.hex \ -crop 0x08010000 0x0801F000 \ -fill 0xFF 0x08010000 0x0801F000 \ -offset 0x00010000 \ -o app_relocated.s19

这条命令执行完,就会生成一个地址范围在0x08020000 - 0x0802F000的连续 S19 文件。注意-offset的正负号,正值往后挪,负值往前挪。还有一点要特别提醒:偏移操作是在裁剪和填充之后才执行的,所以填充范围必须按照原始地址来写,而不是偏移后的地址。

3.2 地址区间是半开区间:一个会坑到所有人的细节

Srecord 的地址参数默认是“起始地址包含,结束地址不包含”,也就是数学上的半开区间[start, end)。这个细节坑过很多人,包括我自己。

举个例子:

srec_cat app.hex -crop 0x08000000 0x08010000 -o first_64k.hex

这个命令保存的数据范围是0x08000000到0x0800FFFF,共 0x10000(64KB)字节,但0x08010000这个地址本身的数据是不会被包含进来的。

为什么要用半开区间?因为这样设计,两个相邻的区间可以无缝拼接:[A, B)和[B, C)中间没有重叠,也没有遗漏。这在做内存分区时非常自然。

如果你习惯用闭区间[start, end]去思考,就会犯一个经典错误:想把0x08000000到0x08010000(前 64KB + 1 个字节)都裁出来,结果写成了-crop 0x08000000 0x08010000,最后发现数据少了一块。要真想保留0x08010000这个地址,必须写成0x08010001。

3.3 hex 文件合并时的重叠检测

-cat系列命令是专门用来合并多个文件的,Srecord 对重叠区域的态度非常严格。如果两个输入文件的地址区间有重叠,srec_cat默认会报错拒绝输出,这是为了保护你不被静默地覆盖数据。

我实际工作中经常遇到的一个场景是:Bootloader 和 App 分区明明是相邻的,但由于链接脚本里定义的 Flash 区域有冗余,两个 hex 文件在边界处多出了几个地址的重叠。这种情况想合并,不能直接-cat,而要在合并前先用-crop把重叠部分裁掉:

srec_cat boot.hex -crop 0x08000000 0x08010000 \ app.hex -crop 0x08010000 0x08040000 \ -o combined.hex

这样两个文件的输出区间就是无缝接合的,不会触发重叠检测。另外,如果合并时同一地址真的发生了数据冲突,Srecord 会提示类似input line ... is out of order或data conflict的错误。看到这类信息不要慌,先检查链接脚本里 Flash 分区是否真的重叠了,这通常意味着内存布局有问题。

4. 进阶实战:CRC 校验、BCS 校验和与执行记录

4.1 在固件末尾自动填充 CRC32

嵌入式产品做 OTA 升级时,Bootloader 拿到新固件第一件事就是做完整性校验,最常见的就是 CRC32。Srecord 内置了 CRC 生成功能,一条命令就能在指定地址写入 CRC 值。

拿 STM32 举例,假设 App 固件有效数据范围是0x08008000到0x0801BFFC,最后 4 个字节0x0801BFFC到0x0801C000我想放 CRC32。命令如下:

srec_cat app.hex \ -fill 0xFF 0x08008000 0x0801C000 \ -crop 0x08008000 0x0801BFFC \ -crc32-b-e 0x0801BFFC \ -o app_with_crc.hex

这条命令到底做了什么?我拆开解释:

  • 首先把整个0x08008000 - 0x0801C000范围内的空白全部填为0xFF,这样计算 CRC 时就不会因为随机空洞导致结果不稳定。
  • 然后裁剪掉最后 4 个字节0x0801BFFC - 0x0801C000,这 4 个字节是留给 CRC 值本身的。
  • -crc32-b-e 0x0801BFFC的意思是:计算当前数据段的 CRC32,并把这个值以大端字节序写到地址0x0801BFFC处。

-crc32-b-e里的-b表示 bit 反转(reflected algorithm),-e表示最终结果再做一次 XOR,对应的是 zlib/ST 标准里常用的 CRC32 算法。如果你的 Bootloader 用的是CRC32/MPEG-2之类不带反转的算法,就得用-crc32-b-e对应的另一套参数-crc32-c之类,具体查一下 Srecord 的手册即可。不同平台 CRC 算法可能不同,务必和 Bootloader 的校验代码保持一致,否则会出现“明明算对了但校验不过”的诡异现象。

4.2 生成执行起始记录,方便调试器加载

很多调试器支持通过 S-record 文件里的执行起始记录(execution start address)来直接定位PC。但并不是所有编译工具链都会在输出文件里带上这个记录,这时候 Srecord 可以手动帮你加上。

假设入口地址是0x08000185,命令如下:

srec_cat app.hex -execution-start-address 0x08000185 -o app_start.hex

加上这个记录之后,用某些调试器直接加载 hex 文件时,它就会自动跳转到入口地址,调试起来会很方便。如果生成的 S19 文件里没有执行起始记录,不妨试试这个命令。

4.3 文件比对:srec_cmp 的一百种用法

发布固件之前,很多人都会做一遍“重新编译两次,确认文件一致”的操作。用cmp或diff直接比较 hex 文本,往往会因为文件头注释、记录切分方式不同而误报差异——明明数据一样,文本却不一样。这时候用srec_cmp就非常省心,它比较的是数据内容而非文本格式。

srec_cmp build_old.hex build_new.hex

如果两份文件的地址和数据完全一致,命令返回 0 退出码,静默结束;如果有差异,会输出差异地址和值。这个命令在 CI 里很有用,比如“确认某次代码改动只影响了预期地址段”,就可以用-crop裁出关心区域再比较,防止无关区域扰乱了 diff。

5. 常见问题与排查技巧实录

5.1 输出文件为空或“no data”

这种情况十有八九是裁剪范围写错了。如果你-crop 0x08010000 0x08020000,但输入文件的数据范围是0x08000000 - 0x0800FFFF,那结果就是空文件。Srecord 处理完裁剪后,如果没有数据,它会直接不生成输出文件,或者生成一个空文件——取决于具体参数。

排查方法很简单:先用srec_info看输入文件的数据范围,再检查自己的-crop范围,注意半开区间。

5.2 填充值到底填 0xFF 还是 0x00

这问题没有标准答案,取决于芯片的 Flash 空白状态。绝大多数 Nor Flash 擦除后是0xFF,所以填充0xFF是最常见的;但某些芯片或分区策略会要求留0x00,比如部分 Bootloader 用0x00作为“无效”标记。还有一类场景是 OTA 差分包处理,填充值必须和压缩算法假定的“空洞值”一致,否则校验全部失败。所以填充之前,先看一下芯片参考手册里 Flash 的擦除值是什么。

5.3 校验和报错:S-record checksum error

这个错误通常在读取一个“不标准”的 S19 文件时出现。常见原因有:

  • 文件被某些 Windows 编辑器打开过,把换行符改掉了,导致解析异常;
  • 文件本身就是其他工具生成的有 BUG 输出;
  • 使用了错误的输入格式类型。

处理方式很简单:先用srec_info看一下文件能否正常解析,如果它自己都报错,那说明问题出在源头,而不是 Srecord 本身。

5.4 大文件转二进制时崩溃或内存溢出

嵌入式固件一般很小,但如果你想处理的是外部 Flash 镜像,像是 16MB 的串行 Flash 全量 bin,那么直接把 S-record 转成 binary 时,二进制文件可能非常大。Srecord 的处理方式是直接代表整个地址空间,中间的空洞全会被填掉。这种场景我一般建议先-crop裁剪成多个小文件再分别处理,避免一次性生成超大镜像。

6. 把 Srecord 集成进构建系统:Makefile、CMake 与 CI

6.1 Makefile 的典型写法

我最早把 Srecord 用起来,就是在 Makefile 里加了一个 target。每次编译完,自动生成带 CRC 的烧录文件:

# 变量定义 OBJ_HEX := build/app.hex CRC_HEX := build/app_crc.hex FLASH_START := 0x08000000 FLASH_END := 0x08020000 CRC_ADDR := 0x0801FFFC # 默认编译目标 all: $(CRC_HEX) $(CRC_HEX): $(OBJ_HEX) srec_cat $< \ -fill 0xFF $(FLASH_START) $(FLASH_END) \ -crop $(FLASH_START) $(CRC_ADDR) \ -crc32-b-e $(CRC_ADDR) \ -o $@ # 清理 clean: rm -rf build

这个写法有个好处:只要app.hex变了,app_crc.hex就会重新生成,不会出现“改了代码忘了更新 CRC”的尴尬。

6.2 CMake 的 add_custom_command

用 CMake 的嵌入式项目也很多,用add_custom_command实现同样的功能:

add_custom_command( OUTPUT ${CMAKE_BINARY_DIR}/app_crc.hex COMMAND srec_cat ${CMAKE_BINARY_DIR}/app.hex -fill 0xFF 0x08000000 0x08020000 -crop 0x08000000 0x0801FFFC -crc32-b-e 0x0801FFFC -o ${CMAKE_BINARY_DIR}/app_crc.hex DEPENDS ${CMAKE_BINARY_DIR}/app.hex COMMENT "Generating CRC32-protected firmware image" )

这里注意DEPENDS一定要写对,否则增量编译时可能不会重新生成处理后的文件。

6.3 CI 流水线中的固件校验

现在的嵌入式项目基本都有 CI。我习惯在 CI 里加一步“构建后处理”和一步“文件比对校验”:

  1. 编译生成原始 hex;
  2. 用 Srecord 生成带 CRC 的烧录包;
  3. 用srec_cmp把当前产物和上次发布版本做对比,确认没有意外差异;
  4. 再用srec_info检查地址范围、执行入口是否符合预期。

这一套下来,发版前的很多低级错误都被自动化拦截了。以前手工拿文件比对,一忙起来就容易漏掉细小的地址偏移问题,现在全都交给命令处理,省心太多。

6.4 配合其他工具链的组合用法

Srecord 不只跟自家命令搭配,它跟objcopy、hexdump、checksum这些工具也能组合。比如我想快速确认某个 bin 文件的 CRC32 是否正确,可以先把 bin 转成 srec:

srec_cat firmware.bin -binary -offset 0x08000000 -o firmware.s19

然后继续用srec_cat计算 CRC:

srec_cat firmware.s19 -crc32-b-e 0x0801FFFC -o /dev/null

很多烧录器、上位机读的其实都是这种派生文件,先转换再计算,能确保两边拿到的是同一个东西。

7. 特殊领域扩展:从“格式处理”到“镜像合规”

7.1 OTA 差分包生成

现在很多产品都支持 OTA,而 OTA 差分包往往要求“只包含变化区域”。Srecord 虽然没有直接生成差分包的命令,但你可以用srec_cat分别裁剪出新旧版本的 bin,再用支持差分压缩的工具去处理。关键是裁剪范围必须准确,这就要用到srec_info先定位数据边界。

7.2 安全启动与签名区域的预留

很多带安全启动的芯片,会在固件的固定偏移处预留一定字节用于签名或者消息认证码。编译链接脚本里一般会预留位置,但实际签名和 MAC 计算是在编译后做的,这时 Srecord 的-fill就特别有用:先把预留区填充好,计算完签名再把签名填回去。由于 Srecord 能精确控制字节范围,整个过程可以写成脚本,完整复现。

7.3 多镜像合并烧录的自动化

量产时如果一片 Flash 里要烧 Bootloader、App、字库、配置文件等多个镜像,各自的地址区段不同,手动用烧录器逐个操作容易出错。用 Srecord 把它们合并成一个 S19 文件,产线只要烧一个文件即可。地址边界、填充策略提前定好,产线效率和可靠性都提高不少。

8. 我对 Srecord 的一点使用体会

Srecord 这工具看着不起眼,但确实是我从写脚本处理 hex 文件的时代“解放”出来的关键。它最大的价值不是某个单一功能,而是把嵌入式文件处理这件事做成了可复用、可脚本化、可审计的流程。有了它,你的构建流程里不需要再依赖某个“老同事写的 Python 脚本”,也不需要担心换一个人就处理出不同的结果。

最后补充一个小技巧:Srecord 的手册(man srec_cat)非常详细,但读起来不够“场景化”。我建议你按“我要做什么”去查参数,而不是从头到尾通读。遇到一个需求,先想清楚是对数据范围做裁剪、填充、偏移,还是计算校验、转换格式,再去手册里找对应参数,这样学习效率最高。用熟了之后,你会发现原来困扰半天的文件加工问题,往往就是一条命令的事。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询