使用keil分析HardFault的一次实战

本文为原创,转载请注明出处,谢谢。
作者:上寻九天

问题描述

平台:gd32f150 arm系列
调试器:keil v5
问题:当按下按键时,系统进入HardFault,从Call Stack可以看出,系统是从kb_handle进入的HardFault。
Call Stack调出方法:view->Call Stack Window

使用keil分析HardFault的一次实战_第1张图片

找到引起HardFault的语句

进一步解析kb_handle,利用加断点和单步调试等功能,找到出现问题的语句。
这里,当执行到507行时,系统就进入HardFault了。
反复查看代码逻辑,并没有发现任何问题。
使用keil分析HardFault的一次实战_第2张图片
利用keil的watch功能,查看变量的地址,
由于这款芯片只有8k的RAM,因此RAM的地址范围应该是0x20000000 ~ 0x20002000。
可以看到kb_ctx变量的地址是合法的,但是key_map变量的地址为0x2000FFFF,已经超过了RAM的地址范围,因此引发非法地址访问,导致HardFault。
使用keil分析HardFault的一次实战_第3张图片

找到变量被篡改的代码

搜遍整个工程,代码里面的key_map变量只有一次赋值的地方,如下图,43行。因此key_map变量很可能是因为数组越界被覆盖了,或者其他指针指向了这个地址更改了里面的内容。
使用keil分析HardFault的一次实战_第4张图片
配置keil输出map文件。打开工程的map文件,找到key_map变量位置。
从地址上看,key_map位于0x200003cc,紧挨着的低地址变量为_key_map_numlock,这是个数组,占用70个bytes的空间。
为何要查看低地址,因为数组的生长方向是向高地址生长的,越界的话就会覆盖掉key_map。
当然,也不排除高地址往低地址轮询的代码产生的越界,这里面先优先查看低地址的可能。
这里面极有可能是对_key_map_numlock相关操作导致key_map被篡改。
使用keil分析HardFault的一次实战_第5张图片
查看所有操作_key_map_numlock的代码后,排除其他部分,发现了这段代码。
利用单步调试,当执行完这段代码后,key_map的内容就变为0x2000FFFF了。
因此可以确定,该段代码产生了越界。
使用keil分析HardFault的一次实战_第6张图片

分析变量被篡改的原因

这是一段从flash读取数据,并存到数组里面的代码,由于flash烧写是按int型烧写的,因此这里面按int读取。

p_dst初始指向了_key_map_numlock的起始地址,由于p_dst是unsigned int型指针,因此p_dst的步进长度为4个bytes。但是_key_map_numlock是unsigned char型数组。
从map文件看_key_map_numlock只占用70个bytes,因此p_dst最后一次赋值时的地址为0x200003CA,会影响到0x200003CA ~ 0x200003CD这段内存。而key_map的地址为0x200003CC,刚好影响了最低两个bytes。

解决办法

有两种方法可以解决问题。

  1. 可以更改p_dst执行unsigned char*类型。由于arm是小端存储,因此取出的数和按int取出的数内容一致。如果是大端,则需要做字节排序转换,这种方法就比较麻烦。
  2. 将_key_map_numlock按int对齐,如下,这里加入了地址对齐指令,__attribute((aligned (4))),意思是变量地址按4字节对齐,
    使用keil分析HardFault的一次实战_第7张图片
    从map文件可以看到,地址是4的倍数。这样也可以避免越界的问题。
    使用keil分析HardFault的一次实战_第8张图片
    当然,建议采用第一种方法,这是比较靠谱的方法,毕竟那段代码本身就存在越界的可能性。

你可能感兴趣的:(c语言,指针,keil,mdk,arm)