底层软件 | STM32启动分析之main函数是怎样跑起来的

应届生面试,基本上嵌入式一般都是基于32的项目,记得我当年面大疆的就是有这个题目。

1、STM32启动规则

  • STM32根据boot0和boot1的电平决定启动位置,boot0=0时从主Flash启动,即0x08000000地址启动。

  • 按照spec,M3核的中断向量表是不变的(中断向量表每一项为4个字节),中断向量表的顺序:栈顶、复位向量、中断向量…。所以复位时0x00000000(映射在0x08000000)的值为栈顶指针,0x00000004(映射在0x08000004)是复位向量。

__Vectors     DCD     __initial_sp               ; Top of StackDCD     Reset_Handler              ; Reset HandlerDCD     NMI_Handler                ; NMI Handler
  • STM32复位时,CPU从0x00000000处获取栈顶指针MSP(默认使用主堆栈),从0x00000004处获取程序计数器PC(复位向量)。
  • STM32启动过程主要分5步:
    • 初始化堆栈指针SP=_initial_sp
    • 初始化PC指针=Reset_Handler
    • 初始化中断向量表 Vector Table
    • 配置系统时钟 SystemInit
    • 初始化c的runtime,例如调_main初始化堆栈,最后调main函数进入用户C程序

2、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。

3、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 是标号代表地址位置,即栈顶位置。

3、堆分配

Heap_Size       EQU     0x00000200AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limitPRESERVE8THUMB

具体的含义和栈分配相似,开辟空间Heap_Mem,大小为Heap_Size。__heap_base和__heap_limit分别表示堆的起点和终点。

PRESERVE8 指当前文件字节对其。ALIGN 伪指令表示对齐填充,可能的取值为2的幂,如 1 、2 、4 、8 、16 等。未跟数字如ALIGN 表示对齐到1个字(2字节)。

THUMB 表示使用的指令集。

4、vector table

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

下面开始建立中断向量表。

中断向量表类似一个全是函数指针的数组,每个函数指针代表对应中断号的中断处理程序入口。

向量表的起点是栈顶。DCD是定义一个word(4字节)的空间。

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

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

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

外设中的一些中断函数:

Default_Handler PROCEXPORT  WWDG_IRQHandler            [WEAK]EXPORT  PVD_IRQHandler             [WEAK]EXPORT  TAMPER_IRQHandler          [WEAK]
// 省略
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandlerB       .ENDP     #先都用死循环代替,标记了WEAK用户可以重写,在外设驱动文件中已经包含了这些中断服务程序。ALIGN

5、堆栈配置与初始化

;------------------------------------------------------------------
; User Stack and Heap initialization
;------------------------------------------------------------------IF      :DEF:__MICROLIB  # 如果启用了MDK的微库microLibEXPORT  __initial_sp    # 导出这三个变量给外部使用EXPORT  __heap_baseEXPORT  __heap_limitELSE  # 如果没有启动微库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      LRALIGNENDIFEND

