MAP文件
链接器生成的列表文件,对分析程序存储占用情况非常有用
STM32堆栈空间大小设置 - 爱码网 (likecs.com)
(63条消息) 关于arm中ZI-data段和RW-data段,以及堆栈起始地址的理解_加油2019的博客-CSDN博客_zi-data
【安富莱】第11期:MDK生成的map和htm文件分析(重要)_哔哩哔哩_bilibili
STM32F1系列之Keil-MDK下散列文件的分析 (stmicroelectronics.cn)
MCU: ARM启动中的分散加载 - 知乎 (zhihu.com)
5.1 基本概念
段(section) : 描述映像文件的代码和数据块,我们简称为程序段
RO: Read-Only的缩写,包括RO-data(只读数据)和RO-code(代码) ,占用占用 FLASH 空间 RW: Read-Write的缩写,主要是RW-data,RW-data由程序初始化初始值。 ,占用 FLASH(存储初值)和 RAM(读写操作),已初始化的可读写全局/静态变量 ZI: Zero-initialized的缩写,主要是ZI-data,由编译器初始化为0,占用 RAM 空间。 .text: 与RO-code同义。未初始化的可读写全局/静态变量 .constdata: 与RO-data同义。 .bss: 与ZI-data同义。 .data: 与RW-data同义
堆和栈被包含在ZI-DATA中
FLASH=CODE+RO-DATA+RW-DATA
SPAM=RW-DATA+ZI-DATA
5.2 map文件主要内容分析
文件里面内容大致分为五大类(按照map文件分类的顺序):
5.2.1 Section Cross References:
模块、段(入口)交叉引用,即不同文件中的调用关系;
以第二行为例
main.o是表明了文件名(该文件由main.c编译生成),
(i.main) 代表是该文件下的main函数
refers to 后面就说明了调用哪些内容,以此处为例没引用了bsp.o(bsp.c)文件下的bsp_Init函数
5.2.2 Removing Unused input sections from the image:
移除未使用的模块;
该段在最后有说明总的删除量
值得注意的是,在MDK中勾选上One ELF Section per Function选项可以大幅提升删减量,提高优化程度,减小内存占用,建议选上
值得注意的是,当设置编译器优化等级为Level 0后,开启One ELF Section per Function,在main函数中定义了static char型变量tets(经测试与static有无没有关系)并且在后续不使用它。
会发现该变量被编译器优化,添加该变量与否不影响编译的大小,输出的Program Size: 信息都是一样的。Code=5352 RO-data=360 RW-data=28 ZI-data=1900
并且在map文件中完全找不到该变量信息,包括在Removing Unused input sections from the image中也没有
尝试将test修改为全局变量,结果一样,不影响Program Size。
并且将One ELF Section per Function选项去掉后(本身编译的大小当然增加了,但比较的是有无tets变量前后结果),结果也一样,Program Size依据是一样的。
因此当一个变量(无论全局还是局部,无论static与否),当定义后未被使用,编译器会自动将其优化掉
5.2.3 Image Symbol Table:
映射符号表;
这一部分又可以分为两个小部分,分别为Local Symbols(局部标号/符号)和Global Symbols(全局标号/符号)
无论哪个都以一下信息为表头
Symbol Name :标号名 Value:所在地址 Ov Type:类型,说明是Number还是Section又或是Data Size Object(Section):所在段
5.2.3.1 Local Symbols
此部分主要包含:1、用 static 声明的函数的大小与地址。2、函数内部用static 声明的局部变量的大小与地址。3、没有被static修饰的函数地址(大小为0)4、汇编文件中的标号地址(作用域: 本文件)
1、函数内部用static 声明的局部变量的大小与地址
在mian函数中用static修饰的int型变量test,为了避免被编译器优化在后面的对其赋了一次值
编译后打开map文件,在Local Symbols段可以找到以下信息,地址为0x20000000 大小为4字节(int型的大小,改为char型就是1)
2、用 static 声明的函数的大小与地址
如果是被static修饰的函数,则只会在局部符号段给出入口地址信息,在Global Symbols段就不会有相关信息,并且此时在 Local Symbols段还会给出大小信息。
以正点原子hal库版本delay_us为例,修改前缀为static,则在 Local Symbols段有以下信息:
i.delay_us说明了函数入口地址0x08001420但大小为0,因为此时只表示函数 入口地址,并不是指令
delay_us说明了函数地址和大小
值得注意的是这里的地址为0x08001421,比上面的地址大1,据说这是因为 ARM 规定 Thumb 指令集的所有指令,其最低位必须为 1
3、没有被static修饰的函数地址(大小为0)
按网上的描述只有用 static 声明的函数的大小与地址会出现在Local Symbols,但实际上函数基本上在局部标号段里都是有的,只不过其大小描述在Global Symbols段中。
以正点原子hal库版本led实验为例,LED_Init函数未被static修饰,
在 Local Symbols段段信息如下
在Global Symbols段中信息如下,同样的地址比上面的加1
1、.text标号
值得注意的是我们可能看到同名的Symbol Name 以下面的为例有很多的.text实际上它表明了startup_stm32f103xe这个启动文件的代码段大小为64字节,其它的同理
2、.data标号
.data的Section类型的标号在delay.o文件中共有4个字节,该大小等于下面的所有在delay.o文件中的Data类型的标号的大小总和,在该文件中只有一个fac_us因此就等于fac_us标号的大小
5.2.3.2 Global Symbols
此部分主要包含:1、全局变量的地址和大小 2、C 文件中函数的地址及其 代码大小3、汇编文件中的标号地址(作用域: 全工程),,这一部分不在赘述
实际上我们无需泰国关心到底在Global Symbols还是在Local Symbols,因为不是在Global Symbols就是在Local Symbols
5.2.4 Memory Map of the image:
映像内存分布图,反应了内存(映射)分布,分为加载域( Load Region)和运行域( Execution Region),一个加载域可以有多个执行域,对STM32F103zet6来说,代码可以直接在FLASH执行,因此有一个执行域在FLASH,还有一个执行域在RAM,负责变量数据的读写。具体可以查看.sct散列文件,其实就是链接文件,表示了代码的加载与执行区域
Image Entry point : 0x08000131
映像入口点即程序开始执行的地方,而FLASH的用户区起始地址为0x08000000,在0x08000000到0x08000131的中间这些地址段储存的是中断向量表。
Load Region LR_IROM1 (Base: 0x08000000, Size: 0x0000166c, Max: 0x01000000, ABSOLUTE)
加载域,基地址是0x08000000,实际使用的大小,也就是存到ROM(FLASH)的大小为0x0000166c,而最大的使用空间是0x01000000,与.sct文件设置的一致,单位为字节,也就是最大1字节,这个会根据你新建工程时选择的芯片而随之变动,也可以直接在Target选项栏直接修改
Execution Region ER_IROM1 (Exec base: 0x08000000, Load base: 0x08000000, Size: 0x00001650, Max: 0x01000000, ABSOLUTE)
执行域,于.sct文件设置的一致,与前面的Load Region LR_IROM1的区别暂时不清楚,据ChatGpt回答,,ER_IROM1存放的是可执行的代码,而LR_IROM1存放的是程序代码和数据,这么看还是有道理的因为LR_IROM1的大小确实比ER_IROM1大
后续分析见散列文件章节
5.2.5 Image component sizes:
存储组成大小。
RW Data+ZI data=占用SRAM的大小
Code+RO Data+RW Data=占用FLASH的大小
我们可以注意启动文件占用的ZI Data非常大,这是因为里面包含了堆栈的大小
如果不使用malloc等函数去使用堆区的空间,可以直接将启动文件Heap_Size修改为0,我们修改后,重新编译,再次打开map文件,如下,可以看到ZI Data大小明显减小,且减小的数量正好为我们取消的堆空间的大小(原本为0x00000200即512字节,而1536-1024=512)
下面一部分是一些编译器调用的库文件(从 Library Member Name这个名字就可以看出来),我们没法进行优化,也无需太过于关心这一部分(_main.o不是我们编写的main是编译器的c库函数)
最后我们看到文件的末尾,是对空间占用的总结
RW Size是使用的SRAM 的大小,ROM Size是使用的Flash的大小
5.2.5.1 验证 RW Data
修改方式1
在main.c增加了一个变量test为了防止被优化在主函数增加了test=2,
查看下图,main.o的Code区增加了8字节,RW Data增加了4字节
原先的Program Size是Code=5352 RO-data=360 RW-data=28 ZI-data=1900 ,现在是RW-data是32字节,增加了4字节,但是不知为何ZI-data反而减小了4字节,可能也与编译器有关
查看下图,ROM Size增加12字节,正是main.o的中的Code与RW Data的增加,
但是Total RW Size没有增加,考虑是被编译器优化了,因为这段代码没有实际意义,没有与其他变量或函数完成交互。
修改方式2
修改代码如下,增加了变量test2并与test1进行比较,防止优化
编译结果如下, main.o的 RW Data增加了8字节,正是两个int型的大小
RW-Data增加了8字节,ZI-Data也没有增加
最后的Total RW Size也增加了8字节(原先是1928)
修改方式3
修改代码为下图,不使用判断,直接使用volatile关键字,修饰两个变量
RW-data的最终结果与第二次修改一致
结论:RW-data是由程序编写者手动完成初始化的可读可写的全局变量或static修饰的静态变量,但是编译器有时会优化掉一些变量,使RW-Data不增加
5.2.5.2 验证ZI-Data
网上的说法是未初始化的全局变量会储存在该处,但经过实验反而会增加RW-Data的大小,后续经过不断尝试
终于发现问题,定义一个未初始化的全局变量数组,当元素个数超过2个,即大于等于3个,就会算作ZI DATA,代码如下,定义了一个test数组,元素个数为3,又在test_zidata函数中调用,防止被优化。
查看MAP文件,确实有12字节的ZI Data
一旦修改元素个数为2,就会被算作RW Data,并且如果不用未初始的全局数组,而是未初始化的全局变量或指针,如下图,也会被算作RW Data
Last updated