在日常写单片机工程的时候,有时候编译通过了,却发现Flash空间快满了,或者RAM不够用,代码体积莫名其妙变大,这时候就会碰到“IAR map文件怎么看,还有map文件里的内存占用怎么分析”这种让人头疼的问题。.map文件并不是普通的运行日志,这个文件是链接器最后输出的结果清单,在文件里面,大家可以看到各种段被放到了什么地方,也能看到全局符号的地址,还有各个模块和库大概占了多少空间。IAR在链接的过程中,系统会自动生成这种map文件,其主要作用就是把section的放置结果、全局符号的地址给列出来,并且把模块和库的内存使用情况做一个汇总。
一、IAR map文件的查看方法
在看IAR生成的map文件的时候,大家不要从第一行开始傻傻地逐字去读。因为map文件里面的内容实在是太多了,在真正去排查程序问题的时候,我们需要把注意力放在内存汇总、section分布、模块占用,还有符号地址这几类关键信息上。
1、首先需要去确认map文件到底有没有生成
大家需要先点击进入【Project】→【Options】→【Linker】→【List】这个界面,把链接列表或者map文件输出的开关给勾选上,接着把工程重新编译一遍。
在编译顺利完成后,.map文件一般就会出现在工程的输出目录里面。因为不同的IAR软件版本,还有不同的工程配置,文件存放的路径可能会有一些小小的差别,所以大家去重点翻一翻当前Debug或者Release配置对应的那个输出目录就行。
2、建议先去翻看文件最末尾的内存汇总
很多map文件的最后面,都会对readonly code、readonly data,还有readwrite data等内容进行统一的汇总。通过看这个地方,大家就能很快地看出来ROM和RAM的占用是不是已经快要接近芯片的上限了。平时千万不能只盯着.hex或者.out文件的大小看,因为.out文件里面往往塞了很多调试信息,这个大小跟实际烧录到芯片里的占用是不一样的。如果想要真正弄清楚Flash和RAM的压力,map文件里面的内存统计数据才更有参考价值。
3、接着需要去观察section的放置位置
所谓的section,其实就是链接器在安排代码和数据时候的基本单位,这里面包含了代码段、只读常量段、已初始化变量段、未初始化变量段,还有栈、堆和一些自定义的段。在IAR中,链接配置文件会负责把存储区域定义好,接着系统会把不同的section塞到指定的区域里面去;而region这个东西,本质上就是由一个或者好几个连续的内存范围组合起来的。大家在看section位置的时候,一定要去重点核对一下,看看它们是不是真的落在了预期的Flash、RAM,或者自定义的地址区里面。
二、IAR map文件里内存占用怎么分析
在分析内存具体占了多少的时候,大家必须先把ROM的占用和RAM的占用给分开。ROM这个东西,主要对应的就是Flash里面的代码、常量,还有初始化数据;而RAM这个东西,主要对应的则是全局变量、静态变量、堆、栈、运行时候的数据,以及一小部分被搬到RAM里面去执行的代码。
1、一定要分清code、ro data和rw data的区别
code通常指的就是程序的指令,这部分是要占用Flash空间的。readonly data大都是一些常量表、字符串或者查找表,它们通常也是死死占用着Flash。至于readwrite data,这就需要大家特别去注意了,因为这部分数据,它不仅需要Flash来保存初始值,而且在运行的时候,还需要RAM来作为存储空间。简单来说的话,一个拿去初始化的全局变量,它可能会同时对ROM和RAM产生影响,所以分析的时候不能只看其中一个。
2、需要把重点放在.bss和.data的增长上面
要是突然发现RAM变得特别大,大家应该先去查一查.bss和.data相关的内容。.bss里面放的多数是没初始化的全局变量或者静态变量,而.data里面放的多数是带了初值的全局变量或者静态变量。像一些大数组、通信用的缓存、图像缓冲、协议栈的缓存,还有RTOS的任务栈,这些东西都很容易让RAM的占用嗖嗖地往上涨。遇到这种问题的时候,去删掉几行业务逻辑的代码根本就起不到什么作用,真正需要去查清楚的,到底是哪些变量占用了大块的内存。
3、库函数和第三方模块也需要仔细检查
要是ROM的体积突然间增加了,大家就要去map文件里看一看,是不是有新拉进来的库函数或者第三方模块。经常容易触发这个问题的地方,主要有printf、scanf、浮点数格式化、数学库、C++运行时、文件系统,以及USB协议栈和网络协议栈等。有时候我们在代码里仅仅只是多加了一行调试用的打印,但是在它的背后,系统却可能把一大堆格式化输出的代码全都给链接进来了,这时候通过map文件,就能比较直观地把这些变化给暴露出来。
三、在分析完map文件后怎么去寻找优化的方向
把map文件看明白之后,大家千万不要直接把编译器的优化等级给拉到最高,而是应该通过文件去把真正占空间的模块、变量,还有库的入口给揪出来。在做嵌入式项目的时候,要是瞎去优化,很有可能会影响到程序的调试和系统稳定性,尤其是像启动代码、中断代码、Bootloader和最底层的驱动,这些地方都是不适合随便去改动的。
1、可以按照模块去对比前后两次的变化
每次要是感觉代码体积增加得不太正常,大家可以先把旧的map文件存一份,然后拿它去跟新的map文件做对比。期间要重点看看是哪些.o文件、哪些库,或者是哪些section增长得比较明显。如果是发现某个驱动模块变大了,那就去查一查是不是不小心打开了多余的外设;如果是标准库变大了,那就得去查一查代码里是不是引入了复杂的格式化、浮点数、动态内存或者C++的一些特性。
2、需要结合链接脚本去核对区域的边界
如果map文件里面已经显示section快要贴近Flash或者RAM的边界了,这时候大家就得回到.icf链接文件里面,去好好检查一下区域的定义。IAR的链接配置文件主要就是用来定义代码和数据的放置区域的,有时候启动代码也会被要求固定放在某个特定的地址上,这些情况大家都需要对照着map文件的结果一起进行核对。
3、栈和堆的真实风险也需要多加关注
虽然map文件能够把栈和堆配置的大小给显示出来,但是这并不能百分之百证明程序在运行的时候就一定是安全的。不过,如果在IAR里面把相关的选项给开启了,map文件里面就能多包含一个专门分析栈使用的章节,这可以用来帮大家评估栈空间。
总结
总的来说,IAR的map文件主要就是看四类东西:内存汇总、section分布、模块占用,还有符号地址。在分析IAR map文件里面的内存占用的时候,大家一定要记得把ROM和RAM拆开来看,并且把自定义的段,挨个拆开来进行分析。用这种方法去调整,比自己凭着感觉去瞎删代码要稳当得多,而且也更加容易找到真正占地方的罪魁祸首。