前言:
为了方便查看博客,特意申请了一个公众号,附上二维码,有兴趣的朋友可以关注,和我一起讨论学习,一起享受技术,一起成长。
本文转载自:第48章 MDK的编译过程及文件类型全解—零死角玩转STM32-F429系列
1. Listing目录下的文件
在 Listing 目录下包含了 .map 及 .lst 文件,它们都是文本格式的,可使用 Windows 的记事本软件打开。其中 lst 文件仅包含了一些汇编符号的链接信息,这里重点分析 map 文件。
map 文件是由链接器生成的,它主要包含交叉链接信息,查看该文件可以了解工程中各种符号之间的引用以及整个工程的 Code、RO-data、RW-data 以及 ZI-data 的详细及汇总信息。它的内容中主要包含了"节区的跨文件引用"、“删除无用节区”、“符号映像表”、“存储器映像索引"以及"映像组件大小”,各部分介绍如下:
1.1 节区的跨文件引用
打开"多彩流水灯 .map" 文件,可看到它的第一部分——节区的跨文件引用(Section Cross References):
Section Cross References
startup_stm32f429_439xx.o(RESET) refers to startup_stm32f429_439xx.o(STACK) for __initial_sp
startup_stm32f429_439xx.o(RESET) refers to startup_stm32f429_439xx.o(.text) for Reset_Handler
startup_stm32f429_439xx.o(RESET) refers to stm32f4xx_it.o(i.NMI_Handler) for NMI_Handler
startup_stm32f429_439xx.o(RESET) refers to stm32f4xx_it.o(i.HardFault_Handler) for HardFault_Handler
/**...以下部分省略****/
main.o(i.main) refers to bsp_led.o(i.LED_GPIO_Config) for LED_GPIO_Config
main.o(i.main) refers to stm32f4xx_gpio.o(i.GPIO_ResetBits) for GPIO_ResetBits
main.o(i.main) refers to main.o(i.Delay) for Delay
main.o(i.main) refers to stm32f4xx_gpio.o(i.GPIO_SetBits) for GPIO_SetBits
bsp_led.o(i.LED_GPIO_Config) refers to stm32f4xx_rcc.o(i.RCC_AHB1PeriphClockCmd) for RCC_AHB1PeriphClockCmd
bsp_led.o(i.LED_GPIO_Config) refers to stm32f4xx_gpio.o(i.GPIO_Init) for GPIO_Init
bsp_led.o(i.LED_GPIO_Config) refers to stm32f4xx_gpio.o(i.GPIO_ResetBits) for GPIO_ResetBits
/**...以下部分省略****/
======================================================================
在这部分中,详细列出了各个 .o 文件之间的符号引用。由于 .o 文件是由 asm 或 c/c++ 源文件编译后生成的,各个文件及文件内的节区间互相独立,链接器根据它们之间的互相引用链接起来,链接的详细信息在这个 "Section Cross References"一一列出。
例如,开头部分说明的是 startup_stm32f429_439xx.o 文件中的 “RESET” 节区分为它使用的 “__initial_sp” 符号引用了同文件 “STACK” 节区。
也许我们对启动文件不熟悉,不清楚这究竟是什么,那我们继续浏览,可看到main.o 文件的引用说明,如说明 main.o 文件的 i.main 节区为它使用的LED_GPIO_Config 符号引用了 bsp_led.o 文件的 i.LED_GPIO_Config 节区。
同样地,下面还有 bsp_led.o 文件的引用说明,如说明了 bsp_led.o 文件的 i.LED_GPIO_Config 节区为它使用的 GPIO_Init 符号引用了 stm32f4xx_gpio.o 文件的 i.GPIO_Init 节区。
可以了解到,这些跨文件引用的符号其实就是源文件中的函数名、变量名。有时在构建工程的时候,编译器会输出 “Undefined symbol xxx (referred from xxx.o)” 这样的提示,该提示的原因就是在链接过程中,某个文件无法在外部找到它引用的标号,因而产生链接错误。例如,见下图,把 bsp_led.c 文件中定义的函数 LED_GPIO_Config 改名为 LED_GPIO_ConfigABCD,而不修改 main.c 文件中的调用,就会出现 main 文件无法找到 LED_GPIO_Config 符号的提示。
1.2 删除无用节区
map 文件的第二部分是删除无用节区的说明 (Removing Unused input sections from the image.)。
=================================================================
Removing Unused input sections from the image.
Removing startup_stm32f429_439xx.o(HEAP), (512 bytes).
Removing system_stm32f4xx.o(.rev16_text), (4 bytes).
Removing system_stm32f4xx.o(.revsh_text), (4 bytes).
Removing system_stm32f4xx.o(.rrx_text), (6 bytes).
Removing system_stm32f4xx.o(i.SystemCoreClockUpdate), (136 bytes).
Removing system_stm32f4xx.o(.data), (20 bytes).
Removing misc.o(.rev16_text), (4 bytes).
Removing misc.o(.revsh_text), (4 bytes).
Removing misc.o(.rrx_text), (6 bytes).
Removing misc.o(i.NVIC_Init), (104 bytes).
Removing misc.o(i.NVIC_PriorityGroupConfig), (20 bytes).
Removing misc.o(i.NVIC_SetVectorTable), (20 bytes).
Removing misc.o(i.NVIC_SystemLPConfig), (28 bytes).
Removing misc.o(i.SysTick_CLKSourceConfig), (28 bytes).
Removing stm32f4xx_adc.o(.rev16_text), (4 bytes).
Removing stm32f4xx_adc.o(.revsh_text), (4 bytes).
Removing stm32f4xx_adc.o(.rrx_text), (6 bytes).
Removing stm32f4xx_adc.o(i.ADC_AnalogWatchdogCmd), (16 bytes).
Removing stm32f4xx_adc.o(i.ADC_AnalogWatchdogSingleChannelConfig), (12 bytes).
Removing stm32f4xx_adc.o(i.ADC_AnalogWatchdogThresholdsConfig), (6 bytes).
Removing stm32f4xx_adc.o(i.ADC_AutoInjectedConvCmd), (24 bytes).
/**...以下部分省略****/
========================================================================
这部分列出了在链接过程它发现工程中未被引用的节区,这些未被引用的节区将会被删除(指不加入到 .axf 文件,不是指在 .o 文件删除),这样可以防止这些无用数据占用程序空间。
例如,上面的信息中说明 startup_stm32f429_439xx.o 中的 HEAP (在启动文件中定义的用于动态分配的"堆"区)以及 stm32f4xx_adc.o 的各个节区都被删除了,因为在我们这个工程中没有使用动态内存分配,也没有引用任何 stm32f4xx_adc.c 中的内容。由此也可以知道,虽然我们把 STM32 标准库的各个外设对应的 C 库文件都添加到了工程,但不必担心这会使工程变得臃肿,因为未被引用的节区内容不会被加入到最终的机器码文件中。
1.3 符号映像表
map 文件的第三部分是符号映像表 (Image Symbol Table),分为 Local Symbols 局部 和 Global Symbols 全局。
Local Symbols 记录了用 static 声明的全局变量地址和大小,C 文件中函数的地址和用 static 声明的函数代码大小,汇编文件中的标号地址(作用域限本文件)。
==============================================================================
Image Symbol Table
Local Symbols
Symbol Name Value Ov Type Size Object(Section)
../clib/microlib/init/entry.s 0x00000000 Number 0 entry.o ABSOLUTE
../clib/microlib/init/entry.s 0x00000000 Number 0 entry9a.o ABSOLUTE
../clib/microlib/init/entry.s 0x00000000 Number 0 entry9b.o ABSOLUTE
/*...省略部分*/
LED_GPIO_Config 0x080002a5 Thumb Code 106 bsp_led.o(i.LED_GPIO_Config)
MemManage_Handler 0x08000319 Thumb Code 2 stm32f4xx_it.o(i.MemManage_Handler)
NMI_Handler 0x0800031b Thumb Code 2 stm32f4xx_it.o(i.NMI_Handler)
PendSV_Handler 0x0800031d Thumb Code 2 stm32f4xx_it.o(i.PendSV_Handler)
RCC_AHB1PeriphClockCmd 0x08000321 Thumb Code 22 stm32f4xx_rcc.o(i.RCC_AHB1PeriphClockCmd)
SVC_Handler 0x0800033d Thumb Code 2 stm32f4xx_it.o(i.SVC_Handler)
SysTick_Handler 0x08000415 Thumb Code 2 stm32f4xx_it.o(i.SysTick_Handler)
SystemInit 0x08000419 Thumb Code 62 system_stm32f4xx.o(i.SystemInit)
UsageFault_Handler 0x08000469 Thumb Code 2 stm32f4xx_it.o(i.UsageFault_Handler)
__scatterload_copy 0x0800046b Thumb Code 14 handlers.o(i.__scatterload_copy)
__scatterload_null 0x08000479 Thumb Code 2 handlers.o(i.__scatterload_null)
__scatterload_zeroinit 0x0800047b Thumb Code 14 handlers.o(i.__scatterload_zeroinit)
main 0x08000489 Thumb Code 270 main.o(i.main)
/*...省略部分*/
==============================================================================
这个表列出了被引用的各个符号在存储器中的具体地址、占据的空间大小等信息。如我们可以查到 LED_GPIO_Config 符号存储在 0x080002a5 地址,它属于 Thumb Code 类型,大小为 106 字节,它所在的节区为 bsp_led.o 文件的 i.LED_GPIO_Config 节区。
Global Symbols 记录了全局变量的地址和大小,C 文件中函数的地址及其代码大小,汇编文件中的标号地址(作用域全工程)。
Symbol Name Value Ov Type Size Object(Section)
BuildAttributes$$THM_ISAv4$P$D$K$B$S$PE$A:L22UL41UL21$X:L11$S22US41US21$IEEE1$IW$USESV6$~STKCKD$USESV7$~SHL$OSPACE$ROPI$EBA8$MICROLIB$REQ8$PRES8$EABIv2 0x00000000 Number 0 anon$$obj.o ABSOLUTE
__ARM_use_no_argv 0x00000000 Number 0 main.o ABSOLUTE
_printf_a 0x00000000 Number 0 stubs.o ABSOLUTE
_printf_c 0x00000000 Number 0 stubs.o ABSOLUTE
_printf_charcount 0x00000000 Number 0 stubs.o ABSOLUTE
_printf_d 0x00000000 Number 0 stubs.o ABSOLUTE
_printf_e 0x00000000 Number 0 stubs.o ABSOLUTE
_printf_f 0x00000000 Number 0 stubs.o ABSOLUTE
_printf_flags 0x00000000 Number 0 stubs.o ABSOLUTE
_printf_fp_dec 0x00000000 Number 0 stubs.o ABSOLUTE
//省略。。。。。。。。。。。。。。。。。
bsp_RunPer1ms 0x08000665 Thumb Code 2 bsp.o(i.bsp_RunPer1ms)
main 0x08000667 Thumb Code 14 main.o(i.main)
Region$$Table$$Base 0x08000674 Number 0 anon$$obj.o(Region$$Table)
Region$$Table$$Limit 0x08000694 Number 0 anon$$obj.o(Region$$Table)
g_iRunTime 0x20000008 Data 4 bsp_timer.o(.data)
__initial_sp 0x20009978 Data 0 startup_stm32f10x_hd.o(STACK)
==============================================================================
各字段解释:
名称 | 解释 |
---|---|
Symbol Name | 符号名称,分为 Local,Global |
Value | 存储对应地址,0x0800xxxx 指存储在 FLASH 里面的代码、变量等;0x2000xxxx 指存储在内存 RAM 中的变量 Data 等 |
Ov Type | 符号对应的类型:Number、Section、Thumb Code、Data 等几种;可以发现全局、静态变量等位于 0x2000xxxx 的内存 RAM 中。 |
Size | 存储大小 |
Object(Section) | 当前符号所在段名,对应函数、变量所在的源文件 |
1.4 存储器映像索引
map 文件的第四部分是存储器映像索引 (Memory Map of the image)。
映像文件可以分为加载域(Load Region)和运行域(Execution Region)。
加载域:反映了 ARM 可执行映像文件的各个段存放在存储器中的位置关系,映像中的入口点就是程序开始执行的位置。
运行域:反映了 ARM 可执行映像文件各个段真正执行时在存储器中的位置关系。
加载域就是程序在 Flash 中的实际存储,而运行域是芯片上电后的运行状态。因为 MCU 没上电时 RAM 中没有数据,所以此时所有的东西(包括代码、变量、初始值等)都是存放在 Flash 中的,当上电后又要把变量等复制到 RAM 中才能正常运行。
==============================================================================
Memory Map of the image
Image Entry point : 0x080001ad
Load Region LR_IROM1 (Base: 0x08000000, Size: 0x000005b0, Max: 0x00100000, ABSOLUTE)
7 Execution Region ER_IROM1 (Base: 0x08000000, Size: 0x000005b0, Max: 0x00100000, ABSOLUTE)
Base Addr Size Type Attr Idx E Section Name Object
0x08000000 0x000001ac Data RO 3 RESET startup_stm32f429_439xx.o
/*..省略部分*/
0x0800020c 0x00000012 Code RO 5161 i.Delay main.o
0x0800021e 0x0000007c Code RO 2046 i.GPIO_Init stm32f4xx_gpio.o
0x0800029a 0x00000004 Code RO 2053 i.GPIO_ResetBits stm32f4xx_gpio.o
0x0800029e 0x00000004 Code RO 2054 i.GPIO_SetBits stm32f4xx_gpio.o
0x080002a2 0x00000002 Code RO 5196 i.HardFault_Handler stm32f4xx_it.o
0x080002a4 0x00000074 Code RO 5269 i.LED_GPIO_Config bsp_led.o
0x08000318 0x00000002 Code RO 5197 i.MemManage_Handler stm32f4xx_it.o
/*..省略部分*/
0x08000488 0x00000118 Code RO 5162 i.main main.o
0x080005a0 0x00000010 Data RO 5309 Region$$Table anon$$obj.o
Execution Region RW_IRAM1 (Base: 0x20000000, Size: 0x00000400, Max: 0x00030000, ABSOLUTE)
Base Addr Size Type Attr Idx E Section Name Object
0x20000000 0x00000400 Zero RW 1 STACK startup_stm32f429_439xx.o
==============================================================================
本工程的存储器映像索引分为 ER_IROM1 及 RW_IRAM1 部分,它们分别对应STM32 内部 FLASH 及 SRAM 的空间。相对于符号映像表,这个索引表描述的单位是节区,而且它描述的主要信息中包含了节区的类型及属性,由此可以区分 Code、RO-data、RW-data 及 ZI-data。
例如,从上面的表中我们可以看到 i.LED_GPIO_Config 节区存储在内部 FLASH 的 0x080002a4 地址,大小 为0x00000074,类型为 Code,属性为 RO。而程序的 STACK 节区(栈空间)存储在 SRAM 的 0x20000000 地址,大小为 0x00000400,类型为 Zero,属性为 RW(即RW-data)。
1.5 映像组件大小
map 文件的最后一部分是包含映像组件大小的信息 (Image component sizes),这也是最常查询的内容。
==============================================================================
Image component sizes
4 Code (inc. data) RO Data RW Data ZI Data Debug Object Name
116 10 0 0 0 578 bsp_led.o
298 10 0 0 0 1459 main.o
36 8 428 0 1024 932 startup_stm32f429_439xx.o
132 0 0 0 0 2432 stm32f4xx_gpio.o
18 0 0 0 0 3946 stm32f4xx_it.o
28 6 0 0 0 645 stm32f4xx_rcc.o
292 34 0 0 0 253101 system_stm32f4xx.o
----------------------------------------------------------------------
926 68 444 0 1024 263093 Object Totals
0 0 16 0 0 0 (incl. Generated)
6 0 0 0 0 0 (incl. Padding)
/*...省略部分*/
==============================================================================
Code (inc. data) RO Data RW Data ZI Data Debug
1012 84 444 0 1024 262637 Grand Totals
1012 84 444 0 1024 262637 ELF Image Totals
1012 84 444 0 0 0 ROM Totals
==============================================================================
Total RO Size (Code + RO Data) 1456 ( 1.42kB)
Total RW Size (RW Data + ZI Data) 1024 ( 1.00kB)
Total ROM Size (Code + RO Data + RW Data) 1456 ( 1.42kB)
==============================================================================
这部分包含了各个使用到的 .o 文件的空间汇总信息、整个工程的空间汇总信息以及占用不同类型存储器的空间汇总信息,它们分类描述了具体占据的 Code、RO-data、RW-data 及 ZI-data 的大小,并根据这些大小统计出占据的 ROM 总空间。
我们仅分析最后两部分信息,如 Grand Totals 一项,它表示整个代码占据的所有空间信息,其中 Code 类型的数据大小为1012字节,这部分包含了 84 字节的指令数据 (inc .data) 已算在内,另外 RO-data 占 444 字节,RW-data 占 0 字节,ZI-data 占 1024 字节。在它的下面两行有一项 ROM Totals 信息,它列出了各个段所占据的 ROM 空间,除了 ZI-data 不占 ROM 空间外,其余项都与 Grand Totals 中相等 (RW-data 也占据 ROM 空间,只是本工程中没有 RW-data 类型的数据而已)。
最后一部分列出了只读数据 (RO)、可读写数据 (RW) 及占据的 ROM 大小。其中只读数据大小为 1456 字节,它包含 Code 段及 RO-data 段; 可读写数据大小为 1024 字节,它包含 RW-data 及 ZI-data 段;占据的 ROM 大小为 1456 字节,它除了 Code 段和 RO-data 段,还包含了运行时需要从 ROM 加载到 RAM 的 RW-data 数据。
综合整个 map 文件的信息,可以分析出,当程序下载到 STM32 的内部 FLASH 时,需要使用的内部 FLASH 是从 0x0800 0000 地址开始的大小为 1456 字节的空间;当程序运行时,需要使用的内部 SRAM 是从 0x20000000 地址开始的大小为 1024 字节的空间。
粗略一看,发现这个小程序竟然需要 1024 字节的 SRAM,实在说不过去,但仔细分析 map 文件后,可了解到这 1024 字节都是 STACK 节区的空间(即栈空间),栈空间大小是在启动文件中定义的,这 1024 字节是默认值 (0x00000400)。它是提供给 C 语言程序局部变量申请使用的空间,若我们确认自己的应用程序不需要这么大的栈,完全可以修改启动文件,把它改小一点,查看前面讲解的 htm 静态调用图文件可了解静态的栈调用情况,可以用它作为参考。