本帖最后由 10xjzheng 于 2016-4-5 21:05 编辑
本帖子的内容主要包括本人两次堆栈溢出的调试经验的分享。
1、首先给小白们介绍下堆栈的作用。
”现在的堆栈基本上指的都是栈,但堆和栈实际是两个不同的概念,这里只讲解栈。堆栈指的的是遵循后进先出原则的一块内存空间,因堆栈有后进先出的特性,所以堆栈主要有以下的几个用途:
1,传递参数(为被调用函数提供参数) 2,保存局部变量 3,在系统中用堆栈保存任务的状态(例如各个寄存器的值) μC/OS-III中每个任务的堆栈需求大小是不一样的,因为每个任务中嵌套调用函数的层数不同,传递的参数等也不同,程序流程一般也都是不确定的,计算过程比较复杂,所以任务需要的最大堆栈也不好确定。在切换任务的时候,需要将当前任务使用到的一些寄存器入栈,也就是保存上下文。下次再执行任务的时候重新将这些从寄存器从任务堆栈中弹出来,继续执行就好像没有发生任何事情一样。 其实堆栈又叫做后进先出的线性表,在前面章节7.4 讲解的消息队列如果是LIFO,那么仅仅需要OutPtr一个指针指向队列的输出端即可管理消息队列,因为队列的输出端和输入端都是一样的。同样的,我们需要唯一的一个指针指向堆栈的一端,这就是堆栈指针SP。SP指向堆栈中最后一个被压进堆栈的地址。在任务切换的时候我们需要保存当前的SP到任务的堆栈中去。后面恢复任务弹栈的时候才知道从哪里开始弹栈。 堆栈在内存中的增长方向可以是从高到低,也有的是从低到高。STM32单片机堆栈的增长方向是从高到低。 在前后台系统中,程序运行也是需要堆栈的,用途见上文。在程序正式运行之前得先初始化堆栈,堆栈的初始化这些操作都在启动文件里面,如有兴趣可以分析下STM32的启动文件。“
2、裸机和ucos中堆栈的应用
在使用M3内核的前后台系统中(M3有MSP和PSP两个堆栈,但是同一时刻只有一个,对外表现为SP),无论是中断还是main函数里面使用的都是同一个堆栈MSP,堆栈的大小在头文件中进行设置,如下所示代码,为程序运行分配了4KBytes的堆栈大小。
- Stack_Size EQU 0x00001000
- AREA STACK, NOINIT, READWRITE, ALIGN=3
- Stack_Mem SPACE Stack_Size
- __initial_sp
复制代码 堆栈如此重要,以至于MCU上电复位之后的头等大事就是设置堆栈指针,我们可以看下中断向量表的开始就是我们上面设置的堆栈指针,MCU上电之后首先从中断向量表开始取出堆栈的首地址放到SP。
- __Vectors DCD __initial_sp ; Top of Stack
- DCD Reset_Handler ; Reset Handler
- DCD NMI_Handler ; NMI Handler
- DCD HardFault_Handler ; Hard Fault Handler
- DCD MemManage_Handler ; MPU Fault Handler
- DCD BusFault_Handler ; Bus Fault Handler
- DCD UsageFault_Handler ; Usage Fault Handler
- DCD 0 ; Reserved
- DCD 0 ; Reserved
- DCD 0 ; Reserved
复制代码
如果是ucos系统的堆栈会是怎么样呢?首先main函数中创建任务的时候,跟上面的说的是完全一样的,当切换到任务运行的时候,用的分别是每个任务各自的堆栈,当进入中断的时候,用的是堆栈空间就是数组OS_CPU_EXT OS_STK OS_CPU_ExceptStk[OS_CPU_EXCEPT_STK_SIZE];的这段空间,如果中断服务函数比较大,记得要将这个空间设置大点。
3、本次现象和调试描述
这次的现象非常之离奇,时不时就会进入hardfault,经验告诉我可能是堆栈溢出了。
关于堆栈检测,ucos首先会将任务堆栈全部初始化为0,然后每次检查堆栈使用情况的时候从堆栈底部开始计数看内存为0就视为没有使用过此内容,但是如果用户想要将使用率其打印出来来看看是否堆栈溢出,这有时是不可取的,因为堆栈溢出的时候可能程序已经不知道跑到哪里去了。
freeRTOS有两种方法,方法1:检查系统的SP,看有没有超出堆栈的范围方法2:在堆栈的最后放上16个特定的值,如果检测到他们被修改了,那说明就已经发生堆栈溢出。
我使用的方法是将进入硬件调试,将断点定在hardfault入口之前,查看所有的任务的堆栈末尾是否为0(包括中断使用的堆栈OS_CPU_ExceptStk[OS_CPU_EXCEPT_STK_SIZE];),经过一番调整各个任务堆栈的大小,还是很奇怪的是发现会到hardfault这里,但是每个任务的堆栈都好好的。接着我无意中发现还没有切换到第一个任务就死掉了,大家看下下面的初始化代码,看能不能看出问题。
- int main(void)
- {
- NVIC_Configuration();
-
- //SystemInit(); // init system core clock
- SystemCoreClockUpdate();// get SystemCoreClock
- SYSTICK_InternalInit((uint32_t)(1000.0F / (float)OS_TICKS_PER_SEC)); // init os tick timer
- SYSTICK_IntCmd(ENABLE); // enable os tick timer interrupt
- SYSTICK_Cmd(ENABLE); // enable os tick timer counter
- memset((uint8 *)OSTaskIdleStk, 0x12, sizeof(OSTaskIdleStk));
- memset((uint8 *)os_task1_stk, 0x34, sizeof(os_task1_stk));
- memset((uint8 *)OSTaskIdleStk, 0x56, sizeof(OSTaskIdleStk));
- memset((uint8 *)OSTmrTaskStk, 0x78, sizeof(OSTmrTaskStk));
- memset((uint8 *)OS_CPU_ExceptStk, 0x9A, sizeof(OS_CPU_ExceptStk));
- memset(App_Task_Ethernetif_Input_Stk, 0xBC, sizeof(App_Task_Ethernetif_Input_Stk));
- memset(((uint8 *)LwIP_Task_Stk), 0xDE, LWIP_STK_SIZE*4);
- memset(((uint8 *)LwIP_Task_Stk)+LWIP_STK_SIZE*4, 0xF1, LWIP_STK_SIZE*4);
- memset(((uint8 *)LwIP_Task_Stk)+LWIP_STK_SIZE*4*2, 0xE2,LWIP_STK_SIZE*4);
- OSInit(); // init uc/os-ii
- // create tasks
- OSTaskCreate(task1, (void *)0, &os_task1_stk[sizeof(os_task1_stk)/sizeof(os_task1_stk[0]) - 1], os_task1_prio);
-
- // lets go now
- OSStart();
- }
复制代码 问题就在于在进入第一个任务之前初始化了心跳中断,这在ucos中是大忌啊,因为心跳中断会处理ucos心跳相关的事务,而整个系统的一些变量和数据结构都还没有进行初始化好,表现在还没有调用OS_init。程序甚至还跑到main后面去了。其实我忽略了系统一开始使用的是__initial_sp之后的那1K的内存。
这个工程是我从论坛上找的,那么正确的初始化过程应该是如下,然后再在第一个任务中初始化心跳中断。
- int main(void)
- {
- NVIC_Configuration();
-
- memset((uint8 *)OSTaskIdleStk, 0x12, sizeof(OSTaskIdleStk));
- memset((uint8 *)os_task1_stk, 0x34, sizeof(os_task1_stk));
- memset((uint8 *)os_task3_stk, 0x45, sizeof(os_task1_stk));
- memset((uint8 *)OSTaskIdleStk, 0x56, sizeof(OSTaskIdleStk));
- memset((uint8 *)OSTmrTaskStk, 0x78, sizeof(OSTmrTaskStk));
- memset((uint8 *)OS_CPU_ExceptStk, 0x9A, sizeof(OS_CPU_ExceptStk));
- memset(App_Task_Ethernetif_Input_Stk, 0xBC, sizeof(App_Task_Ethernetif_Input_Stk));
- memset(((uint8 *)LwIP_Task_Stk), 0xDE, LWIP_STK_SIZE*4);
- memset(((uint8 *)LwIP_Task_Stk)+LWIP_STK_SIZE*4, 0xF1, LWIP_STK_SIZE*4);
- memset(((uint8 *)LwIP_Task_Stk)+LWIP_STK_SIZE*4*2, 0xE2,LWIP_STK_SIZE*4);
-
- OSInit(); // init uc/os-ii
- // create tasks
- OSTaskCreate(task1, (void *)0, &os_task1_stk[sizeof(os_task1_stk)/sizeof(os_task1_stk[0]) - 1], os_task1_prio);
-
- // lets go now
- OSStart();
-
- while(1);
- }
复制代码
4、拓展阅读
其他的堆栈溢出的情况应该可以用我上面使用的方法检测出来,如果是进入hardfault,以下还有我之前在写书籍《μCOS-III源码分析笔记》中做的一个堆栈溢出的实验,也来跟大家进行分享。
上面引用的内容摘抄自本人前些日子跟野火一起合作出版的,非本人同意请勿转载,书籍详情有兴趣请看野火发的帖子,里面有前三章的下载。
http://www.amobbs.com/thread-5641893-1-1.html
|