你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32进阶第一课

[复制链接]
最美最美 发布时间:2015-1-6 09:32
一、      keil MDK运行时库分析


收藏 评论8 发布时间:2015-1-6 09:32

举报

8个回答
最美最美 回答时间:2015-1-6 09:32:37
一、目的:
  通过分析编译出来的执行文件的执行流程,了解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)
最美最美 回答时间:2015-1-6 09:33:01
二、步骤续
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中执行用户程序。
最美最美 回答时间:2015-1-6 09:36:40
二、keil MDK 输出map文件分析
最美最美 回答时间:2015-1-6 09:37:14
一、文件分析流程


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文件的分析,先通过仿真调试,看这两个数值在程序中怎么用。

最美最美 回答时间:2015-1-6 09:39:26
果然,在刚开始执行程序时,R10和R11的值就已经被赋值成了这两个值。



(XQ8]AAUT[Q@6OUJ6~INKUM.png
最美最美 回答时间:2015-1-6 09:39:59
。。。
~UD0K%@02C}C{MXQ57L7I}9.png
最美最美 回答时间:2015-1-6 09:40:49
很快就将0x08002dc0到0x08002dcf处的16个字节,4个双字加载到了R0-R3,我们可以分析一下里面的内容,R0就是程序加载视图的RW区的起始地址(0x08002de0),R1就是要输出的执行视图的RW区的地址(0x20000000),R2就是要复制的RW数据的个数,R3是复制函数 ( __scatterload_copy)的地址,类似于一个回调函数。接下来就要用了:0x0800011E 4718  BX  r3这条指令去执行复制工作。

09CB3SK`JE[8``}`{CRWI5S.png
最美最美 回答时间:2015-1-6 09:41:15
接下来又将0x08002dd0到0x08002ddf处的16个字节,4个双字加载到了R0-R3,我们可以分析一下里面的内容,R0就是程序加载视图的RW区的起始地址(0x08002de0+0x20=0x08002e00),R1就是要输出的执行视图的RW区的地址(0x20000020),R2就是要复制的RW数据的个数,R3是ZI区域建立函数(  __scatterload_zeroinit )的地址。




执行完成后,程序就会进入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)

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
微信公众号二维码 微信公众号
手机版二维码 手机版