4

STM32启动分析之main函数是怎样跑起来的 - 啊哈彭

 1 year ago
source link: https://www.cnblogs.com/pingwen/p/17320181.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

1、MDK目标文件

1)MDK中C程序编译后的结果,即可执行文件数据分类:

  • RAM
    • ZI
      • bss 存储未初始化的或初始化为0的全局变量和静态变量
      • heap 堆,系统malloc和free操作的内存
      • stack 栈,存储函数临时局部变量
    • RW
      • data 已经初始化且不为0的全局变量和静态变量
  • FLASH
    • RO
      • text 代码段,CPU指令,字符串字面值、常数等,keil中叫Code段
      • constdata const常量,keil中叫RO-data

2)目标文件中各类型数据的存储位置

  • ZI-data 在bss段,ZI数据全为0,所以没有必要占用Flash空间,运行时占用RAM。

  • RW-data在RAM中,掉电丢失,所以需要启动时从FLASH拷贝到RAM中去,所以RW占FLASH空间。

  • 由上我们得知keil的编译结果:
    程序占用 Flash = Code + RO data + RW data
    程序运行时候占用 RAM = RW data + ZI data。
    Code + RO data + RW data 的大小也是生成的 bin 文件的大小

  • 类似的,GCC的编译结果:

Memory region        Used Size  Region Size  %age Used 
       FLASH:         480 B        32 KB      1.46%
       RAM:           1200 B         4 KB     29.30%

Flash 的大小:Flash = text + data 。
RAM大小:RAM = data + bss。

2、STM32 startup.s 文件分析

1、栈分配

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp
  • EQU 是伪指令,不生产具体的目标文件,相当于定义了一个宏定义提高可读性。
  • ARER 开辟一段代码段或数据段,后面的关键字表示这个段的属性:
  • STACK : 表示这个段的名字,可以任意命名。
  • NOINIT: 表示此数据段不需要填入初始数据。
  • READWRITE:表示此段可读可写。
  • ALIGN=3: 表示首地址按照2的3次方对齐,所以栈空间是8字节对齐的.
  • SPACE Stack_Memd 段分配 Stack_Size 的空间。
  • __initial_sp 是标号代表地址位置,即栈顶位置。

2、堆分配

Heap_Size       EQU     0x00000200
                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

                PRESERVE8
                THUMB

具体的含义和栈分配相似,开辟空间Heap_Mem,大小为Heap_Size。__heap_base和__heap_limit分别表示堆的起点和终点。
PRESERVE8 指当前文件字节对其。ALIGN 伪指令表示对齐填充,可能的取值为2的幂,如 1 、2 、4 、8 、16 等。未跟数字如ALIGN 表示对齐到1个字(2字节)。
THUMB 表示使用的指令集。

3、vector table

; Vector Table Mapped to Address 0 at Reset
      AREA    RESET, DATA, READONLY # 定义了RESET区域为READONLY即存储在Flash。
      EXPORT  __Vectors    # 中断向量表入口地址,EXPORT 是指该变量可以被导出,外部可以使用
      EXPORT  __Vectors_End  # 中断向量表的结束地址
      EXPORT  __Vectors_Size # 中断向量表的大小

下面开始建立中断向量表。
中断向量表类似一个全是函数指针的数组,每个函数指针代表对应中断号的中断处理程序入口。
向量表的起点是栈顶。DCD是定义一个word(4字节)的空间。

__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                DCD     NMI_Handler                ; NMI Handler
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     MemManage_Handler          ; MPU Fault Handler
                DCD     BusFault_Handler           ; Bus Fault Handler
                DCD     UsageFault_Handler         ; Usage Fault Handler
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     SVC_Handler                ; SVCall Handler
                DCD     DebugMon_Handler           ; Debug Monitor Handler
                DCD     0                          ; Reserved
                DCD     PendSV_Handler             ; PendSV Handler
                DCD     SysTick_Handler            ; SysTick Handler
