搜索
bottom↓
回复: 22

ucos堆栈溢出调试经验分享

[复制链接]

出0入10汤圆

发表于 2016-4-5 17:16:04 | 显示全部楼层 |阅读模式
本帖最后由 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的堆栈大小。
  1. Stack_Size      EQU     0x00001000

  2.                 AREA    STACK, NOINIT, READWRITE, ALIGN=3
  3. Stack_Mem       SPACE   Stack_Size
  4. __initial_sp
复制代码
堆栈如此重要,以至于MCU上电复位之后的头等大事就是设置堆栈指针,我们可以看下中断向量表的开始就是我们上面设置的堆栈指针,MCU上电之后首先从中断向量表开始取出堆栈的首地址放到SP。

  1. __Vectors       DCD     __initial_sp              ; Top of Stack
  2.                 DCD     Reset_Handler             ; Reset Handler
  3.                 DCD     NMI_Handler               ; NMI Handler
  4.                 DCD     HardFault_Handler         ; Hard Fault Handler
  5.                 DCD     MemManage_Handler         ; MPU Fault Handler
  6.                 DCD     BusFault_Handler          ; Bus Fault Handler
  7.                 DCD     UsageFault_Handler        ; Usage Fault Handler
  8.                 DCD     0                         ; Reserved
  9.                 DCD     0                         ; Reserved
  10.                 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这里,但是每个任务的堆栈都好好的。接着我无意中发现还没有切换到第一个任务就死掉了,大家看下下面的初始化代码,看能不能看出问题。
  1. int main(void)
  2. {
  3.     NVIC_Configuration();
  4.    
  5.     //SystemInit();   // init system core clock
  6.     SystemCoreClockUpdate();// get SystemCoreClock
  7.     SYSTICK_InternalInit((uint32_t)(1000.0F / (float)OS_TICKS_PER_SEC)); // init os tick timer
  8.     SYSTICK_IntCmd(ENABLE);        // enable os tick timer interrupt
  9.     SYSTICK_Cmd(ENABLE);    // enable os tick timer counter

  10.     memset((uint8 *)OSTaskIdleStk, 0x12, sizeof(OSTaskIdleStk));
  11.     memset((uint8 *)os_task1_stk, 0x34, sizeof(os_task1_stk));
  12.           memset((uint8 *)OSTaskIdleStk, 0x56, sizeof(OSTaskIdleStk));
  13.           memset((uint8 *)OSTmrTaskStk, 0x78, sizeof(OSTmrTaskStk));
  14.           memset((uint8 *)OS_CPU_ExceptStk, 0x9A, sizeof(OS_CPU_ExceptStk));
  15.           memset(App_Task_Ethernetif_Input_Stk, 0xBC, sizeof(App_Task_Ethernetif_Input_Stk));
  16.           memset(((uint8 *)LwIP_Task_Stk), 0xDE, LWIP_STK_SIZE*4);
  17.           memset(((uint8 *)LwIP_Task_Stk)+LWIP_STK_SIZE*4, 0xF1, LWIP_STK_SIZE*4);
  18.           memset(((uint8 *)LwIP_Task_Stk)+LWIP_STK_SIZE*4*2, 0xE2,LWIP_STK_SIZE*4);

  19.     OSInit();    // init uc/os-ii

  20.     // create tasks
  21.     OSTaskCreate(task1, (void *)0, &os_task1_stk[sizeof(os_task1_stk)/sizeof(os_task1_stk[0]) - 1], os_task1_prio);
  22.    
  23.     // lets go now
  24.     OSStart();
  25. }
复制代码
问题就在于在进入第一个任务之前初始化了心跳中断,这在ucos中是大忌啊,因为心跳中断会处理ucos心跳相关的事务,而整个系统的一些变量和数据结构都还没有进行初始化好,表现在还没有调用OS_init。程序甚至还跑到main后面去了。其实我忽略了系统一开始使用的是__initial_sp之后的那1K的内存。
这个工程是我从论坛上找的,那么正确的初始化过程应该是如下,然后再在第一个任务中初始化心跳中断。
  1. int main(void)
  2. {
  3.           NVIC_Configuration();
  4.          
  5.           memset((uint8 *)OSTaskIdleStk, 0x12, sizeof(OSTaskIdleStk));
  6.     memset((uint8 *)os_task1_stk, 0x34, sizeof(os_task1_stk));
  7.           memset((uint8 *)os_task3_stk, 0x45, sizeof(os_task1_stk));
  8.           memset((uint8 *)OSTaskIdleStk, 0x56, sizeof(OSTaskIdleStk));
  9.           memset((uint8 *)OSTmrTaskStk, 0x78, sizeof(OSTmrTaskStk));
  10.           memset((uint8 *)OS_CPU_ExceptStk, 0x9A, sizeof(OS_CPU_ExceptStk));
  11.           memset(App_Task_Ethernetif_Input_Stk, 0xBC, sizeof(App_Task_Ethernetif_Input_Stk));
  12.           memset(((uint8 *)LwIP_Task_Stk), 0xDE, LWIP_STK_SIZE*4);
  13.           memset(((uint8 *)LwIP_Task_Stk)+LWIP_STK_SIZE*4, 0xF1, LWIP_STK_SIZE*4);
  14.           memset(((uint8 *)LwIP_Task_Stk)+LWIP_STK_SIZE*4*2, 0xE2,LWIP_STK_SIZE*4);
  15.          
  16.     OSInit();    // init uc/os-ii

  17.     // create tasks
  18.     OSTaskCreate(task1, (void *)0, &os_task1_stk[sizeof(os_task1_stk)/sizeof(os_task1_stk[0]) - 1], os_task1_prio);
  19.    
  20.     // lets go now
  21.     OSStart();
  22.                
  23.                 while(1);
  24. }
