STM32启动分析之main函数是怎样跑起来的 - 啊哈彭
source link: https://www.cnblogs.com/pingwen/p/17320181.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
1、MDK目标文件
1)MDK中C程序编译后的结果,即可执行文件数据分类:
- RAM
- ZI
- bss 存储未初始化的或初始化为0的全局变量和静态变量
- heap 堆,系统malloc和free操作的内存
- stack 栈,存储函数临时局部变量
- RW
- data 已经初始化且不为0的全局变量和静态变量
- ZI
- FLASH
- RO
- text 代码段,CPU指令,字符串字面值、常数等,keil中叫Code段
- constdata const常量,keil中叫RO-data
- RO
2)目标文件中各类型数据的存储位置
-
ZI-data 在bss段,ZI数据全为0,所以没有必要占用Flash空间,运行时占用RAM。
-
RW-data在RAM中,掉电丢失,所以需要启动时从FLASH拷贝到RAM中去,所以RW占FLASH空间。
-
由上我们得知keil的编译结果:
程序占用 Flash = Code + RO data + RW data
程序运行时候占用 RAM = RW data + ZI data。
Code + RO data + RW data 的大小也是生成的 bin 文件的大小 -
类似的,GCC的编译结果:
Memory region Used Size Region Size %age Used
FLASH: 480 B 32 KB 1.46%
RAM: 1200 B 4 KB 29.30%
Flash 的大小:Flash = text + data 。
RAM大小:RAM = data + bss。
2、STM32 startup.s 文件分析
1、栈分配
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
- EQU 是伪指令,不生产具体的目标文件,相当于定义了一个宏定义提高可读性。
- ARER 开辟一段代码段或数据段,后面的关键字表示这个段的属性:
- STACK : 表示这个段的名字,可以任意命名。
- NOINIT: 表示此数据段不需要填入初始数据。
- READWRITE:表示此段可读可写。
- ALIGN=3: 表示首地址按照2的3次方对齐,所以栈空间是8字节对齐的.
- SPACE Stack_Memd 段分配 Stack_Size 的空间。
- __initial_sp 是标号代表地址位置,即栈顶位置。
2、堆分配
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8
THUMB
具体的含义和栈分配相似,开辟空间Heap_Mem,大小为Heap_Size。__heap_base和__heap_limit分别表示堆的起点和终点。
PRESERVE8 指当前文件字节对其。ALIGN 伪指令表示对齐填充,可能的取值为2的幂,如 1 、2 、4 、8 、16 等。未跟数字如ALIGN 表示对齐到1个字(2字节)。
THUMB 表示使用的指令集。
3、vector table
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY # 定义了RESET区域为READONLY即存储在Flash。
EXPORT __Vectors # 中断向量表入口地址,EXPORT 是指该变量可以被导出,外部可以使用
EXPORT __Vectors_End # 中断向量表的结束地址
EXPORT __Vectors_Size # 中断向量表的大小
下面开始建立中断向量表。
中断向量表类似一个全是函数指针的数组,每个函数指针代表对应中断号的中断处理程序入口。
向量表的起点是栈顶。DCD是定义一个word(4字节)的空间。
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
//省略中断向量表
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors # 相减计算中断向量表的size
AREA |.text|, CODE, READONLY # 定义了一个只读的名为 .txt的代码段
; Reset handler
Reset_Handler PROC # PROC 汇编程序开始,ENDP汇编程序结束
EXPORT Reset_Handler [WEAK] # WEAK说明此函数可以被用户重写
IMPORT __main # 从外部文件import一个函数
IMPORT SystemInit
LDR R0, =SystemInit # load SystemInit 函数地址,做系统时钟初始化
BLX R0 # 跳转到 SystemInit 函数执行
LDR R0, =__main # load _main
BX R0 # 跳转到_main执行,此_main非彼main
ENDP
其他一些异常中断函数,简单起见直接使用死循环代替(在可靠性系统中用户应该检测并做特殊处理)。
; Dummy Exception Handlers (infinite loops which can be modified)
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
外设中的一些中断函数:
Default_Handler PROC
EXPORT WWDG_IRQHandler [WEAK]
EXPORT PVD_IRQHandler [WEAK]
EXPORT TAMPER_IRQHandler [WEAK]
// 省略
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandler
B .
ENDP #先都用死循环代替,标记了WEAK用户可以重写,在外设驱动文件中已经包含了这些中断服务程序。
ALIGN
4、堆栈初始化
;------------------------------------------------------------------
; User Stack and Heap initialization
;------------------------------------------------------------------
IF :DEF:__MICROLIB # 如果启用了MDK的微库microLib
EXPORT __initial_sp # 导出这三个变量给外部使用
EXPORT __heap_base
EXPORT __heap_limit
ELSE # 如果没有启动微库
IMPORT __use_two_region_memory # 导入__use_two_region_memory 标号
EXPORT __user_initial_stackheap # 导出__user_initial_stackheap 方法
__user_initial_stackheap # 标号,表示堆栈初始化程序入口
LDR R0, = Heap_Mem # 堆空间起点(向上增长)
LDR R1, =(Stack_Mem + Stack_Size) # 栈尾部(向下增长)
LDR R2, = (Heap_Mem + Heap_Size) # 堆大小
LDR R3, = Stack_Mem # 栈空间起点
BX LR
ALIGN
ENDIF
END
3、Keil MDK main函数启动
- main()函数是第一个被执行的函数吗?
- 除了system_init初始化了系统时钟,mian函数启动前还发生了什么?
在startup.s文件中,跳转到_main,这个_main并不是c的main函数,而是编译器内置的一个c库函数,内部执行了三个步骤:初始化rw段,初始化zi段,调用另一个c库函数__rt_entry()。
__rt_entry()该函数先初始化堆栈和库函数,然后即调用主函数main(),从而进入用户程序。可以看出主函数main()若退出,则在__rt_entry()最后会再调用exit()函数进行退出操作。
什么是__rt_entry?标准库或ARM文档有如下描述。
详情见:https://developer.arm.com/documentation/dui0475/m/the-c-and-c---library-functions-reference/--rt-entry
所以main函数既不是c程序第一个执行的函数,也不是c程序最后一个执行的函数。在main启动前,标准库已经做了很多工作,当跳转mian时,堆栈已经完成了初始化、C运行时环境已经就绪。
__rt_entry
The symbol __rt_entry is the starting point for a program using the ARM C library.
Control passes to __rt_entry after all scatter-loaded regions have been relocated to their execution addresses.
Usage
1. The default implementation of __rt_entry:
2. Sets up the heap and stack.
3. Initializes the C library by calling __rt_lib_init.
4. Calls main().
5. Shuts down the C library, by calling __rt_lib_shutdown.
6. Exits.
__rt_entry must end with a call to one of the following functions:
exit()
Calls atexit()-registered functions and shuts down the library.
__rt_exit()
Shuts down the library but does not call atexit() functions.
_sys_exit()
Exits directly to the execution environment. It does not shut down the library and does not call atexit() functions.
startup.s文件中,DCD定义了76个中断服务函数入口,76*4=304=0x130。所以在汇编文件中,Flash地址0x08000000起点是DEC中断函数入口,0x08000130 位置是代码起点。如果进行代码调试跟踪,可以发现这段汇编代码实现的是堆和栈的初始化。
启用微库时的初始化过程,其中__scatterload 即对堆栈进行初始化,对比不启用微库的汇编程序,指令条目数已大幅缩减:
0x080007D8 2000 MOVS r0,#0x00
0x080007DA E001 B 0x080007E0
0x080007DC C101 STM r1!,{r0}
0x080007DE 1F12 SUBS r2,r2,#4
0x080007E0 2A00 CMP r2,#0x00
0x080007E2 D1FB BNE 0x080007DC
0x0800012C 015F DCW 0x015F
0x0800012E 0800 DCW 0x0800
__main:
0x08000130 F8DFD00C LDR.W sp,[pc,#12] ; @0x08000140
_main_scatterload:
0x08000134 F000F82E BL.W __scatterload (0x08000194)
__main_after_scatterload:
0x08000138 4800 LDR r0,[pc,#0] ; @0x0800013C
0x0800013A 4700 BX r0
0x0800013C 0969 DCW 0x0969
0x0800013E 0800 DCW 0x0800
__rt_final_cpp:
0x08000140 0428 DCW 0x0428
0x08000142 2000 DCW 0x2000
151: LDR R0, =SystemInit
0x08000144 4806 LDR r0,[pc,#24] ; @0x08000160
152: BLX R0
0x08000146 4780 BLX r0
153: LDR R0, =__main
0x08000148 4806 LDR r0,[pc,#24] ; @0x08000164
154: BX R0
155: ENDP
156:
157: ; Dummy Exception Handlers (infinite loops which can be modified)
158:
159: NMI_Handler PROC
160: EXPORT NMI_Handler [WEAK]
0x0800014A 4700 BX r0
161: B .
162: ENDP
不启用微库时的初始化过程,启动代码大大增加。
0x08000130 F000F802 BL.W __scatterload (0x08000138)
0x08000134 F000F847 BL.W __rt_entry (0x080001C6)
0x08000138 A00A ADR r0,{pc}+4 ; @0x08000164
0x0800013A E8900C00 LDM r0,{r10-r11}
0x0800013E 4482 ADD r10,r10,r0
0x08000140 4483 ADD r11,r11,r0
0x08000142 F1AA0701 SUB r7,r10,#0x01
0x08000146 45DA CMP r10,r11
0x08000148 D101 BNE 0x0800014E
0x0800014A F000F83C BL.W __rt_entry (0x080001C6)
0x0800014E F2AF0E09 ADR.W lr,{pc}-0x07 ; @0x08000147
0x08000152 E8BA000F LDM r10!,{r0-r3}
0x08000156 F0130F01 TST r3,#0x01
0x0800015A BF18 IT NE
0x0800015C 1AFB SUBNE r3,r7,r3
0x0800015E F0430301 ORR r3,r3,#0x01
0x08000162 4718 BX r3
0x08000164 1244 DCW 0x1244
0x08000166 0000 DCW 0x0000
0x08000168 1264 DCW 0x1264
0x0800016A 0000 DCW 0x0000
0x0800016C 3A10 SUBS r2,r2,#0x10
0x0800016E BF24 ITT CS
0x08000170 C878 LDMCS r0!,{r3-r6}
0x08000172 C178 STMCS r1!,{r3-r6}
0x08000174 D8FA BHI __scatterload_copy (0x0800016C)
0x08000176 0752 LSLS r2,r2,#29
0x08000178 BF24 ITT CS
0x0800017A C830 LDMCS r0!,{r4-r5}
0x0800017C C130 STMCS r1!,{r4-r5}
0x0800017E BF44 ITT MI
0x08000180 6804 LDRMI r4,[r0,#0x00]
0x08000182 600C STRMI r4,[r1,#0x00]
0x08000184 4770 BX lr
0x08000186 0000 MOVS r0,r0
0x08000188 2300 MOVS r3,#0x00
0x0800018A 2400 MOVS r4,#0x00
0x0800018C 2500 MOVS r5,#0x00
0x0800018E 2600 MOVS r6,#0x00
0x08000190 3A10 SUBS r2,r2,#0x10
0x08000192 BF28 IT CS
0x08000194 C178 STMCS r1!,{r3-r6}
0x08000196 D8FB BHI 0x08000190
0x08000198 0752 LSLS r2,r2,#29
0x0800019A BF28 IT CS
0x0800019C C130 STMCS r1!,{r4-r5}
0x0800019E BF48 IT MI
0x080001A0 600B STRMI r3,[r1,#0x00]
0x080001A2 4770 BX lr
_printf_d:
0x080001A4 2964 CMP r1,#0x64
0x080001A6 F000807D BEQ.W _printf_int_dec (0x080002A4)
_printf_percent_end:
0x080001AA 2000 MOVS r0,#0x00
0x080001AC 4770 BX lr
__rt_lib_init:
0x080001AE B51F PUSH {r0-r4,lr}
__rt_lib_init_fp_1:
0x080001B0 E89D0003 LDM sp,{r0-r1}
0x080001B4 F000FB6C BL.W _init_alloc (0x08000890)
__rt_lib_init_atexit_1:
0x080001B8 F000F93A BL.W _initio (0x08000430)
__rt_lib_init_alloca_1:
0x080001BC BD1F POP {r0-r4,pc}
__rt_lib_shutdown:
0x080001BE B510 PUSH {r4,lr}
__rt_lib_shutdown_stdio_2:
0x080001C0 F000F99F BL.W _terminateio (0x08000502)
__rt_lib_shutdown_fp_trap_1:
0x080001C4 BD10 POP {r4,pc}
__rt_entry:
0x080001C6 F000FA02 BL.W __user_setup_stackheap (0x080005CE)
0x080001CA 4611 MOV r1,r2
__rt_entry_li:
0x080001CC F7FFFFEF BL.W __rt_lib_init (0x080001AE)
__rt_entry_main:
0x080001D0 F001F89A BL.W main (0x08001308)
0x080001D4 F000FB06 BL.W exit (0x080007E4)
__rt_exit:
0x080001D8 B403 PUSH {r0-r1}
__rt_exit_ls:
0x080001DA F7FFFFF0 BL.W __rt_lib_shutdown (0x080001BE)
__rt_exit_exit:
0x080001DE BC03 POP {r0-r1}
0x080001E0 F000FCE8 BL.W _sys_exit (0x08000BB4)
151: LDR R0, =SystemInit
0x080001E4 4809 LDR r0,[pc,#36] ; @0x0800020C
152: BLX R0
0x080001E6 4780 BLX r0
153: LDR R0, =__main
0x080001E8 4809 LDR r0,[pc,#36] ; @0x08000210
154: BX R0
155: ENDP
尊重原创技术文章,转载请注明:https://www.cnblogs.com/pingwen/p/17320181.html
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK