KEIL Map文件解析以及如何从Map文件还原内存分布 | 您所在的位置:网站首页 › keil map文件在哪 › KEIL Map文件解析以及如何从Map文件还原内存分布 |
一、什么是Map文件
简单来说,Map文件是编译器编译工程后生成的一个文件,这个文件反映了各个源文件生成的模块间的交叉引用、移除的未使用模块、符合映射表、内存映射以及各个模块的大小和汇总数据等。 所以说,当你在遇到或怀疑存在内存越界或溢出的情况时,首先想到的应该就是分析Map文件,确认嫌疑分子、构建RAM的分布图,还原问题发生的过程,才能从根本上解决问题。 那,我们就一起来看下怎么看Map文件这个问题吧~ 二、如何生成Map文件根据设置的不同,生成的Map文件包含的内容也不同。如图1所示,在“Options for Target ‘XXX’”窗口的Listing页面,通过勾选不同的项目可以定制Map文件中的记录的内容。 图1 PS:点击快捷工具栏的魔法棒按钮 或 菜单Project->“Options for Target ‘XXX’...”可以打开“Options for Target ‘XXX’”窗口。 设置完,代码编译成功后,在指定目录就可以找到生成的Map文件。 三、Map文件解析Map文件已经生成了,那么接下来我们一起看下Map是何方神圣。既然要看,就需要先打开Map文件,那么Map文件怎么打开呢? 很简单,打开Map文件的方式有多种,在KEIL的左侧的Project窗口中目标工程上双击即可打开;或者,直接找到Map文件,用文本编辑器等方式都可以打开查看这里就不再赘述。 按照最全的配置,Map文件包括以下几个部分: 1. Section Cross References主要是指各源文件生成的模块间的相互引用关系。 比如,下面这句表示: spi.c文件编译生成的模块spi.o中调用了stm32f4xx_rcc.c文件编译生成的模块stm32f4xx_rcc.o z中的函数RCC_AHB1PeriphClockCmd。 剩下的也差不多都是这个意思。 Section Cross References …… spi.o(.text) refers to stm32f4xx_rcc.o(.text) for RCC_AHB1PeriphClockCmd …… 2. Removing Unused input sections from the image将未使用的函数之类的删除,以减少image映像的大小。 Removing Unused input sections from the image. …… Removing data_quk.o(.rev16_text), (4 bytes). ……这个从我个人目前接触的内容看,没用到过,如果XDJM在调试程序的过程中有用到这些信息的场景也希望不吝赐教,我也开阔下视野,多谢~ 3. Image Symbol Table映像中涉及的符号表,包括局部符号(Local Symbols)和全局符号(Global Symbols)。 Image Symbol Table// 局部符号Local Symbols// 符号名 // 地址 // 类型 // 大小Symbol Name Value Ov Type Size Object(Section)../clib/angel/boardlib.s 0x00000000 Number 0 boardinit1.o ABSOLUTE..\TASKS\alm_task.c 0x00000000 Number 0 alm_task.o ABSOLUTE......HEAP 0x20006248 Section 512 startup_stm32f40_41xxx.o(HEAP)Heap_Mem 0x20006248 Data 512 startup_stm32f40_41xxx.o(HEAP)STACK 0x20006448 Section 2048 startup_stm32f40_41xxx.o(STACK)Stack_Mem 0x20006448 Data 2048 startup_stm32f40_41xxx.o(STACK)__initial_sp 0x20006c48 Data 0 startup_stm32f40_41xxx.o(STACK)// 全局符号Global Symbols// 符号名 // 地址 // 类型 // 大小Symbol Name Value Ov Type Size Object(Section)......limit_check 0x0800d27d Thumb Code 566 alm_task.o(.text)aaaaa_err 0x20000089 Data 1 global.o(.data)play_cnt 0x2000008a Data 1 global.o(.data)lock_cnt 0x2000008b Data 1 global.o(.data)......Region$$Table$$Base 0x0801bf24 Number 0 anon$$obj.o(Region$$Table)Region$$Table$$Limit 0x0801bf44 Number 0 anon$$obj.o(Region$$Table)......注意,这里的符号包括函数名,变量名。局部的static变量和全局变量在这里都可以找到,如果疑似存在内存越界的变量属于这两种类型,那么可以从这里找到他们的地址,看看他上下左右的小伙伴儿都是谁,就能确定嫌疑分子了。 另外,类型包括Number、Section、Thumb Code、Data。其中,Number是指它并不占据程序空间,而只是具有一定数值的符号,类似于程序中用宏定义define和EQU。 4. Memory Map of the image映像的内存分布,顾名思义,这部分内容主要记录了映像的加载域和运行域的起始地址、大小和最大Size以及各个段的起始地址。 在说介绍之前,我们先了解下这几个段的意义,方便理解: 段名说明.constdata只读常量数据段,属于RO-data。.text代码段。 用来存放程序执行代码的内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读(当然也有些架构允许代码段为可写,即允许修改程序)。也有可能包含一些只读的常数变量,例如字符串常量等。 .data数据段。 data 段用于存储已经赋初值(非零)的全局变量,且变量占有实际的内存空间。本段的内容由程序初始化,因此会占用exe文件空间。 .bss数据段,Block Started by Symbol。 bss段用于存储未赋初值的全局变量和静态局部变量,这些变量在程序运行前会被初始化为0或NULL。 另外,初始化为零的全局变量和静态局部变量也会存储在bss中的数据不分配实际的空间,只体现为一个占位符,只记录数据所需空间的大小,因此不会占用exe文件空间。 bss段的大小从可执行文件中得到 ,然后链接器得到这个大小的内存块,紧跟在data段后面。 heap堆。 用于存放运行中被动态分配的内存段,可动态扩张或缩减。 例如,malloc分配的内存就在堆上。 stack栈。 用于存放程序临时创建的局部变量,即:函数括弧“{}”中定义的临时变量。注意,不包括用static声明的变量,static声明的变量存储在data段中。当函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。 下面是是从Map中摘抄的Memory Map of the image的部分内容: Memory Map of the imageImage Entry point : 0x08000189Load Region LR_IROM1 (Base: 0x08000000, Size: 0x0001c6b4, Max: 0x00080000, ABSOLUTE, COMPRESSED[0x0001c3ac])Execution Region ER_IROM1 (Exec base: 0x08000000, Load base: 0x08000000, Size: 0x0001c080, Max: 0x00080000, ABSOLUTE)Exec Addr Load Addr Size Type Attr Idx E Section Name Object0x08000000 0x08000000 0x00000188 Data RO 458 RESET startup_stm32f40_41xxx.o0x08000188 0x08000188 0x00000008 Code RO 2119 * !!!main c_w.l(__main.o)......0x080002a0 0x080002a0 0x00000478 Code RO 3 .text main.o0x08000718 0x08000718 0x00000016 Code RO 300 .text stm32f4xx_it.o0x0800072e 0x0800072e 0x00000002 PAD0x08000730 0x08000730 0x00000210 Code RO 349 .text system_stm32f4xx.o......0x0801c064 0x0801c064 0x0000001c Data RO 2311 locale$$data c_w.l(lc_numeric_c.o)Execution Region RW_IRAM1 (Exec base: 0x20000000, Load base: 0x0801c080, Size: 0x00006c48, Max: 0x00020000, ABSOLUTE, COMPRESSED[0x0000032c])Exec Addr Load Addr Size Type Attr Idx E Section Name Object0x20000000 COMPRESSED 0x00000014 Data RW 350 .data system_stm32f4xx.o0x20000014 COMPRESSED 0x000000c5 Data RW 377 .data global.o0x200000d9 COMPRESSED 0x00000001 PAD0x200000da COMPRESSED 0x00000200 Data RW 440 .data cr.o......0x20000634 - 0x000008c4 Zero RW 4 .bss main.o0x20000ef8 - 0x000001a0 Zero RW 376 .bss global.o0x20001098 - 0x000004e0 Zero RW 558 .bss uart.o0x20001578 - 0x0000005c Zero RW 603 .bss dsply.o0x200015d4 - 0x00000009 Zero RW 737 .bss spai.o0x200015dd COMPRESSED 0x00000001 PAD0x200015de - 0x00000014 Zero RW 863 .bss data.o......0x20005b04 - 0x000006e0 Zero RW 1970 .bss os_var.o0x200061e4 - 0x00000060 Zero RW 2243 .bss c_w.l(libspace.o)0x20006244 COMPRESSED 0x00000004 PAD0x20006248 - 0x00000200 Zero RW 457 HEAP startup_stm32f40_41xxx.o0x20006448 - 0x00000800 Zero RW 456 STACK startup_stm32f40_41xxx.o从上面Map的内容可以得知,RW_IRAM1起始地址为 0x20000000, 大小为 0x00006c48,最大为0x00020000。起始地址和大小可以在Keil中配置: 从上面的Map内容,还可以得知data段、bss段、heap、stack的起始地址,这里很重要,后面我们会据此进行还原内存分布。 映像组件大小的信息,这部分包含了*.o 文件的空间汇总信息、整个工程的空间汇总信息以及占用不同类型存储器的空间汇总信息,并按照类别Code、 RO-data、 RW-data、ZI-data 、Debug分别统计其占用的大小,最后给出总的统计信息。 名词解释Code (inc. Data)代码,显示代码和内联数据占用了多少字节。 例如: const int a = 10; // 存储在代码段 RO / RO-data只读数据,Read Only,显示只读数据占用了多少字节。 注意:不包括Code列中的已包含的内联数据哦。 例如: const char *msg = "Hello world!"; // 存储在RO-data RW / RW-data可读写的数据,Read Write,显示读写数据占用了多少字节。 Rw-data由程序初始化初始值。 例如: int c = 10; // 存储在data段,RW-data Zl / ZI-data初始化为0的数据,Zero Initialize。 没有初始化的可读写变量(即:程序中用到的且没有显式初始化的变量),编译器默认会是把没有初始化的变量都赋值一个0,显然它是存在RAM中的。 例如: int d; // 存储在bss段,ZI-data Debug调试数据,显示调试数据占用了多少字节。 例如,调试输入节以及符号和字符串。 Object Totals显示链接后生成的映像对象占用了多少字节。(incl. Generated)链接器生成的映像内容。例如,交互操作的中间代码。 如果 Object Totals 行包含此类型的数据,则会显示在该行中。 本例中共有 1440字节的 RO 数据,其中32字节是链接器生成的 RO 数据。 (incl. Padding)链接器根据需要插入填充字节,以实现字节对齐。 本例中97160的Code中,共有8个填充字节。 Grand Totals显示真实映像统计信息。ELF Image Totals (compressed)ELF(Executable and Linking Format)可执行链接格式映像文件大小。ROM Totals显示包含映像所需的 ROM的最小Size。 注意:这里不包括 ZI数据和存储在ROM 中的调试信息。 本例中,Image统计信息如下图所示。在此映像中,有112784字节的代码, 其中包括8648字节的内联数据 (inc. data例如文字池和短字符串);2032字节的RO data;1588字节的RW Data;27308字节的ZI Data。 最终的统计信息如下: RO Size = Code + RO Data = 112784 + 2032 = 114816 字节 RW Size = RW Data + ZI Data = 1588 + 27308 = 28896 字节 ROM Size = Code + RO Data + RW Data = 112784 + 2032 + 812 = 115628 字节 注意: (1)ZI Data在编译时只是一个占位符,故不占最终生成文件的大小。 (2)RW-data既存储在RAM中,也存储在ROM中,已初始化的数据会存储在ROM中,上电会从ROM 搬移至RAM中。 四、内存分布根据前面解析的Map文件内容,我们可以尝试还原运行域的内存分布,即: data段、bss段、heap、stack的分布图,这在分析程序是否存在内存越界方面很有用。 根据RW_IRAM1的运行域的地址,可以反推出Memery的分布图,具体如下: __initial_sp是栈的栈顶指针,在Map文件的Image Symbol Table的Local Symbols中有体现。 而堆和栈的大小,在Map文件的Memory Map of the image有描述,而具体的大小是由代码配置的,具体参考下图,这里栈的大小配置为0x800,堆的大小位0x200。 通过上面的内存分布图可以看出,栈顶指针指向栈地址最大的地方,所以这个栈是从高地址向低地址方向生长的,如果栈空间定义小了,出现下溢,则会影响到堆的数据。 五、栈究竟溢出了吗通过上面的操作,我们还原出了运行域的内存分布图,那么,从这个图怎么看我这个栈究竟溢出了吗,或者出栈溢出后可能的影响呢? 别急,Keil同样给我们提供了参考数据。在生成Map的路径,同样有生成的文件可供我们参考,呶,就是下面这俩货: 文件 “xxxx .bulid_log.htm” 是工程的构建日志,而文件“xxxx.html”是链接器生成的静态调用图。在静态调用图文件中,记录了工程中各个函数之间互相调用的关系,并且还给出了静态占用最深的栈空间数量以及它对应的调用关系链。对,这就是我们需要的关键信息! 好,那我们打开“xxxx.html”这个文件看看:
对,就是这个栈空间的最大使用大小! 注意,这个是静态栈的使用统计,如果你有递归,那么还需要你自行估算。当然,在嵌入式中,递归是能避免还是尽量避免的。我的工程中没用到递归,故,这里只需要参考栈的静态最大使用空间就可以了。 对于这个工程,栈静态的最大使用空间为1300个字节,而前面定义的栈空间大小为0x800(2048)个字节。1300 < 2048,妥妥滴够了,不用担心溢出了。 一般情况下,在资源充足的情况下(即:空间有余量的情况下),一般栈的大小设置为这个静态栈最大使用量的两倍,我这边也差不多够用了,就先保持。如果你的结果是这两个值接近,解决方案有两种:要么扩大栈的大小,要么缩小栈的最大使用大小。要是资源不够,那只能根据本文件反映的函数调用关系,尽可能优化代码,减少调用深度,以期最终缩小栈的最大使用大小。 我这里不改动,还有一个比较投机取巧的点,就是从上面的内存分布图可以看出,在栈上面还有个0x200堆,但实际上并没有用到堆,故,即便是栈溢出了,还有512个字节的缓冲区,发生溢出并影响到上面的bss段数据的概率很低,所以就不用担心了~ 好了,结合Map分析内存分布和是否溢出的问题差不多就这些,如果有不对或更好的方法,也麻烦告诉我哦,谢谢~ 六、参考资料感谢XDJM的慷慨分享,谢谢: 两种存储器,三种内存大小,六段段 (baidu.com) 《学习笔记_问ChatGPT:单片机编译器map文件》 - 哔哩哔哩 (bilibili.com) STM32开发之map文件学习_stm32map文件_wanwanshenyou的博客-CSDN博客 转载记得说明出处哦~ |
CopyRight 2018-2019 实验室设备网 版权所有 |