[原创] 关于Hex文件合并及编辑的点点思考

dige   2017-9-15 08:57 楼主
事情原由来自工厂生产中,一片单片机往往要在不同地址刷入相应的程序。姑且叫做BOOT程序、应用程序等等之类。可以用jlink刷、量产工具刷等等。更有些还在Flash特定地址写上特定的识别字符,增加一些识别功能。总而言之,麻烦,程序多。于是网上搜索了hex文件的格式,经过些许测试,证明可行,遂有此文。
      关于hex文件格式,我就不细表,网上太多,大家可以自行查阅,本文主要讲述如何合并hex以及增加特定内容。大致一个完整的hex包括四个部分:
      第一部分:段地址;以:020000040800F2来说,段地址为0x0800,左移16位得到基地址0x08000000;      第二部分:程序数据;主要存放程序内容,一般一行16字节;
      第三部分:main函数入口地址;以:0400000508000131BD来说,main函数入口地址为0x08000131,这个可以有程序map文件进行核对。
      第四部分:结束,以:00000001FF结尾。
      清楚了上述hex文件的组成,如果将两个hex文件合并。典型的是BOOT程序和应用程序,拿stm32说,程序起始地址一般为0x08000000,一般BOOT的起始地址就是这个,再就是应用程序,往往向后偏移一些,例如0x0800C000之类。所以观察两者的hex文件第一行是相同的,因为段地址都是0x0800。那么合并就好操作了,将BOOT文件的第一部分、第二部分首先放置,再继续添加应用程序的第二部分内容,再添加BOOT程序的第三部分内容及第四部分的结束部分。那么一个典型的合并就完成了。
      如果想在某个特定的地址写入特定的字节,例如,我还想再0x0803F000地址开始处写入0xAAAAFFFF内容,那该怎么操作?由于想写入的段地址不是0x0800而是0x0803,那么需要重新定义段地址部分。如图:将这部分加入程序数据后面即可。
  • 段地址
  • 程序数据
  • main函数入口地址
  • 特定地址写入数据

回复评论 (12)

bin多简单。 cat b1 b2 > b2 就好了。
默认摸鱼,再摸鱼。2022、9、28
点赞  2017-9-15 09:44
引用: freebsder 发表于 2017-9-15 09:44
bin多简单。 cat b1 b2 > b2 就好了。

没有您说的那么简单合并,bin文件是要地址连续的,不过BOOT程序和应用程序之间有很大的空档,还要补FF进行填充,并不是简单的合并。
点赞  2017-9-15 12:49
项目研发人员都需要考虑到后期项目维护成本问题,其中一个很大的维护成本在于软件有Bug,固件更新问题。如果带网络的话,远程升级是成本最低,目前我做的产品都支持远程升级,千里之外更新固件解BUG,不留半点痕迹。
对于IAP升级,程序组成是BOOT区+APP区(一般两块app区),为了减少生产阶段的工作量,把两次烧录搞成一次烧录。这时就需要把boot和app的hex文件,楼主手动修改效率太低了,而且容易出事,可以使用工具直接合并,我现在都是用工具直接把BOTT和APP的hex文件合并生产一个新的hex文件,重新命名好,直接发给生产烧录就行。如果需要工具,我也可以分享
淘宝:https://viiot.taobao.com/Q群243090717 多年专业物联网行业经验,个人承接各类物联网外包项目
点赞  2017-9-15 14:27
我还是直接把工具分享出来,正好公司电脑也有,希望能对楼主有用。我现在都是用这个工具合并hex文件,用的不错,这个也是我之前在网上找的。

    hex合并.rar (2017-9-15 14:32 上传)

    27.5 KB, 下载次数: 235

淘宝:https://viiot.taobao.com/Q群243090717 多年专业物联网行业经验,个人承接各类物联网外包项目
点赞  2017-9-15 14:33
引用: wateras1 发表于 2017-9-15 14:33
我还是直接把工具分享出来,正好公司电脑也有,希望能对楼主有用。我现在都是用这个工具合并hex文件,用的 ...

谢谢,我自己用C#也写了个,应用测试都OK。不过还是感谢您。做了这个后发现应该有人需要,就写了个帖子。
点赞  2017-9-15 15:01
引用: dige 发表于 2017-9-15 12:49
没有您说的那么简单合并,bin文件是要地址连续的,不过BOOT程序和应用程序之间有很大的空档,还要补FF进 ...

那是你不会才搞这样复杂。
默认摸鱼,再摸鱼。2022、9、28
点赞  2017-9-15 15:44
引用: freebsder 发表于 2017-9-15 15:44
那是你不会才搞这样复杂。

人固有自己不熟练地方,不会学了就会了。请问您测试没有?告诉您,您的直接合并的方法是不行的。实践检验真知,我固然喜欢简单方法,您要是有更好的确切可行的摆出来说。
点赞  2017-9-15 15:50
引用: dige 发表于 2017-9-15 15:50
人固有自己不熟练地方,不会学了就会了。请问您测试没有?告诉您,您的直接合并的方法是不行的。实践检验 ...

