STM32启动代码主要是分配堆栈及设置向量表,然后跳转到__main函数。
跳转具体到代码段部分如下:
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
LDR R0, = __main
BX R0
ENDP
当您看到__main函数时,估计应该有不少人认为这个是main函数的别名或是编译之后的名字,否则在启动代码中再也无法找到和main相关的字眼了。可事实是,__main和main是完全两个不同的函数,并且你无法找到__main代码,因为这个是编译器自动创建的。
查看MDK的文档,会发现有这么一句说明:It is automatically created by the linker when it sees a definition of main()。简单点来说,当编译器发现定义了main函数,那么就会自动创建__main。
程序经过汇编启动代码,执行到__main()后,可以看出有两个大的函数:
__scatterload():负责把RW/RO输出段从装载域地址复制到运行域地址,并完成了ZI运行域的初始化工作。
__rt_entry():负责初始化堆栈,完成库函数的初始化,最后自动跳转向main()函数。
分析__scatterload()函数
执行到__main(),先跳转到_scatterload下图红框框中代码所示,执行完后,R10和R11就被赋给成了下面两个值。
Map文件中的symbol
然后执行_scatterload_null代码,将R10对应地址存放的的4个字copy到R0~R3中,可以看出
R0:0x1000表示的是keyled.o加载域起始地址
R1:0x30000100为keyled.o运行域地址
R2:0X160为copy的大小,keyled.o的大小从map文件中得知就是0x160 Byte
R3:0X1E4 是_scatterload_copy 代码的起始地址,实用BXR3 就能跳转到_scatterload_copy来复制代码。
跳到_scatterload_copy,开始copy,循环0x16次,每次搬移4个字(16Byte),共搬移0x16*0x10=0x160
复制完keyled.o代码后,进一步循环到_scatterload_null准备好,ZI段需要清零的地址和范围
执行完这个循环后
R1:0x30050000 为ZI段的起始地址
R2:0x618为ZI段大小,换成十进制是1560.从map文件得知ZI大小就是1560Byte
R3:0x20c 为_scatterload_zeroinit 的地址
执行下面红框框中循环体,共清零0x610Byte范围,然后再执行蓝框框中代码,清零8Byte,总共0x618
ZI段清零(0x30050000~0x30050618)
然后使用BX R14跳转到0x000001BC处,顺序执行到BL __rt_enty 指令
成功跳转到__rt_enty函数
分析__rt_entry()函数
先调用__user_setup_stackheap()函数来建立堆栈
可以看出在这个函数中,会执行到BL__user_initial_stackheap()函数,这样也就明白了,为什么使用分散加载文件,需要设置__user_initial_stackheap这个函数来设置堆栈空间。