stm32 hard fault及堆栈探究
函数一个循环执行完毕时。因此有必要看一下汇编,了解具体对寄存器和内存的数据读写操作:
- 208:if(TimeDisplay==1)
- 209:{
- 0x08000E724C05LDRr4,[pc,#20];@0x08000E88
- 210:uint32_tCounter=0;
- 0x08000E742500MOVSr5,#0x00
- 0x08000E766820LDRr0,[r4,#0x00]
- 0x08000E782801CMPr0,#0x01
- 0x08000E7AD1FCBNE0x08000E76
- 211:Counter=RTC_GetCounter();
- 0x08000E7CF7FFFE2CBL.WRTC_GetCounter(0x08000AD8)
- 212:Time_Display(Counter);
- 0x08000E80F7FFFF98BL.WTime_Display(0x08000DB4)
- 213:TimeDisplay=0;
- 0x08000E846025STRr5,[r4,#0x00]
- 214:}
- 0x08000E86E7F6B0x08000E76
在这一段中,R4存放变量TimeDisplay的地址,R0为TimeDisplay的值。循环的最后一步,寄存器R4中的地址加0作为新地址,R5从内存中的该新地址取值存入。如果R4指向的地址非法,则读取该地址很有可能产生hard fault。
2.查看Time_Display()的汇编
(1)添加了显示变量地址的代码,而无hard fault的情况。
主循环的起始部分汇编代码如下,每次进入循环只需将Time_Display()时入栈的回退地址弹出作为PC。
- 200:voidTime_Show(void)
- 0x08000E44B009ADDsp,sp,#0x24
- 0x08000E46BD00POP{pc}
- 0x08000E48517FSTRr7,[r7,r5]
刚进入Time_Display()时的汇编代码如下,进入时将R0和LR寄存器压入栈中。
- 165:voidTime_Display(uint32_tTimeVar)
- 0x08000DACE8BD4010POP{r4,lr}
- 0x08000DB0F7FFBEEAB.WRTC_WaitForLastTask(0x08000B88)
- 166:{
- 0x08000DB4B501PUSH{r0,lr}
- 0x08000DB6B088SUBsp,sp,#0x20
- 167:uint32_tTHH=0,TMM=0,TSS=0;
- 168:charbuf[10];
此时STM32芯片寄存器和内存的情况如下图所示。


根绝汇编中把R0和LR压入栈中的指令,对应LR和R0的值,在局部变量所在内存空间寻找,可以发现LR最先入栈,接着是函数参数和其余变量,这和最开始打印出的各变量地址也是吻合的。因此,如果buf越界不是太多,只是改写了其余局部变量的数据,不影响回退地址。另外,查看函数所有汇编代码,没有对R4的操作。至函数执行完成并返回,R4的值始终为0x20000000。综上,函数可以继续执行而不会出错。
(2)产生hard fault的情况。
主循环的起始部分汇编代码如下,需要在Time_Display()后的寄存器值和回退地址都弹出。
- 196:voidTime_Show(void)
- 0x08000D90BD1FPOP{r0-r4,pc}
- 0x08000D94517FSTRr7,[r7,r5]
刚进入Time_Display()时的汇编代码如下,将R0-R4,及LR都压入栈中。
- 165:voidTime_Display(uint32_tTimeVar)
- 0x08000D40E8BD4010POP{r4,lr}
- 0x08000D44F7FFBEEAB.WRTC_WaitForLastTask(0x08000B1C)
- 166:{
- 167:uint32_tTHH=0,TMM=0,TSS=0;
- 168:charbuf[10];
- 169:/*ResetRTCCounterwhenTimeis23:59:59*/
- 0x08000D48B51FPUSH{r0-r4,lr}
- 0x08000D4A4604MOVr4,r0
此时STM32芯片寄存器和内存的情况如下图所示。

此时buf的地址为0x200003ec,即R1的起始位置。变量和寄存器值的覆盖关系,或许是编译器检测到R1~R3的值在出栈后将不会被使用,而对内存进行的优化。此时内存中没有其他局部变量的位置,是因为在改动了代码的情况下,编译器判断为,只需在寄存器里就可以完成计算操作,因此改变了函数的汇编代码,没有占用内存空间。buf的赋值是按从低地址到高地址的顺序进行的。从内存的分配图中可以看出,如果buf越界,数组元素超过12个,就将影响到R4的内容。而如1中所述,R4的内容是Time_Display()退出后,需要读取的内存地址。如果经sprintf()后,buf内有15个字符,加上0x00,共16个字符,正好完全覆盖R4,且R4的最高位为0x00,显然是一个非法的内存空间,因此将进入hard fault。如果buf内的字符数落在(12,16)区间内,R4的地址合法(仍为0x20开头),不会进入hard fault,但地址已被修改,错误的内存空间中数值未知,程序跑飞。这些分析与实际测试结果是一致的。
问题得到了解释,也不知花了一天时间分析这些值不值。出错与否,除了程序本身的正确以外,编译器将C翻译成汇编的发挥程度也是很大的决定因素。想避免这些头疼的问题,结论就一句话:数组不要越界。
stm32hardfault堆 相关文章:
- Windows CE 进程、线程和内存管理(11-09)
- RedHatLinux新手入门教程(5)(11-12)
- uClinux介绍(11-09)
- openwebmailV1.60安装教学(11-12)
- Linux嵌入式系统开发平台选型探讨(11-09)
- Windows CE 进程、线程和内存管理(二)(11-09)
