原创 keil MDK运行时库分析01

2009-10-9 20:07 5476 9 9 分类: MCU/ 嵌入式

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条评论)

登录后参与讨论
我要评论
0
9
关闭 站长推荐上一条 /2 下一条