[学习笔记] UCOS III通过M3内核MPU模块实现任务栈溢出检测

[复制链接]
3455|18
 楼主| JasonLee27 发表于 2022-3-1 17:40 | 显示全部楼层 |阅读模式
硬件环境: AC7811通用开发板  ATC-LINK
软件环境:keil 5.23

前段时间有客户提到AC7811怎么实现栈溢出检测,便想到使用MPU模块在栈顶设置一段禁用区,以在栈使用溢出时访问到禁用区,产生memory manage fault,从而进行下一步的处理。再便想到可以通过该功能来实现带操作系统的用户栈溢出检测功能。

MPU的配置:
MPU可以依据实际的使用情况灵活配置,我这里为了简便,便创建了8个任务,每个任务都使用512字节的栈空间,这样便于我配置MPU。
8608621de3f0467eb.png
MPU的配置如下:
  1.     if ((MPU->TYPE & 0x800) == 0)   ///<do not support MPU
  2.     {
  3.         return;
  4.     }
  5.    
  6.     MPU->CTRL = 0x04;  ///<enable backround region, disable MPU
  7.    
  8.     MPU->RNR = 0;   ///<select region 0
  9.     MPU->RBAR = 0x2000E000;
  10.     MPU->RASR = 0x07000017;
  11.    
  12.     MPU->RNR = 1;   ///<select region 1
  13.     MPU->RBAR = 0x2000E000;
  14.     MPU->RASR = 0x03000017;
  15.    
  16.     MPU->CTRL |= 1;
  17.    
  18.    
  19.     SCB->SHCSR |= 1 << 16;  ///<enable memory manage interrupt
通过sct文件将8个任务的栈空间都存放再0x2000E000开始的位置,这里配置了两个region,启用了背景region。region0 用于配置该区域为只读。region1配置该区域可读可写,两个region重叠,因此该区域最终是可读可写的。

任务切换:
  1. void  App_OS_TaskSwHook (void)
  2. {
  3.     if (OSTCBHighRdyPtr->ExtPtr != 0)
  4.     {
  5.         MPU->RNR = 1;   ///<select region 0
  6.         MPU->RBAR = 0x2000E000;
  7.         MPU->RASR = *((uint32_t *)OSTCBHighRdyPtr->ExtPtr);
  8.         
  9.     }        
  10. }


进行任务切换时,在任务切换hook中,重新设置region1,主要为了修改子region。按内核手册的说法,一个region分为8个子region,可以通过SRD寄存器除能响应的子region。因此我们每要切换一个任务时,应该除能除此任务外的其他任务栈的写入权限。这里的配置参数我们通过OS提供的OSTCBHighRdyPtr->ExtPtr参数传入。
  1. static const uint32_t AppTask1Ext = 0x0300FD17;
  2. static const uint32_t AppTask2Ext = 0x0300FB17;
  3. static const uint32_t AppTask3Ext = 0x0300F717;
  4. static const uint32_t AppTask4Ext = 0x0300EF17;
  5. static const uint32_t AppTask5Ext = 0x0300DF17;
  6. static const uint32_t AppTask6Ext = 0x0300BF17;
  7. static const uint32_t AppTask7Ext = 0x03007F17;
该参数在创建任务的时候传入,每个任务只使能自己的子region,除能其他任务的子region。这样其他子region无效,对应的其他栈便适用region0的规则,变成了只读(这里设置只读是因为OS的STAT任务有对各任务栈使用情况进行统计,如果直接禁止访问会导致该任务无法运行)。
对于操作系统自带的任务,我们不纳入管理范围,通过OSTCBHighRdyPtr->ExtPtr的非零判断,可以排除操作系统自己的任务。
最后,在OS进行任务切换时,会同时使用到当前任务栈和就绪的最高优先级任务栈,为了解决此时同时使用两个栈的问题,我在这里暂时关闭了MPU:
  1. OS_CPU_PendSVHandler
  2.     CPSID   I                                                   ; Prevent interruption during context switch
  3. ;add by jason for mpu
  4.     LDR     R1, =0xE000ED94
  5.     LDR     R0, =0x00000004
  6.     STR     R0, [R1]
  7. ;end
  8.     MRS     R0, PSP                                             ; PSP is process stack pointer
  9.     CBZ     R0, OS_CPU_PendSVHandler_nosave                     ; Skip register save the first time

  10.     SUBS    R0, R0, #0x20                                       ; Save remaining regs r4-11 on process stack
  11.     STM     R0, {R4-R11}

  12.     LDR     R1, =OSTCBCurPtr                                    ; OSTCBCurPtr->OSTCBStkPtr = SP;
  13.     LDR     R1, [R1]
  14.     STR     R0, [R1]                                            ; R0 is SP of process being switched out

  15.                                                                 ; At this point, entire context of process has been saved
  16. OS_CPU_PendSVHandler_nosave
  17.     PUSH    {R14}                                               ; Save LR exc_return value
  18.     LDR     R0, =OSTaskSwHook                                   ; OSTaskSwHook();
  19.     BLX     R0
  20.     POP     {R14}

  21.     LDR     R0, =OSPrioCur                                      ; OSPrioCur   = OSPrioHighRdy;
  22.     LDR     R1, =OSPrioHighRdy
  23.     LDRB    R2, [R1]
  24.     STRB    R2, [R0]

  25.     LDR     R0, =OSTCBCurPtr                                    ; OSTCBCurPtr = OSTCBHighRdyPtr;
  26.     LDR     R1, =OSTCBHighRdyPtr
  27.     LDR     R2, [R1]
  28.     STR     R2, [R0]

  29.     LDR     R0, [R2]                                            ; R0 is new process SP; SP = OSTCBHighRdyPtr->StkPtr;
  30.     LDM     R0, {R4-R11}                                        ; Restore r4-11 from new process stack
  31.     ADDS    R0, R0, #0x20
  32.     MSR     PSP, R0                                             ; Load PSP with new process SP
  33.     ORR     LR, LR, #0x04                                       ; Ensure exception return uses process stack
  34.     CPSIE   I
  35. ;add by jason for mpu
  36.     LDR     R1, =0xE000ED94
  37.     LDR     R0, =0x00000005
  38.     STR     R0, [R1]
  39. ;end   
  40.     BX      LR                                                  ; Exception return will restore remaining context

  41.     END