复制代码

4、拓展阅读
其他的堆栈溢出的情况应该可以用我上面使用的方法检测出来,如果是进入hardfault,以下还有我之前在写书籍《μCOS-III源码分析笔记》中做的一个堆栈溢出的实验,也来跟大家进行分享。

上面引用的内容摘抄自本人前些日子跟野火一起合作出版的,非本人同意请勿转载,书籍详情有兴趣请看野火发的帖子,里面有前三章的下载。
http://www.amobbs.com/thread-5641893-1-1.html

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入0汤圆

发表于 2016-4-5 18:02:08 | 显示全部楼层
我都不知道野火有分享ucosIII的书。。。哈哈

出0入10汤圆

 楼主| 发表于 2016-4-5 20:32:09 | 显示全部楼层
WM_CH 发表于 2016-4-5 18:02
我都不知道野火有分享ucosIII的书。。。哈哈

只有一个人顶。

出0入0汤圆

发表于 2016-4-5 20:54:49 | 显示全部楼层
看起来蛮不错的样子哦

出0入0汤圆

发表于 2016-4-6 00:00:53 | 显示全部楼层
学习了,,,,后面应该会用到

出5入8汤圆

发表于 2016-4-6 08:41:46 | 显示全部楼层
学习了。
貌似新版的uCOS 自带了堆栈的红灯区检测。

出100入101汤圆

发表于 2016-4-6 12:51:40 | 显示全部楼层
freeRTOS有用么?稳定么?

出0入0汤圆

发表于 2016-4-6 13:06:12 | 显示全部楼层
很好的资料

出0入0汤圆

发表于 2016-4-6 13:52:06 | 显示全部楼层
mark一下

出0入0汤圆

发表于 2016-4-10 14:40:25 | 显示全部楼层
向有经验的人学习是必须的!支持!!顶!!想问一下楼主,如何自学网络编程?例如lwIP

出0入10汤圆

 楼主| 发表于 2016-4-10 14:44:05 | 显示全部楼层
Yvan 发表于 2016-4-10 14:40
向有经验的人学习是必须的!支持!!顶!!想问一下楼主,如何自学网络编程?例如lwIP ...

买一本老衲五木的书,然后有项目最好,没有项目就自己做一个弄着玩,一步一步地解决问题,后面就会了。

出0入0汤圆

发表于 2016-4-10 15:00:01 | 显示全部楼层
mark一下

出0入0汤圆

发表于 2016-4-10 15:12:20 | 显示全部楼层
10xjzheng 发表于 2016-4-10 14:44
买一本老衲五木的书,然后有项目最好,没有项目就自己做一个弄着玩,一步一步地解决问题,后面就会了。 ...

那应该要买一款开发板玩一玩比较好。有没有好的推荐呢?

出0入0汤圆

发表于 2016-4-10 15:12:35 | 显示全部楼层
不错不错

出0入0汤圆

发表于 2016-4-10 15:16:59 | 显示全部楼层
收藏了 好东西 必须顶起

出0入0汤圆

发表于 2016-4-12 19:27:01 | 显示全部楼层
操作系统不懂呢

出0入0汤圆

发表于 2016-4-16 17:05:29 | 显示全部楼层
uc/os 文档已经说明了,在OS没有初始化前,一定不能启动系统节拍。

出0入10汤圆

 楼主| 发表于 2016-4-17 10:56:33 | 显示全部楼层
tabc_123 发表于 2016-4-16 17:05
uc/os 文档已经说明了,在OS没有初始化前,一定不能启动系统节拍。

嗯,当初我是拿别人的工程来用,没有看到

出0入0汤圆

发表于 2016-4-17 12:58:40 | 显示全部楼层

已经买了,看起来还不错!

出0入10汤圆

 楼主| 发表于 2016-4-17 13:47:12 | 显示全部楼层
Yvan 发表于 2016-4-17 12:58
已经买了,看起来还不错!

谢啦,写书不易。

出0入0汤圆

发表于 2016-4-17 14:13:50 | 显示全部楼层
10xjzheng 发表于 2016-4-17 13:47
谢啦,写书不易。

嗯,只是没项目应用ucos,应用起来学很快就学会的,目前只能看看书。

出0入10汤圆

 楼主| 发表于 2016-4-17 14:32:50 | 显示全部楼层
Yvan 发表于 2016-4-17 14:13
嗯,只是没项目应用ucos,应用起来学很快就学会的,目前只能看看书。

对的,坐坐小项目,跑跑就熟悉怎么用了。

出0入0汤圆

发表于 2016-5-4 11:16:49 来自手机 | 显示全部楼层
谢谢分享!很有用的经验。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-27 07:19

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表