当前位置: 首页 > news >正文

MCU启动文件小解一下

GD32启动文件分析

  • 启动文件的一些指令
  • .s启动文件分析
    • 栈空间分配
    • 堆空间管理
    • 中断向量表定义
    • 堆空间定义
    • Reset_Handler复位程序
    • HardFault_Handler
    • _main文件分析
    • 用户堆栈初始化

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 表示到达文件的末尾,文件结束。
在这里插入图片描述

相关文章:

  • chromedriver安装和环境变量配置
  • 2023美赛A题之Lotka-Volterra【完整思路+代码】
  • C#,欧拉数(Eulerian Number)的算法与源代码
  • 系统架构设计师-22年-下午题目
  • JVM篇----第十七篇
  • iOS17使用safari调试wkwebview
  • 中国的茶文化:历史、传统与生活
  • js实现动漫拼图2.0版
  • C# Onnx yolov8 水表读数检测
  • java servlet勤工助学家教管系统Myeclipse开发mysql数据库web结构java编程计算机网页项目
  • pytorch-metric-learning度量学习工具官方文档翻译
  • 数据库(SQL)
  • Qt知识点总结
  • JAVA线程执行中断方式和ElasticSearch未捕获异常的处理方式
  • Meta开源Code Llama 70B,缩小与GPT-4之间的技术鸿沟
  • [deviceone开发]-do_Webview的基本示例
  • 【399天】跃迁之路——程序员高效学习方法论探索系列(实验阶段156-2018.03.11)...
  • android 一些 utils
  • HTTP中的ETag在移动客户端的应用
  • Mac转Windows的拯救指南
  • Sublime Text 2/3 绑定Eclipse快捷键
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • Vue 重置组件到初始状态
  • weex踩坑之旅第一弹 ~ 搭建具有入口文件的weex脚手架
  • 关于Java中分层中遇到的一些问题
  • 罗辑思维在全链路压测方面的实践和工作笔记
  • 浅谈JavaScript的面向对象和它的封装、继承、多态
  • 让你成为前端,后端或全栈开发程序员的进阶指南,一门学到老的技术
  • 如何合理的规划jvm性能调优
  • 如何借助 NoSQL 提高 JPA 应用性能
  • 实现简单的正则表达式引擎
  • 使用 Xcode 的 Target 区分开发和生产环境
  • 问:在指定的JSON数据中(最外层是数组)根据指定条件拿到匹配到的结果
  • 小程序、APP Store 需要的 SSL 证书是个什么东西?
  • 阿里云重庆大学大数据训练营落地分享
  • 如何在 Intellij IDEA 更高效地将应用部署到容器服务 Kubernetes ...
  • 曜石科技宣布获得千万级天使轮投资,全方面布局电竞产业链 ...
  • ​Spring Boot 分片上传文件
  • ​业务双活的数据切换思路设计(下)
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • (12)Linux 常见的三种进程状态
  • (顶刊)一个基于分类代理模型的超多目标优化算法
  • (附源码)springboot 校园学生兼职系统 毕业设计 742122
  • (生成器)yield与(迭代器)generator
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • (转)Linux整合apache和tomcat构建Web服务器
  • **CI中自动类加载的用法总结
  • *_zh_CN.properties 国际化资源文件 struts 防乱码等
  • .net refrector
  • .net 后台导出excel ,word
  • .NET精简框架的“无法找到资源程序集”异常释疑
  • ::什么意思
  • @RequestBody详解:用于获取请求体中的Json格式参数
  • [ 英语 ] 马斯克抱水槽“入主”推特总部中那句 Let that sink in 到底是什么梗?
  • [20170728]oracle保留字.txt