STM32 _main 里做了什么

Application startup

在大多数嵌入式系统中,进入 main 函数之前需要执行一段初始化序列来设置好系统环境。下图展示的就是这段初始化序列的默认流程:

Figure 1. Default initialization sequence

__main is responsible for setting up the memory and __rt_entry is responsible for setting up the run-time environment.

__main performs code and data copying, decompression, and zero initialization of the ZI data. It then branches to __rt_entry to set up the stack and heap, initialize the library functions and static data, and call any top level C++ constructors. __rt_entry then branches to main(), the entry to your application. When the main application has finished executing, __rt_entry shuts down the library, then hands control back to the debugger.

The function label main() has a special significance. The presence of a main() function forces the linker to link in the initialization code in __main and __rt_entry. Without a function labeled main(), the initialization sequence is not linked in, and as a result, some standard C library functionality is not supported.

__main 负责设置好存储资源;__rt_entry 负责设置好运行时环境。这俩属于c library。

__main 首先复制代码和数据、复制或解压RW数据、初始化ZI数据为零。执行完毕后,流转到 __rt_entry ,它会设置应用程序的堆和栈、初始化库函数和静态数据,之后调用顶层 C++ 构造,进入到 main() 这里就是我们的应用程序。当 main() 应用程序执行完毕后,__rt_entry 将关闭库并将控制权交给调试器。

main() 函数这个函数标签具有特定的意义。main()函数标签的存在才会驱使连接器将 __main__rt_entry 链接进来,否则在没有 main() 函数这个标签的情况下,这个初始化序列( __main__rt_entry )将不会被链接进来,最终会导致一些标准 c 库的功能将不会被支持!

参考:Documentation – Arm Developer

STM32 _main 里做了什么

这里是对STM32f767的分析

编译之后的各种大小:

Program Size: Code=5190 RO-data=562 RW-data=16 ZI-data=1944

转换成hex也就是

Code=0x1446 RO-data=0x232 RW-data=0x10 ZI-data=0x798

下面的分析中,有可能遇到的各种赋值都与他们相关。

Simulator

要分析_main中做了什么需要,先使用调试模式进行调试。

这里直接使用keil的Simulator软模拟模式

  • 勾选LoadApplication at Starup
  • 不勾选 Run to main()

这样就可以直接运行到Reset Handle的地方,然后进入_main

开启调试就能看到下面的地方了,首先要进行SystemInit的初始化动作

 216:                  LDR     R0, =SystemInit
