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

STM32启动分析之main函数是怎样跑起来的的更多相关文章

  1. SequoiaDB 系列之五 :源码分析之main函数

    好久好久没有写博客了,因为一直要做各种事,工作上的,生活上的,这一下就是半年. 时光如梭. 这两天回头看了看写的博客,感觉都是贻笑大方. 但是还是想坚持把SequoiaDB系列写完. 初步的打算已经确 ...

  2. main函数和启动例程

    为什么汇编程序的入口是_start,而C程序的入口是main函数呢?本节就来解释这个问题.在讲例 18.1 “最简单的汇编程序”时,我们的汇编和链接步骤是: $ as hello.s -o hello ...

  3. Linux0.11从开机到准备执行main函数的启动学习

    最近一直在看操作系统以及内核设计的东西,不确定自己有能力会参与到类似的开发之中,但是争取能自己改造这内核玩一下,然后按照Linux From Scratch那样的把改造后的系统编译运行就心满意足了.正 ...

  4. 第七章之main函数和启动例程

    main函数和启动例程 为什么汇编程序的入口是_start,而C程序的入口是main函数呢?本节就来解释这个问题.在讲例 18.1 “最简单的汇编程序”时,我们的汇编和链接步骤是: $ as hell ...

  5. [汇编与C语言关系]2. main函数与启动例程

    为什么汇编程序的入口是_start,而C程序的入口是main函数呢?以下就来解释这个问题 在<x86汇编程序基础(AT&T语法)>一文中我们汇编和链接的步骤是: $ as hell ...

  6. Springboot 应用启动分析

    https://blog.csdn.net/hengyunabc/article/details/50120001#comments 一,spring boot quick start 在spring ...

  7. main函数执行前后还会发生什么

    问题分析 首先main()函数只不过是提供了一个函数入口,在main()函数中的显示代码执行之前,会由编译器生成_main函数,其中会进行所有全局对象的构造以及初始化工作.简单来说对静态变量.全局变量 ...

  8. STM32启动文件详解及SystemInit函数分析(转)

    ;先在RAM中分配系统使用的栈,RAM的起始地址为0x2000_0000 ;然后在RAM中分配变量使用的堆 ;然后在CODE区(flash)分配中断向量表,flash的起始地址为0x0800_0000 ...

  9. STM32启动代码分析 IAR 比较好

    stm32启动代码分析 (2012-06-12 09:43:31) 转载▼     最近开始使用ST的stm32w108芯片(也是一款zigbee芯片).开始看他的启动代码看的晕晕呼呼呼的. 还好在c ...

  10. STM32启动过程--启动文件--分析

    一.概述 1.说明 每一款芯片的启动文件都值得去研究,因为它可是你的程序跑的最初一段路,不可以不知道.通过了解启动文件,我们可以体会到处理器的架构.指令集.中断向量安排等内容,是非常值得玩味的. ST ...

随机推荐

  1. Typora 基本功能

    Typora 基本功能 typora 下载官网:https://www.typora.io/       "安装到C盘" 基本使用 1.该文件后缀 .md2.六级标题 :ctrl+ ...

  2. 关于数据传递 json

    关于这几种语言的json 操作 Lua local cjson2 = require "cjson" local lua_object = { ["name"] ...

  3. MacOS 使用UnblockNeteaseMusic解锁网易云灰色歌曲(主要是想听杰伦)

    最近想听杰伦的音乐 但是网易云木有版权 于是在github上找到了UnblockNeteaseMusic这个项目 不多废话 直接上教程! 第一步 找到该项目的地址 并使用git克隆到本地: https ...

  4. RabbitMq安装配置启动

    RabbitMq安装配置启动 一:安装材料 请前往官方地址下载 Erlang: https://www.erlang.org/downloads rabbitmq: https://www.rabbi ...

  5. 入门文章学习(一)-Beginner Tutorial

    Abstract: 参照"背景知识查阅"一文的学习路径,对几篇文章的学习做了记录.这是"Beginner Tutorial"一文的学习笔记. 文章链接: htt ...

  6. circos plot in R

    FIN=read.table(IN,header=TRUE) circos.clear() circos.par("start.degree" = 90) circos.initi ...

  7. Linux远程挂载目录

    Linun远程挂载文件目录 需求:有两台服务器,一个是nginx,一台服务器是weblogic,图片上传到weblogic服务器上,但是需要nginx做代理,可是nginx访问不到weblogic服务 ...

  8. SQL中通过表字段名称查询对应表名称

    select * from sys.objects as a where a.object_id in(select [OBJECT_ID] from sys.all_columns where na ...

  9. MFC 错误调试总结

    1.  msdia80.dll can not be loaded 该文件的路径应该是:C:\Program Files\Common Files\microsoft shared\VC\msdia8 ...

  10. Java所用相关软件的大致安装流程

    JAVA下载流程 一.相关环境的安装与配置 1.JDK的下载 去官网搜索相应的java版本,并进行下载 官网链接:www.xfdown.com/soft/125774.html在该链接下,可以下载ja ...