stm32 栈溢出 错误

2020-01-15来源: eefocus关键字:stm32  栈溢出  错误

今天搞的一个stm32 的程序发生了错误。全局变量遭到了局部变量的篡改。新手感觉很奇特。


看了一些资料,发现时栈区设置太小所导致的,全局变量向上生长,栈区向下生长。stm32的栈顶是程序自动生成的(暂时是这么认为的,有待进一步确定),程序会地洞生成栈顶。并且栈底和全局变量区是紧挨的,因此如果栈溢出的话,会直接将全局变量去的地址拿来自己用,于是全局变量区的地址和栈区的地址重合,导致全局变量遭到局部变量篡改的错误。


看看下面一些专业的解释会更清晰!


对于单片机这种封闭代码的运行平台,内存分配有2个大方向,一个是静态变量,一个是动态变量,具体到作用域,又分为局部变量和全局变量.

全局静态变量:不管是否调用,它都在那里,比如LZ示例的 line:11 和 line:15,注意这里加了关键字,指明这个变量是并不是真正意义的全局变量,只是在这个文件的所有位置<声明位置以后的所有位置>可用.

局部静态变量:和全局静态变量类似,也是不管拉不拉屎先占坑的货,比如LZ示例的 line:23 .特点是加了关键字,意思是在这个位置,它是唯一的.在函数里使用了递归,但局部静态变量是不在递归里重新分配空间的,原子也是通过这个方式来判断两次进入之间的地址关系.

局部动态变量:这个是最常见的,比如LZ示例的 line:24,在这个示例里,每次声明<神灯啊神灯>,结果出来的都是新的神灯,许了愿就溜掉,是这种变量的特点.它不会记得它曾经是什么.注意,由于每次都喝了孟婆汤,有经验的码农会在召唤时默认赋一个初值,避免出现不可预料的使用.

全局动态变量:存在吗?全局可见但又可以踢掉的奇葩吗?抱歉,这句话对<全局>是个误解.<全局>的意思是变量本身没有编译器指定的生命周期,也就是<作用域>,但还有代码指定的生命周期.在LZ的示例里,<堆>就是这么一个东西,代码说<你在>就在,<你不在>就不在.申请了堆后,只要谁(任何位置的代码)知道这个位置是可以用的,谁都可以用(**具有进程内存保护的平台除外**),即使申请空间的变量<挂了>,这个空间也一直存在,直到有代码把它<销毁>掉.

顺便推销老帖http://www.openedv.com/posts/list/19693.htm

修改+注释.
**新的linux把uclinux统一了,不知道是否在单片机实现进程内存保护,同求证.不过这也不在<封闭代码平台>这个前提下了.


一、内存基本构成  
可编程内存在基本上分为这样的几大部分:静态存储区、堆区和栈区。他们的功能不同,对他们使用方式也就不同。  

静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量。  

栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。  

堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。 但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,否则,我们认为发生了内存泄漏现象。


按照这个说法,我在.s文件里面设置了:

Heap_Size       EQU     0x00000000

也就是,没有任何动态内存分配。
这样,内存=静态存储区+栈区了。
不存在堆!!!
因为我没有用malloc来动态分配内存。
因此,前面提到的一切堆区,其实就是静态存储区。


栈增长和大端/小端问题是和CPU相关的两个问题.

1,首先来看:栈(STACK)的问题.

函数的局部变量,都是存放在"栈"里面,栈的英文是:STACK.STACK的大小,我们可以在stm32的启动文件里面设置,以战舰stm32开发板为例,在startup_stm32f10x_hd.s里面,开头就有:

Stack_Size      EQU     0x00000800

表示栈大小是0X800,也就是2048字节.这样,CPU处理任务的时候,函数局部变量做多可占用的大小就是:2048字节,注意:是所有在处理的函数,包括函数嵌套,递归,等等,都是从这个"栈"里面,来分配的.


所以,如果一个函数的局部变量过多,比如在函数里面定义一个u8 buf[512],这一下就占了1/4的栈大小了,再在其他函数里面来搞两下,程序崩溃是很容易的事情,这时候,一般你会进入到hardfault....


这是初学者非常容易犯的一个错误.切记不要在函数里面放N多局部变量,尤其有大数组的时候!

对于栈区,一般栈顶,也就是MSP,在程序刚运行的时候,指向程序所占用内存的最高地址.比如附件里面的这个程序序,内存占用如下图:

图中,我们可以看到,程序总共占用内存:20+2348字节=2368=0X940
那么程序刚开始运行的时候:MSP=0X2000 0000+0X940=0X2000 0940.
事实上,也是如此。

MSP就是:0X2000 0940.
程序运行后,MSP就是从这个地址开始,往下给函数的局部变量分配地址.

再说说栈的增长方向,我们可以用如下代码测试: 

//保存栈增长方向
//0,向下增长;1,向上增长.
static u8 stack_dir;

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


这个代码不是我写的,网上抄来的,思路很巧妙,利用递归,判断两次分配给dummy的地址,来比较栈是向下生长,还是向上生长.
如果你在STM32测试这个函数,你会发现,STM32的栈,是向下生长的.事实上,一般CPU的栈增长方向,都是向下的.

2,再来说说,堆(HEAP)的问题.

全局变量,静态变量,以及内存管理所用的内存,都是属于"堆"区,英文名:"HEAP"
与栈区不同,堆区,则从内存区域的起始地址,开始分配给各个全局变量和静态变量.
堆的生长方向,都是向上的.在程序里面,所有的内存分为:堆+栈. 只是他们各自的起始地址和增长方向不同,他们没有一个固定的界限,所以一旦堆栈冲突,系统就到了崩溃的时候了.
同样,我们用附件里面的例程测试:


stack_dir的地址是0X20000004,也就是STM32的内存起始端的地址.
这里本来应该是从0X2000 0000开始分配的,但是,我仿真发现0X2000 0000总是存放:0X2000 0398,这个值,貌似是MSP,但是又不变化,还请高手帮忙解释下.
其他的,全局变量,则依次递增,地址肯定大于0X20000004,比如cpu_endian的地址就是0X20000005.


这就是STM32内部堆的分配规则.

3,再说说,大小端的问题.
大端模式:低位字节存在高地址上,高位字节存在低地址上 
小端模式:高位字节存在高地址上,低位字节存在低地址上

STM32属于小端模式,简单的说,比如u32 temp=0X12345678;
假设temp地址在0X2000 0010.
那么在内存里面,存放就变成了:
地址              |            HEX         |
0X2000 0010  |  78   56   43  12  |

CPU到底是大端还是小端,可以通过如下代码测试:
//CPU大小端
//0,小端模式;1,大端模式.
static u8 cpu_endian;

//获取CPU大小端模式,结果保存在cpu_endian里面
void find_cpu_endian(void)

 int x=1;
 if(*(char*)&x==1)cpu_endian=0; //小端模式 
 else cpu_endian=1;    //大端模式  
}
以上测试,在STM32上,你会得到cpu_endian=0,也就是小端模式.


3,最后说说,STM32内存的问题.
    还是以附件工程为例,在前面第一个图,程序总共占用内存:20+2348字节,这么多内存,到底是怎么得来的呢?
我们可以双击Project侧边栏的:Targt1,会弹出test.map,在这个里面,我们就可以清楚的知道这些内存到底是怎么来的了.在这个test.map最后,Image 部分有:
==============================================================================

Image component sizes


      Code (inc. data)   RO Data    RW Data    ZI Data      Debug   Object Name

[1] [2] [3]
关键字:stm32  栈溢出  错误 编辑:什么鱼 引用地址:http://news.eeworld.com.cn/mcu/ic485731.html 本网站转载的所有的文章、图片、音频视频文件等资料的版权归版权所有人所有,本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如果本网所选内容的文章作者及编辑认为其作品不宜公开自由传播,或不应无偿使用,请及时通过电子邮件或电话通知我们,以迅速采取适当措施,避免给双方造成不必要的经济损失。

上一篇:STM32 堆和栈的学习(二)
下一篇:最后一页

关注eeworld公众号 快捷获取更多信息
关注eeworld公众号
快捷获取更多信息
关注eeworld服务号 享受更多官方福利
关注eeworld服务号
享受更多官方福利

推荐阅读

