ucos堆栈溢出调试经验分享
本帖最后由 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_EXTOS_STK OS_CPU_ExceptStk;的这段空间,如果中断服务函数比较大,记得要将这个空间设置大点。
3、本次现象和调试描述
这次的现象非常之离奇,时不时就会进入hardfault,经验告诉我可能是堆栈溢出了。
关于堆栈检测,ucos首先会将任务堆栈全部初始化为0,然后每次检查堆栈使用情况的时候从堆栈底部开始计数看内存为0就视为没有使用过此内容,但是如果用户想要将使用率其打印出来来看看是否堆栈溢出,这有时是不可取的,因为堆栈溢出的时候可能程序已经不知道跑到哪里去了。
freeRTOS有两种方法,方法1:检查系统的SP,看有没有超出堆栈的范围方法2:在堆栈的最后放上16个特定的值,如果检测到他们被修改了,那说明就已经发生堆栈溢出。
我使用的方法是将进入硬件调试,将断点定在hardfault入口之前,查看所有的任务的堆栈末尾是否为0(包括中断使用的堆栈OS_CPU_ExceptStk;),经过一番调整各个任务堆栈的大小,还是很奇怪的是发现会到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) - 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) - 1], os_task1_prio);
// lets go now
OSStart();
while(1);
}
4、拓展阅读
其他的堆栈溢出的情况应该可以用我上面使用的方法检测出来,如果是进入hardfault,以下还有我之前在写书籍《μCOS-III源码分析笔记》中做的一个堆栈溢出的实验,也来跟大家进行分享。
上面引用的内容摘抄自本人前些日子跟野火一起合作出版的,非本人同意请勿转载,书籍详情有兴趣请看野火发的帖子,里面有前三章的下载。
http://www.amobbs.com/thread-5641893-1-1.html
我都不知道野火有分享ucosIII的书。。。哈哈 WM_CH 发表于 2016-4-5 18:02
我都不知道野火有分享ucosIII的书。。。哈哈
只有一个人顶。 看起来蛮不错的样子哦 学习了,,,,后面应该会用到 学习了。
貌似新版的uCOS 自带了堆栈的红灯区检测。 freeRTOS有用么?稳定么? 很好的资料 mark一下 向有经验的人学习是必须的!支持!!顶!!想问一下楼主,如何自学网络编程?例如lwIP Yvan 发表于 2016-4-10 14:40
向有经验的人学习是必须的!支持!!顶!!想问一下楼主,如何自学网络编程?例如lwIP ...
买一本老衲五木的书,然后有项目最好,没有项目就自己做一个弄着玩,一步一步地解决问题,后面就会了。 mark一下 10xjzheng 发表于 2016-4-10 14:44
买一本老衲五木的书,然后有项目最好,没有项目就自己做一个弄着玩,一步一步地解决问题,后面就会了。 ...
那应该要买一款开发板玩一玩比较好。有没有好的推荐呢? 不错不错 收藏了 好东西 必须顶起 操作系统不懂呢 uc/os 文档已经说明了,在OS没有初始化前,一定不能启动系统节拍。 tabc_123 发表于 2016-4-16 17:05
uc/os 文档已经说明了,在OS没有初始化前,一定不能启动系统节拍。
嗯,当初我是拿别人的工程来用,没有看到 10xjzheng 发表于 2016-4-5 20:32
只有一个人顶。
已经买了,看起来还不错! Yvan 发表于 2016-4-17 12:58
已经买了,看起来还不错!
谢啦,写书不易。 10xjzheng 发表于 2016-4-17 13:47
谢啦,写书不易。
嗯,只是没项目应用ucos,应用起来学很快就学会的,目前只能看看书。 Yvan 发表于 2016-4-17 14:13
嗯,只是没项目应用ucos,应用起来学很快就学会的,目前只能看看书。
对的,坐坐小项目,跑跑就熟悉怎么用了。 谢谢分享!很有用的经验。
页:
[1]