Cortex-M系列HardFault_Handler()最强分析“CmBacktrace”
一、简介先引用一句话 “俗话说,工欲善其事,必先利其器。所以有时候做事效率低的原因也许是,你会用的工具种类太少。”对于从 C51 、MSP430 等简单单片机转而使用更加复杂的 ARM 新人来说,时不时出现的 "hard falut" 死机会让新人瞬间懵掉。定位错误的方法也往往是连接上仿真器,一步步 F10/F11 单步,定位到具体的错误代码,再去猜测、排除、推敲错误原因,或者使用“屏蔽大法”,这
一、简介
先引用一句话 “俗话说,工欲善其事,必先利其器。所以有时候做事效率低的原因也许是,你会用的工具种类太少。”
对于从 C51 、MSP430 等简单单片机转而使用更加复杂的 ARM 新人来说,时不时出现的 "hard falut" 死机会让新人瞬间懵掉。定位错误的方法也往往是连接上仿真器,一步步 F10/F11 单步,定位到具体的错误代码,再去猜测、排除、推敲错误原因,或者使用“屏蔽大法”,这种过程往往浪费几天时间且十分痛苦。
慢慢的大家知道可以通过故障寄存器信息来定位故障原因及故障代码地址,虽然这样能解决一小部分问题,但是重复的、繁琐的分析过程也会耽误很多时间。而且对于一些复杂问题,只依靠代码地址是无法解决的,必须得还原错误现场的函数调用逻辑关系。虽然连接仿真器可以查看到的函数调用栈,但故障状态下是无法显示的,所以还是得一步步 F10/F11 单步去定位错误代码的位置。另外,还有两种场景,
- 1、很多产品真机调试时必须断开仿真器
- 2、问题确实存在,但是极难被重现
所以定位这类问题就显得难上加难。
本次将介绍一个工具“CmBacktrace”:
上述所有问题都迎刃而解,可以将错误信息输出到控制台上,还可以将错误信息使用 EasyFlash 的 Log 功能保存至 Flash 中,设备死机后重启依然能够读取上次的错误信息。CmBacktrace 输出的信息包括函数调用栈、故障诊断结果、堆栈、故障寄存器及产品固件信息,极大的提升了错误定位的效率及准确性。
二、移植(以FreeRTOS+Cortex-M4为例)
1.下载“CmBacktrace”
下载后文件夹如下图所示:
2.拷贝文件夹cm_backtrace到自己的工程目录,如下图所示:
3.根据自己使用的系统、内核修改cmb_cfg.h,如下图所示:
4.在FreeRTOS的task.c中加入以下函数:
/*< Support For CmBacktrace >*/
uint32_t *vTaskStackAddr()
{
return pxCurrentTCB->pxStack;
}
uint32_t vTaskStackSize()
{
#if ( portSTACK_GROWTH > 0 )
return (pxNewTCB->pxEndOfStack - pxNewTCB->pxStack + 1);
#else /* ( portSTACK_GROWTH > 0 )*/
return pxCurrentTCB->uxSizeOfStack;
#endif /* ( portSTACK_GROWTH > 0 )*/
}
char *vTaskName()
{
return pxCurrentTCB->pcTaskName;
}
4.在typedef struct tskTaskControlBlock中添加:
#if( portSTACK_GROWTH <= 0)
UBaseType_t uxSizeOfStack; /*< Support For CmBacktrace >*/
#endif
如下图所示:
5.在函数prvInitialiseNewTask(...)中添加以下代码:
pxNewTCB->uxSizeOfStack = ulStackDepth; /*< Support For CmBacktrace >*/
如下图所示:
6.打开文件FreeRTOS.h,在typedef struct xSTATIC_TCB中添加以下代码:
#if(portSTACK_GROWTH <= 0)
UBaseType_t uxSizeOfStack; /*< Support For CmBacktrace >*/
#endif /* ( portSTACK_GROWTH > 0 )*/
如下图所示:
7,完成以上操作就基本移植完了,然后调用cm_backtrace_init函数就可以实现错误追踪:
比如我调用的地方:在freeRTOS启动调度器时调用
8,有可能会报错找不到_sstack、_stext等类似的错误,那是因为我们工程中的链接脚本没有定义这些变量,实际上cmback的宏定义:
CMB_CSTACK_BLOCK_START;
CMB_CSTACK_BLOCK_END;
CMB_CODE_SECTION_START;
CMB_CODE_SECTION_END;
主要是为了知道栈起始、结束地址和text段起始、结束地址。
因此,要么改这个宏让cmback知道堆栈起止地址和code段起止地址,要么在自己工程中添加这些变量的定义,比如我的工程更改如下:
三、使用范例
1,除零错误范例
在线程中故意给出了除零错误,如下图所示:
编译后shell终端打印除了如下信息:
打印的信息就很全,首先是进入HardFault_Handler()后至少可以知道出错的线程名字,不用像以前一样摸不着头脑,然后可以看到因为什么原因进的HardFault_Handler(),最后此工具给出了出错的代码位置,使用addr2line就可以定位到代码出错位置,例子如下:
复制00016756 0001673a 0001599e,然后在vscode的终端中执行make addr2line,然后在将复制的3个代码粘贴:
然后敲回车可以看到以下信息:
如上图可以看出,出错的代码位置是F:\Project\lc_manage_project\src/thread_data_center_entry.c:229
咱们加入代码的测试位置是:
可以看出,定位的完全没错,一下子就找到了进HardFault_Handler()原因和代码。
2,非对齐访问范例
在线程中故意给出非对齐访问代码,如下图所示:
打印的信息如下:
同样的,出错线程名字、出错原因、出错代码都已经给出,使用addr2line获得的代码行如下:
定位到代码中:
与咱们故意给出的代码行数一致。
3,使用未初始化的指针出错范例
如下线程中定义了局部变量:
而这个变量中有个指针:
此时定义了局部变量后这个指针将是一个随机值,一旦访问立刻就会进入HardFault_Handler(),而之前就产生了这个错误:
这时候编译完以后shell终端打印了如下信息:
shell打印了异常,我一下子就知道了出错的线程是在thread_dlt698or645里面,然后出错的原因是数据总线错误(pc指针错误),然后我使用addr2line定位到了代码的错误行:
然后一下子就知道了出错的原因是访问指针错误:
最终定位到了未初始化的局部变量:
于是修改方法如下:
1,初始化改局部变量:
2,加入防御性代码:
至此,解决了改异常的问题,花费时间不过短短几分钟,如果不会使用这个工具,那么排查该问题将是几个小时甚至花费1天时间来定位。。。
更多推荐
所有评论(0)