打印
[应用相关]

stm32 栈溢出 错误

[复制链接]
1762|7
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
heisexingqisi|  楼主 | 2017-12-21 09:15 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
今天搞的一个stm32 的程序发生了错误。全局变量遭到了局部变量的篡改。新手感觉很奇特。
看了一些资料,发现时栈区设置太小所导致的,全局变量向上生长,栈区向下生长。stm32的栈顶是程序自动生成的(暂时是这么认为的,有待进一步确定),程序会地洞生成栈顶。并且栈底和全局变量区是紧挨的,因此如果栈溢出的话,会直接将全局变量去的地址拿来自己用,于是全局变量区的地址和栈区的地址重合,导致全局变量遭到局部变量篡改的错误。

看看下面一些专业的解释会更清晰!
对于单片机这种封闭代码的运行平台,内存分配有2个大方向,一个是静态变量,一个是动态变量,具体到作用域,又分为局部变量和全局变量.

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

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

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

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

沙发
heisexingqisi|  楼主 | 2017-12-21 09:15 | 只看该作者
修改+注释.
**新的linux把uclinux统一了,不知道是否在单片机实现进程内存保护,同求证.不过这也不在<封闭代码平台>这个前提下了.
一、内存基本构成  
可编程内存在基本上分为这样的几大部分:静态存储区、堆区和栈区。他们的功能不同,对他们使用方式也就不同。  

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

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

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

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

Heap_Size       EQU     0x00000000

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

使用特权

评论回复
板凳
heisexingqisi|  楼主 | 2017-12-21 09:15 | 只看该作者
栈增长和大端/小端问题是和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

使用特权

评论回复
地板
heisexingqisi|  楼主 | 2017-12-21 09:23 | 只看该作者
     Code (inc. data)   RO Data    RW Data    ZI Data      Debug   Object Name
       172         10          0          4          0        995   delay.o//delay.c里面,fac_us和fac_ms,共占用4字节
       112         12          0          0          0        427   led.o
       72         26        304          0       2048        828   startup_stm32f10x_hd.o  //启动文件,里面定义了Stack_Size为0X800,所以这里是2048.
       712         52          0          0          0       2715   sys.o
       348        154          0          6          0     208720   test.o//test.c里面,stack_dir和cpu_endian 以及*addr  ,占用6字节.
       384         24          0          8        200       3050   usart.o//usart.c定义了一个串口接收数组buffer,占用200字节.
    ----------------------------------------------------------------------
      1800        278        336         20       2248     216735   Object Totals //总共2248+20字节
         0          0         32          0          0          0   (incl. Generated)
         0          0          0          2          0          0   (incl. Padding)//2字节用于对其
    ----------------------------------------------------------------------
      Code (inc. data)   RO Data    RW Data    ZI Data      Debug   Library Member Name
         8          0          0          0          0         68   __main.o
       104          0          0          0          0         84   __printf.o
        52          8          0          0          0          0   __scatter.o
        26          0          0          0          0          0   __scatter_copy.o
        28          0          0          0          0          0   __scatter_zi.o
        48          6          0          0          0         96   _printf_char_common.o
        36          4          0          0          0         80   _printf_char_file.o
        92          4         40          0          0         88   _printf_hex_int.o
       184          0          0          0          0         88   _printf_intcommon.o
         0          0          0          0          0          0   _printf_percent.o
         4          0          0          0          0          0   _printf_percent_end.o
         6          0          0          0          0          0   _printf_x.o
        12          0          0          0          0         72   exit.o
         8          0          0          0          0         68   ferror.o
         6          0          0          0          0        152   heapauxi.o
         2          0          0          0          0          0   libinit.o
         2          0          0          0          0          0   libinit2.o
         2          0          0          0          0          0   libshutdown.o
         2          0          0          0          0          0   libshutdown2.o
         8          4          0          0         96         68   libspace.o          //库文件(printf使用),占用了96字节
        24          4          0          0          0         84   noretval__2printf.o
         0          0          0          0          0          0   rtentry.o
        12          0          0          0          0          0   rtentry2.o
         6          0          0          0          0          0   rtentry4.o
         2          0          0          0          0          0   rtexit.o
        10          0          0          0          0          0   rtexit2.o
        74          0          0          0          0         80   sys_stackheap_outer.o
         2          0          0          0          0         68   use_no_semi.o
         2          0          0          0          0         68   use_no_semi_2.o
       450          8          0          0          0        236   faddsub_clz.o
       388         76          0          0          0         96   fdiv.o
        62          4          0          0          0         84   ffixu.o
        38          0          0          0          0         68   fflt_clz.o
       258          4          0          0          0         84   fmul.o
       140          4          0          0          0         84   fnaninf.o
        10          0          0          0          0         68   fretinf.o
         0          0          0          0          0          0   usenofp.o
    ----------------------------------------------------------------------
      2118        126         42          0        100       1884   Library Totals  //调用的库用了100字节.
        10          0          2          0          4          0   (incl. Padding)   //用于对其多占用了4个字节
    ----------------------------------------------------------------------
      Code (inc. data)   RO Data    RW Data    ZI Data      Debug   Library Name
       762         30         40          0         96       1164   c_w.l
      1346         96          0          0          0        720   fz_ws.l
    ----------------------------------------------------------------------
      2118        126         42          0        100       1884   Library Totals
    ----------------------------------------------------------------------
