STM32启动过程及启动模式详解

一. MDK 编译生成文件简介

MDK 编译 STM32 工程时,会在我是HAL库生成的路径大概是MDK-ARM\01_LED在这个文件夹,这是我之间一个freertos的代码,主要实现一个智能旅行箱。这里生成一堆中间文件,最终才生成能下载进单片机的 .hex

大概流程是:

  • 编译 :先把 .c/.s 变成 .o
  • 链接 : 把所有 .o 合成 .axf
  • 转换 :把 .axf 转成 .hex 供下载

对于MDK工程来说,基本上任何工程在编译过程中都会有这11类文件,常见的MDK 编译过程生产文件类型如表1.1所示:

文件类型作用(小白版)
.o每个 C / 汇编文件编译后的中间对象文件
.axf最终可调试、可仿真的完整程序文件
.hex能直接下载到 STM32 的固件文件
.crf记录函数 / 变量在哪里被引用
.d记录每个 .o 依赖哪些文件
.dep整个工程的总依赖文件
.lnp给链接器用的命令输入文件
.lst编译器生成的汇编列表文件
.htm查看栈深度、函数调用关系
.build_log.htm最近一次编译的日志
.map查看内存占用、函数地址、代码大小

想知道其他文件类型及说明,请大家参考:

二、.htm文件解析

1、基本文件构成

组成部分核心含义工程作用
最大栈使用量静态分析得出的最小必需栈大小确定栈空间最低配置值
最大栈深调用链栈消耗最高的函数调用路径定位栈开销大户、优化方向
互斥递归函数自递归 / 相互递归的函数排查死循环、栈溢出风险
函数指针引用被向量表 / 指针调用的函数验证中断、回调函数绑定
全局符号栈信息全局函数的栈大小、调用关系查看业务函数栈占用
本地符号栈信息静态函数的栈大小、调用关系查看内部函数栈占用

2、最大栈深和最大栈深调用链

点开.htm这个文件可知静态可追踪的最大栈深。主栈(MSP)、任务栈(PSP)配置不能低于这个值。

最大栈深调用链:

StartDefaultTask ⇒ parseGpsBuffer ⇒ atof ⇒ __strtod_int ⇒ _local_sscanf ⇒ _scanf_real

什么是最大栈深调用链,这是编译器逐行遍历代码,找到的函数嵌套最深的一条路;函数每嵌套一层,就会在栈(Stack) 里存数据(返回地址、局部变量等)。

找到startup_stm32f103xb.s文件,打开可知默认栈深 (Stack_Size) = 0x400 字节,0x400 = 1024 字节 = 1KB,计算为4*16^2=1024

3、全局符号栈信息和本地符号栈信息

Global Symbols(全局符号栈信息)代表片段

parseGpsBuffer (Thumb, 284 bytes, Stack size 32 bytes, gps.o(i.parseGpsBuffer))
[Stack]
Max Depth = 256
Call Chain = parseGpsBuffer ⇒ atof ⇒ __strtod_int ⇒ _local_sscanf ⇒ _scanf_real
[Calls]
>>   __aeabi_d2f
>>   strchr
>>   strncpy
>>   atof
[Called By]
>>   StartDefaultTask
>>   USART1_IRQHandler

Local Symbols(本地符号栈信息)代表片段

prvCopyDataFromQueue (Thumb, 38 bytes, Stack size 8 bytes, queue.o(i.prvCopyDataFromQueue))
[Stack]
Max Depth = 8
Call Chain = prvCopyDataFromQueue
[Calls]
>>   __aeabi_memcpy
[Called By]
>>   xQueueReceive

格式如下:

字段含义示例
函数名符号名称Get_Weight, prvInitialiseNewTask
Thumb指令集模式(Thumb/ARM)(Thumb, 122 bytes)
字节数函数代码占用空间122 bytes
Stack size函数自身栈占用24 bytes
所属文件定义该符号的目标文件hx711.o(i.Get_Weight)
[Stack]栈深度与调用链Max Depth = 112
Call Chain函数调用路径Get_Weight ⇒ __aeabi_dmul ⇒ ...
[Calls]该函数调用的其他函数>> __aeabi_f2d, >> Read_HX711
[Called By]调用该函数的其他函数>> StartTask03
[Address Reference Count]地址引用次数[Address Reference Count : 1]

学习这个 .htm ,能快速查到最大栈深与调用链,安全配置栈大小、避免栈溢出死机;还能定位递归、自调用等风险代码,快速排查 HardFault 等死机问题;同时可看清每个函数的栈占用与调用关系,精准优化内存与代码体积。

三、.map文件解析

.map 文件是编译器链接时生成的一个文件,它主要包含了交叉链接信息。通过.map 文件,我们可以知道整个工程的函数调用关系、FLASH 和 RAM 占用情况及其详细汇总信息,能具体到单个源文件(.c/.s)的占用情况,根据这些信息,我们可以对代码进行优化。.map 文件可以分为以下 5 个组成部分:

组成部分简介
程序段交叉引用关系描述各文件之间函数调用关系
删除映像未使用的程序段描述工程中未用到而被删除的冗余程序段 (函数 / 数据)
映像符号表描述各符号(程序段 / 数据)在存储器中的地址、类型、
映像内存分布图描述各个程序段(函数)在存储器中的地址及占用大小
映像组件大小给出整个映像代码(.o)占用空间信息

1、map 文件的 MDK 设置