//省略中断向量表
__Vectors_End
__Vectors_Size  EQU  __Vectors_End - __Vectors  # 相减计算中断向量表的size
                
AREA    |.text|, CODE, READONLY  # 定义了一个只读的名为 .txt的代码段
; Reset handler
Reset_Handler   PROC   # PROC 汇编程序开始,ENDP汇编程序结束
                EXPORT  Reset_Handler             [WEAK]  # WEAK说明此函数可以被用户重写
                IMPORT  __main      # 从外部文件import一个函数
                IMPORT  SystemInit
                LDR     R0, =SystemInit  # load SystemInit 函数地址,做系统时钟初始化
                BLX     R0   # 跳转到 SystemInit 函数执行
                LDR     R0, =__main   # load _main
                BX      R0  # 跳转到_main执行,此_main非彼main
                ENDP

其他一些异常中断函数,简单起见直接使用死循环代替(在可靠性系统中用户应该检测并做特殊处理)。

; Dummy Exception Handlers (infinite loops which can be modified)
NMI_Handler     PROC
                EXPORT  NMI_Handler                [WEAK]
                B       .
                ENDP

外设中的一些中断函数:

Default_Handler PROC

                EXPORT  WWDG_IRQHandler            [WEAK]
                EXPORT  PVD_IRQHandler             [WEAK]
                EXPORT  TAMPER_IRQHandler          [WEAK]
// 省略
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandler
                B       .

                ENDP     #先都用死循环代替,标记了WEAK用户可以重写,在外设驱动文件中已经包含了这些中断服务程序。

                ALIGN

4、堆栈初始化

;------------------------------------------------------------------
; User Stack and Heap initialization
;------------------------------------------------------------------
                 IF      :DEF:__MICROLIB  # 如果启用了MDK的微库microLib
               
                 EXPORT  __initial_sp    # 导出这三个变量给外部使用
                 EXPORT  __heap_base
                 EXPORT  __heap_limit
                
                 ELSE  # 如果没有启动微库
                
                 IMPORT  __use_two_region_memory   # 导入__use_two_region_memory 标号
                 EXPORT  __user_initial_stackheap  # 导出__user_initial_stackheap  方法
                 
__user_initial_stackheap  # 标号,表示堆栈初始化程序入口

                 LDR     R0, =  Heap_Mem  # 堆空间起点(向上增长)
                 LDR     R1, =(Stack_Mem + Stack_Size)   # 栈尾部(向下增长)
                 LDR     R2, = (Heap_Mem +  Heap_Size)  # 堆大小
                 LDR     R3, = Stack_Mem   # 栈空间起点
                 BX      LR

                 ALIGN
                 ENDIF
                 END

3、Keil MDK main函数启动

  • main()函数是第一个被执行的函数吗?
  • 除了system_init初始化了系统时钟,mian函数启动前还发生了什么?

在startup.s文件中,跳转到_main,这个_main并不是c的main函数,而是编译器内置的一个c库函数,内部执行了三个步骤:初始化rw段,初始化zi段,调用另一个c库函数__rt_entry()。
__rt_entry()该函数先初始化堆栈和库函数,然后即调用主函数main(),从而进入用户程序。可以看出主函数main()若退出,则在__rt_entry()最后会再调用exit()函数进行退出操作。

什么是__rt_entry?标准库或ARM文档有如下描述。
详情见:https://developer.arm.com/documentation/dui0475/m/the-c-and-c---library-functions-reference/--rt-entry
所以main函数既不是c程序第一个执行的函数,也不是c程序最后一个执行的函数。在main启动前,标准库已经做了很多工作,当跳转mian时,堆栈已经完成了初始化、C运行时环境已经就绪。

__rt_entry
The symbol __rt_entry is the starting point for a program using the ARM C library.
Control passes to __rt_entry after all scatter-loaded regions have been relocated to their execution addresses.
Usage
1. The default implementation of __rt_entry:
2. Sets up the heap and stack.
3. Initializes the C library by calling __rt_lib_init.
4. Calls main().
5. Shuts down the C library, by calling __rt_lib_shutdown.
6. Exits.
__rt_entry must end with a call to one of the following functions:

