STM32
超强工具——STM32CubeMX 你会用吗?
集结出发! STM32全国研讨会系列之一:ST智能门铃中国首秀
关于STM32启动文件的几个小问题
【银杏科技ARM+FPGA双核心应用】STM32H7系列35——USB_VCP_FS
【银杏科技ARM+FPGA双核心应用】STM32H7系列28——USB_HID
粉丝分享 | 图说CRC原理应用及STM32硬件CRC外设
STM32L151进入低功耗,并由RTC唤醒的故事
[转]stm32控制NFC模块(PN532)源码(P2P,模拟卡,读写卡等
STM32G070RB+LVGL移植
微信公众号
手机版
通过分析编译出来的执行文件的执行流程,了解C运行库所做的工作,加深对编译器、可执行文件装载映像组成、内存执行映像组成和初始化工作。
二、步骤
01、选择一个STM32的MDK工程,设置为在模拟仿真器上调试的方式,编译、进入调试模式。
02、第一条指令:是向量表中复位向量所指向的地址对应的指令。执行第一条指令时,MSP已经初始化为一个值0x2000,04A0,Thread模式。
这里是 LDR R0, =__main
BX R0 __main是C运行时库的入口,只要用户程序包含main函数,则编译器利用此段代码完成以下工作(将可执行文件的部分代码、全局已初始化数据从装载视图转移到执行视图-复制-主要是执行文件中RW部分,然后进行ZI部分执行视图的建立-清零,接着建立全局堆栈,进行库的初始化工作),然后跳入用户的main程序运行。
仔细看一下汇编代码部分,可以看到__main函数前面就是向量表(从0x0800,0000开始),此时系统的0x0地址是映射到该flash地址的。
ResetHandler的地址是0x0800,0164,而在向量表相应位置是0165,0800,小端模式的特征,并且采用Thumb模式。
向量表总共是59项,0x3B*4=0xec,所以__main函数的起始地址正好是0x0800,00ec。
在地址0x0800,0164处执行代码:LDR R0, =__main对应的汇编代码为LDR R0,[PC,#32],所以__main的地址是经过链接器定位的,具体地址写在下面的一个地址中(0x0800,0188),这用做的好处是什么呢?如果程序中有多处用到__main的地址,则都可以使用相对地址指令,而在最终定位时,只要在该地址写入最终的地址值。
接下来BX R0跳转到__main执行。
问题:(1)SP的初始值为什么是0x2000,04a0,首先RW的起始地址0x2000,0000链接器是知道的,堆栈的大小在.s文件中定义为0x400,估计该程序的全局变量共占据的空间大小为0xa0。
(2)此时装载视图的具体数据可以从哪里获得?即RO部分的大小和结束地址,RW部分的大小和结束地址,ZI有多大?链接器肯定把这个数据写在了某个地方,猜测为地址0x0800,0188那块区域,就是刚刚放__main地址的地方。
(3)在链接映像文件得到以下数据:
装载映像:Base: 0x08000000, Size: 0x00002db4
执行映像:Base: 0x08000000, Size: 0x00002d94;该区域为代码和RO DATA。
Base: 0x20000000, Size: 0x000004a0;其中RW DATA只有0x20大小,bss大小为0x80,堆栈大小为0x400。
那么应该复制的数据在:0x0800 2d94到2db4.
3、_main区域
有两条指令: 0x080000EC F000F802 BL.W __scatterload_rt2_thumb_only (0x080000F4) 和 0x080000F0 F001FB65 BL.W __rt_entry (0x080017BE)
先进入前面那个子程序,可以从字面推测为thumb模式下的分散加载程序。
4、在__scatterload_rt2_thumb_only中
接下来在使得R10=0x2ca0然后变成 0x0800,2dc0,R11=0x2cc0然后变成 0x0800,2de0.
R7=R10-1.
5、接下来进入__scatterload_null
先比较R10和R11,不相等则执行BNE 0x0800010A
处的指令。相等则BL.W __rt_entry (0x080017BE)跳入运行时库的入口执行。
6.在0x0800010A的代码
0x0800010A F2AF0E09 ADR.W lr,{pc}-0x07 ; @0x08000103,该处就是__scatterload_null,可以说先设置了返回地址。
0x0800010E E8BA000F LDM r10!,{r0-r3};该处指令读入0x0800,2dc0处的数据,并且R10变为0x0800,2dd0.
数据分别为0x0800 2de0,0x2000 0000,0x20,0x0800 0129.共16个字节,最后四个为__scatterload_copy的跳转地址。
0x08000112 F0130F01 TST r3,#0x01
0x08000116 BF18 IT NE
0x08000118 1AFB SUBNE r3,r7,r3
0x0800011A F0430301 ORR r3,r3,#0x01;将R3变成0x0800 0129,因为下面的指令带状态切换。
0x0800011E 4718 BX r3;该处的指令为 __scatterload_copy:
7.__scatterload_copy做了什么
0x08000128 3A10 SUBS r2,r2,#0x10;每次复制10个,
0x0800012A BF24 ITT CS
0x0800012C C878 LDMCS r0!,{r3-r6};将R0=0x0800,2de0处的数据复制0x20个到R1所指向的地址。
0x0800012E C178 STMCS r1!,{r3-r6}
0x08000130 D8FA BHI __scatterload_copy (0x08000128);直到20个复制完成,才执行下面的语句。
0x08000132 0752 LSLS r2,r2,#29
0x08000134 BF24 ITT CS
0x08000136 C830 LDMCS r0!,{r4-r5}
0x08000138 C130 STMCS r1!,{r4-r5}
0x0800013A BF44 ITT MI
0x0800013C 6804 LDRMI r4,[r0,#0x00]
0x0800013E 600C STRMI r4,[r1,#0x00]
0x08000140 4770 BX lr;回到__scatterload_null
8、回来以后__scatterload_null重新执行
首先取得0x0800 2dd0处的16个数据到R0-R3,R0为数据的源地址0x0800 2e00,R1为目的地址0x2000,0020。因为前面已经复制了0x20个数据。这下是清零操作。R2=操作数据个数,R3为返回地址。
9、清零操作完成,又回到__scatterload_null重新执行
此时R10=R11.BL.W __rt_entry (0x080017BE)跳入运行时库的入口执行。
10、__rt_entry代码分析
0x080017BE F3AF8000 NOP.W
0x080017C2 F7FFFFEA BL.W __rt_stackheap_init (0x0800179A);先进行堆栈的初始化。
0x080017C6 B403 PUSH {r0-r1};这里用到了参数,进行不同平台下的后处理操作。
0x080017C8 F000F93C BL.W _platform_post_stackheap_init (0x08001A44)
0x080017CC BC03 POP {r0-r1}
0x080017CE F000F8D9 BL.W __rt_lib_init (0x08001984);进行库初始化。
0x080017D2 B40F PUSH {r0-r3};需要四个参数。
0x080017D4 F000F93B BL.W _platform_post_lib_init (0x08001A4E)
0x080017D8 BC0F POP {r0-r3};恢复堆栈的初始状态。
0x080017DA F7FFFC8B BL.W main (0x080010F4);
main函数一般是个循环,不会执行到下面语句,但如果不写成循环,就会执行到下面的语句。
0x080017DE F000F8CB BL.W exit (0x08001978)
11、在__rt_stackheap_init中
__rt_stackheap_init:
0x0800179A 4674 MOV r4,lr;将返回地址保存在R4中;
0x0800179C F000F82E BL.W __user_setup_stackheap (0x080017FC)
0x080017A0 46A6 MOV lr,r4
0x080017A2 B50D PUSH {r0,r2-r3,lr};第一次用到了堆栈,保存了程序返回值。
0x080017A4 F000F850 BL.W __rt_stackheap_storage (0x08001848)
0x080017A8 0004 MOVS r4,r0;子程序的返回值。
0x080017AA E8BD400D POP {r0,r2-r3,lr}
0x080017AE 0011 MOVS r1,r2
0x080017B0 4770 BX lr
12、在__user_setup_stackheap中,
0x080017FC 4675 MOV r5,lr;将返回地址保存在R5中;
0x080017FE F000F8A5 BL.W 0x0800194C;
13、0x0800194C的功能分析;该段程序在_sys_exit中。
0x0800194C 4800 LDR r0,[pc,#0] ; @0x08001950
0x0800194E 4770 BX lr;将0x2000 0040保存在r0中,返回。
14、 __user_setup_stackheap:继续
__user_setup_stackheap:
0x080017FC 4675 MOV r5,lr
0x080017FE F000F8A5 BL.W 0x0800194C
0x08001802 46AE MOV lr,r5;从上面返回后,恢复lr
0x08001804 0005 MOVS r5,r0
0x08001806 4669 MOV r1,sp
0x08001808 4653 MOV r3,r10
0x0800180A F0200007 BIC r0,r0,#0x07
0x0800180E 4685 MOV sp,r0
0x08001810 B018 ADD sp,sp,#0x60;堆栈的起始点0x2000 00a0.
0x08001812 B520 PUSH {r5,lr}
0x08001814 F7FEFCB2 BL.W __user_initial_stackheap (0x0800017C)
15、 __user_initial_stackheap ;
跳到用户定义的函数,因为堆栈和用户堆得大小都是在程序中
自己定义的。
在用户程序中定义的BX LR就是返回到该函数中。
0x08001818 E8BD4020 POP {r5,lr}
0x0800181C F04F0600 MOV r6,#0x00
0x08001820 F04F0700 MOV r7,#0x00
0x08001824 F04F0800 MOV r8,#0x00
0x08001828 F04F0B00 MOV r11,#0x00
0x0800182C F0210107 BIC r1,r1,#0x07
0x08001830 46AC MOV r12,r5
0x08001832 E8AC09C0 STM r12!,{r6-r8,r11}
0x08001836 E8AC09C0 STM r12!,{r6-r8,r11}
0x0800183A E8AC09C0 STM r12!,{r6-r8,r11}
0x0800183E E8AC09C0 STM r12!,{r6-r8,r11}
0x08001842 468D MOV sp,r1;将sp设置为0x0800 04a0;
0x08001844 4770 BX lr;返回到前一级程序__user_setup_stackheap。
16、在__user_setup_stackheap继续执行
0x080017A0 46A6 MOV lr,r4;将保存在R4中的子程序返回值送回LR。
0x080017A2 B50D PUSH {r0,r2-r3,lr};利用堆栈保存参数。
0x080017A4 F000F850 BL.W __rt_stackheap_storage (0x08001848)
在该程序中,主要是在r0中保存了一个值0x2000 0050。
0x080017A8 0004 MOVS r4,r0;返回值保存到r4中。
0x080017AA E8BD400D POP {r0,r2-r3,lr}
0x080017AE 0011 MOVS r1,r2
0x080017B0 4770 BX lr;回到__rt_entry中执行。
17、又回到__rt_entry了,其实走了一圈,具体也没做什么事。
0x080017C6 B403 PUSH {r0-r1}
0x080017C8 F000F93C BL.W _platform_post_stackheap_init (0x08001A44);具体平台的堆栈相关初始化:本程序中没做什么事,估计是可以人工定义改变的。
0x080017CC BC03 POP {r0-r1}
0x080017CE F000F8D9 BL.W __rt_lib_init (0x08001984)
18、进入__rt_lib_init
0x08001984 E92D43FE PUSH {r1-r9,lr}
0x08001988 4604 MOV r4,r0
0x0800198A 460D MOV r5,r1
0x0800198C F000F872 BL.W _fp_init (0x08001A74);进入浮点初始化
19、进入 _fp_init
0x08001A74 B510 PUSH {r4,lr}
0x08001A76 F7FFFFF7 BL.W __rt_fp_status_addr (0x08001A68);马上又进入到__rt_fp_status_addr。
0x0800198C F000F872 BL.W _fp_init (0x08001A74);回来了,其实也没做什么事,就在0x2000 0044处存了一个数据,什么用途暂时不知。
0x08001990 2000 MOVS r0,#0x00
0x08001992 E9CD4500 STRD r4,r5,[sp,#0]
0x08001996 F3AF8000 NOP.W
0x0800199A 9002 STR r0,[sp,#0x08]
0x0800199C 4668 MOV r0,sp
0x0800199E F7FEFBDF BL.W __ARM_argv_veneer (0x08000160)
20、在 __ARM_argv_veneer中
0x08000160 F001BC80 B.W __ARM_get_argv (0x08001A64)it (0x08001A44)马上又跳转到另一个函数。又单纯的返回而已。
0x080019C2 4680 MOV r8,r0
0x080019C4 F000F848 BL.W __rt_locale (0x08001A58)
0x080019C8 2100 MOVS r1,#0x00;最后又回到__rt_entry中。
0x080017D2 B40F PUSH {r0-r3}
0x080017D4 F000F93B BL.W _platform_post_lib_init (0x08001A4E)
0x080017D8 BC0F POP {r0-r3};最后的操作马上机会进入到主函数main中执行用户程序。
1、第一部分:Section Cross References
主要是各个源文件生成的模块之间相互引用的关系。
stm32f10x.o(STACK) refers (Special) to stkheap2.o(.text) for __use_two_region_memory
比如上面这句话,stm32f10x.o是stm32f10x.s生成的目标文件模块,(STACK)是文件内定义的一个段,链接器把它视为一个Section,输入节。它引用了模块stkheap2.o输入节(.text)里面的一个全局符号__use_two_region_memory(可能是一个函数或变量)。这个(Special)不知道是什么含义。
剩下的基本都是这用的意思。
stm32f10x_vector.o(.text) refers to __main.o(!!!main) for __main
__main.o(!!!main) refers to kernel.o(.text) for __rt_entry
kernel.o(.text) refers to usertask.o(.text) for main
上面这几个对于程序意义比较重大用户在启动代码中调用了__main.o模块中的__main函数,__main又调用了kernel.o中的__rt_entry函数,最后kernel.o又调用了用户定义的main主函数。
2、第二部分:Removing Unused input sections from the image.
就是将库中没有用到的函数从可执行映像中删除掉,减小程序的体积。
Removing os_mbox.o(.text), (1094 bytes).
Removing os_mutex.o(.text), (1744 bytes).
Removing os_sem.o(.text), (1016 bytes).
3、第三部分:Image Symbol Table
Local Symbols
符号表里的局部符号。
../../angel/boardlib.s 0x00000000 Number 0 boardinit1.o ABSOLUTE
../../angel/handlers.s 0x00000000 Number 0 __scatter_copy.o ABSOLUTE
../../angel/kernel.s 0x00000000 Number 0 kernel.o ABSOLUTE
../../angel/rt.s 0x00000000 Number 0 rt_raise.o ABSOLUTE
../../angel/scatter.s 0x00000000 Number 0 __scatter.o ABSOLUTE
../../angel/startup.s 0x00000000 Number 0 __main.o ABSOLUTE
../../angel/sys.s 0x00000000 Number 0 sys_exit.o ABSOLUTE
../../angel/sysapp.c 0x00000000 Number 0 sys_wrch.o ABSOLUTE
../../armsys.c 0x00000000 Number 0 _get_argv.o ABSOLUTE
../../division_7m.s 0x00000000 Number 0 rtudiv10.o ABSOLUTE
../../fpinit.s 0x00000000 Number 0 fpinit.o ABSOLUTE
../../heapalloc.c 0x00000000 Number 0 hrguard.o ABSOLUTE
../../printf.c 0x00000000 Number 0 _printf_outstr_char.o ABSOLUTE
../../signal.c 0x00000000 Number 0 defsig_exit.o ABSOLUTE
../../stdlib.c 0x00000000 Number 0 exit.o ABSOLUTE
../../stkheap.s 0x00000000 Number 0 heapext.o ABSOLUTE
以上是一些系统内部的局部符号,还有用户的一些局部符号
4、第四部分:Global Symbols
全局符号
_terminate_user_alloc - Undefined Weak Reference
_terminateio - Undefined Weak Reference
__Vectors 0x08000000 Data 4 stm32f10x_vector.o(RESET)
__main 0x08000131 Thumb Code 8 __main.o(!!!main)
__scatterload 0x08000139 Thumb Code 0 __scatter.o(!!!scatter)
__scatterload_rt2 0x08000139 Thumb Code 44 __scatter.o(!!!scatter)
这些是一些系统的全局符号
Font8x16 0x08001a82 Data 2048 tft018.o(.constdata)
Font8x8 0x08002282 Data 2056 tft018.o(.constdata)
codeGB_16 0x08002a8a Data 770 tft018.o(.constdata)
Region$$Table$$Base 0x08002dc0 Number 0 anon$$obj.o(Region$$Table)
Region$$Table$$Limit 0x08002de0 Number 0 anon$$obj.o(Region$$Table)
后面这两个符号我认为很重要,在运行库代码将可执行映像从加载视图转变为可执行视图的过程中起到了关键作用。Number是指它并不占据程序空间,而只是一个具有一定数值的符号,类似于程序中用define和EQU定义的。所以这里,我先放下map文件的分析,先通过仿真调试,看这两个数值在程序中怎么用。
执行完成后,程序就会进入BL.W __rt_entry处进行库的初始化工作
经过这么一分析,现在我对于程序的加载映像和执行映像有了较深的理解:程序的RO_Code加上RO_Data总共是0x2dc0这么大,地址范围0x0800,0000到0x8000,2dbf。然后在0x0800,2dc0-2dcf共16个字节放了RW加载映像地址(0x0800,2de0)、执行映像地址(0x2000,0000)、RW长度(0x20)和将该段数据从加载映像复制到执行映像的函数地址。在0x0800,2dd0-2ddf共16个字节放了ZI加载映像地址(0x0800,2e00)、执行映像地址(0x2000,0020)、ZI长度(0x480)和建立ZI、HEAP和STACK执行映像的函数地址。
在上面的第二个阶段,将ZI清零阶段,程序的ZI长度实际上只有0x20,而库代码留出了0x60的长度。因此数据区的顶端为0x2000,00a0-1。接下来从0x2000,00a0开始为堆的起始地址,堆长度加上程序栈长度为0x2000,04a0,这就是堆栈顶端,也是__initial_SP的初始值。
程序进入_rt_entry后,还要对heapstack进行处理,但我没有看到有什么用的变化。从中对库留出的ZI数据区进行了一些处理,我暂时也看不明白。好了,调试就到这里,回到map文件分析的正途。
5、第五部分:
Memory Map of the image
//映像的内存分布
Image Entry point : 0x080000ed
//程序的入口点:这里应该是RESET_Handler的地址
Load Region LR_IROM1 (Base: 0x08000000, Size: 0x00002e00, Max: 0x00020000, ABSOLUTE)
//程序的加载映像地址和长度,2e00=2dc0(代码和常数)+0x20(Region Table是RW的加载和执行地址、ZI与HEAPSTACK的执行地址)+0x20(已经初始化的数据)。
Execution Region ER_IROM1 (Base: 0x08000000, Size: 0x00002de0, Max: 0x00020000, ABSOLUTE) //这段RO区域的加载映像和执行映像一致。
Base Addr Size Type Attr Idx E Section Name Object
0x08000000 0x000000ec Data RO 3 RESET stm32f10x.o
0x080000ec 0x00000008 Code RO 191 * !!!main __main.o(c_w.l)
Execution Region RW_IRAM1 (Base: 0x20000000, Size: 0x000004a0, Max: 0x00005000, ABSOLUTE) //RW数据区 ZI数据区 Heap和Stack数据区。
Base Addr Size Type Attr Idx E Section Name Object
0x20000000 0x00000001 Data RW 100 .data tft018.o
x20000040 0x00000060 Zero RW 212 .bss libspace.o(c_w.l)
0x200000a0 0x00000000 Zero RW 2 HEAP stm32f10x.o
0x200000a0 0x00000400 Zero RW 1 STACK stm32f10x.o
6、第六部分:Image component sizes
这是指出各个模块的输入节的大小
Code (inc. data) RO Data RW Data ZI Data Debug Object Name
972 58 0 10 32 2416 can.o
824 168 0 15 0 1791 candemo.o
928 88 0 0 0 4529 stm32_init.o
52 18 236 0 1024 2700 stm32f10x.o
1836 32 4874 1 0 8076 tft018.o
最后给出总长度:这个11744应该=0x2dc0,1184应该0x4a0。11776应该是=0x2e00。
Total RO Size (Code + RO Data) 11744 ( 11.47kB)
Total RW Size (RW Data + ZI Data) 1184 ( 1.16kB)
Total ROM Size (Code + RO Data + RW Data) 11776 ( 11.50kB)