stm32专题三十六:MDK编译过程和文件类型(三)

生成 bin 和 hex文件

若编译过程无误,即可把工程生成前面对应的*.axf 文件,而在 MDK 中使用下载器(DAP/JLINK/ULINK 等)下载程序或仿真的时候, MDK 调用的就是*.axf 文件,它解释该文件,然后控制下载器把*.axf 中的代码内容下载到 STM32 芯片对应的存储空间,然后复位后芯片就开始执行代码了。

然而,脱离了 MDK 或 IAR 等工具,下载器就无法直接使用*.axf 文件下载代码了,它们一般仅支持 hex 和 bin 格式的代码数据文件。默认情况下 MDK 都不会生成 hex 及 bin 文件,需要配置工程选项或使用 fromelf 命令。

fromelf的命令描述:

bin 和 hex 文件详解

bin文件就是纯二进制数据,无特殊格式

hex 是 Intel 公司制定的一种使用 ASCII 文本记录机器码或常量数据的文件格式,这种文件常常用来记录将要存储到 ROM 中的数据,绝大多数下载器支持该格式。

hex格式的具体描述如下所示,非常重要。

5个记录,每部分的具体意义:

接下来,我们分析一下这几条hex文件的内容和意义:

用vscode打开我们生成的hex文件如下,可以看到,与上述hex格式编码要求一致。

为了更清楚地对比 bin、 hex 及 axf 文件的差异,我们来查看这些文件内部记录的信息来进行对比:

注意,bin文件直接用记事本打开会乱码,因为windows记事本默认是ASCII编码格式,而bin文件不是ASCII格式存储,所以记事本打开会是乱七八糟的乱码。

接下来,我们对比看一下bin hex文件的大小:

bin文件:

可以看到,bin文件大小为1440字节,而占用的空间是4096字节。为什么会有这个区别?

因为博主使用的windows系统,一个文件最小是4096字节,文件占用的空间都必须是4096的倍数。再进一步分析,为什么bin文件是1440字节?

实际上,1440 = 1096 + 336 + 8,bin文件的大小,就是实际上要存储在flash的数据大小。所以,bin文件是一种很纯粹的数据格式。

 

hex文件:

可以看到,hex的实际大小为4101字节(>4096),只比4096字节超出一点点。但windows使用的文件系统,就是会占用到

8192 = 2 * 4096字节的存储空间。

 

axf文件:

axf文件,可以看到,要比bin文件和hex文件大很多,因为其中还存储了各种MDK文件类型信息。我们可以直接使用fromelf工具,来对axf文件进行反汇编,来得到汇编代码,进而破解别人的程序。

下面是bin hex elf反汇编的比较:

 

 

 

htm静态调用图文件

我们打开对应的流水灯.htm文件,如下所示:

该文件说明了本工程的静态栈空间最大占用 40 字节(Maximum Stack Usage:40bytes),这个占用最深的静态调用为“ main->LED_GPIO_Config->GPIO_Init”。注意这里给出的空间只是静态的栈使用统计,链接器无法统计动态使用情况,例如链接器无法知道递归函数的递归深度。在本文件的后面还可查询到其它函数的调用情况及其它细节。利用这些信息,我们可以大致了解工程中应该分配多少空间给栈有空间余量的情况下,一般会设置比这个静态最深栈使用量大一倍在 STM32 中可修改启动文件改变堆栈的大小;如果空间不足,可从该文件中了解到调用深度的信息,然后优化该代码。

map文件

如何打开 map 文件?1 直接在Listing目录下打开;2 在 MDK 中双击目标选项,即可打开 map 文件;

注意,在查看 map 文件时,要勾选 MDK 的如下选项配置:

选项One ELF Section per Function的主要功能是对冗余函数的优化。通过这个选项,可以在最后生成的二进制文件中将冗余函数排除掉(虽然其所在的文件已经参与了编译链接),以便最大程度地优化最后生成的二进制代码。

而该选项实现的机制是将每一个函数作为一个优化的单元,而并非整个文件作为参与优化的单元。

选项One ELF Section per Function所具有的这种优化功能特别重要,尤其是在对于生成的二进制文件大小有严格要求的场合。人们习惯将一系列接口函数放在一个文件里,然后将其整个包含在工程中,即使这个文件将只有一个函数被用到。这样,最后生成的二进制文件中就有可能包含众多的冗余函数,造成了宝贵存储空间的浪费。

选项One ELF Section per Function对于一个大工程的优化效果尤其突出,有时候甚至可以达到减半的效果。当然,对于小工程或是少有冗余函数的工程来说,其优化效果就没有那么明显了。

打开“流水灯.map”文件,可看到它的第一部分——节区的跨文件引用(Section Cross References):

我们来看一下 map 文件:

在这部分中,详细列出了各个*.o 文件之间的符号引用。由于*.o 文件是由 asm 或 c/c++源文件编译后生成的,各个文件及文件内的节区间互相独立,链接器根据它们之间的互相引用链接起来,链接的详细信息在这个“Section Cross References”一一列出。例如,开头部分说明的是 startup_stm32f10x.o 文件中的“RESET”节区分为它使用的“__initial_sp” 符号引用了同文件“STACK”节区。

也许我们对启动文件不熟悉,不清楚这究竟是什么,那我们继续浏览,可看到 main.o文件的引用说明,如说明 main.o 文件的 i.main 节区为它使用的 LED_GPIO_Config 符号引用了 bsp_led.o 文件的 i.LED_GPIO_Config 节区。

可以了解到,这些跨文件引用的符号其实就是源文件中的函数名、变量名。
 

常见的调试错误:

比如,我们现在改一下函数名(改成LED_GPIO_Config0),如下:

直接编译文件,没有问题,结果如下:

而当点击build(编译链接)时,就会出现错误如下:

其实,我在写代码时,有时会经常遇到这个错误。它提示的是,在main.o中,没有找到引用的函数(符号)LED_GPIO_Config,实际上,main.o是main.c编译后生成的目标文件,这个目标文件引用的LED_GPIO_Config在所有的目标文件中都没有找到,因为我们把函数名修改成了LED_GPIO_Config0。找不到,那么自然就提示错误了。

删除无用节区

map 文件的第二部分是删除无用节区的说明(Removing Unused input sections from the image)

这部分列出了在链接过程它发现工程中未被引用的节区,这些未被引用的节区将会被删除(指不加入到*.axf 文件,不是指在*.o 文件删除),这样可以防止这些无用数据占用程序空间。

再看一下gpio,因为我们之前使用了gpio_init的初始化函数,进行对比一下:

对比左右两边可以发现,我们在程序中调用过的GPIO_Init函数,没有出现在删除无用节区的列表中。而没有调用到的函数,被直接被删除掉以节省存储空间。

 

符号映像表

map 文件的第三部分是符号映像表
 

这个表列出了被引用的各个符号在存储器中的具体地址、占据的空间大小等信息。如我们可以查到 LED_GPIO_Config 符号存储在 0x08000251 地址,它属于 Thumb Code 类型,大小为 92 字节,它所在的节区为 bsp_led.o 文件的 i.LED_GPIO_Config 节区。

 

存储器映像索引
 

本工程的存储器映像索引分为 ER_IROM1 及 RW_IRAM1 部分,它们分别对应 STM32内部 FLASH 及 SRAM 的空间。相对于符号映像表,这个索引表描述的单位是节区,而且它描述的主要信息中包含了节区的类型及属性,由此可以区分 Code、 RO-data、 RW-data及 ZI-data。

例如,从上面的表中我们可以看到 i.LED_GPIO_Config 节区存储在内部 FLASH 的0x08000250 地址,大小为 0x00000060,类型为 Code,属性为 RO。而程序的 STACK 节区(栈空间)存储在 SRAM 的 0x20000208 地址,大小为 0x00000400,类型为 Zero,属性为RW(即 RW-data) 。
 

映像组件大小
map 文件的最后一部分是包含映像组件大小的信息(Image component sizes),这也是最常查询的内容。

首先是前面的表格数据,可以明显的看到中断向量表和启动文件栈空间:

 

这部分包含了各个使用到的*.o 文件的空间汇总信息、整个工程的空间汇总信息以及占用不同类型存储器的空间汇总信息,它们分类描述了具体占据的 Code、 RO-data、 RW-data及 ZI-data 的大小,并根据这些大小统计出占据的 ROM 总空间。

数据分析:

 

 

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值