exit()
Calls atexit()-registered functions and shuts down the library.

__rt_exit()
Shuts down the library but does not call atexit() functions.

_sys_exit()
Exits directly to the execution environment. It does not shut down the library and does not call atexit() functions.

startup.s文件中,DCD定义了76个中断服务函数入口,76*4=304=0x130。所以在汇编文件中,Flash地址0x08000000起点是DEC中断函数入口,0x08000130 位置是代码起点。如果进行代码调试跟踪,可以发现这段汇编代码实现的是堆和栈的初始化。

启用微库时的初始化过程,其中__scatterload 即对堆栈进行初始化,对比不启用微库的汇编程序,指令条目数已大幅缩减:

0x080007D8 2000      MOVS     r0,#0x00
0x080007DA E001      B        0x080007E0
0x080007DC C101      STM      r1!,{r0}
0x080007DE 1F12      SUBS     r2,r2,#4
0x080007E0 2A00      CMP      r2,#0x00
0x080007E2 D1FB      BNE      0x080007DC
0x0800012C 015F      DCW      0x015F
0x0800012E 0800      DCW      0x0800
                 __main:
0x08000130 F8DFD00C  LDR.W    sp,[pc,#12]  ; @0x08000140
                 _main_scatterload:
0x08000134 F000F82E  BL.W     __scatterload (0x08000194)
                 __main_after_scatterload:
0x08000138 4800      LDR      r0,[pc,#0]  ; @0x0800013C
0x0800013A 4700      BX       r0
0x0800013C 0969      DCW      0x0969
0x0800013E 0800      DCW      0x0800
                 __rt_final_cpp:
0x08000140 0428      DCW      0x0428
0x08000142 2000      DCW      0x2000
   151:                 LDR     R0, =SystemInit 
0x08000144 4806      LDR      r0,[pc,#24]  ; @0x08000160
   152:                 BLX     R0                
0x08000146 4780      BLX      r0
   153:                 LDR     R0, =__main 
0x08000148 4806      LDR      r0,[pc,#24]  ; @0x08000164
   154:                 BX      R0 
   155:                 ENDP 
   156:                  
   157: ; Dummy Exception Handlers (infinite loops which can be modified) 
   158:  
   159: NMI_Handler     PROC 
   160:                 EXPORT  NMI_Handler                [WEAK] 
0x0800014A 4700      BX       r0
   161:                 B       . 
   162:                 ENDP 

不启用微库时的初始化过程,启动代码大大增加。

0x08000130 F000F802  BL.W     __scatterload (0x08000138)
0x08000134 F000F847  BL.W     __rt_entry (0x080001C6)
0x08000138 A00A      ADR      r0,{pc}+4  ; @0x08000164
0x0800013A E8900C00  LDM      r0,{r10-r11}
0x0800013E 4482      ADD      r10,r10,r0
0x08000140 4483      ADD      r11,r11,r0
0x08000142 F1AA0701  SUB      r7,r10,#0x01
0x08000146 45DA      CMP      r10,r11
0x08000148 D101      BNE      0x0800014E
0x0800014A F000F83C  BL.W     __rt_entry (0x080001C6)
0x0800014E F2AF0E09  ADR.W    lr,{pc}-0x07  ; @0x08000147
0x08000152 E8BA000F  LDM      r10!,{r0-r3}
0x08000156 F0130F01  TST      r3,#0x01
0x0800015A BF18      IT       NE
0x0800015C 1AFB      SUBNE    r3,r7,r3
0x0800015E F0430301  ORR      r3,r3,#0x01
0x08000162 4718      BX       r3
0x08000164 1244      DCW      0x1244
0x08000166 0000      DCW      0x0000
0x08000168 1264      DCW      0x1264
0x0800016A 0000      DCW      0x0000
0x0800016C 3A10      SUBS     r2,r2,#0x10
0x0800016E BF24      ITT      CS
0x08000170 C878      LDMCS    r0!,{r3-r6}
0x08000172 C178      STMCS    r1!,{r3-r6}
0x08000174 D8FA      BHI      __scatterload_copy (0x0800016C)
0x08000176 0752      LSLS     r2,r2,#29
0x08000178 BF24      ITT      CS
0x0800017A C830      LDMCS    r0!,{r4-r5}
0x0800017C C130      STMCS    r1!,{r4-r5}
0x0800017E BF44      ITT      MI
0x08000180 6804      LDRMI    r4,[r0,#0x00]
0x08000182 600C      STRMI    r4,[r1,#0x00]
0x08000184 4770      BX       lr
0x08000186 0000      MOVS     r0,r0
0x08000188 2300      MOVS     r3,#0x00
0x0800018A 2400      MOVS     r4,#0x00
0x0800018C 2500      MOVS     r5,#0x00
0x0800018E 2600      MOVS     r6,#0x00
0x08000190 3A10      SUBS     r2,r2,#0x10
0x08000192 BF28      IT       CS
0x08000194 C178      STMCS    r1!,{r3-r6}
0x08000196 D8FB      BHI      0x08000190
0x08000198 0752      LSLS     r2,r2,#29
0x0800019A BF28      IT       CS
0x0800019C C130      STMCS    r1!,{r4-r5}
0x0800019E BF48      IT       MI
0x080001A0 600B      STRMI    r3,[r1,#0x00]
0x080001A2 4770      BX       lr
                 _printf_d:
0x080001A4 2964      CMP      r1,#0x64
0x080001A6 F000807D  BEQ.W    _printf_int_dec (0x080002A4)
                 _printf_percent_end:
0x080001AA 2000      MOVS     r0,#0x00
0x080001AC 4770      BX       lr
                 __rt_lib_init:
0x080001AE B51F      PUSH     {r0-r4,lr}
                 __rt_lib_init_fp_1:
0x080001B0 E89D0003  LDM      sp,{r0-r1}
0x080001B4 F000FB6C  BL.W     _init_alloc (0x08000890)
                 __rt_lib_init_atexit_1:
0x080001B8 F000F93A  BL.W     _initio (0x08000430)
                 __rt_lib_init_alloca_1:
0x080001BC BD1F      POP      {r0-r4,pc}
                 __rt_lib_shutdown:
0x080001BE B510      PUSH     {r4,lr}
                 __rt_lib_shutdown_stdio_2:
0x080001C0 F000F99F  BL.W     _terminateio (0x08000502)
                 __rt_lib_shutdown_fp_trap_1:
0x080001C4 BD10      POP      {r4,pc}
                 __rt_entry:
0x080001C6 F000FA02  BL.W     __user_setup_stackheap (0x080005CE)
0x080001CA 4611      MOV      r1,r2
                 __rt_entry_li:
0x080001CC F7FFFFEF  BL.W     __rt_lib_init (0x080001AE)
                 __rt_entry_main:
0x080001D0 F001F89A  BL.W     main (0x08001308)
0x080001D4 F000FB06  BL.W     exit (0x080007E4)
                 __rt_exit:
0x080001D8 B403      PUSH     {r0-r1}
                 __rt_exit_ls:
0x080001DA F7FFFFF0  BL.W     __rt_lib_shutdown (0x080001BE)
                 __rt_exit_exit:
0x080001DE BC03      POP      {r0-r1}
0x080001E0 F000FCE8  BL.W     _sys_exit (0x08000BB4)
   151:                 LDR     R0, =SystemInit 
0x080001E4 4809      LDR      r0,[pc,#36]  ; @0x0800020C
   152:                 BLX     R0                
0x080001E6 4780      BLX      r0
   153:                 LDR     R0, =__main 
0x080001E8 4809      LDR      r0,[pc,#36]  ; @0x08000210
   154:                 BX      R0 
   155:                 ENDP 

尊重原创技术文章,转载请注明:https://www.cnblogs.com/pingwen/p/17320181.html


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK