MCU启动文件小解一下

GD32启动文件分析

GD32启动文件主要做了以下工作:
初始化SP=_initial_sp , PC=Reset_Handler指针,设置堆栈大小,初始化中断向量表,调用SystemInit,_main,最终调用到main函数。

启动文件的一些指令

指令名称 作用
EQU 给数字常量取一个符号名,相当于 C 语言中的 define
AREA 汇编一个新的代码段或者数据段
ALIGN 编译器对指令或者数据的存放地址进行对齐,一般需要跟一个立即数,缺省表示 4 字节对齐。要注意的是,这个不是 ARM 的指令,是编译器的,这里放到一起为了方便。
SPACE 分配内存空间
PRESERVE8 当前文件堆栈需要按照 8 字节对齐
THUMB 表示后面指令兼容 THUMB 指令。在 ARM 以前的指令集中有 16 位的THUMBM 指令,现在 Cortex-M 系列使用的都是 THUMB-2 指令集,THUMB-2 是 32 位的,兼容 16 位和 32 位的指令,是 THUMB 的超级版。
EXPORT 声明一个标号具有全局属性,可被外部的文件使用
DCD 以字节为单位分配内存,要求 4 字节对齐,并要求初始化这些内存
PROC 定义子程序,与 ENDP 成对使用,表示子程序结束
WEAK 弱定义,如果外部文件声明了一个标号,则优先使用外部文件定义的标号,如果外部文件没有定义也不会出错。要注意的是,这个不是 ARM 的指令,是编译器的,这里放到一起为了方便。
IMPORT 声明标号来自外部文件,跟 C 语言中的 extern 关键字类似
LDR 从存储器中加载字到一个存储器中
BLX 跳转到由寄存器给出的地址,并根据寄存器的 LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到 LR
BX 跳转到由寄存器/标号给出的地址,不用返回
B 跳转到一个标号
IF,ELSE,ENDIF 汇编条件分支语句,跟 C 语言的类似
END 到达文件的末尾,文件结束

主要是一些arm指令,ARM指令还能够从keil的IDE ->help 里找到

.s启动文件分析

栈空间分配

EQU :宏定义的伪指令,给数字常量取一个符号名,类似与 C 中的 define。定义栈大小为 0x00000400 字节,即 1024B(1KB),常量的符号是 Stack_Size。
AREA : 汇编一个新的代码段或者数据段。段名为 STACK,段名可以任意命名;
NOINIT 表示不初始化; READWRITE 表示可读可写;ALIGN=3,表示按照 2^3 对齐,

即 8 字节对齐。
SPACE 分配内存指令,分配大小为 Stack_Size 字节连续的存储单元给栈空间。
__initial_sp 紧挨着 SPACE 放置,表示栈的结束地址,栈是从高往低生长,所以结束地址就是栈顶地址。

通过map文件查看栈的initial_sp可以看到恰好栈由栈顶高地址到低地址是0x400

堆空间管理

开辟堆的大小

为 0x00000000(0 字节),段名为 HEAP,不初始化,可读可写,8 字节对齐。__heap_base表示堆的起始地址,__heap_limit 表示堆的结束地址。堆和栈的生长方向相反的,堆是由低向高生长,而栈是从高往低生长。

这里我这只为0,因为我用了freertos里heap的方式进行内存管理,就不用系统默认的堆了,因为malloc缺点比较多,容易造成较大的内存碎片。

堆可以用于动态内存分配,比如通过malloc()、calloc()和 realloc()等函数
malloc() 函数在堆上分配参数size字节大小的内存,并返回指向新分配内存起始位置处的指针,其所分配的内存未经初始化。
calloc() 函数用于给一组相同对象分配内存。参数mumitems指定分配对象的数量,size指定每个对象的大小与malloc()不同,calloc()会将已分配的内存初始化为0。
realloc() 函数用来调整(通常是增加)一块内存的大小

PRESERVE8 :指示编译器按照 8 字节对齐。
THUMB:指示编译器之后的指令为 THUMB 指令。

中断向量表定义

定义一个数据段,名字为 RESET, READONLY 表示只读。EXPORT 表示声明一个标号

具有全局属性,可被外部的文件使用。这里是声明了__Vectors、__Vectors_End 和__Vectors_Size 三个标号具有全局性,可被外部的文件使用。

当内核响应了一个发生的异常后,对应的异常服务例程(ESR)就会执行。为了决定 ESR的入口地址, 内核使用了向量表查表机制。向量表其实是一个 WORD(32 位整数)数组。

每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。

在复位后,该寄存器的值为 0。因此,在地址 0 (即 FLASH 地址 0) 处必须包含一张向量表,用于初始时的异常分配。

当然,我们如果说有bootloader的话,我们的起始地址将不会在0,这时候就需要通过 NVIC 中的一个重定位寄存器来指出向量表的地址。

举个例子,如果发生了异常 SVCall,则 NVIC 会计算出偏移移量是 11x4=0x2C,然后从那里取出服务例程的入口地址并跳入。要注意的是这里有个另类:地址 0x0000 0000 并不是什么入口地址,而是给出了复位后 MSP 的初值。

灰色部分是系统内核中断,后面就是用户的中断。

堆空间定义

接下来是定义只读代码段,定义一个段命为.text,只读的代码段,在 CODE 区。

Reset_Handler复位程序

PROC、ENDP 一对伪指令作为程序的起始和结束。

通过EXPORT 和 week 来声明其全局属性,同时将其声明为弱函数,外部可以定义覆盖。

IMPORT 表示SystemInit和__main来自外部文件。

LDR表示将Systeminit和_main存储到寄存器R0中。

BLX 表示跳转到由寄存器给出的地址,并根据寄存器的 LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到 LR。

BX 表示跳转到由寄存器/标号给出的地址,不用返回。这里表示切换到__main地址,最终调用 main 函数,不返回,进入 C 的世界。

HardFault_Handler

若定义了HardFault_Handler,则执行我们自己的HardFault_Handler,若没有定义,则运行B . 这条汇编,进行原地跳转。

其他的中断也是如下。

_main文件分析

当看到__main 函数时,估计有不少人认为这个是 main 函数的别名或是编译之后的名字,

否则在启动代码中再也无法找到和 main 相关的字眼了。可事实是,_main 和 main 是两个完全不同的函数。_main 代码是编译器自动创建的,因此无法找到_main 代码。MDK 文档中有一句说明:it is automatically craated by the linker when it sees a definition of main() 。大体意

思可以理解为:当编译器发现定义了 main 函数,那么就会自动创建_main。

程序经过汇编启动代码,执行到__main()后,可以看出有两个大的函数:

__scatterload():负责把 RW/RO 输出段从装载域地址复制到运行域地址,并完成了 ZI运行域的初始化工作。

1._scatterload_null :判断r10和r11是否相等,不相等就跳到_scatterload_copy

2._scatterload_copy 复制好之后,在会到_scatterload_null判断是否相等,相等后进入__scatterload_zeroinit

3.__scatterload_zeroinit :对ZI段进行清零,最后跳回__scatterload

然后跳转到__rt_entry()

__rt_entry() :负责初始化堆栈,完成库函数的初始化,最后自动跳转向 main()函数。

先调用 __user_setup_stackheap 函数,然后调用__user_libspace进行一些微库的初始化,最后到__user_setup_stackheap初始化堆栈,调用__rt _entry_main最后回到main。

用户堆栈初始化

ALIGN 表示对指令或者数据的存放地址进行对齐,一般需要跟一个立即数,缺省表示4 字节对齐。

首先判断是否定义了__MICROLIB。关于__MICROLIB 这个宏定义,我们是在 KEIL里面配置。

如果定义__MICROLIB,声明__initial_sp、__heap_base 和__heap_limit

这三个标号具有全局属性,可被外部的文件使用。__initial_sp 表示栈顶地址,__heap_base表示堆起始地址,__heap_limit 表示堆结束地址。

没有定义__MICROLIB,实际的情况就是我们没有定义__MICROLIB,所以使用默认的 C 库运行。堆栈的初始化由 C 库函数__main 来完成。

IMPORT 声明__use_two_region_memory 标号来自外部文件。

EXPORT 声明__user_initial_stackheap 具有全局属性,可被外部的文件使用。

标号__user_initial_stackheap,表示用户堆栈初始化程序入口。

接下来进行堆栈空间初始化,堆是从低到高生长,栈是从高到低生长,是两个互相独立的数据段,并且不能交叉使用。

保存堆起始地址。

保存栈大小。

保存堆大小。

保存栈顶指针。

跳转到 LR 标号给出的地址,不用返回。

END 表示到达文件的末尾,文件结束。

相关推荐
唐诺1 小时前
几种广泛使用的 C++ 编译器
c++·编译器
XH华2 小时前
初识C语言之二维数组(下)
c语言·算法
jinhuazhe20132 小时前
如何解决vscode powershell乱码
ide·vscode·编辑器
冷眼看人间恩怨2 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客3 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
析木不会编程3 小时前
【小白51单片机专用教程】protues仿真独立按键控制LED
单片机·嵌入式硬件·51单片机
Lenyiin3 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
yuanbenshidiaos4 小时前
c++---------数据类型
java·jvm·c++
十年一梦实验室5 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
taoyong0015 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法