认识MAP文件
MDK编译工程,会生成一些中间文件(如.o、.axf、.map 等),最终生成hex文件,以便下载到MCU上面执行。这些文件分为 11 个类型,其中4种文件比较重要。
比如:
本文主要讲解map文件。
map文件的组成如下:
.map 文件是编译器链接时生成的一个文件,它主要包含了交叉链接信息。通过.map 文件,我们可以知道整个工程的函数调用关系、FLASH 和 RAM 占用情况及其详细汇总信息,能具体到单个源文件(.c/.s)的占用情况,根据这些信息,我们可以对代码进行优化。
.map 文件可以分为以下 5 个组成部分:
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 文件的 MDK 设置
2, map 文件的基础概念
3, map 文件的组成部分说明
map 文件的 MDK 设置
要生成 map 文件,我们需要在 MDK 的魔术棒→Listing 选项卡里面,进行相关设置,如图 2.1.1 所示:
图 2.1.1 .map 文件生成设置
图 2.1.1 中红框框出的部分就是我们需要设置的,默认情况下,MDK 这部分设置就是全勾选的,如果我们想取消掉一些信息的输出,则取消相关勾选即可(一般不建议)。
如图 2.1.1 设置好 MDK 以后,我全编译当前工程,当编译完成后(无错误),就会生成.map文件。在 MDK 里面打开.map 文件的方法如图 2.1.2 所示:
注意,如果双击没反应,则需要调整Listing到对应的存放位置。
map 文件的基础概念
为了更好的分析 map 文件,我们先对需要用到的一些基础概念进行一个简单介绍,相关概念如下:
● Section:描述映像文件的代码或数据块,我们简称程序段
● RO:Read Only 的缩写,包括只读数据(RO data)和代码(RO code)两部分内容,
占用 FLASH 空间
● RW:Read Write 的缩写,包含可读写数据(RW data,有初值,且不为 0),占用 FLASH
(存储初值)和 RAM(读写操作)
● ZI:Zero initialized 的缩写,包含初始化为 0 的数据(
ZI data),占用 RAM 空间。
● .text:相当于 RO code
● .constdata:相当于 RO data
● .bss:相当于 ZI data
● .data:相当于 RW data
map 文件的组成部分说明
程序段交叉引用关系(Section Cross References)
这部分内容描述了各个文件(.c/.s 等)之间函数(程序段)的调用关系,如图 2.1.1.1
所示:
上图中,红框框出的部分:main.o(i.main) refers to sys.o(i.sys_stm32_clock_init) for sys_stm32_clock_init,表示:main.c文件中的main函数,调用了sys.c中的sys_stm32_clock_init函数。其中:i.main 表示 main 函数的入口地址,同理i.sys_stm32_clock_init 表示sys_stm32_clock_init 的入口地址。
删除映像未使用的程序段(Removing Unused input sections from the image)
这部分内容描述了工程中由于未被调用而被删除的冗余程序段(函数/数据),如图2.1.2.1 所示:
上图中,列出了所有被移除的程序段,比如 stm32h7xx_hal_usart_ex.c 文件里面的
USARTEx_SetNbDataToProcess 函数就被移除了,因为该例程没用到这个函数。
另外,在最后还有一个统计信息: 361 unused section(s) (total 43234 bytes) removed from the image. 表示总共移除了 361 个程序段(函数/数据),大小为 43234 字节。即给我们的
MCU 节省了 43234 字节的程序空间。
为了更好的节省空间,我们一般在 MDK→魔术棒→C/C++选项卡里面勾选:One ELF Section per Function,如图 2.1.2.2 所示:
映像符号表(Image Symbol Table)
映像符号表(Image Symbol Table)描述了被引用的各个符号(程序段/数据)在存储器中的存储地址、类型、大小等信息。映像符号表分为两类:本地符号(Local Symbols)和全局符号(Global Symbols)。
本地符号(Local Symbols)
本地符号(Local Symbols)记录了用 static 声明的全局变量地址和大小,c 文件中函数的地址和用 static 声明的函数代码大小,汇编文件中的标号地址(作用域:限本文件),本地符号如图 2.1.3.1.1 所示:
图中红框框处部分,表示 sys.c 文件中的 sys_stm32_clock_init 函数的入口地址为:0x08002bc8,类型为:Section(程序段),大小为 0。因为:i. sys_stm32_clock_init 仅仅表示sys_stm32_clock_init 函数入口地址,并不是指令,所以没有大小。在全局符号段,会列出sys_stm32_clock_init 函数的大小。
全局符号(Global Symbols)
全局符号(Global Symbols)记录了全局变量的地址和大小,C 文件中函数的地址及其代码大小,汇编文件中的标号地址(作用域:全工程),全局符号如图 2.1.3.2.1 所示:
图中红框框处部分,表示 sys.c 文件中的 sys_stm32_clock_init 函数的入口地址为: 0x08002bc9,类型为:Thumb Code(程序段),大小为 344 字节。
注意,此处的地址用的 0x08002bc9,和 2.1.3.1 节的 0x08002bc8 地址不符,这是因为 ARM 规定 Thumb 指令集的所有指令,其最低位必须为 1,0x08002bc9 = 0x08002bc8 +1, 所以才会有 2 个不同的地址,且总是差 1,实际上就是同一个函数。
映像内存分布图(Memory Map of the image)
映像文件分为加载域(Load Region)和运行域(Execution Region),一个加载域必须有 至少一个运行域(可以有多个运行域),而一个程序又可以有多个加载域。加载域为映像程 序的实际存储区域,而运行域则是 MCU 上电后的运行状态。加载域和运行域的简化关系(这里仅表示一个加载域的情况)图如图 2.1.4.1 所示:
由图可知,RW 区也是存放在 ROM(FLASH)里面的,在执行 main 函数之前,RW(有初值且不为 0 的变量)数据会被拷贝到 RAM 区,同时还会在 RAM 里面创建 ZI 区(初始化为 0 的变量)。
了解了加载域和运行域的作用及关系,我们再来看映像内存分布图(H750 例程),如图
2.1.4.2 所示:
① 处,表示映像的入口地址,也就是整个程序运行的起始地址,为:0X0800 0299。实际地址为:0X0800 0298(Thumb 指令最低位是 1)。
② 处,表示 LR_m_stmflash 加载域,其起始地址为:0X0800 0000;占用大小为:0X0000 2D8C;最大地址范围为:0X0002 0000。其内部包含两个运行域:ER_m_stmflash和RW_m_stmsram。
③ 处,表示 ER_m_stmflash 运行域,其起始地址为:0X0800 0000;占用大小为:0X0000 2D6C;最大地址范围为:0X0002 0000;即内部 FLASH 运行域,所有需要放内部FLASH的代码,都应该放到这个运行域里面。对于 STM32F1/F4/F767 等开发板,我们例程所有的代码,都是放在这个运行域的(名字可能不一样)。
④ 处,表示 RW_m_stmsram 运行域,其起始地址为:0X2400 0000;占用大小为:0X00002D6C;最大地址范围为:0X0008 0000;即内部 SRAM 运行域,所有 RAM(包括 RW 和 ZI)都是放在这个运行域里面。
⑤ 处,表示 LR_m_qspiflash 加载域,其起始地址为:0X9000 0000;占用大小为:0X0000 0720;最大地址范围为:0X0080 0000。其内部包含一个运行域:ER_m_qspiflash。
⑥ 处,表示 ER_m_qspiflash 运行域,其起始地址为:0X9000 0000;占用大小为:0X0000 0720;最大地址范围为:0X0080 0000;即外部 QSPI FLASH 运行域,所有需要放外部QSPI FLASH 的代码,都应该放到这个运行域里面。
图 2.1.4.2 中,列出了所有加载域及其运行域的具体内存分布,我们可以很方便的查看任何一个函数所在的运行域、入口地址、占用空间等信息。如 sys_stm32_clock_init 函数:
该函数在 ER_m_stmflash 运行域;入口地址为:0X0800 2BC8;大小为:0X168 字节;是 sys.c里面的函数。了解这些信息,对我们分析及优化程序非常有用。
映像组件大小(Image component sizes)
映像组件大小(Image component sizes)给出了整个映像所有代码(.o)占用空间的汇
总信息,对我们比较有用,如图 2.1.5.1 所示:
上图中,框出的三处信息对我们比较有用,接下来分别介绍:
① 处,表示.c/.s 文件生成对象所占空间大小(单位:字节,下同),即.c/.s 文件编译后所占代码空间的大小。每个项所代表的意义如下:
Code(inc.data):表示包含内联数据(inc.data)后的代码大小。如 delay.o(即delay.c)所占的 Code 大小为 142 字节,其中 8 字节是内联数据。
RO Data:表示只读数据所占的空间大小,一般是指 const 修饰的数据大小。
RW Data:表示有初值(且非 0)的可读写数据所占的空间大小,它同时占用 FLASH和RAM 空间。
ZI Data:表示初始化为 0 的可读写数据所占空间大小,它只占用 RAM 空间。
Debug:表示调试数据所占的空间大小,如调试输入节及符号和字符串。
Object Totals:表示以上部分链接到一起后,所占映像空间的大小。
(incl.Generated):表示链接器生产的映像内容大小,它包含在 Object Totals 里面了,这里仅仅是单独列出,我们一般不需要关心。
(incl.Padding):表示链接器根据需要插入填充以保证字节对齐的数据所占空间的大小,它也包含在 Object Totals 里面了,这里单独列出,一般无需关心。
② 处,表示被提取的库成员(.lib)添加到映像中的部分所占空间大小。各项意义同①中的说明。我们一般只用看 Library Totals 来分析库所占空间的大小即可。
③ 处,表示本工程全部程序汇总后的占用情况。其中:
Grand Totals:表示整个映像所占空间大小。
ELF Image Totals:表示 ELF 可执行链接格式映像文件的大小,一般和 Grand Totals一样大小。
ROM Totals:表示整个映像所需要的 ROM 空间大小,不含 ZI 和 Debug 数据。
Total RO Size:表示 Code 和 RO 数据所占空间大小,本例程为:13452 字节。
Total RW Size:表示 RW 和 ZI 数据所占空间大小,即本映像所需 SRAM 空间的大小,本例程为:3032 字节。
Total ROM Size:表示 Code、RO 和 RW 数据所占空间大小,即本映像所需 FLASH空间的大小,本例程为:13484 字节。
图 2.1.5.1 中,我们未框出的:Library Name 部分,实际和②处是一个意思,只是 Library Name 说明了②处的那些.o 文件来自什么库,这里实际上就是:fpinit.o 来自 fz_wv.l 库,其 他部分来自 c_w.l 库。fz_wv.l 和 c_w.l 是库名字。
MAP 文件的分析就给大家介绍到这里。