KEIL Map文件解析以及如何从Map文件还原内存分布

news2024/12/27 15:44:38

一、什么是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 image

  Image Entry point : 0x08000189

  Load 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        Object

    0x08000000   0x08000000   0x00000188   Data   RO          458    RESET               startup_stm32f40_41xxx.o
    0x08000188   0x08000188   0x00000008   Code   RO         2119  * !!!main             c_w.l(__main.o)
	......
    0x080002a0   0x080002a0   0x00000478   Code   RO            3    .text               main.o
    0x08000718   0x08000718   0x00000016   Code   RO          300    .text               stm32f4xx_it.o
    0x0800072e   0x0800072e   0x00000002   PAD
    0x08000730   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        Object

    0x20000000   COMPRESSED   0x00000014   Data   RW          350    .data               system_stm32f4xx.o
    0x20000014   COMPRESSED   0x000000c5   Data   RW          377    .data               global.o
    0x200000d9   COMPRESSED   0x00000001   PAD
    0x200000da   COMPRESSED   0x00000200   Data   RW          440    .data               cr.o
	......
    0x20000634        -       0x000008c4   Zero   RW            4    .bss                main.o
    0x20000ef8        -       0x000001a0   Zero   RW          376    .bss                global.o
    0x20001098        -       0x000004e0   Zero   RW          558    .bss                uart.o
    0x20001578        -       0x0000005c   Zero   RW          603    .bss                dsply.o
    0x200015d4        -       0x00000009   Zero   RW          737    .bss                spai.o
    0x200015dd   COMPRESSED   0x00000001   PAD
    0x200015de        -       0x00000014   Zero   RW          863    .bss                data.o
	......
    0x20005b04        -       0x000006e0   Zero   RW         1970    .bss                os_var.o
    0x200061e4        -       0x00000060   Zero   RW         2243    .bss                c_w.l(libspace.o)
    0x20006244   COMPRESSED   0x00000004   PAD
    0x20006248        -       0x00000200   Zero   RW          457    HEAP                startup_stm32f40_41xxx.o
    0x20006448        -       0x00000800   Zero   RW          456    STACK               startup_stm32f40_41xxx.o

       从上面Map的内容可以得知,RW_IRAM1起始地址为 0x20000000, 大小为 0x00006c48,最大为0x00020000。起始地址和大小可以在Keil中配置:

       从上面的Map内容,还可以得知data段、bss段、heap、stack的起始地址,这里很重要,后面我们会据此进行还原内存分布。

5. Image component sizes

     映像组件大小的信息,这部分包含了*.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博客

      转载记得说明出处哦~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/831721.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RISC-V基础之函数调用(三)保留寄存器(包含实例)

RISC-V将寄存器分为保留和非保留两类。保留寄存器是指在函数调用前后必须保持相同值的寄存器&#xff0c;因为调用者期望在调用后能够继续使用这些寄存器的值。保留寄存器包括s0到s11&#xff08;因此称为saved&#xff09;&#xff0c;sp和ra。非保留寄存器&#xff0c;也称为…

2023年华数杯B题

B 题 不透明制品最优配色方案设计 日常生活中五彩缤纷的不透明有色制品是由着色剂染色而成。因此&#xff0c;不透明 制品的配色对其外观美观度和市场竞争力起着重要作用。然而&#xff0c;传统的人工配色 存在一定的局限性&#xff0c;如主观性强、效率低下等。因此&#xff0…

uniapp封装request请求

在基础文件里面创建一个api文件 在创建两个 js文件 http.js 里面封装 request 请求 let baseUrl https://white.51.toponet.cn; //基地址 export const request (options {}) > {//异步封装接口&#xff0c;使用Promise处理异步请求return new Promise((resolve, reject…

html学习7(iframe)

1、通过使用iframe标签定义框架&#xff0c;可在同一个浏览器中显示不止一个画面。 2、height和width属性用于定义框架的高度与宽度。 3、属性frameborder‘0’用于是否显示边框。 4、iframe可以显示一个目标链接的页面&#xff0c;链接的target属性设置为相应的iframe名称。…

【无网络】win10更新后无法联网,有线无线都无法连接,且打开网络与Internet闪退

win10更新后无法联网&#xff0c;有线无线都无法连接&#xff0c;且打开网络与Internet闪退 法1 重新配置网络法2 更新驱动法3 修改注册表编辑器法4 重装系统 自从昨晚点了更新与重启后&#xff0c;今天电脑就再也不听话了&#xff0c;变着花样地连不上网。 检查路由器&#xf…

win10笔记本显示器根据页面显示亮度自动调节亮度的问题

系统是win10企业版&#xff0c;针对这个问题查了很多种方法&#xff0c;比如&#xff1a; 1、控制面板->硬件和声音->电源选项->点击当前电源计划的更改计划设置->更改高级电源设置->显示->启用自适应亮度 但是我发现我的电源计划只有平衡这一种&#xff0c…

Linux系统jenkins+newman+postman持续集成环境搭建

1、首先安装nodejs 下载nodejs压缩包&#xff0c;下载地址&#xff1a;nodejs官网下载 建议不用下载最新的&#xff0c;我这里用的是推荐的v12.18版本 下载和解压命令 wget https://nodejs.org/dist/v12.18.3/node-v12.18.3-linux-x64.tar.xz解压安装包&#xff08;记得没有z&…

