【超级详细!!】Keil编译下的.map文件详解与实战

目录

一、概述

二、keil如何生成.map文件

三、详解

1、Section Cross References:程序段交叉引用

2、Removing Unused input sections from the image:删除映像未使用的程序段

3、Image Symbol Table:映像符号表

4、Memory Map of the image:映像的内存映射

5、Image component sizes:映像组件大小

四、map文件实战

1、通过.map文件查看变量是否被优化

2、通过.map文件检查内存分配

3、通过.map文件查看堆栈分配

4、通过.map文件查看优化存储段

5、通过.map文件判断中断向量表覆盖

6、通过.map文件的检测栈溢出

五、总结

一、概述

        开发过程中难免会遇到很多奇怪的bug,比如一个变量被赋值后,通过日志输出还是原来的数值,貌似没有改变,这很可能是被优化掉了。这个时候我们可以通过.map文件查看是否被优化掉,可以通过volatile来防止被优化。本章节将介绍如果生成.map文件,以及.map文件详解,最后会通过案例来实战.map的应用。

二、keil如何生成.map文件

确定后记得编译一下,这样就可以在Listings文件中找到.map文件了

三、详解

.MAP文件的组成分为五个部分:

1、Section Cross References:程序段交叉引用 
2、Removing Unused input sections from the image:删除映像未使用的程序段 
3、Image Symbol Table:映像符号表
3.1	Local Symbols 本地/局部符号	
3.2    Global Symbols 全局符号  
4、Memory Map of the image:映像的内存映射 
5、Image component sizes:映像组件大小 
1、Section Cross References:程序段交叉引用

        这一块的内容就是精准记录函数与模块的调用关系,可以在这一部分看到函数之间的调用关系和链接关系。

        可以看到其中的链接文件,main文件中的main函数调用debug文件中的Debug_Init函数。main文件中的main函数调用main文件中的demo_fun函数。后续追踪的结果如下图

2、Removing Unused input sections from the image:删除映像未使用的程序段

        这一段的作用就是将未使用到的函数和全局变量从映像中删除掉,以此减小程序的体积,这在实战中可以极大的帮助优化存储空间。如下图可以看到我们在main定义中的static(静态)和const(只读)数据未使用,这里被优化掉了。static全局静态变量属于.data数据段,而const定义的数据属于.constdata数据段,也就是只读数据段。被优化掉的数据程序不占用最后bin文件的大小。

不单单变量会被优化掉,有时候不使用的函数也会被优化掉。

3、Image Symbol Table:映像符号表

        映像符号表属于.map文件的核心模块之一,用于记录程序中所有符号(函数、变量、标号等)的存储地址、类型、大小及所属模块。通俗一点讲,它可以很清楚的记录全局变量、静态全局变量、静态函数、非静态函数的存储地址与大小等详细信息。其中映像符号表分为Local Symbols(本地/局部符号)和Global Symbols(全局符号)。

(1)Local Symbols(局部符号):记录用 static 声明的全局变量、函数,以及汇编文件中的标号地址(作用域限于本文件)。如下,rw_static_data3的变量名存放在0x20000003地址,属于Data段,大小100字节节,存放在main文件中。

Symbol Name                              Value     Ov Type        Size  Object(Section)
rw_static_data3                          0x20000003   Data         100  main.o(.data)

        我们可以看到,当我们定义静态全局变量和静态函数时候,他们属于Local Symbols局部符号的内容,其函数的起始地址存放在Flash区,全局变量因为重定位的原因,存放在内存区。如下图所示,我们定义全局静态变量rw_static_data3和静态函数demo_static_fun,可以看到.map中的分布。demo_static_fun静态函数定义在Flash区,地址0x080009d5,占用16字节。

(2)Global Symbols(全局符号):记录全局变量、非静态函数等(作用域全工程)。我们可以看到,非静态型的函数和变量存放在全局符号区,如demo_global__fun函数的起始地址0x08000a75,占用58字节。

4、Memory Map of the image:映像的内存映射

        该模块用于描述程序在存储器和运行时内存中的具体布局,是分析内存分配、优化资源使用和排查问题的关键工具,在这个段中,我们可以看到函数的分布地址与占用大小。能够通过文件辅助查看Flash和内存是否溢出。同时在上电初始化过程中,也可以看到重定位操作的区域,如将Flash中的data段或者bss段复制到对应内存区。以及堆栈分布的位置和字节大小等数据。补充基础知识:

        加载域(Load Region)与运行域(Execution Region),加载域:程序在 Flash 中的实际存储位置,包括代码段(.text)、只读数据(.rodata)和已初始化的可读写数据(.data)。运行域:程序运行时内存的实际状态,例如 .data 段从 Flash 复制到 RAM,未初始化数据(.bss)在 RAM 中清零 。

  • Code:代码段(.text),存储在 Flash 中。
  • RO Data:只读数据(如常量字符串),存储在 Flash。
  • RW Data:已初始化的全局变量,运行时从 Flash 复制到 RAM。
  • ZI Data:未初始化的全局变量,在 RAM 中初始化为0。

Flash区域分布情况

        如下图,LR_IROM1(加载区域)表示程序Flash中的存储位置,包含代码和数据的初始值。ER_IROM1(执行区域)是执行区域,属于LR_IROM1的子区域,表示代码在Flash中原地执行。

  • Base: 0x08000000 : Flash起始地址。
  • Size: 0x00000c4c : 表示已占用空间大小。
  • Max: 0x00100000 : 允许的最大占用空间。

根据上图我们可以看到在Flash区域中分布的相关情况,接下俩我们来看内存中的分布情况。