开发STM32MP1,没有一块好的开发办怎么行
STM32MP1系列的出现吸引了很多STM32的新老用户的关注,但是很多的人都会担心一个问题:以前是基于Cortex M系列MCU惊醒开发,对于cortex-A架构的处理器以及Linux系统都不熟悉。如何高效地从MCU跨越到MPU是大家都关心的话题。 作为ST官方合作伙伴,米尔电子推出了开发套件MYD-YA157C,该套件由核心板MYC-YA157C和底板MYB-YA157C组成。该平台采用STM32MP157系列高性能处理器,提供了高性能显示,千兆以太网,WIFI/蓝牙,RS232/RS485/CAN等丰富接口。贴合应用场景的产品定义,稳定可靠的硬件设计,丰富的软件和学习资源,能帮助大家轻松实现跨越,是一块值得推荐
发表于 2020-01-09
开发STM32MP1,没有一块好的开发办怎么行
stm8s输入捕获
输入捕获,简而言之,即 记录信号到来时刻的CNTR的值,然后把该值传给CCR。下面以PC1为例,代码如下:    TIM1_CR1_DIR = 0;//计数器向上计数    TIM1_IER_UIE = 1;//使能TIM1溢出中断      TIM1_PSCRH = 0x00;    TIM1_PSCRL = 0x05; //配置TIM1为6分频->0.75us记一次数     TIM1_ARRH = 0xFF;     TIM1_ARRL = 0xFF; //自动重
发表于 2020-01-09
STM32F10x_硬件I2C读写EEPROM(标准外设库版本)
Ⅰ、写在前面上一篇文章是“STM32F10x_模拟I2C读写EEPROM”,讲述使用IO口模拟I2C总线通信,对EEPROM(AT24Xxx)进行读写操作的过程。上一篇文章主要内容:I2C协议、模拟I2C底层驱动、EEPROM(AT24Xxx)单字节读写操作。本文主要内容:STM32硬件I2C详细配置、EEPROM(AT24Xxx)多字节读写操作、ST官方I2C存在问题。实例实验效果:1、多字节读写:任意地址(66), 写入任意长度(129)、读取并打印出来2、单字节读写:任意地址(0),写入1字节数据、 读取并打印出来实验说明:1.多字节读写实验为什么是从66地址写? 为什么是写入129字节?答案:验证对EEPROM多字节
发表于 2020-01-09
STM32F10x_硬件I2C读写EEPROM(标准外设库版本)
STM32系统学习——I2C (读写EEPROM)
I2C 通讯协议(Inter-Integrated Circuit)引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。 在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设;STM32 标准库则是在寄存器与用户代码之间的软件层。对于通讯协议,我们也以分层的方式来理解,最基本的是把它分为物理层和协议层。 物理层规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。简单来说物理层规定我们用嘴巴还是用肢体来交流,协议层
发表于 2020-01-09
STM32系统学习——I2C (读写EEPROM)
STM8的IAP在线升级
IAP(In Application Program)在线应用编程官方资料(STVD开发环境):例程AN2659,《 AN2659 Application Note.pdf 》要实现在线升级,MCU代码须分为 : bootloader和 用户代码App 两个部分。图1.STM8下IAP程序的存储方式用户启动区域(UBC): (可理解为用户自定义的bootloader的存放区域)包含有复位和中断向量表,它可用于存储IAP及通讯程序。UBC有一个两级保护结构可保护用户代码及数据在IAP编程中免于无意的擦除或修改。这意味着该区域总是写保护的,而且写保护不能通过使用MASS密钥来解锁。它的大小可通过配置option bytes 设置
发表于 2020-01-09
STM8的IAP在线升级
STM8做BUS OFF快慢恢复策略
前言:CAN总线上出现错误会导致CAN控制器进入BUS OFF状态,具体的可以参考CAN的规范。CAN控制器提供自动恢复和手动恢复功能。1.自动恢复自动恢复比较简单,初始化的时候打开自动恢复功能。要求不高的话建议打开,否则CAN BUS OFF一直无法恢复通信。CAN->MCR |= CAN_MasterCtrl_AutoBusOffManagement;/*atuo bus off recovery */2.手动恢复一般车厂要求ECU不能自动恢复,而是先快恢复后慢恢复。常用的就是:先100ms恢复5次,然后是1000ms恢复一次。具体的就是:(1)发生BUS OFF后,立即关闭TX,然后重置CAN控制器(2)快恢复次数+1(
发表于 2020-01-09
小广播
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号 电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright © 2005-2019 EEWORLD.com.cn, Inc. All rights reserved