你手段太low,早几年就自动生成bin了。
直接合并当然也需要借助外部小程序。首先需要一个小程序能取得bin的大小,然后写入一个头文件。如果是gcc的话,有现成的objdump命令可以拿到。
然后在第二个,姑且叫app的工程,pre build的时候用这个小程序生成头文件,然后在link file 里面include 进去就取得了boot的大小。然后link脚本就可以知道实际合并之后的链接地址,通过脚本可以把app自身的启动部分放在最开始,这样可以紧接着boot。boot里面需要调用app吗?它产生的时候可以把link脚本里面的flash结尾export出来,然后+4,就知道app的启动地址。
然后在app 的 after build里面 直接cat 或者windows 的字节拷贝,就行了。
以上boot和app有任何改变,整个手段不需要调整。
默认摸鱼,再摸鱼。2022、9、28
点赞  2017-9-15 17:03
引用: freebsder 发表于 2017-9-15 17:03
你手段太low,早几年就自动生成bin了。
直接合并当然也需要借助外部小程序。首先需要一个小程序能取得bi ...

恕我愚笨,没有完全理解您的意思,但大概明白您说的流程。貌似我的单片机应用和您的不在一个频道,基本上做BOOT+APP方式的都是包含有远程更新功能的,而APP一般包含两块,一块应用,一块备份。拿stm32说,APP的起始地址就是固定了,不存在随着程序变动修改而APP的起始地址变来变去的情况。prebuild我确实用的少,afterbuild做些事情还常见。请问您如何解决远程更新的问题?
点赞  2017-9-15 18:12
引用: dige 发表于 2017-9-15 18:12 恕我愚笨,没有完全理解您的意思,但大概明白您说的流程。貌似我的单片机应用和您的不在一个频道,基本上 ...
升级的其中一个问题就是app的偏移地址的确定,这涉及到符号定位。一旦确定地址,剩下的你以前怎么搞升级的,以后还怎么搞。 前面可变地址的情况复杂一些,但是你既然要固定地址的,那就更简单了,只需要脚本就基本能达成。 gcc强大到根本不用费脑。下面是iar的(keil我不会)。 假设你的flash有100k,而boot小于20k,你打算在20k起始的地方放置app,也就是0x5000字节偏移 define symbol __ICFEDIT_region_ROM_start__ = 0x08000000; define symbol __ICFEDIT_region_ROM_end__ = 0x08004FFF; // boot 20k,app可以从0x08005000 start xxx // 正常放置各种rom,ram // 最后 define section fill { udata32 0xbeadbead; }; "FILL": place noload at end of ROM_region { section fill }; keep {section fill}; // 填充满整个bin空间 如此,boot的bin大小就被固定在20k。 你要在这里面的某地方填什么标记,数据之类的,你可以用 define symbol FLAG_end_offset = 0x20; // 随便举例的地址 define section flag {udata32 0xabcdabcd, 0x12345678;}; // 需要填写的标志,等等 "FLAG": place noload at address (__ICFEDIT_region_ROM_end__ - FLAG_end_offset) { section flag }; define section fill { udata32 0xbeadbead; }; "FILL": place noload at end of ROM_region { section fill }; keep {section flag, section fill}; 来设定flag。 然后就可以在 after build的时候copy /b boot.bin+app.bin bootapp.bin 了。 用link来搞这个事情,本来它是工程的一个文件,当然会被版本管理,另外这里面的符号,都可以export出来,整个程序就只依赖这一个地方的设定,而不会c里面一个#define ADDRESS,外面命令行你还需要编写或者告诉操作者这个ADDRESS一定要一致。 留一个完整版作参考。 [mw_shl_code=c,false]/*###ICF### Section handled by ICF editor, don't touch! ****/ /*-Editor annotation file-*/ /* IcfEditorFile="$TOOLKIT_DIR$\config\ide\IcfEditor\cortex_v1_0.xml" */ /*-Specials-*/ define symbol __ICFEDIT_intvec_start__ = 0x08000000; /*-Memory Regions-*/ define symbol __ICFEDIT_region_ROM_start__ = 0x08000000; define symbol __ICFEDIT_region_ROM_end__ = 0x08004FFF; define symbol __ICFEDIT_region_RAM_start__ = 0x20000000; define symbol __ICFEDIT_region_RAM_end__ = 0x20004FFF; /*-Sizes-*/ define symbol __ICFEDIT_size_cstack__ = 0x800; define symbol __ICFEDIT_size_heap__ = 0x800; /**** End of ICF editor section. ###ICF###*/ define memory mem with size = 4G; define region ROM_region = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__]; define region RAM_region = mem:[from __ICFEDIT_region_RAM_start__ to __ICFEDIT_region_RAM_end__]; define block CSTACK with alignment = 8, size = __ICFEDIT_size_cstack__ { }; define block HEAP with alignment = 8, size = __ICFEDIT_size_heap__ { }; initialize by copy { readwrite }; do not initialize { section .noinit }; place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec }; place in ROM_region { readonly }; place in RAM_region { readwrite, block CSTACK, block HEAP }; define symbol FLAG_end_offset = 0x20; define section flag {udata32 0xabcdabcd, 0x12345678;}; "FLAG": place noload at address (__ICFEDIT_region_ROM_end__ - FLAG_end_offset) { section flag }; define section fill { udata32 0xbeadbead; }; "FILL": place noload at end of ROM_region { section fill }; keep {section flag, section fill}; [/code] 本帖最后由 freebsder 于 2017-9-15 20:51 编辑
默认摸鱼,再摸鱼。2022、9、28
点赞  2017-9-15 20:40
引用: freebsder 发表于 2017-9-15 20:40
升级的其中一个问题就是app的偏移地址的确定,这涉及到符号定位。一旦确定地址,剩下的你以前怎么搞升级 ...

谢谢指点,受教!
点赞  2017-9-15 23:39

楼主,你还有C#写的合并hex的源码吗?想学习下,可以发我邮箱吗1451804815@qq.com   感谢

点赞  2019-7-11 22:44
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复