>0x08000298 4809      LDR           r0,[pc,#36]  ; @0x080002C0217:                  BLX     R00x0800029A 4780      BLX           r0218:                  LDR     R0, =__main0x0800029C 4809      LDR           r0,[pc,#36]  ; @0x080002C4219:                  BX      R0220:                  ENDP
__scatterload

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

0x080001F8 F000F802  BL.W          __scatterload (0x08000200)
0x080001FC F000F83C  BL.W          __rt_entry (0x08000278)
0x08000200 A00A      ADR           r0,{pc}+0x2C  ; @0x0800022C
0x08000202 E8900C00  LDM           r0,{r10-r11}
0x08000206 4482      ADD           r10,r10,r0
0x08000208 4483      ADD           r11,r11,r0
0x0800020A F1AA0701  SUB           r7,r10,#0x01
0x0800020E 45DA      CMP           r10,r11
0x08000210 D101      BNE           0x08000216

跳转到__scatterload (0x08000200)

ADR,adr是小范围的地址读取伪指令,实际上adr是将基于PC相对偏移的地址值或基于寄存器相对地址值读取的为指令

LDM,Load from memory into register,批量加载内存到寄存器,指令运行的方向和LDR是不一样的,是从左到右运行的。该指令是将内存中堆栈内的数据,批量的赋值给寄存器,即是出栈操作;其中堆栈指针一般对应于SP。

在这里则是把r0的内容内容给了r10和r11

r10+=r0

r11+=r0

r7=r10-1

r10-r11

BNE 是不相等跳转

所以如果r10和r11不相等就跳转到0x08000216

这里是不相等的,所以跳到了0x08000216

0x08000212 F000F831  BL.W          __rt_entry (0x08000278)
0x08000216 F2AF0E09  ADR.W         lr,{pc}-0x07  ; @0x0800020F
0x0800021A E8BA000F  LDM           r10!,{r0-r3}
0x0800021E F0130F01  TST           r3,#0x01
0x08000222 BF18      IT            NE
0x08000224 1AFB      SUBNE         r3,r7,r3
0x08000226 F0430301  ORR           r3,r3,#0x01
0x0800022A 4718      BX            r3

首先是把0x0800020F存到了lr中,后面需要回到上面的cmp时候进行跳转。

r10! 这个!表示写回到r10中

TST,测试某一个位是否为0

这里是测试r3的第一位是否为0

IT,用于根据特定条件来执行紧随其后的1~4条指令,NE表示不等于

TST与IT连用也就是检测r3第一位是否为0 为0则执行接下来的1条指令

SUBNE,条件执行减法运算(NE),就是不等于0的情况下执行

ORR,或指令

r3=0x01,然后跳转到r3地址
__scatterload_copy
0x08000234 3A10      SUBS          r2,r2,#0x10
0x08000236 BF24      ITT           CS
0x08000238 C878      LDMCS         r0!,{r3-r6}
0x0800023A C178      STMCS         r1!,{r3-r6}
0x0800023C D8FA      BHI           __scatterload_copy (0x08000234)
0x0800023E 0752      LSLS          r2,r2,#29
0x08000240 BF24      ITT           CS
0x08000242 C830      LDMCS         r0!,{r4-r5}
0x08000244 C130      STMCS         r1!,{r4-r5}
0x08000246 BF44      ITT           MI
0x08000248 6804      LDRMI         r4,[r0,#0x00]
0x0800024A 600C      STRMI         r4,[r1,#0x00]
0x0800024C 4770      BX            lr

可以分析一下里面的内容,R0就是程序加载视图的RW区的起始地址(0x08002de0),R1就是要输出的执行视图的RW区的地址(0x20000000),R2就是要复制的RW数据的个数,R3是复制函数(__scatterload_copy)的地址。

r2-=0x10

ITT则是根据C位的情况来执行下面的两条指令

其实就开始了循环

BHI,是无符号数大于跳转到后面的地址

这里不大于所以没有跳转

一直执行就会跳回到前面的lr,然后继续走到下面这个位置

0x08000250 2300      MOVS          r3,#0x00
0x08000252 2400      MOVS          r4,#0x00
0x08000254 2500      MOVS          r5,#0x00
0x08000256 2600      MOVS          r6,#0x00
0x08000258 3A10      SUBS          r2,r2,#0x10
0x0800025A BF28      IT            CS
0x0800025C C178      STMCS         r1!,{r3-r6}
0x0800025E D8FB      BHI           0x08000258
0x08000260 0752      LSLS          r2,r2,#29
0x08000262 BF28      IT            CS
0x08000264 C130      STMCS         r1!,{r4-r5}
0x08000266 BF48      IT            MI
0x08000268 600B      STRMI         r3,[r1,#0x00]
0x0800026A 4770      BX            lr

清空了r3,r4,r5,r6,然后r2此时等于0x798 r1=0x20020010,这个和我当前工程有关

r2-=16

接着就是循环递减r2,把就是每次16字节的把r1的内存地址全部置为0

这里其实是ZI段清零的操作

清零完成以后,跳回到之前保存的lr cmp的位置上

__rt_entry()

然后就进入了__rt_entry

0x0800020E 45DA      CMP           r10,r11
0x08000210 D101      BNE           0x08000216
0x08000212 F000F831  BL.W          __rt_entry (0x08000278)
__user_setup_stackheap
0x08000278 F000F833  BL.W          __user_setup_stackheap (0x080002E2)
0x0800027C 4611      MOV           r1,r2

跳转到0x080002E2 设置用户堆栈

0x080002E2 4675      MOV           r5,lr
0x080002E4 F000F82C  BL.W          __user_libspace (0x08000340)0x08000340 4800      LDR           r0,[pc,#0]  ; @0x08000344
0x08000342 4770      BX            lr

继续运行,然后初始化堆栈

0x080002E8 46AE      MOV           lr,r5
0x080002EA 0005      MOVS          r5,r0
0x080002EC 4669      MOV           r1,sp
0x080002EE 4653      MOV           r3,r10
0x080002F0 F0200007  BIC           r0,r0,#0x07
0x080002F4 4685      MOV           sp,r0
0x080002F6 B018      ADD           sp,sp,#0x60
0x080002F8 B520      PUSH          {r5,lr}
0x080002FA F7FFFFDB  BL.W          __user_initial_stackheap (0x080002B4)
__user_initial_stackheap
__user_initial_stackheap:
0x080002B4 4804      LDR           r0,[pc,#16]  ; @0x080002C8
0x080002B6 4905      LDR           r1,[pc,#20]  ; @0x080002CC
0x080002B8 4A05      LDR           r2,[pc,#20]  ; @0x080002D0
0x080002BA 4B06      LDR           r3,[pc,#24]  ; @0x080002D4
0x080002BC 4770      BX            lr

完成以后跳回之前的位置继续执行。

0x080002FE E8BD4020  POP           {r5,lr}
0x08000302 F04F0600  MOV           r6,#0x00
0x08000306 F04F0700  MOV           r7,#0x00
0x0800030A F04F0800  MOV           r8,#0x00
0x0800030E F04F0B00  MOV           r11,#0x00
0x08000312 F0210107  BIC           r1,r1,#0x07
0x08000316 46AC      MOV           r12,r5
0x08000318 E8AC09C0  STM           r12!,{r6-r8,r11}
0x0800031C E8AC09C0  STM           r12!,{r6-r8,r11}
0x08000320 E8AC09C0  STM           r12!,{r6-r8,r11}
0x08000324 E8AC09C0  STM           r12!,{r6-r8,r11}
0x08000328 468D      MOV           sp,r1
0x0800032A 4770      BX            lr

回到入口位置

__rt_lib_init
0x0800027C 4611      MOV           r1,r2exit:
0x0800027E F7FFFFF5  BL.W          __rt_lib_init (0x0800026C)

__rt_lib_init 初始化

                __rt_lib_init:
0x0800026C B51F      PUSH          {r0-r4,lr}__rt_lib_init_fp_1:
0x0800026E F001F9E1  BL.W          _fp_init (0x08001634)
...0x08001634 F04F7040  MOV           r0,#0x30000000x08001638 EEE10A10  VMSR           FPSCR, r00x0800163C 4770      BX            lr
...__rt_lib_init_alloca_1:
0x08000272 BD1F      POP           {r0-r4,pc}
进入main函数
                __re_entry_main:
0x08000282 F001F98F  BL.W          main (0x080015A4)
...
0x080015A4 B086      SUB           sp,sp,#0x18ial_stackheap
...

从这里往后就全是main函数了

总结

启动文件的整个过程,分为如下:

  1. 系统初始化,包括对中断向量表的
  2. 加载 RW 段
  3. ZI 段清零
  4. 初始化用户堆
  5. 初始化微库
  6. 调用 main 函数。

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

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

相关文章

Java八股文——MySQL「SQL 基础篇」

NOSQL和SQL的区别? 面试官您好,SQL(关系型数据库)和NoSQL(非关系型数据库)是当今数据存储领域的两大主流阵营。它们之间不是“谁取代谁”的关系,而是两种完全不同的设计哲学,适用于…

华为OD机考-数字螺旋矩阵(JAVA 2025B卷)

public class RotateMatrix {public static void main(String[] args) {// 顺时针螺旋矩阵printMatrixV1();// 逆时针螺旋矩阵//printMatrixV2();}private static void printMatrixV2() {Scanner scan new Scanner(System.in);while(scan.hasNextLine()){String[] line scan.…

【Java工程师面试全攻略】Day7:分布式系统设计面试精要

一、分布式系统概述 分布式系统已成为现代互联网应用的标配架构,据LinkedIn统计,分布式系统设计能力是高级Java工程师薪资差异的关键因素。今天我们将深入解析分布式系统的核心理论和实践,帮助你掌握面试中的系统设计问题。 二、分布式理论…

Excel处理控件Aspose.Cells教程:在Excel 文件中创建、操作和渲染时间线

您可以使用数据透视表时间轴,而无需调整过滤器来显示日期——这是一种动态过滤器选项,可让您轻松按日期/时间进行过滤,并使用滑块控件放大所需的时间段。Microsoft Excel 允许您通过选择数据透视表,然后单击“插入”>“时间轴”…

Python----神经网络发(神经网络发展历程)

年份网络名称突出点主要成就论文地址1989LeNet首个现代卷积神经网络(CNN),引入卷积、池化操作手写数字识别先驱,奠定CNN基础MNIST Demos on Yann LeCuns website2012AlexNet首次大规模使用深度卷积神经网络进行图像识别&#xff1…

mvc与mvp

mvc MVC 架构中,Activity/Fragment(作为 View 和 Controller)直接持有 Model 或异步任务的引用,当页面销毁时,这些长生命周期对象若未正确释放,会导致 Activity 无法被 GC 回收,形成内存泄漏。…

商业智能中的地图可视化模板:助力数据高效呈现

引言 在数字化浪潮席卷的当下,数据可视化的重要性愈发凸显。企业和组织需要从海量的数据中提取有价值的信息,以便做出明智的决策。而可视化地图组件作为数据可视化的关键部分,能够将数据与地理位置相结合,以直观、美观的方式展示…

Opencv 相机标定相关API及原理介绍

Opencv 相机标定相关API及原理介绍 相机标定是计算机视觉中的基础任务,旨在确定相机的​​内参矩阵​​、​​畸变系数​​以及(可选)​​外参​​(相机相对于世界坐标系的旋转和平移)。OpenCV提供了完整的相机标定工具链,核心函数为cv2.calibrateCamera,其原理基于张正…

深入剖析AI大模型:Prompt 从理论框架到复杂任务的全场景实现

今天我们就Prompt实战,实现一下复杂场景,通过这些实战我们就可以更好的理解大模型工作的原理和机制了。我个人觉得Prompt是AI大模型中非常重要的的环节。首先我们还是温习一下Prompt的框架和基础原则。然后我们就文本生成、问答任务及复杂任务三个方面分…

Fractal Generative Models论文阅读笔记与代码分析

何恺明分型模型这篇文章在二月底上传到arXiv预出版网站到现在已经过了三个月,当时我也听说这篇文章时感觉是大有可为,但是几个月不知道忙啥了,可能错过很多机会,但是亡羊补牢嘛,而且截至目前,该文章应该也还…

IntelliJ IDEA代码提示忽略大小写设置详解

目录 前言一、设置步骤1. 打开设置界面2. 进入代码补全设置3. 配置大小写敏感选项新版本(2023及以上)旧版本(2022及以下) 4. 保存并应用设置 二、效果验证示例三、注意事项与常见问题1. **适用范围**2. **版本兼容性**3. **设置未…

Oracle集群OCR磁盘组掉盘问题处理

问题描述 填写问题的基础信息。 系统名称 - IP地址 - 操作系统 HP-UNIX 数据库 Oracle 11.2.0.4 两节点RAC 症状表现 问题的症状表现如下 集群的OCR磁盘组掉了一块盘(/dev/rdisk/disk52): 查询集群仲裁盘发现只有两块(原来是有三块)&#xff…

在WordPress中彻底关闭生成缩略图的方法

在WordPress中彻底关闭生成缩略图有多种方法,以下是几种常见的方法: 方法一:通过修改主题的functions.php文件 登录WordPress后台:进入WordPress后台管理界面。 编辑主题文件: 在左侧菜单中找到“外观”选项&#…

安全-Linux基线核查项点

Linux基线加固/整改 1.限制超级管理员远程登录 修改远程管理程序ssh的配置文件 vi /etc/ssh/sshd_config PermitRootLogin no 重启sshd服务 systemctl restart sshd 2. 修改默认密码生存周期 一个好的密码时间策略如下: vi /etc/login.defs PASS_MAX_DAY 90 最长…

在微信小程序中使用骨架屏

在微信小程序中使用骨架屏可以优化用户体验,避免页面加载时出现白屏现象。以下是详细的使用方法和注意事项: 使用方法 生成骨架屏代码: 打开微信开发者工具,进入需要添加骨架屏的页面。在模拟器面板右下角点击三个点&#xff0c…

网络的那些事——初级——OSPF(1)

💎什么是OSPF? OSPF(Open Shortest Path First,开放最短路径优先)是一种基于链路状态的内部网关协议(IGP),广泛应用于中大型企业及运营商网络。其核心设计目标是解决早期协议(如RI…

前端导出PDF(适配ios Safari浏览器)

目前市面上常用的前端导出PDF库组合一般为: 1. html2canvas js-pdf 2. html2canvaspdf-lib 3. domtoimagepdf-lib 因本人项目中导出pdf需求为导出30页及以上的多页pdf,考虑性能问题,选择了 html2canvaspdf-lib 及domtoimagepdf-lib两种方…

physicsnemo开源程序是开源深度学习框架,用于使用最先进的 Physics-ML 方法构建、训练和微调深度学习模型

​一、软件介绍 文末提供程序和源码下载 NVIDIA PhysicsNeMo 是一个开源深度学习框架,用于使用最先进的 SciML 方法构建、训练、微调和推理物理 AI 模型,以实现 AI4 科学和工程。PhysicsNeMo 提供 python 模块来构建可扩展和优化的训练和推理管道&#…

JDBC接口开发指南

1.简介 JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具…

Shell 脚本:系统管理与任务自动化的利器

在开发者忙碌的日常工作中,效率就是生命线。当面对大量重复、繁琐的系统管理任务与开发流程时,一款得力的编程工具能让工作事半功倍。Shell 脚本,这把在 Linux 和 Unix 系统环境下闪耀着光芒的利器,凭借其强大的自动化能力&#x…