==============================================================================

      Code (inc. data)   RO Data    RW Data    ZI Data      Debug  
      3918        404        378         20       2348     217111   Grand Totals
      3918        404        378         20       2348     217111   ELF Image Totals
      3918        404        378         20          0          0   ROM Totals
==============================================================================
    Total RO  Size (Code + RO Data)                 4296 (   4.20kB)
    Total RW  Size (RW Data + ZI Data)              2368 (   2.31kB)   //总共占用:2248+20+100=2368.
    Total ROM Size (Code + RO Data + RW Data)       4316 (   4.21kB)
==============================================================================

通过这个文件,我们就可以分析整个内存,是怎么被占用的,具体到每个文件,占用多少.一目了然了.

4,最后,看看整个测试代码:
main.c代码如下,工程见附件.
#include "sys.h"
#include "usart.h"  
#include "delay.h"
#include "led.h"
#include "beep.h"   
#include "key.h"   
//ALIENTEK战舰STM32开发板堆栈增长方向以及CPU大小端测试
//保存栈增长方向
//0,向下增长;1,向上增长.
static u8 stack_dir;
//CPU大小端
//0,小端模式;1,大端模式.
static u8 cpu_endian;


//查找栈增长方向,结果保存在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,那么说明栈增长方向是向下的.  
}
}
//获取CPU大小端模式,结果保存在cpu_endian里面
void find_cpu_endian(void)
{
int x=1;
if(*(char*)&x==1)cpu_endian=0; //小端模式
else cpu_endian=1;    //大端模式  
}
int main(void)
{   
Stm32_Clock_Init(9); //系统时钟设置
uart_init(72,9600);   //串口初始化为9600
delay_init(72);       //延时初始化
LED_Init();      //初始化与LED连接的硬件接口  
    printf("stack_dir:%x\r\n",&stack_dir);
    printf("cpu_endian:%x\r\n",&cpu_endian);

find_stack_direction(); //获取栈增长方式
find_cpu_endian();  //获取CPU大小端模式
  while(1)
{
  if(stack_dir)printf("STACK DIRCTION:向上生长\r\n\r\n");
  else printf("STACK DIRCTION:向下生长\r\n\r\n");
  if(cpu_endian)printf("CPU ENDIAN:大端模式\r\n\r\n");
  else printf("CPU ENDIAN:小端模式\r\n\r\n");
  delay_ms(500);
  LED0=!LED0;  
}  
}

使用特权

评论回复
5
heisexingqisi|  楼主 | 2017-12-21 09:28 | 只看该作者
4、内存基本构成  
可编程内存在基本上分为这样的几大部分:静态存储区、堆区和栈区。他们的功能不同,对他们使用方式也就不同。  

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

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

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

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

Heap_Size       EQU     0x00000000

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

使用特权

评论回复
6
pentral0311| | 2017-12-21 11:11 | 只看该作者
小编,这种**要加精

使用特权

评论回复
7
heisexingqisi|  楼主 | 2017-12-21 11:32 | 只看该作者
pentral0311 发表于 2017-12-21 11:11
小编,这种**要加精

我看不错转来的,感觉写的不错,加精就不必了。哈哈,并不是所有人需要。

使用特权

评论回复
8
wahahaheihei| | 2017-12-21 13:57 | 只看该作者
指针越位容易修改其他变量。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

131

主题

2615

帖子

2

粉丝