MAP文件浅析-正点原子
1. MDK编译生成文件简介MDK编译工程,会生成一些中间文件(如.o、.axf、.map等),最终生成hex文件,以便下载到MCU上面执行,以北极星STM32H750的新建工程实验(实验0)为例(其他开发板类似),编译过程产生的所有文件,都存放在OBJ文件夹下,如图1.1所示:
图1.1 MDK编译过程生成的文件 可以看到,这里总共生成了22个文件,共11个类型,分别是:.axf、.crf、.d、.dep、 .hex、.lnp、.lst、.o、.htm、bulild_log.htm和.map。22个文件看着不是很多,但是随着工程的增大,这些文件也会越来越多,大项目编译一次,可以生成几百甚至上千个这种文件,不过文件类型基本就是上面这些。 对于MDK工程来说,基本上任何工程在编译过程中都会有这11类文件,常见的MDK编译过程生产文件类型如表1.1所示: 文件类型 | | | 可重定向1对象文件,每个源文件(.c/.s等)编译都会生成一个.o文件 | | 由ARMCC编译生产的可执行对象文件,不可重定向2(绝对地址) 多个.o文件链接生成.axf文件,我们在仿真的时候,需要用到该文件 | | Intel Hex格式文件,可用于下载到MCU,.hex文件由.axf文件转换而来 | | | | 由ARMCC/GCC编译生产的依赖文件(.o文件所对应的依赖文件) 每个.o文件,都有一个对应的.d文件 | | | | | | | | | | | | 连接器生成的列表文件/MAP文件,该文件对我们非常有用! |
表1.1 常见的中间文件类型说明 注1,可重定向是指该文件包含数据/代码,但是并没有指定地址,它的地址可由后续链接的时候进行指定。 注2,不可重定向是指该文件所包含的数据/代码都已经指定地址了,不能再改变。 表1.1中,加粗的5种文件类型,对我们学习来说比较关心,尤其是.map文件,是本文档需要重点介绍的。这里我们先简单说一下这5种文件的作用。 .o文件,它是由编译器编译.c/.s文件时所产生的可重定向对象文件,其文件名同.c/.s文件一模一样,只是后缀为.o。所以,我们看到.o就应该想到与之对应的.c或者.s文件,我们在分散加载里面经常会用到.o文件。 .axf文件,它是由armlink链接器,将整个工程参与编译的.o文件链接成一个可执行对象文件,它是不可重定向的。有了该文件,我们就可以用仿真器来下载到MCU进行仿真调试了。注意:各类仿真器,在进行下载调试的时候,都是使用的.axf文件。 .hex文件,它是由.axf转换而来的一个可执行对象文件。.hex文件和.bin文件的区别是:.bin文件不含地址信息,全部都是可执行代码;而.hex文件则是包含地址信息的可自行代码。同样的.bin文件也是由.axf文件转换而来的。我们在使用ISP软件进行程序下载的时候,一般使用的是.hex文件,由ISP软件解析.hex文件包含的地址信息来实现程序下载。而我们在进行BootLoader升级的时候,一般使用.bin文件,地址由Bootloader程序指定。 .htm文件,它是编译器在编译代码的时候生成的一个列表文件,包含了整个工程的静态调用图,最大的用处就是可以查看栈深度(最小深度),方便设置栈大小。.htm文件可以直接由浏览器打开(双击打开)。.htm文件包含两部分内容: 1, 整个工程最大栈(Stack)深度及其调用关系 我们打开:新建工程例程àOBJàtest.htm 文件(双击,注意:必须整个工程编译一遍,才会生成test.htm文件,否则是找不到这个文件的!),可以看到如图1.2所示:
图1.2 .htm文件显示最大栈深及调用关系 可以看到,例程的最大栈深度是128字节,最大栈深时的调用关系为:__rt_entry_main ⇒ main ⇒ __2printf ⇒ _printf_char_file ⇒_printf_char_common ⇒ __printf。 不过需要注意的是,这里的最大栈深度仅仅是最低要求(静态栈),因为它并没有统计无栈深的函数(用内存管理)、递归函数、以及无法追踪的函数(函数指针)等所包含的栈(Stack)。 不过它给我们指明了最低需求,我们在分配栈深度的时候,就可以参考这个值来做设置,一般不低于静态栈的2倍。我们例程默认设置的栈深为0X800(通过.s文件设置),即2048字节,如图1.2所示:
图1.2 栈深度(大小)设置 2, 各个函数的栈深及其调用关系 .htm文件还给出了每个函数所使用的栈深度以及其调用关系,如图1.3所示:
图1.2 单个函数栈深度及其调用关系 上图给出了Stm32_Clock_Init函数的最大栈深度及其调用关系,并且列出了其所调用的函数及其所被调用的函数。 .htm文件,最大的用 .map文件,对我们编程非常有帮助,是本文档重点要给大家介绍的,因此另起一节,进行重点说明。 其他文件类型及说明,请大家参考:MDKàhelpàuVision HelpàB. File Types,如图1.3所示:
图1.3 MDK uVision Help查看文件类型说明
2. map文件分析.map文件是编译器链接时生成的一个文件,它主要包含了交叉链接信息。通过.map文件,我们可以知道整个工程的函数调用关系、FLASH和RAM占用情况及其详细汇总信息,能具体到单个源文件(.c/.s)的占用情况,根据这些信息,我们可以对代码进行优化。 .map文件可以分为以下5个组成部分: 1, 程序段交叉引用关系(Section Cross References) 2, 删除映像未使用的程序段(Removing Unused inputsections 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文件的组成部分说明 2.1 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所示:
图2.1.2 打开.map文件 ① ,先确保工程编译成功(无错误)。 ② ,双击Target1,打开.map文件。 ③ ,map文件打开成功。 2.2 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
2.3 map文件的组成部分说明我们前面说了,map文件分为5个部分组成,接下来分别介绍这个5个部分。这里还是以STM32H750开发板的新建工程实验(实验0)为例进行介绍,其他开发板请参考着学习。 2.1.1 程序段交叉引用关系(Section Cross References) 这部分内容描述了各个文件(.c/.s等)之间函数(程序段)的调用关系,如图2.1.1.1所示:
图2.1.1.1 程序段交叉引用关系图 上图中,框出部分:test.o(i.main) refers tosys.o(i.Stm32_Clock_Init) for Stm32_Clock_Init 表示:test.c文件中的main函数,调用了sys.c中的Stm32_Clock_Init函数。其中:i.main表示main函数的入口地址,同理i.Stm32_Clock_Init表示Stm32_Clock_Init的入口地址。 2.1.2 删除映像未使用的程序段(RemovingUnused input sections from the image)这部分内容描述了工程中由于未被调用而被删除的冗余程序段(函数/数据),如图2.1.2.1所示:
图2.1.2.1 删除未用到的程序段 上图中,列出了所有被移除的程序段,比如delay.c里面的delay_us函数就被移除了,因为该例程没用到delay_us函数。 另外,在最后还有一个统计信息:23 unused section(s) (total 496 bytes) removed from the image.表示总共移除了23个程序段(函数/数据),大小为496字节。即给我们的MCU节省了496字节的程序空间。 为了更好的节省空间,我们一般在MDKà魔术棒àC/C++选项卡里面勾选:OneELF Section per Function,如图2.1.2.2所示:
2.1.2.2MDK勾选One ELFSection per Function 2.1.3 映像符号表(ImageSymbol Table) 映像符号表(Image Symbol Table)描述了被引用的各个符号(程序段/数据)在存储器中的存储地址、类型、大小等信息。映像符号表分为两类:本地符号(Local Symbols)和全局符号(Global Symbols)。 2.1.3.1 本地符号(Local Symbols) 本地符号(Local Symbols)记录了用static声明的全局变量地址和大小,c文件中函数的地址和用static声明的函数代码大小,汇编文件中的标号地址(作用域:限本文件),本地符号如图2.1.3.1.1所示:
图2.1.3.1.1 本地符号(Local Symbols) 图中红框框处部分,表示sys.c文件中的GPIO_Set函数的入口地址为:0x0800046c,类型为:Section(程序段),大小为0。因为:i.GPIO_set仅仅表示GPIO_Set函数入口地址,并不是指令,所以没有大小。在全局符号段,会列出GPIO_Set函数的大小。 2.1.3.2 全局符号(Global Symbols)全局符号(Global Symbols)记录了全局变量的地址和大小,C文件中函数的地址及其代码大小,汇编文件中的标号地址(作用域:全工程),全局符号如图2.1.3.2.1所示:
图2.1.3.2.1 本地符号(Global Symbols) 图中红框框处部分,表示sys.c文件中的GPIO_Set函数的入口地址为:0x0800046D,类型为:Thumb Code(程序段),大小为222字节。 注意,此处的地址用的0x0800046D,和2.1.3.1节的0x0800046C地址不符,这是因为ARM规定Thumb指令级的所有指令,其最低位必须为1,0x0800046D=0x0800046C+1,所以才会有2个不同的地址,且总是差1,实际上就是同一个函数。 2.1.4 映像内存分布图(MemoryMap of the image) 映像文件分为加载域(Load Region)和运行域(Execution Region),一个加载域必须有至少一个运行域(可以有多个运行域),而一个程序又可以有多个加载域。加载域为映像程序的实际存储区域,而运行域则是MCU上电后的运行状态。加载域和运行域的简化关系(这里仅表示一个加载域的情况)图如图2.1.4.1所示:
图2.1.4.1 加载域运行域关系 由图可知,RW区也是存放在ROM(FLASH)里面的,在执行main函数之前,RW(有初值且不为0的变量)数据会被拷贝到RAM区,同时还会在RAM里面创建ZI区(初始化为0的变量)。 了解了加载域和运行域的作用及关系,我们再来看映像内存分布图(H750例程),如图2.1.4.2所示:
图2.1.4.2 映像内存分布图 ① 处,表示映像的入口地址,也就是整个程序运行的起始地址,为:0X08000299。实际地址为:0X08000298(Thumb指令最低位是1)。 ② 处,表示LR_m_stmflash加载域,其起始地址为:0X0800 0000;占用大小为:0X0000 0EB0;最大地址范围为:0X0002 0000。其内部包含两个运行域:ER_m_stmflash和RW_m_stmsram。 ③ 处,表示ER_m_stmflash运行域,其起始地址为:0X0800 0000;占用大小为:0X0000 0EA4;最大地址范围为:0X0002 0000;即内部FLASH运行域,所有需要放内部FLASH的代码,都应该放到这个运行域里面。对于STM32F1/F4/F767等开发板,我们例程所有的代码,都是放在这个运行域的(名字可能不一样)。 ④ 处,表示RW_m_stmsram运行域,其起始地址为:0X2400 0000;占用大小为:0X0000 0938;最大地址范围为:0X0008 0000;即内部SRAM运行域,所有RAM(包括RW和ZI)都是放在这个运行域里面。 ⑤ 处,表示LR_m_qspiflash加载域,其起始地址为:0X9000 0000;占用大小为:0X0000 0214;最大地址范围为:0X0080 0000。其内部包含一个运行域:ER_m_qspiflash。 ⑥ 处,表示ER_m_qspiflash运行域,其起始地址为:0X9000 0000;占用大小为:0X0000 0214;最大地址范围为:0X0080 0000;即外部QSPI FLASH运行域,所有需要放外部QSPI FLASH的代码,都应该放到这个运行域里面。 图2.1.4.2中,列出了所有加载域及其运行域的具体内存分布,我们可以很方便的查看任何一个函数所在的运行域、入口地址、占用空间等信息。如delay_xms函数:该函数在ER_m_stmflash运行域;入口地址为:0X0800 0D0C;大小为:0X54字节;是dealy.c里面的函数。了解这些信息,对我们分析及优化程序非常有用。 2.1.5 映像组件大小(Imagecomponent sizes)映像组件大小(Image component sizes)给出了整个映像所有代码(.o)占用空间的汇总信息,对我们比较有用,如图2.1.5.1所示:
图2.1.5.1 映像组件大小 上图中,框出的三处信息对我们比较有用,接下来分别介绍: ① 处,表示.c/.s文件生成对象所占空间大小(单位:字节,下同),即.c/.s文件编译后所占代码空间的大小。每个项所代表的意义如下: Code(inc.data):表示包含内联数据(inc.data)后的代码大小。如delay.o(即delay.c)所占的Code大小为162字节,其中12字节是内联数据。 RO Data:表示只读数据所占的空间大小,一般是指const修饰的数据大小。 RW Data:表示有初值(且非0)的可读写数据所占的空间大小,它同时占用FLASH和RAM空间。 ZI Data:表示初始化为0的可读写数据所占空间大小,它只占用RAM空间。 Debug:表示调试数据所占的空间大小,如调试输入节及符号和字符串。 Object Totals:表示以上部分链接到一起后,所占映像空间的大小。 (incl.Generated):表示链接器生产的映像内容大小,它包含在Object Totals里面了,这里仅仅是单独列出,我们一般不需要关心。 (incl.Padding):表示链接器根据需要插入填充以保证字节对齐的数据所占空间的大小,它也包含在Object Totals里面了,这里单独列出,一般无需关心。 ② 处,表示被提取的库成员(.lib)添加到映像中的部分所占空间大小。各项意义同①中的说明。我们一般只用看LibraryTotals来分析库所占空间的大小即可。 ③ 处,表示本工程全部程序汇总后的占用情况。其中: Grand Totals:表示整个映像所占空间大小。 ELF Image Totals:表示ELF可执行链接格式映像文件的大小,一般和Grand Totals一样大小。 ROM Totals:表示整个映像所需要的ROM空间大小,不含ZI和Debug数据。 Total RO Size:表示Code和RO数据所占空间大小,本例程为:4280字节。 Total RW Size:表示RW和ZI数据所占空间大小,即本映像所需SRAM空间的大小,本例程为:2360字节。 Total ROM Size:表示Code、RO和RW数据所占空间大小,即本映像所需FLASH空间的大小,本例程为:4292字节。 图2.1.5.1中,我们未框出的:Library Name部分,实际和②处是一个意思,只是Library Name说明了②处的那些.o文件来自什么库,这里实际上就是:fpinit.o来自fz_wv.l库,其他部分来自c_w.l库。fz_wv.l 和c_w.l是库名字。 MAP文件的分析就给大家介绍到这里。
PDF版本(下载阅读):
MAP文件浅析(正点原子)_V1.0.pdf
(1013.93 KB, 下载次数: 130)
|