# MAP文件

链接器生成的列表文件，对分析程序存储占用情况非常有用

[STM32堆栈空间大小设置 - 爱码网 (likecs.com)](https://www.likecs.com/show-204638766.html#sc=290.4000244140625)

[(63条消息) 关于arm中ZI-data段和RW-data段，以及堆栈起始地址的理解\_加油2019的博客-CSDN博客\_zi-data](https://blog.csdn.net/qq_40036519/article/details/88377453)

[【安富莱】第11期：MDK生成的map和htm文件分析（重要）\_哔哩哔哩\_bilibili](https://www.bilibili.com/video/BV1t3411C7Pu/?spm_id_from=333.337.search-card.all.click\&vd_source=3d652d6ce183145af7fbfe477961404f)

[STM32F1系列之Keil-MDK下散列文件的分析 (stmicroelectronics.cn)](https://shequ.stmicroelectronics.cn/thread-636528-1-1.html)

[MCU: ARM启动中的分散加载 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/22861542?from_voters_page=true)

## 5.1    基本概念

**段(section) ：** 描述映像文件的代码和数据块，我们简称为程序段

**RO：** Read-Only的缩写，包括RO-data(只读数据)和RO-code(代码) ，占用占用 FLASH 空间 **RW：** Read-Write的缩写，主要是RW-data，RW-data由程序初始化初始值。  ，占用 FLASH（存储初值）和 RAM（读写操作），已初始化的可读写全局/[静态变量](https://www.zhihu.com/search?q=%E9%9D%99%E6%80%81%E5%8F%98%E9%87%8F\&search_source=Entity\&hybrid_search_source=Entity\&hybrid_search_extra=%7B%22sourceType%22%3A%22article%22%2C%22sourceId%22%3A%22376136174%22%7D) **ZI：** Zero-initialized的缩写，主要是ZI-data，由编译器初始化为0，占用 RAM 空间。 **.text：** 与RO-code同义。未初始化的可读写全局/静态变量\
\&#xNAN;**.constdata：** 与RO-data同义。 \
\&#xNAN;**.bss：** 与ZI-data同义。\
\&#xNAN;**.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：

模块、段(入口)交叉引用，即不同文件中的调用关系；

![](https://s2.loli.net/2023/02/24/lPnQgXz2SJ3jeCh.png)

以第二行为例

* **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：

移除未使用的模块；

![](https://s2.loli.net/2023/02/24/chHjgBaiSuQVNeJ.png)

该段在最后有说明总的删除量

![](https://s2.loli.net/2023/02/24/PHqyE9Rd6wWxMzN.png)

值得注意的是，在MDK中勾选上One ELF Section per Function选项可以大幅提升删减量，提高优化程度，减小内存占用，建议选上

![](https://s2.loli.net/2023/02/24/QAGasTS4XJFpoDM.png)

> 值得注意的是，当设置编译器优化等级为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 &#x20;
>
> 并且在map文件中完全找不到该变量信息，包括在Removing Unused input sections from the image中也没有
>
> **尝试将test修改为全局变量**，结果一样，不影响Program Size。
>
> **并且将One ELF Section per Function选项去掉后（本身编译的大小当然增加了，但比较的是有无tets变量前后结果）**，结果也一样，Program Size依据是一样的。
>
> 因此当一个变量（无论全局还是局部，无论static与否），当定义后未被使用，编译器会自动将其优化掉

![](C:%5CUsers%5C31468%5CAppData%5CRoaming%5Cmarktext%5Cimages%5C2023-02-24-21-10-37-image.png)

### 5.2.3    Image Symbol Table：

映射符号表；

这一部分又可以分为两个小部分，分别为**Local Symbols（局部标号/符号）和Global Symbols（全局标号/符号）**

无论哪个都以一下信息为表头

**Symbol Name ：标号名 Value：所在地址 Ov Type:类型,说明是Number还是Section又或是Data Size Object(Section):所在段**

![](C:%5CUsers%5C31468%5CAppData%5CRoaming%5Cmarktext%5Cimages%5C2023-02-24-21-25-38-image.png)

#### 5.2.3.1    Local Symbols

此部分主要包含：**1、用 static 声明的函数的大小与地址。2、函数内部用static 声明的局部变量的大小与地址。3、没有被static修饰的函数地址(大小为0)4、汇编文件中的标号地址（作用域： 本文件）**

**1、函数内部用static 声明的局部变量的大小与地址**

在mian函数中用static修饰的int型变量test，为了避免被编译器优化在后面的对其赋了一次值

![](https://s2.loli.net/2023/02/24/elPYXZya65wWbFn.png)

编译后打开map文件，在Local Symbols段可以找到以下信息，地址为0x20000000 大小为4字节（int型的大小，改为char型就是1）

![](C:%5CUsers%5C31468%5CAppData%5CRoaming%5Cmarktext%5Cimages%5C2023-02-24-20-19-37-image.png)

**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

![](https://s2.loli.net/2023/02/24/paXset7NJW6nR5v.png)

**3、没有被static修饰的函数地址(大小为0)**

按网上的描述只有用 static 声明的函数的大小与地址会出现在Local Symbols，但实际上函数基本上在局部标号段里都是有的，只不过其大小描述在Global Symbols段中。

以正点原子hal库版本led实验为例，LED\_Init函数未被static修饰，

在 Local Symbols段段信息如下

![](https://s2.loli.net/2023/02/24/UkjbYuEefMWRdyH.png)

在Global Symbols段中信息如下，同样的地址比上面的加1

![](https://s2.loli.net/2023/02/24/f1bdAB7v6PykOIM.png)

> **1、.text标号**
>
> 值得注意的是我们可能看到同名的Symbol Name 以下面的为例有很多的.text实际上它表明了startup\_stm32f103xe这个启动文件的代码段大小为64字节，其它的同理

![](https://s2.loli.net/2023/02/24/PRoVwE2A5fBFTCJ.png)

> **2、.data标号**
>
> .data的Section类型的标号在delay.o文件中共有4个字节，该大小等于下面的所有在delay.o文件中的Data类型的标号的大小总和，在该文件中只有一个fac\_us因此就等于fac\_us标号的大小

![](C:%5CUsers%5C31468%5CAppData%5CRoaming%5Cmarktext%5Cimages%5C2023-02-24-22-12-00-image.png)

#### 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散列文件，其实就是链接文件，表示了代码的加载与执行区域**

![](https://s2.loli.net/2023/02/27/Xz6rjWDdsfp19Ru.png)

* 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选项栏直接修改**

![](https://s2.loli.net/2023/02/27/rY7R2SOKdwQop1q.png)

* 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非常大，这是因为里面包含了堆栈的大小

![](https://s2.loli.net/2023/02/27/G56Yz84iuMQIdxW.png)

如果不使用malloc等函数去使用堆区的空间，可以直接将启动文件Heap\_Size修改为0，我们修改后，重新编译，再次打开map文件，如下，可以看到ZI Data大小明显减小，且减小的数量正好为我们取消的堆空间的大小（原本为0x00000200即512字节，而1536-1024=512）

![](https://s2.loli.net/2023/02/27/4hQyPBrt6snKEqT.png)

下面一部分是一些编译器调用的库文件（从 Library Member Name这个名字就可以看出来），我们没法进行优化，也无需太过于关心这一部分（\_main.o不是我们编写的main是编译器的c库函数）

![](https://s2.loli.net/2023/02/27/MB4ZmLd7KuSDUAO.png)

最后我们看到文件的末尾，是对空间占用的总结

RW Size是使用的SRAM 的大小，ROM Size是使用的Flash的大小

![](https://s2.loli.net/2023/02/27/2AhMHltefXcr3vT.png)

#### 5.2.5.1 验证 RW Data

#### 修改方式1

在main.c增加了一个变量test为了防止被优化在主函数增加了test=2,

![](https://s2.loli.net/2023/02/27/Xedz8Z1EcTHb7kF.png)

查看下图，main.o的Code区增加了8字节，RW Data增加了4字节

![](https://s2.loli.net/2023/02/27/6njvmT2YoktySAL.png)

原先的Program Size是Code=5352 RO-data=360 RW-data=28 ZI-data=1900 ，现在是RW-data是32字节，增加了4字节，但是不知为何ZI-data反而减小了4字节，可能也与编译器有关

![](https://s2.loli.net/2023/02/27/mzVl5c49MuUTPXD.png)

查看下图，ROM Size增加12字节，正是main.o的中的Code与RW Data的增加，

但是Total RW Size没有增加，考虑是被编译器优化了，因为这段代码没有实际意义，没有与其他变量或函数完成交互。

![](https://s2.loli.net/2023/02/27/juYpxUnvbCy3oAK.png)

#### 修改方式2

修改代码如下，增加了变量test2并与test1进行比较，防止优化

![](https://s2.loli.net/2023/02/27/UdQGhnlXmWSEMvZ.png)

编译结果如下， main.o的 RW Data增加了8字节，正是两个int型的大小

![](https://s2.loli.net/2023/02/27/N76bhYIgXQGJEAt.png)

RW-Data增加了8字节，ZI-Data也没有增加

![](https://s2.loli.net/2023/02/27/yQC7gNrEP1aB3Hn.png)

最后的Total RW Size也增加了8字节（原先是1928）

![](https://s2.loli.net/2023/02/27/bmQKTMZFouOU4GB.png)

#### 修改方式3

修改代码为下图，不使用判断，直接使用**volatile关键字**，修饰两个变量

![](https://s2.loli.net/2023/02/27/MhSY5ERuwcPbf3I.png)

RW-data的最终结果与第二次修改一致

![](https://s2.loli.net/2023/02/27/tN9XfCmsDqUnxrG.png)

![](https://s2.loli.net/2023/02/27/YKMLFqbnfUSAP29.png)

![](https://s2.loli.net/2023/02/27/VrwmBEnsS7vRfHb.png)

> 结论：RW-data是由程序编写者手动完成初始化的可读可写的全局变量或static修饰的静态变量，但是编译器有时会优化掉一些变量，使RW-Data不增加

#### 5.2.5.2 验证ZI-Data

网上的说法是未初始化的全局变量会储存在该处，但经过实验反而会增加RW-Data的大小，后续经过不断尝试

终于发现问题，定义一个未初始化的全局变量数组，当元素个数超过2个，即大于等于3个，就会算作ZI DATA，代码如下，定义了一个test数组，元素个数为3，又在test\_zidata函数中调用，防止被优化。

![](https://s2.loli.net/2023/03/04/bSM6DvpoQjzGW1Y.png)

查看MAP文件，确实有12字节的ZI Data

![](https://s2.loli.net/2023/03/04/5nhIi1Vq9uXa72G.png)

一旦修改元素个数为2，就会被算作RW Data，并且如果不用未初始的全局数组，而是未初始化的全局变量或指针，如下图，也会被算作RW Data

![](https://s2.loli.net/2023/03/04/aruE6jJbPfqCS2c.png)
