ARTICLE DETAIL

资讯详情

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

嵌入式开发必懂:hex、bin、axf三种固件文件的区别与选择

嵌入式开发必懂:hex、bin、axf三种固件文件的区别与选择 干了这么多年嵌入式经常有新人拿着工程问我“我编译完到底该烧哪个文件hex、bin、axf长得差不多为什么有时候用这个有时候用那个”说实话这三类文件放在一起确实容易让人犯迷糊。它们本质上是同一份程序在不同阶段的三种“载体”但结构、用途、调试能力完全不同。你要是能把它们的区别和来龙去脉彻底弄明白从Keil编译到量产烧录、再到在线调试整个流程里的很多坑都会提前避开。这篇文章就围绕hex、bin、axf三类文件从格式原理、生成过程到实际工程里的转换与排错一条线讲清楚。每个环节我都会附上实际项目里的操作细节和踩坑记录适合刚入门的新手也适合已经在用Keil/IAR但从来没深究过文件格式的工程师。1. 先搞清楚本质bin是“裸数据”hex是“带地址的裸数据”axf是“带调试信息的完整映像”1.1 为什么bin文件必须烧录到指定地址才能运行bin文件最直观它就是纯粹的程序机器码一个字节挨着一个字节没有任何附加信息。你用十六进制编辑器打开一个bin文件看到的全是连续的指令和数据但里面没有告诉烧录器“该放到哪里”。所以烧录bin时必须手动指定起始地址比如STM32的0x08000000、许多Cortex-M3/M4平台的0x00000000。地址错了程序要么根本跑不起来要么跑起来完全乱套。我做过一个挺典型的失误一块板子的Flash起始地址在链接脚本里从0x08000000改成0x08010000结果只改了代码里的偏移忘了烧录工具里的下载地址直接把bin烧回了0x08000000。BootLoader跳过去之后野指针满天飞查了好几个小时。所以说bin虽然简单但地址信息全靠“外部约定”一旦工程结构复杂很容易在烧录环节埋雷。1.2 hex为什么自带地址信息hex文件的通用叫法是Intel HEX本质是ASCII文本按行存储每一行都包含“起始地址 数据长度 数据内容 校验和”。烧录工具读到hex后会按行解析自动把数据写入对应地址所以不需要你手动指定起始地址。这也是为什么很多工程师说“hex比bin更不容易烧错”因为地址信息就在文件里躺着。但需要注意一点hex的地址机制是“局部地址”。比如STM32的Flash地址是0x08000000hex里每一行的地址通常会写成0x08000000附近的地址靠一个特殊记录来扩展高位。这就引出了另一个关键技术点后面我专门开一节讲Intel HEX的记录类型尤其是“扩展线性地址记录”因为网上很多搜索词都在问“hex start linear address record到底干什么用的”。1.3 axf才是“完全体”但烧录和量产通常不用它axf是ARM编译器ARMCC或AC6生成的可执行映像文件内部其实是ELF结构的变种——你可以理解为带调试信息、带符号表、带重定位信息、甚至带局部变量类型描述的“超级bin”。调试器Keil的ULINK/J-Link、IAR的I-jet加载axf后能看到函数名、行号、局部变量直接单步、打断点、查看内存。这是bin和hex完全做不到的。但是axf不适合直接烧录到量产产线原因在于它的体积通常比bin大很多携带大量调试信息。它不是纯Flash镜像头部有ELF文件头、段表、符号表。部分烧录器不直接识别axf作为烧录镜像需要先用工具转换成bin或hex。所以在我的日常流程里axf是“开发阶段的主角”hex是“中试和小批量烧录的常用格式”bin是“量产、OTA、BootLoader差分升级时最常用的格式”。三者不是互斥关系而是按场景依次切换。2. Intel HEX格式深度解析从记录类型到校验和2.1 HEX文件的记录类型一张表讲明白很多人用hex文件用了一年也没真正打开看过内容。其实你用记事本打开hex第一眼会被吓到“这啥玩意”我来把它彻底拆开。Intel HEX每一行格式为:LLAAAATTHHHH...HHCC各段含义字段长度说明:1字符起始标志LL2字符本行数据字节数一个字节是2个十六进制字符AAAA4字符本行数据的起始地址16位TT2字符记录类型HH...HH可变数据区LL个字节CC2字符校验和记录类型TT最常用的是类型码名称作用00Data Record普通数据记录存储程序内容01End of File Record文件结束标记02Extended Segment Address Record扩展段地址记录用于8086模式地址扩展03Start Segment Address Record起始段地址记录一般少见04Extended Linear Address Record扩展线性地址记录设置高16位地址05Start Linear Address Record起始线性地址记录CPU入口地址这里重点说04和05。04每条记录后面的数据只有两个字节比如:020000040800F2解析一下LL02表示后面有2个数据字节AAAA0000表示本行数据区起始地址为0TT04表示这是扩展线性地址记录数据是0800意味着接下来所有00类型记录的AAAA都要加上0x08000000的高位。也就是说后面的数据实际地址是0x08000000 AAAA。这正是为了覆盖STM32这类基地址在0x08000000的芯片而设的。05记录则标记程序入口地址比如:040000050800019D表示入口地址是0x0800019D。有些烧录器或者调试工具会参考它确定复位后PC的初始值不过大多数Cortex-M平台的烧录流程里这个入口地址并不是决定性因素真正起作用的是向量表里偏移0x04处的复位向量。2.2 校验和怎么算一行一行破译hex校验和CC的计算方式倒不算难把:后面的所有字节LL、AAAA、TT、数据区求和取低8位然后取补码使得总和含校验和的低8位等于零。以这条记录为例:0300300002337A1ELL03AAAA0030TT00数据02 33 7A求和03 00 30 00 02 33 7A 0xE2校验和0x100 - 0xE2 0x1E正好是记录末尾的1E所以如果哪一行hex被不小心改坏一格烧录器会直接报校验错误。遇到这个问题别慌多半不是烧录器坏了而是hex文件被文本编辑器或者传输过程中改了字符。2.3 为什么搜索热词里有“hex文件反编译成c语言”网友经常搜“hex转C语言”“hex反编译”我要提醒一句hex文件里只有机器码没有源码结构。即使你把它加载进IDA Ghidra这类反汇编工具也只能还原汇编无法还原你写的那个printf、那个while循环、那个结构体赋值。所谓“反编译成C语言”在嵌入式领域更多是指“反汇编后人工分析逻辑”顶多能帮你逆向出大概的时序和寄存器操作绝不是像变魔术一样把源码变回来。这背后的原因是编译过程本身是信息丢失的变量名、注释、宏定义、编译优化后的语句重组都在生成axf之前就已经被处理掉了。axf里还保留符号和行号所以拿到axf还能反推一部分hex是纯净物连符号都没有反编译难度高好几个量级。所以不要指望有什么“百分百还原C代码”的工具这个搜索热词背后其实是不少人对文件格式的信息量差异有误解。3. axf文件的结构与工程价值为什么调试离不开它3.1 你写的代码是怎么一步步变成axf的我们常说的“编译”在嵌入式工具链里其实包含四个阶段预处理展开头文件、宏替换、条件编译。常见问题比如头文件路径配错、宏没定义、条件编译分支选错。编译C/C源码变成汇编文件.s或.asm这个阶段做语法分析、类型检查、优化。汇编汇编文件变成目标文件.o每个.o里都是相对地址的机器码和符号。链接把多个.o和启动文件、库文件链接起来解析符号引用分配绝对地址生成axf。axf本质上是一个ELF文件它包含ELF头文件类型、目标架构、入口点、程序头表和节头表偏移。节头表列出所有节.text、.data、.bss、.rodata每个节有地址、大小、对齐方式。程序头表描述如何把节加载到内存里烧录器其实主要看程序头表来拿“烧录区域”。调试节.debug_info、.debug_line等行号、变量类型、调用栈信息都在这。符号表全局函数名、全局变量名、静态变量名全都以字符串形式保存在里面。所以说你用J-Link下载程序时如果选择的是axf那么J-Link会从ELF的程序头里找到.loadable段把数据提取出来写入Flash调试时再根据.debug_line把当前PC映射到具体源码行。烧录和调试一个文件全搞定。3.2 为什么keil生成的axf会报错常见的三种坑很多人在搜索“axf文件报错”我就把自己踩过的三种坑列出来路径和文件名带中文或空格。Keil安装目录、工程目录只要出现非ASCII字符链接器生成的axf路径就可能带乱码。出错信息往往是cannot open file xxx.axf。解决办法就是全英文路径干净利落。内存溢出导致链接失败。如果代码量超过芯片Flash或RAM大小链接器会直接报L6220E: Region ... overflowed by ... bytes。这时候axf不会被生成。但很多人不看Build Output的完整日志只看到“AXF: Error”就慌了。其实滚动上去看真正的失败原因是溢出、未定义符号或重复定义。调试器版本太老或芯片型号选错。有时候axf本身没问题但调试器加载时报RDDI-DAP Error或者Mismatched Debug Architecture。这类问题多半出在芯片型号配置和CMSIS-DAP/ST-Link版本不匹配上先检查Debug设置里选的型号是不是和实际芯片一致。3.3 axf、map、启动文件三者的关系除了axf本身链接过程还会生成一个.map文件它记录每个符号、每个节的最终地址以及内存占用情况。排查“怎么ram用这么多”“为什么这个变量地址落在诡异区域”时直接看.map比看axf更直接。而启动文件startup_xxx.s的作用是设置初始栈指针、调用SystemInit、然后跳转到main。它也是在链接阶段决定向量表布局的关键。我一般拿到一个陌生工程会先打开.map确认三件事向量表复位向量是不是0x08000000附近。.data段是否有合适的加载地址和运行地址涉及初始化拷贝。栈顶__initial_sp是否落在RAM合法范围内。如果这三项都正确烧录后程序往往能跑如果跑飞多数是启动文件或分散加载文件sct配置有误。这些在axf生成阶段不会报错但运行起来就露馅。4. 实操环节Keil里怎么生成bin文件ISE 14.7又怎么生成bin4.1 Keil中生成bin的几种方法与参数选择Keil MDK默认只生成hex和axfbin文件默认不生成。想在Keil里输出bin最通用的方式是在User选项卡的After Build/Rebuild里加一句fromelf命令。我实际在工程里用的命令是fromelf --bin --output.\Output\project.bin .\Output\project.axf解释一下参数--bin告诉fromelf输出bin格式这是最核心的开关。--outputxxx.bin输出文件路径。最后一个参数是要转换的axf文件的相对路径。如果希望输出带CRC校验的镜像、或者要去掉某些段还可以加--bin --base之类但常规裸机量产上面这句足够了。这里要注意Keil的fromelf支持一个很有用的变体叫--bincombined。它在多核芯片工程里会把多个核的镜像合成一个binZynq的双核开发就会用到这个功能。如果你在做Zynq或者带M4核其他核的芯片直接搜“zynq双核生成bin文件”最后落地方案大概率就是fromelf --bincombined。关于生成bin的时机一定要选After Build/Rebuild不要放在Before Build。因为bin是axf的“衍生品”必须在链接完成后才会出现。4.2 fromelf命令的坑分隔符和路径fromelf的命令行参数在不同MDK版本里写法的容错程度不一样。我试过MDK 5.36之后路径最好用正斜杠/反斜杠\有时候会被转义。另外如果输出路径的目录还没有创建fromelf不会自动创建目录所以最好先确认Output目录存在否则报错再说找不到文件其实是路径问题。还有一种情况有些工程会勾选Create HEX File然后hex正常生成但bin一直不出现。这有可能是因为fromelf的输出路径写的是绝对路径而编译机换了电脑或目录结构改变路径失效。我的建议是工程内所有文件生成路径都写成相对工程文件的路径工程移动后重新编译也不会挂。4.3 ISE 14.7怎么生成bin文件FPGA方向热词里有“ISE14.7怎么生成bin文件”这个我简单带一句。Xilinx ISE里工程默认生成的是bit文件要在Generate Programming File阶段用iMPACT或PromGen工具生成bin或者直接把配置过程保存成二进制镜像。实际操作里ISE 14.7下可以用以下方式生成bin在Process窗口里双击Generate Programming File先在Process Properties里把Programming File Format设为Binary。重新运行Generate Programming File输出目录会出现.bin。如果要用烧写器直接烧SPI Flash建议再检查字节顺序Byte Order和比特位顺序Bit OrderFPGA配置需要MSB First。不过FPGA的bin和ARM的bin不是一个概念。ARM bin烧的是程序执行指令FPGA bin烧的是配置比特流是硬件逻辑的“装配图”。别混着理解就行。4.4 十六进制和十进制的转换为什么hex转十进制也常被搜很多新人在看hex文件、调试内存窗口或者分析协议时需要把hex数值转成十进制。比如你在调试一个传感器读到的寄存器值是0x1A3C心里得立刻知道等于6700左右1A3C 6716才能判断采集的数据合不合理。我的常用姿势是计算器程序员模式直接切HEX/DEC。命令行里用printf(%d, 0x1A3C)但注意嵌入式C里printf格式化是%d如果值是0xFFFFFFFF会输出-1而不是4294967295要看清楚数据类型。Python一行print(int(0x1A3C, 16))。这里提醒一下十六进制转十进制本身不复杂难的是“地址换算”。比如hex文件里某一行的地址是0800开头你该知道这是扩展地址的高16位不是真实的物理地址。老老实实读Type 04记录把高位拼上才是真地址。5. 烧录、调试、量产阶段文件选型和常见问题排查5.1 下载算法和烧录文件的关系用Keil调试时你选hex还是axf烧录其实都行但背后调用的Flash算法不一样。Keil的Flash Download页面里配置了Programming Algorithm这个算法是烧录器把数据写入Flash的驱动代码。axf和hex最终都会被转换成纯Flash数据后进入算法所以这块的坑不在于文件格式而在于算法选错。举个真实案例有人把STM32F103C8T6的工程当成F103RCT6来做Flash算法选成了512KB的型号结果下载能过程序能跑一部分但在Flash超过128KB的位置读回全是0。这跟你用hex还是bin没有关系纯粹是Flash算法型号不匹配。排查到后来看.map里的代码量并不大但算法区域的地址范围写错一样会导致写入失败或写后校验失败。5.2 典型报错AXF文件无法打开、找不到AXF怎么排查我总结了四个排查顺序新手可以直接照抄看Build Output窗口最底部的完整错误信息。很多所谓的“axf文件报错”其实是“上一行的链接错误导致axf没有生成”。检查Output目录下是否存在axf。如果存在但调试器打不开多半是路径/文件名问题。检查调试器配置。J-Link或ST-Link在Settings里选的是不是“Application”而不是“CPU DLL”或者“Flash”。重新编译一次确定axf的时间戳是新生成的。有时候Debug配置和Release配置共用输出目录旧axf匹配不上新源码也会出诡异问题。5.3 烧录bin文件时地址偏移的实战记录我在做BootLoader App结构的项目时有一段很深刻的经历。BootLoader占0x08000000开始的32KBApp从0x08008000开始。App工程里链接起始地址要改成0x08008000同时要把向量表偏移寄存器SCB-VTOR设置成0x08008000。这是两个独立的操作漏掉任何一个都不行。我最初只改了工程Option里的Target标签页ROM起始地址忘了在代码里设置VTOR结果App跑到一半中断就进不了正确回调。后来我在SystemInit或者main开头加上SCB-VTOR 0x08008000;并把中断向量表有真实意义的数据也重映射一遍整个程序才稳定。对应到文件层面每个固件版本我都会同时产出两份binApp工程生成的bin含地址偏移直接烧到0x08008000。BootLoader工程生成的bin烧到0x08000000。量产时先用烧录器烧BootLoaderApp合成镜像或者分开两步烧。OTA时只下发App的binBootLoader负责接收并写入App区域然后跳转。bin没有地址信息所以OTA协议里必须约定好目标地址和长度这是设计OTA协议时特别要注意的。5.4 常见问题速查表hex/bin/axf搜出来的高频问题现象原因处理方案Keil无法生成bin文件没有配置fromelf命令或路径不对在After Build里加fromelf命令并检查输出目录存在hex烧录成功但程序不运行地址记录被误改、芯片型号不符、Flash算法错误用文本方式检查Type 04记录和Type 00记录地址范围axf调试时无法单步、断点无效优化等级太高导致代码行与源码错位把Optimization改成-O0或-Og重新编译内存窗口看到的值和源码变量不符访问了外部外设寄存器或volatile缺失给寄存器指针加volatile确认类型宽度bin文件直接烧录后跑飞烧录地址错误或向量表没有二次偏移确认链接地址和SCB-VTOR一致hex转bin后大小不对hex里包含非连续段的填充字节使用support分散加载的转换工具或检查Type 05入口5.5 关于OTA里bin文件“加壳加校验”的经验这里单独分享一个我认为特别实用的工程技巧量产OTA下发bin时不要直接发裸bin。我通常会在固件bin前面追加一个自定义头部包含魔数、版本号、固件长度、CRC32校验、时间戳。BootLoader收到后先校验魔数和CRC再检查版本号合法性最后才搬运到Flash。为什么这么做因为裸bin没有边界信息传输中丢一个字节接收端根本不知道。加上头部后哪怕丢包也能快速判断固件不完整并请求重传。这不是文件格式本身的要求而是实际工程可靠性需要。很多人只是把hex或bin文件丢给上位机结果OTA成功率只有七成加上校验和头部之后轻松提到99.9%。6. 我在实际工程里的习惯和小建议说实话做嵌入式开发这么多年文件格式问题表面上看起来小实际上在关键时刻能卡你三天。我现在的习惯是每个固件工程在编译完成后都会自动生成axf、hex、bin三个文件并且统一放到Output目录下按版本号命名。开发调试用axf小批量烧录用hex量产和OTA用bin。这样无论生产那边用什么工具都能找到合适的镜像文件。另外我会在版本发版前做一次“bin文件回读校验”用烧录器把Flash内容读出来和原始bin逐字节比对。别觉得这一步多余有一次我发现量产烧录工具把bin的高位地址忽略了导致3块板子中1块程序启动异常。回读校验救回了一整批货的时间。以上这些都是我在真实项目中踩过、查过、并最终沉淀下来的经验。文件格式看着基础但越是基础的东西越值得我们彻底搞透。希望这篇文章能帮你少走一些弯路尤其是在从开发环境向生产环境交接的时候文件选型和格式理解会直接决定你加不加班。
返回列表