4、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      0x2000151:                 LDR     R0, =SystemInit 
0x08000144 4806      LDR      r0,[pc,#24]  ; @0x08000160152:                 BLX     R0                
0x08000146 4780      BLX      r0153:                 LDR     R0, =__main 
0x08000148 4806      LDR      r0,[pc,#24]  ; @0x08000164154:                 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       r0161:                 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]  ; @0x0800020C152:                 BLX     R0                
0x080001E6 4780      BLX      r0153:                 LDR     R0, =__main 
0x080001E8 4809      LDR      r0,[pc,#36]  ; @0x08000210154:                 BX      R0 155:                 ENDP 

原文链接

  • https://www.cnblogs.com/pingwen/p/17320181.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/41756.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

构建工程化:多种不同的工程体系如何编写MakeFile

源码分析 核心MakeFile 这个 Makefile 是一个复杂的构建脚本,用于管理和构建一个大型项目。它包括多个目标、条件判断和递归调用 make 命令来处理多个子项目和子目录。让我们逐部分进行详细解析。 伪目标和变量定义 .PHONY: all clean install build test init.…

依赖注入的优点、解决的问题以及其底层原理和逻辑

依赖注入(Dependency Injection, DI)是一种设计模式,用于实现控制反转(Inversion of Control, IoC)。它通过将对象的依赖关系从类内部转移到外部配置或注入,从而提高代码的可维护性、可测试性和可扩展性。以…

使用Spring Boot和Apache Camel集成第三方服务

使用Spring Boot和Apache Camel集成第三方服务 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将探讨如何利用Spring Boot和Apache Camel来集成第三方服…

pycharm如何使用jupyter

目录 配置jupyter新建jupyter文件别人写的方法(在pycharm种安装,在网页中使用) pycharm专业版 配置jupyter 在pycharm终端启动一个conda虚拟环境,输入 conda install jupyter会有很多前置包需要安装: 新建jupyter…

一文理清LK光流

举出几种光流方法,说明LK光流的建模方式? 光流方法是用于估计图像序列中像素点运动的技术,广泛应用于计算机视觉和视频处理领域。以下是几种常见的光流方法: Lucas-Kanade (LK) 方法: 一种基于局部窗口的光流估计方法…

代理IP在未来将面临哪些挑战?

今天我们来聊聊代理IP在未来可能会面临的挑战。虽然代理IP技术目前应用广泛,但随着科技的发展和网络环境的变化,代理IP也将面临一些新的挑战。让我们一起来看看这些挑战是什么吧! 1. 更严格的网络封锁和检测 现代社会各行各业都在飞速发展&…

可变参数 Collections 不可变集合 Stream流

目录 1.可变参数: 2.Collections: 3.不可变集合: 4.Stream流: 1、什么是流 2、如何生成流 1.单列集合获取Stream流 2.双列集合获取Stream流 3.数组获取Stream流: 4.一堆零散数据: Stream接口中的静态方法 3.Stream流的…

解决分布式环境下session共享问题

在分布式环境下,session会存在两个问题 第一个问题:不同域名下,浏览器存储的jsessionid是没有存储的。比如登录时认证服务auth.gulimall.com存储了session,但是搜索服务search.gulimall.com是没有这个session的; 第二个问题&…

基于SpringBoot的校园台球厅人员与设备管理系统

本系统是要设计一个校园台球厅人员与设备管理系统,这个系统能够满足校园台球厅人员与设备的管理及用户的校园台球厅人员与设备管理功能。系统的主要功能包括首页、个人中心、用户管理、会员账号管理、会员充值管理、球桌信息管理、会员预约管理、普通预约管理、留言…

【SSRF】

SSRF (Server-Side Request Forgery 服务端请求伪造) 文章目录 0x01 是什么?0x02 怎么判断是否存在SSRF漏洞?0x03 防御0x04 绕过手段 0x01 是什么? 是什么?   答:攻击者构造请求,…

w3wp.exe 中发生未处理的 Microsoft ,NETFramework 异常。

🏆本文收录于「Bug调优」专栏,主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&…

Spring 6.1.10版本源码编译

每篇一句 我们对时间的感知其实非常主观,我们越习惯于我们的生活方式,生活里面的新鲜感就越少,我们对时间 的感知就越快,生命就越短。 1.源码下载 进入Spring官网 https://spring.io/ 按照上图步骤进入如下Spring Framework链…

罗剑锋的C++实战笔记学习(二):容器、算法库、多线程

4、容器 1)、容器的通用特性 所有容器都具有的一个基本特性:它保存元素采用的是值(value)语义,也就是说,容器里存储的是元素的拷贝、副本,而不是引用 容器操作元素的很大一块成本就是值的拷贝…

RAG 工业落地方案框架(Qanything、RAGFlow、FastGPT、智谱RAG)细节比对!CVPR自动驾驶最in挑战赛赛道,全球冠军被算力选手夺走了

RAG 工业落地方案框架(Qanything、RAGFlow、FastGPT、智谱RAG)细节比对!CVPR自动驾驶最in挑战赛赛道,全球冠军被算力选手夺走了。 本文详细比较了四种 RAG 工业落地方案 ——Qanything、RAGFlow、FastGPT 和智谱 RAG,重…

git push之后回滚到某个版本

背景 因为粗心在主分支上修改了代码,push了上去,污染了主分支,希望将主分支之后的修改回滚,包括提交记录,就是远程的记录中回到希望回到的版本,保持干净。 git push -f 可以做到,会冲掉所有的…

SwiftUI 6.0(iOS 18.0)滚动视图新增的滚动阶段(Scroll Phase)监听功能趣谈

何曾几时,在 SwiftUI 开发中的秃头小码农们迫切需要一种能够读取当前滚动状态的方法。 在过去,他们往往需要借助于 UIKit 的神秘力量。不过这一切在 SwiftUI 6.0 中已成“沧海桑田”。 在本篇博文中,您将学到如下内容: 1. Scroll…

一份适合新手的软件测试练习项目

最近,不少读者托我找一个能实际练手的测试项目。开始,我觉得这是很简单的一件事,但当我付诸行动时,却发现,要找到一个对新手友好的练手项目,着实困难。 我翻了不下一百个web网页,包括之前推荐练…

nginx的知识面试易考点

Nginx概念 Nginx 是一个高性能的 HTTP 和反向代理服务。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好。 Nginx 专为性能优化而开发,性能是其最重要的考量指标,实现上非常注重效率&#…

C#用链表和数组分别实现堆栈

1.链表 实现栈的四个基本功能 入栈 出栈 长度 栈顶值 public class 基础 : MonoBehaviour {public class MyStack{//定义每一个元素的数据结构 //下一个元素 和 该元素的值public class StackData{public StackData next;public object data;public StackData(StackData next,…

linux驱动编程 - kfifo先进先出队列

简介: kfifo是Linux Kernel里面的一个 FIFO(先进先出)数据结构,它采用环形循环队列的数据结构来实现,提供一个无边界的字节流服务,并且使用并行无锁编程技术,即当它用于只有一个入队线程和一个出…