μC/OS-Ⅱ移植之FPU堆栈平衡

http://cration.rcstech.org/embedded/2014/03/29/ucos-stm32f4-port/

μC/OS-Ⅱ移植之FPU堆栈平衡

  2014年03月28日,帮胖子调试毕设的程序,现象是:执行一个STM32的数学库函数(CFFT)后,进入hardfault_handler。以下描述调试流程。

  ①首先猜测,hardfault多半是非法指针导致的,检查了参数的值,发现并无异常。
  ②单步跟踪进出错的函数,发现函数体执行时没有出错,函数返回时,进入hardfault。更细粒度的跟踪时发现,在函数体内执行运算时,函数返回值保存的位置被修改,导致返回时错误。
  ③再次跟踪并观察参数、变量、寄存器的值,发现栈顶指针SP(R13)进入一个全局static数组内部,而这个全局数组正是该函数的参数之一,在执行函数体时,修改了数组内容,从而导致函数返回地址被修改。 进一步观察,发现作为该任务堆栈的数组,和作为函数参数的数组,在地址上是紧邻的(都是全局static数组,编译器在分配地址空间时自然将其紧邻放置)。而在进入任务函数后,发现栈顶指针SP反向溢出了。如下图所示:

 

 

  ④进一步跟踪,试图找出SP是在哪一步反向溢出的。但是由于μC/OS-Ⅱ任务调度比较复杂,这一步不是很顺利。最终利用内存断点找到了任务堆栈反向溢出的位置,在文件os_cpu_a.asm的OS_CPU_PendSVHandler函数的最后一条语句:BX LR。执行BX LR之前,栈顶指针的值都是符合期望的,但执行BX LR后,PC指针切到任务函数头部执行,此时发现SP指针反向溢出了。

 
  1. OS_CPU_PendSVHandler
  2. CPSID I ; Prevent interruption during context switch
  3. MRS R0, PSP ; PSP is process stack pointer
  4. CBZ R0, OS_CPU_PendSVHandler_nosave ; Skip register save the first time
  5.  
  6. SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack
  7. STM R0, {R4-R11}
  8.  
  9. LDR R1, =OSTCBCur ; OSTCBCur->OSTCBStkPtr = SP;
  10. LDR R1, [R1]
  11. STR R0, [R1] ; R0 is SP of process being switched out
  12.  
  13. ; At this point, entire context of process has been saved
  14. OS_CPU_PendSVHandler_nosave
  15. PUSH {R14} ; Save LR exc_return value
  16. LDR R0, =OSTaskSwHook ; OSTaskSwHook();
  17. BLX R0
  18. POP {R14}
  19.  
  20. LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy;
  21. LDR R1, =OSPrioHighRdy
  22. LDRB R2, [R1]
  23. STRB R2, [R0]
  24.  
  25. LDR R0, =OSTCBCur ; OSTCBCur = OSTCBHighRdy;
  26. LDR R1, =OSTCBHighRdy
  27. LDR R2, [R1]
  28. STR R2, [R0]
  29.  
  30. LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->StkPtr;
  31. LDM R0, {R4-R11} ; Restore r4-11 from new process stack
  32. ADDS R0, R0, #0x20
  33. MSR PSP, R0 ; Load PSP with new process SP
  34. ORR LR, LR, #0x04 ; Ensure exception return uses process stack
  35. CPSIE I
  36. BX LR ; Exception return will restore remaining context
  37.  
  38. END

  ⑤查阅手册得知,BX LR指令,在LR寄存器的高16位为0xFFFF时,表示异常返回,返回方式与LR的取值有关。观察LR寄存器,发现其值为0xFFFFFFED,这种情况下,返回时出栈的栈帧大小为26个words(一个word为32位),不开启FPU的情况下是8个words。

 

 

  到此,问题基本上浮出水面了。在任务创建时,μC/OS-Ⅱ“伪造”了一个进入异常的环境,但是在构建环境时没有将FPU相关的寄存器入栈;而在任务调度时,模拟异常返回的情况,却试图在栈中取出FPU相关的寄存器的值,因此导致了栈反向溢出。 任务创建时的堆栈操作代码如下:

 
  1. OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
  2. {
  3. OS_STK *stk;
  4.  
  5.  
  6. (void)opt; /* 'opt' is not used, prevent warning */
  7. stk = ptos; /* Load stack pointer */
  8.  
  9. /* Registers stacked as if auto-saved on exception */
  10. *(stk) = (INT32U)0x01000000uL; // xPSR
  11. *(--stk) = (INT32U)task; // Entry Point
  12. *(--stk) = (INT32U)OS_TaskReturn; // R14 (LR)
  13. *(--stk) = (INT32U)0x12121212uL; // R12
  14. *(--stk) = (INT32U)0x03030303uL; // R3
  15. *(--stk) = (INT32U)0x02020202uL; // R2
  16. *(--stk) = (INT32U)0x01010101uL; // R1
  17. *(--stk) = (INT32U)p_arg; // R0 : argument
  18.  
  19. // Remaining registers saved on process stack
  20. *(--stk) = (INT32U)0x11111111uL; // R11
  21. *(--stk) = (INT32U)0x10101010uL; // R10
  22. *(--stk) = (INT32U)0x09090909uL; // R9
  23. *(--stk) = (INT32U)0x08080808uL; // R8
  24. *(--stk) = (INT32U)0x07070707uL; // R7
  25. *(--stk) = (INT32U)0x06060606uL; // R6
  26. *(--stk) = (INT32U)0x05050505uL; // R5
  27. *(--stk) = (INT32U)0x04040404uL; // R4
  28.  
  29. return (stk);
  30. }

  找到问题后,只需要在创建任务和异常返回时加上FPU寄存器的保护与恢复即可。分别修改函数OSTaskStkInit与OS_CPU_PendSVHandler如下:

 
  1. OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
  2. {
  3. int i = 0;
  4. OS_STK *stk;
  5. (void)opt; /* 'opt' is not used, prevent warning */
  6.  
  7. #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
  8.  
  9. stk = ptos; /* Load stack pointer */
  10.  
  11. /* Registers stacked as if auto-saved on exception */
  12.  
  13. *(stk) = (INT32U)0x00000000uL;
  14. *(--stk) = (INT32U)0x00000000uL;
  15.  
  16. for (i = 0; i < 16; ++i)
  17. {
  18. *(--stk) = (INT32U)(0x00000000uL);
  19. }
  20.  
  21. *(--stk) = (INT32U)0x01000000uL; // xPSR
  22. *(--stk) = (INT32U)task; // Entry Point
  23. *(--stk) = (INT32U)OS_TaskReturn; // R14 (LR)
  24. *(--stk) = (INT32U)0x12121212uL; // R12
  25. *(--stk) = (INT32U)0x03030303uL; // R3
  26. *(--stk) = (INT32U)0x02020202uL; // R2
  27. *(--stk) = (INT32U)0x01010101uL; // R1
  28. *(--stk) = (INT32U)p_arg; // R0 : argument
  29.  
  30. // Remaining registers saved on process stack
  31. *(--stk) = (INT32U)0x11111111uL; // R11
  32. *(--stk) = (INT32U)0x10101010uL; // R10
  33. *(--stk) = (INT32U)0x09090909uL; // R9
  34. *(--stk) = (INT32U)0x08080808uL; // R8
  35. *(--stk) = (INT32U)0x07070707uL; // R7
  36. *(--stk) = (INT32U)0x06060606uL; // R6
  37. *(--stk) = (INT32U)0x05050505uL; // R5
  38. *(--stk) = (INT32U)0x04040404uL; // R4
  39.  
  40. for (i = 0; i < 16; ++i)
  41. {
  42. *(--stk) = (INT32U)(0x00000000uL);
  43. }
  44.  
  45. #else
  46.  
  47. stk = ptos; /* Load stack pointer */
  48.  
  49. /* Registers stacked as if auto-saved on exception */
  50. *(stk) = (INT32U)0x01000000uL; // xPSR
  51. *(--stk) = (INT32U)task; // Entry Point
  52. *(--stk) = (INT32U)OS_TaskReturn; // R14 (LR)
  53. *(--stk) = (INT32U)0x12121212uL; // R12
  54. *(--stk) = (INT32U)0x03030303uL; // R3
  55. *(--stk) = (INT32U)0x02020202uL; // R2
  56. *(--stk) = (INT32U)0x01010101uL; // R1
  57. *(--stk) = (INT32U)p_arg; // R0 : argument
  58.  
  59. // Remaining registers saved on process stack
  60. *(--stk) = (INT32U)0x11111111uL; // R11
  61. *(--stk) = (INT32U)0x10101010uL; // R10
  62. *(--stk) = (INT32U)0x09090909uL; // R9
  63. *(--stk) = (INT32U)0x08080808uL; // R8
  64. *(--stk) = (INT32U)0x07070707uL; // R7
  65. *(--stk) = (INT32U)0x06060606uL; // R6
  66. *(--stk) = (INT32U)0x05050505uL; // R5
  67. *(--stk) = (INT32U)0x04040404uL; // R4
  68.  
  69. #endif
  70.  
  71. return (stk);
  72. }
 
  1. OS_CPU_PendSVHandler
  2. CPSID I ; Prevent interruption during context switch
  3. MRS R0, PSP ; PSP is process stack pointer
  4. CBZ R0, OS_CPU_PendSVHandler_nosave ; Skip register save the first time
  5.  
  6. SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack
  7. STM R0, {R4-R11}
  8.  
  9. #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
  10. SUB R0, R0, #0x40
  11. VSTM R0, {D8-D15}
  12. #endif
  13.  
  14. LDR R1, =OSTCBCur ; OSTCBCur->OSTCBStkPtr = SP;
  15. LDR R1, [R1]
  16. STR R0, [R1] ; R0 is SP of process being switched out
  17.  
  18. ; At this point, entire context of process has been saved
  19. OS_CPU_PendSVHandler_nosave
  20. PUSH {R14} ; Save LR exc_return value
  21. LDR R0, =OSTaskSwHook ; OSTaskSwHook();
  22. BLX R0
  23. POP {R14}
  24.  
  25. LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy;
  26. LDR R1, =OSPrioHighRdy
  27. LDRB R2, [R1]
  28. STRB R2, [R0]
  29.  
  30. LDR R0, =OSTCBCur ; OSTCBCur = OSTCBHighRdy;
  31. LDR R1, =OSTCBHighRdy
  32. LDR R2, [R1]
  33. STR R2, [R0]
  34.  
  35. LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->StkPtr;
  36.  
  37. #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
  38. VLDM R0, {D8-D15}
  39. ADD R0, R0, #0x40
  40. #endif
  41.  
  42. LDM R0, {R4-R11} ; Restore r4-11 from new process stack
  43. ADDS R0, R0, #0x20
  44.  
  45. #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
  46. BIC.W LR, LR, #0x10
  47. #endif
  48.  
  49. MSR PSP, R0 ; Load PSP with new process SP
  50. ORR LR, LR, #0x04 ; Ensure exception return uses process stack
  51. CPSIE I
  52. BX LR ; Exception return will restore remaining context
  53.  
  54. END

References

Making the best use of the available breakpoints
M4在IAR环境下移植ucosii问题
Cortex-M4 Device Generic User Guide: Exception entry and return
移植ucos2 到 STMF4 支持浮点,一点心得
The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors

Post Info

  • Copyright Notice: Creative Commons BY-NC-ND 3.0

你可能感兴趣的:(μC/OS-Ⅱ移植之FPU堆栈平衡)