在模式棒找到如下配置,默认情况下,MDK 这部分设置就是 全勾选的,如果我们想取消掉一些信息的输出,则取消相关勾选即可(一般不建议)。

编译后双击工程即可打开,

若不能打开,打开魔术棒找到listing电机Select Folder for Listings在当前工程新建一个Listings文件夹,然后选中就可以重复上一步操作。

2、 map 文件的基础概念

为了更好的分析 map 文件,我们先对需要用到的一些基础概念进行一个简单介绍,相关概念如下:

  • Section:描述映像文件的代码或数据块,我们简称程序段
  • RO:Read Only 的缩写,包括只读数据(RO data)和代码(RO code)两部分内容,占用 FLASH 空间
  • RW:Read Write 的缩写,包含可读写数据(RW data,有初值,且不为 0),占用 FLASH(存储初值)和 RAM(读写操作)
  • ZI:Zero initialized 的缩写,包含初始化为 0 的数据(ZI data),占用 RAM 空间。
  • .text:相当于 RO code
  • .constdata:相当于 RO data
  • .bss:相当于 ZI data
  • .data:相当于 RW data

3、map 文件的组成部分说明

1、程序段交叉引用关系(Section Cross References)

这是 .map 文件里的 程序段交叉引用 (Section Cross References),是链接器记录的符号依赖关系,不只是函数调用,还包括函数引用全局变量、启动文件引用中断向量,是编译器把所有文件链接成可执行文件时,标记谁需要用到谁的清单。

这一段main函数主要意思是,完成了 HAL 库初始化、系统时钟配置、GPIO / 串口 / 定时器等外设初始化、硬件驱动初始化,最终实现 FreeRTOS 内核的初始化与调度启动,同时引用了定时器、串口的全局变量,同时主要有相互引用的关系,如:freertos.o(i.MX_FREERTOS_Init) refers to freertos.o(i.StartDefaultTask) for StartDefaultTasky意思是目标文件 freertos.o 内的 MX_FREERTOS_Init 函数段,引用并调用了该目标文件中的 StartDefaultTask 任务函数符号。

2、删除映像未使用的程序段

搜索:Removing Unused input sections from the image.

这部分内容描述了工程中由于未被调用而被删除的冗余程序段(函数/数据)。

上图中,列出了所有被移除的程序段。最下面统计出625 unused section(s) (total 43669 bytes) removed from the image.即一共清理了 625 处、约 42KB 的无用内容。

精准地删掉没用到的函数

打开魔术棒

勾选后One ELF Section per Function后,每个函数都在自己的段里。链接器扫描时,如果发现某个段里的函数没有被任何地方调用,就可以直接把这个段整个删掉,只保留用到的代码,没用的函数全被清掉,固件体积大幅减小。

4、映像符号表

映像符号表(Image Symbol Table)描述了被引用的各个符号(程序段/数据)在存储器 中的存储地址、类型、大小等信息。映像符号表分为两类:本地符号(Local Symbols)和全 局符号(Global Symbols)。

(1)本地符号

本地符号(Local Symbols)记录了用static声明的全局变量地址和大小,c文件中函数的地址和用 static 声明的函数代码大小,汇编文件中的标号地址,作用域:限本文件。

任意举一个例子:i.Get_Weight 函数

i.Get_Weight                             0x08000ef4   Section        0  hx711.o(i.Get_Weight)

表示hx711文件中的 Get_Weight函数的入口地址为:0x08000fe,类型为:Section(程序段),大小为0。因为:i. Get_Weight仅仅表示Get_Weight函数入口地址,并不是指令,所以没有大小。在全局符号段,会列出 Get_Weight函数的大小。

(2)全局符号

全局符号(Global Symbols)记录了全局变量的地址和大小,C文件中函数的地址及其 代码大小,汇编文件中的标号地址(作用域:全工程)。

找到Get_Weight函数,表示 hx711.c 文件中的 Get_Weight函数的入口地址为: 0x08000ef5,类型为:Thumb Code(程序段),大小为122字节。

  Get_Weight                               0x08000ef5   Thumb Code   122  hx711.o(i.Get_Weight)

可以发现Get_Weight地址不一样,这是因为 ARM规定Thumb指令集的所有指令,其最低位必须为0x08000ef5= 0x08000ef4+ 1, 所以才会有2个不同的地址,且总是差1,实际上就是同一个函数。

5、映像内存分布图

映像文件分为加载域(Load Region)和运行域(Execution Region),一个加载域必须有 至少一个运行域(可以有多个运行域),而一个程序又可以有多个加载域。加载域为映像程序的实际存储区域,而运行域则是MCU上电后的运行状态。加载域和运行域的简化关系(这里仅表示一个加载域的情况)。

RW区也是存放在ROM(FLASH)里面的,在执行main函数之前,RW(有初值且不为0的变量)数据会被拷贝到RAM区,同时还会在RAM里面创建ZI区(初始化为0的变量)。

6、映像组件大小

映像组件大小(Image component sizes)给出了整个映像所有代码(.o)占用空间的汇总信息,这是用得最多的。

这部分记录工程内所有目标文件(.o)、驱动库、启动代码、自定义函数与全局变量的分配信息,精准标注各程序段、数据段的物理存储地址、字节占用容量及所属源文件;同时汇总生成映像组件占用统计,统计固件整体 Flash、RAM 资源消耗。

其中FLASH放的是Code、RO Date以及RW Date;SRAM放的是RW Date和ZI Date两项。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值