开头的话:
之前一直用现成的LED工程demo,改改就上,也没细究。直到做MQTT移植的时候,发现malloc始终出错,开始找问题,于是写本文。(前前后后摘抄、参考、改进本文,侵删)

一、STM32上电启动

BOOT1 BOOT0 启动方式
X 0 从STM32内置flash启动,JTAG或者SWD固化程序位置
1 1 从STM32内置SRAM启动,由于SRAM没有程序存储能力,这个模式一般用于程序debug
0 1 从STM32内置ROM启动,使用串口借助bootloader下载程序至flash,即ISP
  • 补充说明

Main Flash memory
从STM32内置的Flash启动

System memory
从系统ROM启动,这种模式启动的程序功能是由厂家设置的。一般来说,这种启动方式用的比较少。系统存储器是芯片内部一块特定的区域,STM32在出厂时,由ST在这个区域内部预置了一段BootLoader,也就是我们常说的ISP程序, 这是一块ROM,出厂后无法修改。一般来说,我们选用这种启动模式时,是为了从串口下载程序,因为在厂家提供的BootLoader中,提供了串口下载程序的固件,可以通过这个BootLoader将程序下载到系统的Flash中。但是这个下载方式需要以下步骤:

Step1:
将BOOT0设置为1,BOOT1设置为0,然后按下复位键,这样才能从系统存储器启动BootLoader
Step2:
最后在BootLoader的帮助下,通过串口下载程序到Flash中
Step3:
程序下载完成后,又有需要将BOOT0设置为GND,手动复位,这样,STM32才可以从Flash中启动可以看到, 利用串口下载程序还是比较的麻烦。可以参考原子哥一键下载电路,此处不贴了。

Embedded Memory
内置SRAM,用于快速的程序调试

----------Flash锁死解决办法
修改为BOOT0=1,BOOT1=0 即可从系统存储器ROM启动,通过JTAG或SWD重新烧写程序后,可将BOOT模式重新更换到BOOT0=0,BOOT1=X即可正常使用

二、keil编译信息

1. log信息

本文以STM32F407ZGT6为分析平台,写了一个最简单的程序,包含usart和delay

Code: 存储程序代码
RO: 存储常量
RW:存储初始化不为0的全局变量
ZI(zero initial):存储初始化为0或未初始化的全局变量

  • Flash存储项包括Code + RO + RW
    原因:
    Code 保存程序用
    RW 初始化不为零的变量值需要断电保存
    RO 常量值需要断电保存

  • Ram加载项包括RW + ZI
    RW 要开始运行程序了,全局变量必不可少,且变量不能总是从flash中读取,那样的话,值都和上电的一样了
    ZI 变量不能从flash读取,理由同上

2. map信息

看一下生成的map信息(双击Keil工程名)

看到当中有HEAP和STACK,最左列是Base Addr,次之是Size,可以看到
HEAP起始地址0x200000f0,size 0x00000200
STACK续上,起始地址0x200002f0,size 0x00000400

3.堆栈结构

这里引出了一个问题,STM32内部的堆栈结构,先看STM32内存地址映射(图片来自:https://blog.csdn.net/qq_15232177/article/details/73336374)

本质上,STM32对外设的操作都是对地址的操作,RAM以0x20000000起始,实际大小取决于芯片系列。内置flash以0x08000000起始,实际大小取决于芯片系列。芯片启动程序就是从0x08000000开始

继续说Ram,见下图(图片改编自:https://blog.csdn.net/qlexcel/article/details/78916934)

自0x20000000起,分别是静态存储区,HEAP和STACK。不妨以STM32F407ZGT6为例子看程序启动,如下图

debug刚启动,SP指向0x200006F0,即栈顶。当程序运行后,MSP就是从这个地址开始,往下给函数的局部变量分配地址。另外,STM32栈增长方向为向下,堆增长方向为向上。
插一段比较巧的代码,借助递归函数检查栈的增长方向:

void find_stack_direction(void)
{static u8 *addr=NULL;   //用于存放第一个dummy的地址。u8dummy;                  //用于获取栈地址if(addr==NULL)         //第一次进入{                          addr=&dummy;       //保存dummy的地址find_stack_direction (); //递归}else               //第二次进入{  //第二次dummy的地址大于第一次dummy,那么说明栈增长方向是向上的.if(&dummy>addr)stack_dir=1; else//第二次dummy的地址小于第一次dummy,那么说明栈增长方向是向下的.stack_dir=0;            }
}

言归正传,堆栈区别:
RAM分为静态常量区、栈区和堆区

  • STACK
    存放函数内局部变量,形参,函数运行结束后自动释放

  • HEAP
    存放由malloc/new开辟的空间,自由使用,但必须通过free/delete释放

  • 静态常量区
    内存在程序编译的时候已经分配好,主要存放全局数据(初始化&未初始化)和常量

从堆栈空间分布即可看出,由于堆栈增长方向相反,因此,存在堆栈干扰的情况。(当malloc很大的区域时,或者局部变量定义大数组时。比如,malloc一个正常堆范围的内存,同时某个函数内部定义了大数组,有可能改动数组就影响到malloc的内存内容,严重的直接程序崩溃)

4. flash

再说flash,看配置表
onchip ROM 可以看出,自0x08000000起,大小为0x00100000,即1MB,
onchip RAM1自0x20000000起,大小为0x00020000,即128KB,RAM2自0x10000000起,大小为0x00010000,即64KB,合计192KB

STM32上电启动后,根据BOOT0和BOOT1选择flash启动还是ram启动。如前所述,最常见还是flash启动。启动后,搬运RW到RAM,但不会搬运Code,即STM32每条指令都是从flash读取执行。当然,为了提高指令加载速度,也可以一次性加载到RAM,但不这么做,因为RAM本来就小,不值得。
另外,一般程序复位、IAP都是将指针指向0x08000000,实现重新加载。(STM32的IAP可参考链接文章)

三、关于STM32启动过程的一点记录

debug启动后,PC指向0x0800091C,即指向main函数。其实这并不是完整的启动过程,
在s文件183行打断点,可以看到先运行了SystemInit(PC指向0x0800019E),然后才执行main

全面的启动运行流程图如下(图片来自https://blog.csdn.net/menghuanbeike/article/details/78866013):

四、正题

1. 堆栈溢出问题

先上代码,

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "stdlib.h"
volatile char vgdata[200] = {0};int main(void)
{ int i = 10;char* p = (char*)malloc(200);char idata[1392] = {0};for(i = 0;i < 200;i++){*(p+i) = 0x22;}for(i = 0;i < 200;i++){vgdata[i] = 1;}vgdata[0] = 0x66;vgdata[1] = ((uint32_t)(&i) &0xff000000) >> 24;vgdata[2] = ((uint32_t)(&i) &0x00ff0000) >> 16;vgdata[3] = ((uint32_t)(&i) &0x0000ff00) >> 8;vgdata[4] = ((uint32_t)(&i) &0x000000ff);vgdata[5] = ((uint32_t)(&p) &0xff000000) >> 24;vgdata[6] = ((uint32_t)(&p) &0x00ff0000) >> 16;vgdata[7] = ((uint32_t)(&p) &0x0000ff00) >> 8;vgdata[8] = ((uint32_t)(&p) &0x000000ff);vgdata[9] = ((uint32_t)(idata) &0xff000000) >> 24;vgdata[10] = ((uint32_t)(idata) &0x00ff0000) >> 16;vgdata[11] = ((uint32_t)(idata) &0x0000ff00) >> 8;vgdata[12] = ((uint32_t)(idata) &0x000000ff);for(i = 0;i < 1392;i++){idata[i] = 0x44;}idata[1391] = 0x99;while(1);
}

全局区定义字符数组,大小200。main函数什么也没做,

首先,malloc 200字节大小的空间,并将首字节赋值2
然后,初始化全局数组vgdata,并将首字节赋值0x66,顺次存入i,p,idata地址,其余赋值1
最后,定义1392字节大小的局部数组,并赋值0x44
末尾,局部数组最后一个值改成0x99

生成的map文件如下:

43行for循环断点,watch memory发现

在0x2000_0000行,第17个数开始(即0x2000_0010)顺序写入0x66,变量i地址,指针p地址,数组idata地址,0x01 ...,直至(0x2000_00CC + 0X0B),共计200个字节
在0x2000_0198行,第17个数有02写入,其地址为0x2000_0198 + 0x10 = 0x2000_01a8
即在堆地址0x2000_01a0基础上偏移8,敲重点

即:

  • malloc空间范围
地址 归属
0x2000_026F 0x22终止地址
0x2000_01a8 0x22起始地址

共计200个字节

  • 全局变量vgdata范围
地址 归属
0x2000_00D8 0x01终止地址
0x2000_0010 0x66起始地址

共计200个字节
着重注意一下:

变量i地址是 20 00 05 9C,在上图底部红色标出,其值为C8 00 00 00,由于STM32为小端模式,故而低字节在低地址,因此数据为0x0000_00C8 = 200,第一个for循环结束,i = 200 对的上

指针p地址是20 00 05 98,同样从上图可知,其值为A8 01 00 20,小端的原因,数据为0x2000_01A8,即malloc后写入0x22的首地址,对的上

数组idata地址是20 00 00 28,这下不得了,直接覆盖堆区跑到静态存储区了,看下面的断点运行

49行while(1)断点,

  • 局部变量idata范围
地址 归属
0x2000_0597 0x99终止地址
0x2000_0028 0x44起始地址

共计1392个字节

问题出现:自0x2000_0028起,原数据全被数据0x44覆盖,直至0x2000_0597的0x99。不仅malloc开辟的空间数据丢失,连全局静态存储区的数据也丢失。这就是堆栈溢出,相当危险!!!

  • 至此,总结如下

由前述堆栈结构图知,栈向下,堆向上。malloc开辟空间不足,会返回NULL,不会向上影响栈区,没有什么致命伤。但是,STACK 就不一样,当需要临时变量比较大时,系统从当前栈指针开始向下开辟空间,这就可能污染到HEAP和静态区,造成堆栈溢出

针对上述问题,本质上还是栈空间大小不合适导致,增大startup_stm32f40_41xxx.s文件中的Stack_Size EQU 0x00000200即可解决。同样,如果malloc失败,即堆空间不足,亦可增大Heap_Size EQU 0x00000200解决。那在确定的HEAP size下,究竟能malloc多大空间?

2. malloc到底能分配多大空间

先上代码

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "stdlib.h"
volatile char vgdata[200] = {0};int main(void)
{ int i = 10;char* p = (char*)malloc(0x200 - 12);char idata[1392] = {0};for(i = 0;i < (0x200 - 12);i++){*(p+i) = 0x22;}for(i = 0;i < 200;i++){vgdata[i] = 1;}vgdata[0] = 0x66;vgdata[1] = ((uint32_t)(&i) &0xff000000) >> 24;vgdata[2] = ((uint32_t)(&i) &0x00ff0000) >> 16;vgdata[3] = ((uint32_t)(&i) &0x0000ff00) >> 8;vgdata[4] = ((uint32_t)(&i) &0x000000ff);vgdata[5] = ((uint32_t)(&p) &0xff000000) >> 24;vgdata[6] = ((uint32_t)(&p) &0x00ff0000) >> 16;vgdata[7] = ((uint32_t)(&p) &0x0000ff00) >> 8;vgdata[8] = ((uint32_t)(&p) &0x000000ff);vgdata[9] = ((uint32_t)(idata) &0xff000000) >> 24;vgdata[10] = ((uint32_t)(idata) &0x00ff0000) >> 16;vgdata[11] = ((uint32_t)(idata) &0x0000ff00) >> 8;vgdata[12] = ((uint32_t)(idata) &0x000000ff);for(i = 0;i < 1392;i++){idata[i] = 0x44;}idata[1391] = 0x99;while(1);
}

HEAP和STACK都取0x200
经实验,malloc能开辟的最大空间是(0x200 - 12),问题来自两个方面

malloc开辟空间的指针p,指向的是HEAP地址偏移8个字节
malloc最大的范围只能到堆结束位置之前4个字节
综上,偏差12字节

  • malloc最大空间测试
地址 归属
0x2000_039F HEAP终止地址
0x2000_039B malloc 0x22终止地址
0x2000_01A8 malloc 0x22起始地址
0x2000_01A0 HEAP起始地址

就算精简如下,也还是前差8字节,后剩4字节,此是问题1

int main(void)
{ int i = 10;char* p = (char*)malloc(0x200 - 12);for(i = 0;i < (0x200 - 12);i++){*(p+i) = 0x22;}while(1)
}

但另一方面,如果我把idata大小改成1393,malloc直接GG,1个都分配不了,此是问题2

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "stdlib.h"
volatile char vgdata[200] = {0};int main(void)
{ int i = 10;char* p = (char*)malloc(1);char idata[1393] = {0};for(i = 0;i < (1);i++){*(p+i) = 0x22;}for(i = 0;i < 200;i++){vgdata[i] = 1;}vgdata[0] = 0x66;vgdata[1] = ((uint32_t)(&i) &0xff000000) >> 24;vgdata[2] = ((uint32_t)(&i) &0x00ff0000) >> 16;vgdata[3] = ((uint32_t)(&i) &0x0000ff00) >> 8;vgdata[4] = ((uint32_t)(&i) &0x000000ff);vgdata[5] = ((uint32_t)(&p) &0xff000000) >> 24;vgdata[6] = ((uint32_t)(&p) &0x00ff0000) >> 16;vgdata[7] = ((uint32_t)(&p) &0x0000ff00) >> 8;vgdata[8] = ((uint32_t)(&p) &0x000000ff);vgdata[9] = ((uint32_t)(idata) &0xff000000) >> 24;vgdata[10] = ((uint32_t)(idata) &0x00ff0000) >> 16;vgdata[11] = ((uint32_t)(idata) &0x0000ff00) >> 8;vgdata[12] = ((uint32_t)(idata) &0x000000ff);for(i = 0;i < 1393;i++){idata[i] = 0x44;}idata[1392] = 0x99;while(1);
}

看地址数据发现,虽然给idata分配1393字节,但系统为了4字节对齐,实际分配了1396字节

但前述的问题1与问题2现象比较奇怪:

问题1,前8后4,整个工程也没找到第二处malloc,这个12字节哪去了?
问题2,main是先malloc,后定义栈数组,改数组大小怎么会导致malloc失败,而且是一个都malloc不了

后续再解决吧,时间不早了,得干活了

[东拼西凑]STM32单片机启动流程及RAM和Flash的配置关系和堆栈溢出现象相关推荐

  1. STM32 单片机启动流程

    STM32 单片机启动流程 刚接触ARM的cortex-m系列单片机时,被告知一切都从main() 函数开始,要将程序写在main()函数中.而仿真时也貌似是从main() 函数开始的,以STM32F ...

  2. STM32单片机启动流程分析

    本章内容 STM32单片机是如何执行自己的代码的? 官方给的启动流程图如下 启动地址 启动文件分析 堆栈定义 向量表 复位程序 中断服务程序 堆栈初始化 总结一下STM32从Flash的启动流程 ST ...

  3. STM32单片机启动过程详解

    本文详细介绍STM32单片机启动的过程,既从上电Reset_Handle跳转到main()函数的过程.其中,会着重解释__main的汇编代码 STM32启动过程 1. 从Reset_Handler开始 ...

  4. 单片机启动流程(以STM32为例)

    ST公司都提供了现成的直接可用的启动文件,程序开发人员可以直接引用启动文件后直接进行C应用程序的开发.这样能大大减小开发人员从其它微控制器平台跳转至STM32平台,也降低了适应STM32微控制器的难度 ...

  5. 单片机断电后静态存储区里面还有数据吗_单片机启动流程和存储架构详解

    最近在给公司的ADAS DCU做内存分配(Memory Allocation),在这儿记录一下相关知识点,也算是给中文社区做贡献了. 目录: 1. ECU启动流程 2. 存储空间解析 3. TC397 ...

  6. STM32的完整启动流程分析-----在外存flash中运行代码

    1. 根据boot引脚决定三种启动模式 复位后,在 SYSCLK 的第四个上升沿锁存 BOOT 引脚的值.BOOT0 为专用引脚,而 BOOT1 则与 GPIO 引脚共用.一旦完成对 BOOT1 的采 ...

  7. STM32开发 -- 启动流程

    如需转载请注明出处:https://blog.csdn.net/qq_29350001/article/details/80586534 启动模式讲完了,我们知道是主闪存存储器启动的(主闪存存储器就是 ...

  8. 单片机中的ROM,RAM和FLASH的作用

    本文部分参考自:http://blog.sina.com.cn/s/blog_98ca54fc01017y4t.html 并在此基础上进行整理,添加了关于flash的问题. 之前从较为抽象的角度介绍了 ...

  9. SpringCloud→分布式解决方案、包含主要工具、启动流程、web发展阶段、实现配置中心

    SpringCloud SpringCloud简介 SpringCloud分布式解决方案 SpringCloud启动流程 web发展阶段 配置中心 配置中心MAVEN引入依赖 模拟服务端开启配置中心 ...

最新文章

  1. python swapcase用法_Python swapcase函数有什么用
  2. 怎么用css控制border成为三角形
  3. Session丢失的解决办法小结
  4. java 年计算_用Java计算leap年
  5. 算法题——Cantor表
  6. 功能测试——医疗管理系统
  7. UI实用素材|字体在设计中的重要性
  8. Android异常总结---3.Failed to install *.apk on device 'emulator-5554': timeout 错误提示:
  9. 面向全场景模块化设计,京东智联云的服务器部署有多灵活?
  10. Hibernate中saveOrUpdate()和merge()的区别
  11. java word转html 报错org/apache/poi/xwpf/usermodel/IRunBody
  12. 电子元件的测量方法及在电路中的作用
  13. Android 集成Tinker的gradle脚本
  14. Node实现支付宝网页支付流程(沙箱环境)
  15. 获取文件夹中所有图片文件
  16. 解决Server2008 R2 AD服务器 域组策略 XP桌面图标蓝底
  17. 菜鸟保税仓成全球商家进中国首选 秒级通关领先全球
  18. computed 和 watch的区别
  19. 【Django】admin.ModelAdmin的源码-20220105
  20. Excel图表5——旋风图(对称条形图)

热门文章

  1. ELK-“线上标准文档”——测试
  2. Android学习笔记之-:对Android图像色调饱和度亮度处理
  3. Linux环境下安装ssh2模块
  4. [单片机框架][bsp层][nrf52832][nrf52840][nrf52810][nrf52820][ESB(2.4G)] ESB(2.4G)使用说明
  5. U8g2菜单界面+按键操作在线模拟
  6. 51单片机驱动TCS3200颜色识别传感器
  7. 20小时上手移动端自动化测试-张璇-专题视频课程
  8. 希尔伯特-包络分析步骤与实例
  9. strip().split()怎么用
  10. 如果高冷地看待市面上培训班?