基于Kubernetes环境的高扩展机器学习部署利器——KServe

随着ChatGPT的发布&#xff0c;人们越来越难以回避利用机器学习的相关技术。从消息应用程序上的文本预测到智能门铃上的面部识别&#xff0c;机器学习&#xff08;ML&#xff09;几乎可以在我们今天使用的每一项技术中找到。 如何将机器学习技术交付给消费者是企业在开发过程中…

使用cmd查看3568主板相关

主要是说清楚思路的 rk3568主板能运行的程序都在system/bin里面&#xff0c;这个是我们直接可以使用cmd用到的 所以&#xff0c;往后我们想通过cmd了解RK3568的某一项参数的时候&#xff0c;或者想使用RK3568某一个系统功能的时候。应该先去system/bin里面查找对应的系统程序。…

Jenkins配置测试报告后无法正常显示或显示空白 的解决方法(问题集锦)

Jenkins配置测试报告后无法正常显示或显示空白&#xff1f; 现象1&#xff1a;界面样式错乱原因分析临时策略永久策略 现象2&#xff1a;报告路径不存在原因分析解决策略 现象3&#xff1a;docker和操作系统版本不匹配原因分析解决策略 现象1&#xff1a;界面样式错乱 打开报告…

实验笔记之——apk生成

最近拾回Android开发&#xff08;实验笔记之——Windows下的Android环境开发搭建_gwpscut的博客-CSDN博客&#xff09;&#xff0c;花了两天时间&#xff0c;写了个二维码识别的app 忘记如何生成apk了&#xff0c;写个笔记来记录一下 需要创建一下 对应的设置好 这里勾选上记住…

系统保留分区被误删怎么办?

当您在全新的磁盘上安装Windows时&#xff0c;将在磁盘的开头创建一个名系统保留的分区&#xff0c;大小约为100MB&#xff0c;然后是系统驱动器&#xff0c;然后是其他的驱动器。通常&#xff0c;系统保留分区在Windows 8中为350MB&#xff0c;在Windows 10中为500MB。系统保留…

网络安全(黑客)自学就业

前段时间&#xff0c;遇到网友提问&#xff0c;说为什么我信息安全专业的找不到工作&#xff1f; 造成这个结果主要是有两大方面的原因。 第一个原因&#xff0c;求职者本身的学习背景问题。那这些问题就包括学历、学校学到的知识是否扎实&#xff0c;是否具备较强的攻防实战…

webpack基础知识三:说说webpack中常见的Loader?解决了什么问题?

一、是什么 loader 用于对模块的"源代码"进行转换&#xff0c;在 import 或"加载"模块时预处理文件 webpack做的事情&#xff0c;仅仅是分析出各种模块的依赖关系&#xff0c;然后形成资源列表&#xff0c;最终打包生成到指定的文件中。如下图所示&#…

智能灯控无线蓝牙解决方案_SKB369串口BLE蓝牙模块

​蓝牙技术受益于其智能、低功耗、高连接速度等特性&#xff0c;在物联网市场呈现爆发式增长是物联网工程师有目共睹的。目前国际物联网最常用的无线通信技术标准主要有三种&#xff1a;WiFi、Zigbee和蓝牙&#xff0c;据不完全统计显示&#xff0c;在民用领域以WiFi和蓝牙为通…

C语言每日一题:13《数据结构》环形链表。

题目链接&#xff1a; 一.环形链表运动基础。 使用快慢指针利用相对移动的思想&#xff1a; 1.第一种情况&#xff1a; 1,令快指针&#xff08;fast&#xff09;速度为2. 2.慢指针&#xff08;slow&#xff09;速度为1. 3.以慢指针进入环中开始。 4。假设slow刚刚进入环中fast…

微信新技能解锁——微信图片可以转Excel表格!

大家好呀&#xff0c;你们是不是跟我一样&#xff0c;收到表格图片没法修改&#xff01; 今天我教大家一个微信隐藏功能&#xff0c;图片可以一键转Excel表格&#xff0c;还能继续编辑&#xff0c;而且还特别简单简直太爱了&#xff5e; 只需打开微信&#xff0c;点击聊天界面…

Openssh高危漏洞CVE-2023-38408修复方案

0x01 漏洞简述 2023年07月21日&#xff0c;360CERT监测发现OpenSSH发布了OpenSSH的风险通告&#xff0c;漏洞编号为CVE-2023-38408&#xff0c;漏洞等级&#xff1a;高危&#xff0c;漏洞评分&#xff1a;8.1。 OpenSSH 是 Secure Shell (SSH) 协议的开源实现&#xff0c;提供…

SpringBoot中mybatis分页插件的使用--【pagehelper组件】

SpringBoot系列文章目录 SpringBoot知识范围-学习步骤【JSB系列之000】 文章目录 SpringBoot系列文章目录本系列校训 SpringBoot技术很多很多环境及工具&#xff1a;上效果图目前流行的mybatis分页插件在Spring Boot里使用pagehelperJAVA查询列表时页面上的使用还要注意的是 …

零基础强化学习入门分享

&#xff08;一&#xff09;前言&#xff1a;强化学习入门顺序。 以前主要学习硬件PCB单片机等知识&#xff0c;后来接触的项目也大多与电气相关&#xff0c;从一窍不通到稍微找到点门道&#xff0c;中间走过不少弯路&#xff0c;误打误撞中&#xff0c;也留下了一些经验。 我的…