Keil使用malloc异常进入hardfault调试故障手记

最近在做PID控温的应用,使用了自己写的菜单项目和位置式PID的相关扩展算法。效果还算满意。前几天调试程序的时候就发现在菜单定义页面的CPP文件内增加全局变量时,程序在第二级菜单项目选中时会进入hardfault,开始很纳闷,跟踪发现是malloc直接进入异常处理。想了个变通的方法来解决了问题。那就是把全局变量想办法放到函数里,变成局部变量调用,效果上确实不进hardfault了,标记了下这个错误,后面就开始做其他模块了。今天又开始在菜单项目下新增动态菜单页面,结果万幸,又进hardfault了。跟了一下C代码,还是在malloc执行过程中进入异常。下面就是详细的查错过程。

首先在hardfault上打断点,如期进入hardfault后,在keil的“call stack + locals”中观察进入异常之前的状态,如下图所示:Keil使用malloc异常进入hardfault调试故障手记_第1张图片

在进入hf之前程序运行的地址为0x8009438。在Disassembly窗口中找到相应地址的代码如下:

Keil使用malloc异常进入hardfault调试故障手记_第2张图片

此时代码执行于malloc函数中的LDR指令上。指令的含义是将地址(r5+4)的内容赋给r2。为了明白LDR指令发生了什么异常,我在malloc函数的0x800941c地址打断点,并且跟踪运行结果。发现在0x8009424地址上,r5被赋值为0x270016b8,如图:

Keil使用malloc异常进入hardfault调试故障手记_第3张图片

现在问题比较明朗了,因为在0x8009438处使用了(r5+4)间接寻址,而在0x8009424处,r5被赋值为0x270016b8,此值已经超出了正常内存空间范围,所以进入异常也是理所当然。

接下来继续跟踪r5寄存器的值,在0x8009420地址,执行了一个跳转,单步跟踪到跳转的位置,如图

Keil使用malloc异常进入hardfault调试故障手记_第4张图片

r0被赋值为0x20001660,根据上面跟踪的结果,会有LDR r5,[r0,#0x00],那么,r5出错的根源就在于r0地址中的内容有问题。而r0的地址就是我们跟踪到的0x20001660。

在本案例的RAM配置中,0x20000000 - 0x200016c0之间是全局变量,0x200016c0 - 0x200036c0之间是heap,0x200036c0 - 0x20003ec0之间是stack。

所以被篡改的内存空间属于全局变量范围,我们为了跟踪方便,在keil中找到malloc之前的代码设置断点,单步跟踪,并且监视0x20001660内存单元的数据变化,如图:



在执行到图标绘制函数的时候,0x20001660单元出错了。此时程序在向显示缓冲写入图标数据。找到缓冲地址,发现显示缓冲和0x20001660地址单元是相邻的,到这里,可以初步猜想是绘制图标的时候内存操作越界了。检查内存位图,发现图片取模的时候是按照理想大小8*52像素去计算的,但是实际软件会以液晶驱动最小单元8bits为单位出模。所以实际大小是8*56像素。将绘制函数中的长宽重新设置后,故障解除。

从进入hardfault到内存越界,其实是很平常的问题,不过开始进到hardfault的时候很难想象问题是这样的连锁反应。开始在菜单文件中添加全局变量会让编译器重新分配空闲ram空间。如果运气好,显存和0x20001660地址之间相差大于4.那么就不会进异常。所以问题也变得离奇了一些。

此文乃本人原创,供大家学习参考。


你可能感兴趣的:(硬件开发)