内存区域分布情况

        如下图所示,Exec base: 0x20000000 表示该区域在RAM中的起始地址,所有运行的全局变量堆栈等都在此区域分配,Load base: 0x08000bd0表示该区域的初始化数据在Flash中的存储地址,程序启动时,这些地址会从Flash区域的0x08000bd0复制RAM的0x20000000,大小为0x000012e0。同时也可以查看堆栈分布情况,堆(HEAP)的起始地址0x200000e0,大小为0x00000200。栈(STACK)的存放地址0x200002e0,大小为0x00001000。我们可以通过startup_stm32f40_41xxx.s文件中修改堆栈大小。

修改堆栈大小,如下图。

5、Image component sizes:映像组件大小

        该模块用于详细统计程序中每个目标文件(Object)和库(Library)的内存占用情况,包括代码段、只读数据、可读写数据等的大小。

    • 代码段(Code):统计每个目标文件或库成员的机器指令大小(.text段)。
    • 只读数据(RO Data):常量数据(如字符串、全局常量)的存储占用(.rodata段)。
    • 可读写数据(RW Data):已初始化的全局变量(.data段)的存储占用,需在启动时从Flash复制到RAM。
    • 未初始化数据(ZI Data):未初始化的全局变量(.bss段)的RAM占用,启动时被清零。
    • 调试信息(Debug):调试符号和元数据的大小,影响调试效率但不影响运行时内存。

        如下图startup_stm32f40_41xxx.o文件中,Zi Data 为4608字节,通常为堆栈的预留空间。关于Library Member Name(库函数)统计,对于不需要的库函数可以通过优化选项移除来节约空间。

最后来到.map文件末尾,如下图。

  • Total RO Size:Code + RO Data = 3024字节,决定Flash的代码和常量存储需求。
  • Total RW Size:RW Data + ZI Data = 4832字节,决定RAM的运行时需求。
  • Total ROM Size : Code + RO Data +RW Data = 3148字节,与bin文件大小一样,也是烧录到芯片中的实际数据量,和Load Region LR_IROM1中的大小一样。不包含ZI Data数据段,是因为在运行时由启动代码进行初始化为零操作。

四、map文件实战

1、通过.map文件查看变量是否被优化

        在开发过程中经常会发现一个变量被重新赋值后,其值并没有变化,这是因为很可能被优化掉了,如下例子不是最好的例子,但是检查原理一样。

        如下图所示,明明已经定义了,但是在map文件中并没有找到,我们可以在Removing Unused input sections from the image段中找到被移除的数据。

被移除的数据段。

        这个例子是因为编译器进行了优化,常量传播中,当ro_data2未被取地址,且仅用于读取值时,编译器会将直接替换为0x11,无需分配存储空间。但是输出数据并不影响。在开发过程中,如果不确定一个变量是否被优化掉,我可以通过加上volatile防止被编译器优化。如下图所示。加上之后,就可以在map文件中找到ro_data2,可以通过查询对应变量的方式确定是否被优化。

2、通过.map文件检查内存分配

        在初始化过程中,启动代码把Flash中的data段和bss段复制到RAM中,随后分配堆栈的空间。我们基本可以根据RW Data + ZI Data 小于总内存,可以根据这个判定是否判断溢出。

        查看Size是否小于总内存量,有的单片机有多块内存,如下图所示。当然,关于内存溢出问题,尽量避免使用大字节的局部变量,同时尽量减少函数的深层次调用等。

3、通过.map文件查看堆栈分配

        在前面的例子中有查看堆栈配置的方法,如下图所示,map文件中相关信息,也可以通过startup_stm32f40_41xxx.s文件中修改堆栈大小。

修改方法

4、通过.map文件查看优化存储段

        如果在keil中设置了优化代码,如下图所示,那么最后生成的bin文件会将Removing Unused input sections from the image段中代码不参与烧写,通俗一点讲,在这个段内的代码都被优化掉了,极大的节约了存储开销。

这个段中所有移除的都不参与,不占用内存。

        我们在main中未调用demo_global__fun函数,这里在map文件中查看已经被优化掉了,我们查看bin大小为2.57KB,我们再对比查看调用的大小。

        我们在main中调用demo_global__fun函数,此时demo_global__fun在全局符号区,此时bin文件的大小为2.85KB,说明未使用的时候确实被优化掉了。

5、通过.map文件判断中断向量表覆盖

.map 文件的 Memory Map 部分,确认以下内容是否与向量表地址范围重叠:

  • 代码段(.text):若代码段起始地址早于向量表结束地址,可能覆盖向量表。
  • 初始化数据段(.data):若 .data 段被错误链接到 Flash 区域(如未正确分离加载域与执行域),可能覆盖向量表。
  • 其他自定义段:如用户定义的常量表或代码块可能占用向量表空间。

此时,代码段 .text 与向量表 .vectors 地址重叠,导致向量表被覆盖

.text           0x08000000 - 0x08001000(覆盖向量表)
.vectors        0x08000000 - 0x08000100(被代码段覆盖)
6、通过.map文件的检测栈溢出

        通过map文件可以查看到堆栈边界,我们就可以编写简单的测试软件达到测试栈溢出问题。如下图,我们确定栈边界后,编写简单的测试程序。


uint32_t *stackGuard = (uint32_t*)0x2000C1F8;  // 栈顶地址
if (*stackGuard != 0xDEADBEEF) { /* 溢出处理 */ }

五、总结

本文介绍了.map的生成、使用与实战,通过map文件,能够更快的定位错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值