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指针反向溢出了。
⑤查阅手册得知,BX LR指令,在LR寄存器的高16位为0xFFFF时,表示异常返回,返回方式与LR的取值有关。观察LR寄存器,发现其值为0xFFFFFFED,这种情况下,返回时出栈的栈帧大小为26个words(一个word为32位),不开启FPU的情况下是8个words。
到此,问题基本上浮出水面了。在任务创建时,μC/OS-Ⅱ“伪造”了一个进入异常的环境,但是在构建环境时没有将FPU相关的寄存器入栈;而在任务调度时,模拟异常返回的情况,却试图在栈中取出FPU相关的寄存器的值,因此导致了栈反向溢出。 任务创建时的堆栈操作代码如下:
找到问题后,只需要在创建任务和异常返回时加上FPU寄存器的保护与恢复即可。分别修改函数OSTaskStkInit与OS_CPU_PendSVHandler如下:
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