2009-10-02 09:52:19
一、目的:
通过分析编译出来的执行文件的执行流程,了解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)
文章评论(0条评论)
登录后参与讨论