ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

嵌入式开发必懂:hex、bin、axf文件格式区别与转换实战

嵌入式开发必懂:hex、bin、axf文件格式区别与转换实战 做过嵌入式开发的兄弟应该都有过这样的困惑Keil编译完工程目录里冒出来一堆后缀各异的文件hex、bin、axf到底有什么区别为什么下载程序用hex做OTA升级要bin调试的时候又依赖axf搞不清楚这几个文件的关系轻则烧录之后程序跑飞找不到原因重则批量生产时固件搞错变砖。这篇文章把嵌入式开发中最常见的三种文件格式一次讲透包括它们的格式原理、适用场景、互相转换的方法以及我在实际项目中踩过的坑。不管你是在校学生准备入门还是刚转岗来做嵌入式开发的工程师搞清楚这些底层文件格式对后续排查问题、理解编译链接流程都特别有帮助。1. 三种文件的“出身”从代码到芯片的完整链路先说结论axf是编译链接后生成的带调试信息的可执行文件hex和bin都是从axf或者说从axf背后的elf派生出来的烧录镜像。这三者的关系就像你写了一份Word文档axf是带修订痕迹和批注的完整版hex和bin是按照不同打印需求导出的PDF或纯文本版。1.1 编译链接时的“源头文件”axf从哪来用Keil开发ARM芯片项目时点击Build按钮后编译器armcc或armclang把每一个.c文件编译成.o目标文件然后链接器根据分散加载文件sct文件和链接脚本把所有.o文件、库文件组织在一起最终生成一个.axf文件。axf是ARM eXtended Format的缩写本质上是ELFExecutable and Linkable Format格式的一种ARM变体。它内部包含的内容非常多代码段RO段你的函数实现、常量数据数据段RW段初始化了的全局变量、静态变量零初始化段ZI段未初始化的全局变量运行时置零调试信息变量名、函数名、源码行号对应关系符号表全局符号的地址映射这些内容里前三个是真正会烧录到Flash里的东西后面两个是给调试器用的“附加服务”。axf文件之所以体积比hex和bin大很多就是因为它塞进了调试信息。1.2 烧录镜像的“两副面孔”hex和bin的诞生拿到axf之后还不能直接烧录因为调试信息和符号表芯片并不需要芯片只需要纯机器码和数据。这时候就要从axf里提取出RO、RW、ZI段整理成烧录镜像。Hex文件是Intel公司制定的一种文本格式本质上就是“地址数据”的ASCII文本描述。Keil里的ARM从elf指令fromelf或者第三方工具链能把axf里的每一条数据按地址排列输出成hex文件。Bin文件是最纯粹的二进制镜像就是芯片Flash里的原始字节流烧录到Flash哪个地址那里的字节就变成什么。它不带地址信息全靠烧录工具或烧录算法把它放到指定位置。用一个生活化的类比axf是装修设计图纸标注了哪里放什么家具、用什么材质hex是带坐标的施工图“在坐标X放一张桌子”bin是实际搬进屋的家具本体没有坐标你把它放哪儿它就呆在哪儿。1.3 为什么Keil默认输出hex生成bin还要额外配置Keil默认配置下编译结束只生成axf和hex不生成bin。原因很简单hex有地址信息配套的下载算法如ULINK、J-Link的Flash算法可以直接解析并烧录对开发者最方便。但实际项目里很多时候必须要bin。比如OTA升级设备通过网络或蓝牙接收升级包固件直接写入Flash的某个偏移地址这时候升级程序接收的就是裸数据根本没法用带地址的hex。再比如把固件交给生产车间烧录用批量烧录器时bin格式更直接可控。所以需要在Keil里加一条自定义命令调用fromelf工具从axf里生成bin文件。具体方法后面细说。2. Hex文件格式深度拆解从文本里看懂固件布局2.1 Intel HEX的“一行一记录”结构Hex文件看起来是一堆十六进制文本每一行都是一个记录Record。标准的Intel HEX记录格式如下: 1A 0000 00 547687960000000000000000000000000000000000000000 F2逐段拆解这一行冒号:每一行的起始标记1A本行数据的字节数这里26个字节0000本条数据的起始地址16位00记录类型00表示数据记录5476...真正的数据内容十六进制ASCIIF2校验和这里最容易懵的是地址。Intel HEX为了兼容老古董的16位地址空间设置了多种记录类型用类型字段区分记录类型名称作用00数据记录Data存放实际数据01文件结束记录EOF标记hex文件结束02扩展段地址记录Extended Segment Address用段地址方式扩展高16位地址少见04扩展线性地址记录Extended Linear Address指定高16位基地址常用05启动地址记录Start Linear Address记录程序入口地址可选网络上搜“hex start linear address record”说的就是类型05这条记录。它一般出现在文件的末尾、EOF之前表示程序入口地址。用J-Link或Keil烧录时调试器会读取这个地址用于设置PC指针的初值。没有这条记录也不影响烧录但影响调试复位后的行为比如你看不到程序停在main函数入口。2.2 校验和算法手工检查hex文件有没有被改坏Hex每行最后两位是校验和算法很简单本行所有字节从长度字节开始到最后一个数据字节求和取低8位然后取反加一也就是求补码使总和低8位等于0。举个例子刚才那行: 1A 0000 00 547687... F2把1A 00 00 00 数据字节逐个求和 F2 0x100低8位为0就是校验通过。这个算法不复杂但实际项目里很少有人手工去算都是靠工具。我用过几个在线hex校验工具都不太靠谱下载文件后建议用Python写一段小脚本验一下几十行代码就能搞定对生产环节特别实用——批量烧录前先验hex校验和能避免镜像文件损坏导致大规模烧录失败。2.3 从hex里能读出哪些关键信息拿到一个hex文件除了烧录还能做几件实用的事查看固件入口地址看05记录知道程序启动后从哪里执行确认烧录起始地址看第一条04记录和后面00记录的地址比如04记录写着0x0800数据地址从0000开始那第一条数据地址就是0x08000000对应STM32的Flash起始地址统计固件大小把每条00记录的数据长度加起来就是总字节数但要注意每条记录之间可能有地址空洞真实占用Flash空间应该是“最后一条数据地址长度-起始地址”我经常遇到的情况是编译完不知道固件到底多大直接看bin文件属性再对照芯片Flash容量判断剩余空间够不够加功能。但hex文件因为带地址信息想从hex推断实际占用Flash范围需要写脚本解析比较繁琐不如直接看bin文件大小直接。3. Bin文件最朴素的固件最容易被忽略的坑3.1 Bin文件怎么来的地址去哪了Bin文件的生成逻辑非常简单把axf/elf里的加载地址LMA对应的数据按Flash地址从小到大的顺序一字排开输出成一个连续文件。它不记录任何地址信息不知道放哪里全靠烧录者把“这个文件应该烧到哪个基地址”告诉烧录工具。使用J-Flash烧录bin时必须手动填一个“Start address”起始地址。很多新手栽跟头就在这默认起始地址是0x08000000如果你的芯片是STM32F103Flash基地址正好是这个没问题但如果用了带Boot区域的芯片或者程序放在外部Flash、放在0x08004000这种偏移位置就必须手动改否则程序写进去也跑不起来。3.2 Keil中生成bin文件的两种正确姿势网上的教程教的是这两种方法方法一在Keil的Options for Target → User选项卡After Build/Rebuild栏勾选Run User Program After Build填命令fromelf.exe --bin -o ./Output/项目名.bin ./Output/项目名.axf其中fromelf.exe的完整路径一般在Keil安装目录的ARM/ARMCC/bin下建议用绝对路径或者%KEXPATH%环境变量。方法二不修改工程直接用命令行手动执行。打开Keil安装目录的ARM Compiler环境执行同样的fromelf命令。注意fromelf命令一定要确保axf路径正确而且axf必须是刚编译成功的最新版本。有几次我改了代码重新编译但生成bin的命令路径指向了旧目录结果bin文件还是老版本烧进板子怎么调都不对折腾半天才发现是bin文件没更新。3.3 Bin文件的“地址漂移”问题Bin文件最典型的坑是它只包含从链接脚本指定的起始地址开始的数据。如果你的工程里把程序链接到0x08008000比如做了一个带Bootloader的App那么bin文件烧录时起始地址就必须填0x08008000而不是0x08000000。更隐蔽的问题是链接脚本里如果分散加载代码段、数据段之间有大量未使用的空洞bin文件并不会填充这些空洞它是一段连续的数据流。比如Flash中函数放在0x08000000常量池放在0x08010000那么bin文件的字节范围就是0x08000000到0x08010000加上常量池的长度中间0x08008000到0x0800FFFF这段哪怕没烧东西bin文件也会用空白填上导致bin文件比实际代码大很多。这时候就需要检查分散加载文件设置尽量把可执行段放在连续区域。4. Axf文件调试利器也是出问题的“背锅侠”4.1 Axf和bin/hex的关系调试信息到底有多大价值Axf里最有价值的是调试信息。用Keil的Debugger连上ST-Link或J-Link后之所以能实现打断点、看变量值、单步执行时高亮源码行靠的就是axf里的调试信息把机器指令和源码建立映射。在Release版本里如果去掉调试信息axf就退化成纯粹的ELF可执行文件跟bin的内容几乎一样只是多了ELF文件头和各段信息。很多人直接用WinHex打开axf发现能看到“ELF”魔数和一堆段表但对普通开发者来说直接用bin更省事。我个人的习惯是调试阶段保留axf量产阶段只保留hex和bin。axf文件在没优化的情况下可能比hex大十几倍而且它包含源码的完整符号信息对商业项目来说是敏感资产不该随意外发。4.2 Axf反汇编看编译器到底做了什么AxF非常实用的一个功能是反汇编。Keil的Debug模式下你可以在Disassembly窗口直接看到每条C语句对应的汇编指令。这个能力在排查疑难bug时特别有用检查优化后的代码流程开了-O3优化之后源码顺序和实际执行顺序完全不同单步调试看到“乱跳”不要慌打开反汇编看真实执行路径定位HardFault程序跑飞进入HardFault后查看PC指针和LR寄存器的值在反汇编窗口搜索对应地址能看到是哪个函数哪条指令出了问题验证volatile是否生效一个变量明明改了值但读出来没变化反汇编看是不是编译器把这个变量优化成了寄存器变量根本不写内存网上搜“axf文件报错”很多情况是在Keil调试时报错“cannot load driver”或者“axf file not found”。这种错误九成是因为工程路径包含中文字符或空格或者编译中途失败导致axf文件不完整。把工程放到纯英文路径下重新编译立刻就好。4.3 Axf文件损坏或丢失怎么办有几次我把Keil工程拷到同事电脑上打开后一编译就报错“file xxx.axf not found”。排查后原因很统一工程没有清理干净obj目录下的axf是上次编译的残留或者路径变了导致链接器没法覆盖写。最简单的解决办法是Project → Clean Targets然后重新Build强制全量编译axf会重新生成。更稳妥的做法是定期把整个工程目录打包备份axf跟obj文件一起删掉再重编避免增量编译产生“伪成功”的axf文件。5. 三者的关键区别与选型什么时候用哪个5.1 一张表说清核心差异对比项hexbinaxf文件格式ASCII文本纯二进制ELF二进制含调试信息是否带地址带每条记录都有地址不带带段地址烧录方式调试器解析地址→烧录手动指定起始地址→烧录调试器直接加载调试典型用途J-Link/ST-Link下载调试OTA升级、量产烧录Keil调试、反汇编分析肉眼可读性可用文本编辑器打开必须十六进制工具需要专用工具典型大小中等ASCII会膨胀最小最大含调试信息主要缺点文本解析慢、不适合大固件没地址易烧错位置太大、不适合分发5.2 线上升级为什么必须用bin这是很多做IOT和嵌入式产品的人必须想明白的问题OTA升级用的升级包标准答案就是bin。原因有几个第一hex是文本格式同样数据比bin大1倍以上字节变ASCII占两个字符对流量敏感的产品来说不可接受。第二hex带地址信息如果App在设备上有多个可运行区域比如A/B分区升级你没法简单地把hex中的地址改掉bin是纯数据接收端想放哪段Flash就放哪段灵活得多。第三升级包的校验大多用CRC32或SHA256直接对整个bin文件计算简单可靠hex还要先解析出数据段再算多了一步且容易出错。5.3 生产烧录选hex还是bin看你的工具链量产烧录现在主流有两种用J-Flash批量烧录或者用专门的烧录器配合离线文件。如果用的是J-Flash强烈建议用hex。因为J-Flash能自动解析hex里的地址你不用手动设置烧录起始地址减少人为错误。而bin文件还需要你对照工程里的链接地址手动填填错了就是整批板子变砖的问题。如果是厂商提供的专用烧录软件比如STM32CubeProgrammer、NXP的Flash Tools都支持hex和bin但同样hex更省心。我见过太多工厂里的技术员把bin烧错地址的情况所以量产文件我从来只发hex不解释原因省事。6. 工具实操hex、bin、axf转换与排查手册6.1 环境准备与基础转换命令环境准备安装Keil MDK自带fromelf或者安装ARM Development Studio。也可以用开源的GNU Arm Embedded Toolchain里面的arm-none-eabi-objcopy和arm-none-eabi-objdump也能干同样的活。Hex转Bin用fromelf从axf直接出两种文件fromelf.exe --bin -o output.bin input.axf fromelf.exe --i32 -o output.hex input.axfBin转Hex用J-Flash或者Python脚本实际项目中我遇到过客户只给bin文件需要转成hex灌进烧录器。J-Flash操作如下打开J-FlashFile → Open data file选择bin文件弹窗里填起始地址比如0x08000000File → Save data file选Intel Hex格式保存S19文件转Hex飞思卡尔/NXP芯片的工程经常产出S19文件跟hex一样是带地址的文本但记录类型不同。可以用srec_cat这个开源工具SRecord工具包里的瑞士军刀一行命令转换srec_cat input.s19 -o output.hex -IntelHex转Bin的脚本方案通用如果手头只有hex文件又没法跑Keil写个Python小脚本解析Intel HEX。流程很简单逐行判断类型、解析地址和数据、按地址写入字节数组、最后输出bin。核心代码如下def hex_to_bin(hex_path, bin_path): data {} base_addr 0 with open(hex_path, r) as f: for line in f: line line.strip() if not line.startswith(:): continue byte_len int(line[1:3], 16) addr int(line[3:7], 16) rectype int(line[7:9], 16) payload bytes.fromhex(line[9:9 byte_len * 2]) if rectype 0x04: base_addr int.from_bytes(payload, big) 16 elif rectype 0x00: full_addr base_addr addr data[full_addr] payload elif rectype 0x01: break if not data: raise ValueError(no data records) start min(data.keys()) end max(data.keys()) len(data[max(data.keys())]) buf bytearray(end - start) for addr, payload in data.items(): off addr - start buf[off:off len(payload)] payload with open(bin_path, wb) as f: f.write(buf)这段代码把每个地址上的数据都填到对应偏移位置缺的洞补0x00注意hex文件不会主动补洞这里补的是预设的默认值实际如果链接脚本有空洞bin里对应区域就是FF还是00取决于hex里有没有数据严谨做法是先全填0xFF再覆盖有数据的部分。6.2 Keil里一步生成hexbinaxf的完整配置以MDK 5.38为例完整配置流程如下第一步配置Output选项卡。Options for Target → Output勾选Creates HEX File这样编译后自动产出hex。axf是默认就有的不需要配置。第二步配置User选项卡。Options for Target → User在After Build/Rebuild栏勾选Run User Program After Build点击右侧“...”按钮选择fromelf.exe路径然后在后面追加参数--bin -o ./obj/App.bin ./obj/App.axf第三步验证输出。编译完成后打开工程目录的obj文件夹正常情况应该有App.axf、App.hex、App.bin三个文件比对三者生成时间是否一致。提示fromelf路径含空格时Keil的User命令解析可能出问题。稳妥做法是给fromelf.exe加双引号但双引号后面的参数不能也加双引号否则会当成路径的一部分。比如C:\Keil_v5\ARM\ARMCLANG\bin\fromelf.exe --bin -o ./obj/App.bin ./obj/App.axf注意fromelf后面的参数不带引号但路径本身带引号没问题。实际操作中很多人在这一步卡住因为Keil的User命令窗口边框很小复制粘贴长命令容易出错建议直接在命令行窗口先测试一遍fromelf命令能跑通再贴进Keil配置界面。6.3 实用工具清单与适用场景这些年折腾hex、bin、axf我攒了一套顺手的工具箱拿出来共享Keil MDK自带的fromelf最趁手的转换工具不用额外装环境axf转hex/bin一步到位J-Flash烧录必备还能做bin转hex、hex转bin也会显示flash校验结果STM32CubeProgrammerST官方工具界面友好支持带地址解析烧录还能查看Flash内容SRecordsrec_cat/srec_info处理s19、hex、bin转换的命令行神器批量处理场景效率极高VSCode加Hex Editor插件偶尔需要直接改二进制时用比如验证bin文件里某个字节对不对Beyond Compare固件对比神器两个bin文件做二进制比对支持十六进制视图排查“代码改了但bin没变”这类问题非常高效7. 常见文件报错与排查实录7.1 Keil报“axf文件找不到”的处理流程这个报错我至少遇到十几次核心排查顺序记牢看编译输出窗往下翻找到第一次出现error的地方很多情况是之前的编译就失败了axf文件压根没生成检查工程路径中文路径、空格、过长路径是经典杀手把工程移到D:\proj\test下再试清理重建Project → Clean Targets然后重新Build检查杀毒软件公司电脑装了360或McAfee经常拦截axf写入在杀毒软件里加目录白名单7.2 Hex烧录后程序跑飞大概率是地址配置问题症状程序烧录成功但上电不是白屏就是复位循环。 排查思路先看hex的04记录如果第四条记录的地址不是芯片Flash基地址比如STM32F1是0x08000000说明你的分散加载文件有问题或者下载算法选错芯片再看05记录如果没有启动地址记录有些烧录器默认PC从0开始程序直接跑飞最后看bin的起始地址如果烧的是bin99%是起始地址没填对检查你填的地址是否等于链接脚本里的RO Base7.3 Bin文件烧录成功但程序不运行排查步骤这种情况比烧录失败更恶心因为看起来一切正常但板子没反应。我总结的排查顺序检查起始地址你烧录时填的地址和工程的linker脚本里指定的地址要一致先烧一次全擦除再烧bin有些芯片的Flash里还留着旧Bootloader或旧代码bin没覆盖到的区域跑的还是老代码互相干扰确认bin文件大小用编辑器打开bin文件检查末尾有没有意外截断我有一次U盘拷贝bin文件出差错文件被截断成原来一半烧进去程序不跑对比文件大小才发现用J-Flash校验烧录完成后做个verify看Flash内容和bin是否完全一致7.4 Keil下载时报“Error: Flash Download failed - Target DLL has been cancelled”这种报错其实跟文件格式无关但经常被误以为是hex/axf问题。实际原因是调试器的RTT或SWO功能占用了下载通道或者芯片没进入Debug模式。解决方法是Options for Target → Debug → Settings把Port改成SW或JTAGReset and Run前确认Reset模式是Normal。另外确认板子供电正常、复位引脚没被拉死。7.5 Hex反编译成C语言能还是不能网上搜“hex文件反编译成C语言”先给个明确结论hex文件里只有机器码没有符号信息想反编译成可读的C语言基本不可能。你能做的最多是反汇编成汇编代码工具可以用Keil自带的ARM DisassemblerGhidra开源支持ARM对新手友好IDA Pro老牌但正版贵学习成本高但如果项目里有对应的axf文件那情况完全不同。Axf里有完整的调试信息和符号表反编译之后能恢复出大部分函数名、变量名代码逻辑可读性高了很多。所以如果有人拿个hex文件找你“帮忙看下这段逻辑”先问他有没有axf没有的话基本只能看汇编硬啃。8. 进阶拓展从hex/bin反推Flash布局8.1 用hex文件直接画出Flash占用图在排查“为什么编译器报Flash溢出”的时候有一个直观操作写脚本解析hex把每条记录的地址区间画出来就能清楚地看到代码段、数据段分别占了多少Flash。虽然Keil的Build Output窗口也会report Program Size但那个显示的是CodeRORW总大小不展示布局。我写的脚本逻辑很简单遍历00记录把地址和长度登记到一个区间列表最后合并重叠区间输出各个区间起始地址、长度、总占用。这样能发现一个常见问题分散加载文件配置不合理导致代码段里有巨大空洞hex文件看着大小正常但bin却大得离谱因为空洞被补0了。8.2 从bin文件反推链接脚本参数这是排查问题时最实用的一招。手里只有一个bin文件不知道它烧到哪个地址怎么做用十六进制工具打开bin文件看前32个字节。ARM Cortex-M芯片的中断向量表开头是初始SP值和复位向量复位向量是第2个字偏移4字节处。比如一个bin文件的前8字节是00 00 00 20 09 00 00 08那么SP初值是0x20000000复位向量是0x08000009bit0是Thumb标志实际地址是0x08000008。从这个值能反推出链接脚本的Flash基地址是0x08000000附近。这个方法在拿到陌生固件、或者忘了工程配置的时候能快速定位固件烧录地址配合调试器直接干分析。我在给客户做逆向兼容的时候靠这个办法反推出了好几块板子的固件起始地址。8.3 生成带版本号的bin固件实际产线上经常遇到一个痛点固件更新了但车间里的文件还是老版本烧完板子才发现不对。我的做法是写一个自动化脚本在编译前自动生成一个包含版本号信息的结构体编译进固件。具体做法是在代码里定义一张版本表存到固定Flash地址比如0x08080000然后写个脚本在bin文件生成后把版本号、编译时间、Git提交哈希写进去。这样拿任何bin文件出来打开都能直接看到是什么时候编译的哪个版本。网上搜“bin文件打补丁”也是类似思路在固定偏移位置修改特定字节。实际操作中记得在分散加载文件里为版本信息预留空间并且主程序启动时主动读取校验防止版本信息被意外覆盖。9. 踩坑实录与个人总结写了这么多最后说说我自己在这个话题上掉过的坑你们参考的时候能少走几步弯路。第一个坑是混淆hex和bin的适用范围。刚入行时做OTA升级想当然地给设备推送hex文件结果接收端解析hex里的冒号和地址信息处理逻辑复杂到爆还不支持断点续传。后来统一改成bin文件接收端直接按块写入Flash代码量立马减少一大半。第二个坑是Keil里生成bin文件的路径问题。有段时间我用的工程名带“V2”后缀fromelf命令里的输出文件名写死了旧名字导致每次生成bin都是旧版本排障排了一整天。现在我的习惯是工程目录下建一个build文件夹输出文件名用固定的app.bin不跟工程名绑定省心很多。第三个坑是调试时依赖axf但分发固件时忘记剥离调试信息。给客户发升级包之前一定要确认发的是hex或bin而不是axf。axf里除了源码信息还带有完整的符号表对商业固件来说是严重的泄露风险。我现在只要做对外发布一律只发bin并在发布脚本里自动丢弃axf。最后再分享一个实用小技巧改完代码编译后先看bin文件的修改时间再看文件大小如果大小没变但时间更新了十有八九是优化选项变了或者代码改动只涉及数据段。这时候别急着烧录反汇编确认一下改动是否真的进去了才能避免“改了没生效”的错觉。嵌入式开发里hex、bin、axf是天天打交道的三兄弟花半小时把它们的格式原理、转换方法和排查思路理清楚后面做开发、调bug、量产、升级的时候能省下的时间远远不止半小时。
返回列表