什么是map文件?
简单的说:map文件是通过编译器编译之后,集程序、数据及IO空间的一种映射文件。
Keil生成map文件的设置如下图所示:
生成文件保存路径与Listings选择路径相同。
keil 主要包含配置:
Memory Map:内存映射
Callgraph:图像映射
Symbols:符号
Cross Reference:交叉引用
Size Info:大小信息
Totals Info:统计信息
Unused Section Info:未调用模块信息
Veneers Info:装饰信息
【如何查看map文件】
1.到对应文件生成文件目录下查看(KEIL即为Listings目录)
2.双击工程目标名则会打开对应工程的map文件(需要是编译完成的情况下)
MAP文件基本概念
段(section):描述映像文件的代码和数据块
RO:Read-Only的缩写,包括RO-data(只读数据)和RO-code(代码)
RW:Read-Write的缩写,主要是RW-data,RW-data由程序初始化初始值
ZI:Zero-initialized的缩写,主要是ZI-data,由编译器初始化为0。
.text:与RO-code同义
.constdata:与RO-data同义
.bss:与ZI-data同义
.data:与RW-data同义
文件分析
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:存储组成大小
第一部分:Section Cross References(模块、段的交叉引用关系)
主要是各个源文件生成的模块之间相互引用的关系。“refer to”是引用的意思,比如:
首先main.c和led.c会被编译成目标文件main.o和led.o。i.main是main.c中main函数的入口(也是main函数编译出的段,函数编译后以段的形式存在,函数之间的引用,也就是段与段之间的引用)。i.LED_Init是led.c中LED_Init函数的入口(也是LED_Init函数编译出的段)。因此上面这段的意思就是main.c中的main函数引用了led.c中的LED_Init函数,该部分剩余的其他语句基本都类似。
第二部分:Removing Unused input sections from the image(移除未使用的段)
就是将库中没有用到的函数从可执行映像中删除掉,减小程序的体积。
删除(指不加入到*.axf 文件,不是指在*.o 文件删除),这样可以防止这些无用数据占用程序空间
最后一栏有个总的统计结果:
总共移除了666个未使用的段,共51615字节。
第三部分:Image Symbol Table(映射符号表,列出了各个段所存储的对应地址)
分为Local Symbols局部 和 Global Symbols全局。
局部标号(用static声明的全局变量地址和大小,C文件中函数的地址和用static声明的函数代码大小,汇编文件中的标号地址(作用域限本文件)
全局标号,全局变量的地址和大小,C文件中函数的地址及其代码大小,汇编文件中的标号地址(作用域全工程)
Local Symbols记录了用static声明的全局变量地址和大小,C文件中函数的地址和用static声明的函数代码大小,汇编文件中的标号地址(作用域限本文件),下面是部分截图:
1、Symbol Name:符号名称
2、Value:存储对应的地址;
大家会发现有0x0800xxxx、0x2000xxxx这样的地址。
0x0800xxxx指存储在FLASH里面的代码、变量等。
0x2000xxxx指存储在内存RAM中的变量Data等。
3、Ov Type:符号对应的类型
符号类型大概有几种:Number、Section、Thumb Code、Data等;
细心的朋友会发现:全局、静态变量等位于0x2000xxxx的内存RAM中。
4、Size:存储大小
这个容易理解,我们怀疑内存溢出,可以查看代码存储大小来分析。
5、Object(Section):当前符号所在段名
这里一般指所在模块(所在源文件)。
举例:
LED_Init 符号存储在 0x08001861 地址,它属于 Thumb Code 类型,大小为 62 字节,它所在的节区为 led.o 文件的 i.LED_Init 节区。
以下这段位于Local Symbols中,可以判断LCD_Gpio_Init是一段static的函数,函数占用flash 150字节,在lcd.o中定义了。
堆和栈也在Local Symbols中,可以看出HEAP和STACK大小分别是512字节和1024字节。
lcd_buf位于Local Symbols中,因此lcd_buf是一个static变量,大小为1152字节,符号类型是Data,在bss段可以判定没有初始化,【bss(Block Started by Symbol segment):未初始化静态变量, 未初始化数据】,定义在lcd.o文件
第四部分:Memory Map of the image(映像的内存分布)
映像文件可以分为加载域(Load Region)和运行域(Execution Region):加载域反映了ARM可执行映像文件的各个段存放在存储器中的位置关系。下面是部分截图,另外映像中的入口点就是程序开始执行的位置。
1、Exec Addr:运行域地址
2、Load Addr:加载域地址
3、Size:存储大小
4、Type:类型
Data:数据类型
Code:代码类型
Zero:未初始化变量类型
PAD:这个类型在map文件中放在这个位置,其实它不能算这里的类型。要翻译的话,只能说的“补充类型”。
ARM处理器是32位的,如果定义一个8位或者16位变量就会剩余一部分,这里就是指的“补充”的那部分,会发现后面的其他几个选项都没有对应的值。
5、Attr 属性 RO:存储与ROM中的段;RW:存储与RAM中的段
6、Section Name 段名
7、Object 目标
运行域反映了ARM可执行映像文件各个段真正执行时在存储器中的位置关系:
Load Region LR_IROM1 (Base: 0x08000000, Size: 0x000122a8, Max: 0x00080000, ABSOLUTE, COMPRESSED[0x000121a0])
指加载域位于LR_IROM1开始地址0x08000000,大小有0x000122a8,这块区域最大为0x00080000
Execution Region ER_IROM1 (Exec base: 0x08000000, Load base: 0x08000000, Size: 0x0001213c, Max: 0x00080000, ABSOLUTE)
Execution Region RW_IRAM1 (Exec base: 0x20000000, Load base: 0x0801213c, Size: 0x00011de0, Max: 0x00018000, ABSOLUTE, COMPRESSED[0x00000064])
执行区域:对应我们目标配置中的区域
【补充:加载域和运行域】
加载域就是程序在Flash中的实际存储,而运行域是芯片上电后的运行状态,因为MCU没上电时RAM中没有数据,所以此时所有的东西(包括代码、变量、初始值等)都是存放在flash中的,当上电后又要把变量等复制到RAM中才能正常运行。
通过上面的框图可以看出,RW区也是要存储到ROM/Flash里面的。在执行映像之前,必须将已初始化的RW数据从ROM中复制到RAM中的执行地址并创建ZI Section(初始值为0的变量区),这样才算完成了MCU运行的准备。
第五部分:Image component sizes(映像组成大小)
Code (inc. Data) :显示代码占用了多少字节。 在此映像中,有19442字节的代码, 其中包括1832字节的内联数据 (inc. data),例如文字池和短字符串。
RO Data :显示只读数据占用了多少字节(比如const char buf[] = “123456”)。这是除 Code (inc. data) 列中包括的内联数据之外的数据。
RW Data :显示读写数据占用了多少字节。
ZI Data :显示零初始化的数据占用了多少字节。
Debug :显示调试数据占用了多少字节,例如,调试输入节以及符号和字符串。
Object Totals :显示链接到一起以生成映像的对象占用了多少字节。
(incl. Generated):链接器会生成的映像内容,例如,交互操作中间代码。 如果 Object Totals 行包含此类型的数据,则会显示在该行中。本例中共有 1016 字节的 RO 数据,其中32字节是链接器生成的 RO 数据。
(incl. Padding) :链接器根据需要插入填充,以强制字节对齐。
最后是整个映像文件的总结说明:
Grand Totals:显示映像的真实大小。
ELF Image Totals:ELF(Executable and Linking Format)可执行链接格式映像文件大小。
ROM Totals:显示包含映像所需的 ROM的最小大小。这不包括 ZI数据和存储在ROM 中的调试信息。
【补充:KEIL 计算程序占用大小】
如图是MDK编译之后的信息:
Code:指代码的大小;
Ro-data:指除了内联数据(inline data)之外的常量数据;
RW-data:指可读写(RW)、已初始化的变量数据;
ZI-data:指未初始化(ZI)的变量数据;
提醒:
A.Code、Ro-data:位于FLASH中;
B.RW-data、ZI-data:位于RAM中;
C.RW-data已初始化的数据会存储在Flash中,上电会从FLASH搬移至RAM。
占用的FLASH = Code + RO-data + RW-data
占用的SRAM = RW-data + ZI-data
(为什么RW-data既在FLASH中又在SRAM?—因为RW-data已初始化的数据会存储在Flash中,上电会从FLASH搬移至RAM。)
关系如下:
RO Size = Code + RO Data
RW Size = RW Data + ZI Data
ROM Size = Code + RO Data + RW Data
(此数据在map文件末尾就可以查看)
【补充:内存分布】
text:程序 文字区段(text segment)也称为程序段(code segment),存放可执行命令(instructions)。
该区段通常位于 heap 或 stack 之後,避免因 heap 或 stack 溢出或者覆盖 CPU 指令。
data:初始化静态变量 初始化数据区段(initialized data segment)存储已经初始化的静态变量,例如有经过初始化的 C 语言的全局变量(global variables)以及静态变量(static variables),分为RW-data和RO-data。
bss(Block Started by Symbol segment):未初始化静态变量 未初始化数据
stack:栈(stack segment)用于存储函数的局部变量,以及各种函数调用时需要存储的信息(比如函数返回的存储器地址、函数状态等),每一次的函数调用就会在栈上建立一个 stack frame(栈帧),存储该次调用的所有数据于状态,这样以来同一个函数被调用多次时,就会有不同栈帧,不会相互干扰。
heap:堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减,例如 C 语言的 malloc 以及 C++ 的 new 所建立的变量都是储存于此。
栈(Stack)一般的状态会从高地址往低地址生长,而 heap 相反。
前面提到的运行域从Section Name也可以印证上面的内存分布图。
参考鸣谢:
https://blog.csdn.net/qlexcel/article/details/78884379
https://www.cnblogs.com/chengeputongren/p/12177423.html
https://blog.csdn.net/IT_B_O_Y/article/details/93722989