最后就是核心的异常处理了:
  1. void MemManage_Handler(void)
  2. {
  3.     OS_ERR      err;
  4.     printf("%s is overflow\r\n", OSTCBCurPtr->NamePtr);
  5.     OS_RdyListRemove(OSTCBCurPtr);
  6.     OSTaskDel(OSTCBCurPtr, &err);
  7.     OSSched();
  8.     //while(1);
  9. }
这里我在发生栈溢出后,移除并删除了当前任务,同时进行新的任务调度(这里对UCOS III的运行还不熟悉,所以这里使用上比较粗暴直接了,不确定是否会有其他问题)。

测试:
测试的方法也很简单,我在task2中申请了一个大于512字节的局部数组,然后对超过512字节后面的位置进行写入操作。此时的访问应当会落在其他任务的栈空间上,因为其他任务栈空间已经配置只读,所以会产生memmanage fault。
  1. static  void  AppTask2 (void *p_arg)
  2. {
  3.     OS_ERR      err;
  4.     uint32_t testdata[130];  
  5.     p_arg = p_arg;
  6.     while(1)
  7.     {
  8.         printf("Task2\r\n");
  9.         testdata[130] = testdata[130];
  10.         OSTimeDlyHMSM(0, 0, 1, 0,
  11.                       OS_OPT_TIME_HMSM_STRICT,
  12.                       &err);
  13.     }
  14. }
测试log输出如下:
似乎不需要额外的执行testdata[130] = testdata[130];因为printf打印函数运行的时候已经栈溢出了,所以printf函数一旦访问栈空间,就会产生memmanage fault。接着我们删除了task2.因此其他任务还可以照常执行。
  1. Creating Application Tasks...
  2. Creating Application Events...
  3. Task1
  4. App Task2 is overflow
  5. Task3
  6. Task4
  7. Task5
  8. Task6
  9. Task7
  10. Task1
  11. Task3
  12. Task4
  13. Task5
  14. Task6
  15. Task7


总结:这功能想了想感觉实际用途不是很大,但在设计之初,我们总会发生一些意外的栈溢出问题,在OS下会很难定位到具体是哪个任务哪个函数导致了栈溢出。这个方法或许会比较方便大家定位栈溢出问题?当然,这里面还有很多不成熟的地方,比如第一个任务发生溢出的话,溢出的位置小于0x2000E000,不在只读范围内,因此可能还需要在这个区域额外设置一个只读的临界区。

最后附上测试用的代码:
UCOSIII_sample.rar (2.37 MB, 下载次数: 3)
caigang13 发表于 2022-3-1 20:44 来自手机 | 显示全部楼层
感谢分享,还在用ucosII
chenjun89 发表于 2022-3-2 07:48 来自手机 | 显示全部楼层
表示还在用ucosII的举个手。
 楼主| JasonLee27 发表于 2022-3-3 10:17 | 显示全部楼层
chenjun89 发表于 2022-3-2 07:48
表示还在用ucosII的举个手。

只是在UCOS III上做了个测试,其他系统都是可以做的,UCOS III主要是把中断纳入了任务管理
tpgf 发表于 2022-4-1 10:42 | 显示全部楼层
这个系统在哪里可以下载到呢
heimaojingzhang 发表于 2022-4-1 10:52 | 显示全部楼层
会不会占用资源太多呢
keaibukelian 发表于 2022-4-1 11:10 | 显示全部楼层
我还是用的老系统
labasi 发表于 2022-4-1 11:18 | 显示全部楼层
这两个系统的差别是什么呢
paotangsan 发表于 2022-4-1 11:25 | 显示全部楼层
现在这个运行稳定吗
renzheshengui 发表于 2022-4-1 11:31 | 显示全部楼层
会不会读硬件要求更高啊
biechedan 发表于 2022-4-1 12:41 | 显示全部楼层
这个可行吗
wengh2016 发表于 2022-4-1 12:49 | 显示全部楼层
栈溢出不是都会报错吗
updownq 发表于 2022-4-1 13:12 | 显示全部楼层
数组溢出有检测的方法吗
lihuami 发表于 2022-4-1 13:36 | 显示全部楼层
任务栈溢出检测怎么实现的呢
mnynt121 发表于 2022-4-1 14:03 | 显示全部楼层
必须通过MPU模块吗
xiaoyaozt 发表于 2022-4-1 14:40 | 显示全部楼层
会不会 hard fault
foxsbig 发表于 2022-4-9 16:16 | 显示全部楼层
貌似会报错吧
guijial511 发表于 2022-4-11 20:29 来自手机 | 显示全部楼层
这样也行?稳定吗?
yangxiaor520 发表于 2022-4-12 08:06 来自手机 | 显示全部楼层
这样做可靠吗?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

66

主题

415

帖子

12

粉丝
快速回复 在线客服 返回列表 返回顶部