跳转到主要内容

基于CmBacktrace库,如何快速追踪和定位产生HardFault的原因

cathy 提交于

<strong><font color="#4e5e9e">概述</font> </strong>

在使用ARM Cortex-M系列MCU时(如AT32 MCU),有时会出现程序运行异常。当通过编译器在debug模式查原因时,会发现程序跑到HardFault_Handler函数中,产生HardFault,即硬件错误。

<center><img src="http://mcu.eetrend.com/files/2022-06/wen_zhang_/100560992-256546-1.png&…; alt=“图1. HardFault_Handler函数"></center><center><i>图1. HardFault_Handler函数</i></center>

本文档主要介绍一种基于CmBacktrace库,快速追踪和定位产生HardFault原因的方法。

<strong><font color="#4e5e9e">HardFault产生原因</font> </strong>

常见产生HardFault产生的原因大概有如下几类:

<ul>
<li>
<p>数组越界操作;</p>
</li>
<li>
<p>内存溢出,访问越界;</p>
</li>
<li>
<p>堆栈溢出,程序跑飞;</p>
</li>
<li>
<p>中断处理错误。</p>
</li>
</ul>

<strong>数组越界</strong>

程序中使用了静态数组,而在动态传参时数组赋值溢出。或者动态分配内存太小,导致程序异常。

<strong>内存溢出</strong>

重点检查RAM区域,程序编译后执行的RAM数据量大小为多少是否可能越界。一般不要设置到极致的情况,程序中的一些动态数组传参时会导致异常。

<strong>堆栈溢出</strong>

这在使用操作系统的代码中尤其容易发生,在操作系统中,任务的变量均分配放置在任务所申请的堆栈空间中。

例如FreeRTOS中调用xTaskCreate来创建任务,该函数以参数usStackDepth指定任务堆栈的大小,如果指定的堆栈太小,则会堆栈申请不足,进入HardFault。

<strong>中断处理异常</strong>

程序中开启了某些中断,例如USART,TIMER,RTC等。

但在程序执行中,满足中断条件,但并未能查找到该部分对应的中断服务函数,则可能会出现该异常。

<strong><font color="#4e5e9e">HardFault分析方法</font> </strong>

常见的分析方法是:发生异常之后可首先查看LR寄存器中的值,确定当前使用堆栈为MSP或PSP,然后找到相应堆栈的指针,并在内存中查看相应堆栈里的内容。由于异常发生时,内核将R0~R3、R12 Returnaddress、PSR、LR寄存器依次入栈,其中Return address即为发生异常前PC将要执行的下一条指令地址。

但以上方法要求对ARM内核比较熟悉,且操作较为繁琐。

以下重点介绍采用开源库CmBacktrace作为快速分析的方法。

<strong>基于CmBacktrace库分析方法</strong>

CmBacktrace(Cortex Microcontroller Backtrace)是一款针对ARM Cortex-M系列MCU的错误代码自动追踪、定位,错误原因自动分析的开源库。主要特性如下:

<ul>
<li>
<p>支持的错误包括:</p>
<p> 1) 断言(Assert)</p>
<p> 2) 故障(Hard Fault, Memory Management Fault, Bus Fault, Usage Fault, Debug Fault)</p>
</li>
<li>
<p>故障原因自动诊断:可在故障发生时,自动分析出故障的原因,定位发生故障的代码位置,而无需再手动分析繁杂的故障寄存器;</p>
</li>
<li>
<p>适配Cortex-M0/M3/M4/M7 MCU;</p>
</li>
<li>
<p>支持IAR、KEIL、GCC编译器;</p>
</li>
<li>
<p>支持FreeRTOS、UCOSII、UCOSIII、RT-Thread等OS;</p>
</li>
</ul>

<strong>基于MDK的CmBacktrace库使用流程</strong>

基于MDK的移植方法按如下步骤进行:

步骤一 添加cm_backtrace库文件到MDK中

<center><img src="http://mcu.eetrend.com/files/2022-06/wen_zhang_/100560992-256547-2.png&…; alt=“图2. cm_backtrace库文件夹"></center><center><i>图2. cm_backtrace库文件夹</i></center>

把cm_backtrace文件夹复制到我们的工程目录下,并添加至keil工程中。

<center><img src="http://mcu.eetrend.com/files/2022-06/wen_zhang_/100560992-256548-3.png&…; alt=“图3. 添加cm_backtrace后keil工程目录"></center><center><i>图3. 添加cm_backtrace后keil工程目录</i></center>

步骤二 添加头文件、勾选C99模式

<center><img src="http://mcu.eetrend.com/files/2022-06/wen_zhang_/100560992-256549-4.png&…; alt=“图4. Keil中配置C99和头文件"></center><center><i>图4. Keil中配置C99和头文件</i></center>

步骤三 编译和调试

首先,cmb_cfg.h文件按以下提示配置修改。

<center><img src="http://mcu.eetrend.com/files/2022-06/wen_zhang_/100560992-256550-5.png&…; alt=“图5. cmb_cfg.h文件配置"></center><center><i>图5. cmb_cfg.h文件配置</i></center>

这时候编译有一个错误,这是因为cmb_fault.c与at32f4xx_int.c中的HardFault_Handler函数重复定义:

<center><img src="http://mcu.eetrend.com/files/2022-06/wen_zhang_/100560992-256551-6.png&…; alt=“图6. at32f4xx_it.c编译报错"></center><center><i>图6. at32f4xx_it.c编译报错</i></center>

需要把at32f4xx_int.c中的HardFault_Handler函数屏蔽掉。

<center><img src="http://mcu.eetrend.com/files/2022-06/wen_zhang_/100560992-256552-7.png&…; alt=“图7. HardFault_Handler函数屏蔽"></center><center><i>图7. HardFault_Handler函数屏蔽</i></center>

步骤四 测试与查看

这时候就可以编译通过了。下面测试这个库的功能。

测试函数如下:

<center><img src="http://mcu.eetrend.com/files/2022-06/wen_zhang_/100560992-256553-8.png&…; alt=“图8. 编写除零错误函数"></center><center><i>图8. 编写除零错误函数</i></center>

然后在主函数中调用cm_backtrace_init();来初始化cm_backtrace,并调用该测试函数:

<center><img src="http://mcu.eetrend.com/files/2022-06/wen_zhang_/100560992-256554-9.png&…; alt=“图9. main函数调用除零错误函数"></center><center><i>图9. main函数调用除零错误函数</i></center>

下载运行程序,PC端接收串口信息:

<center><img src="http://mcu.eetrend.com/files/2022-06/wen_zhang_/100560992-256555-10.png…; alt=“图10. 串口助手输出错误信息"></center><center><i>图10. 串口助手输出错误信息</i></center>

可以看到,列出了出错原因(除0)和一条命令。运行这个命令需要用到addr2line.exe工具,该工具在tools文件夹中:

<center><img src="http://mcu.eetrend.com/files/2022-06/wen_zhang_/100560992-256556-11.png…; alt=“图11. 定位addr2line.exe位置"></center><center><i>图11. 定位addr2line.exe位置</i></center>

有32bit和64bit两个版本,根据环境选择,并拷贝到keil工程目录下的.axf文件所在的文件夹中,如demo中所附工程,则拷贝到如下目录:

AN0028_SourceCode_V2.0.0\utilities\AN0028_demo\non_os\mdk_v5\objects

<center><img src="http://mcu.eetrend.com/files/2022-06/wen_zhang_/100560992-256557-12.png…; alt=“图12. 拷贝addr2line.exe工具"></center><center><i>图12. 拷贝addr2line.exe工具</i></center>

进入到cmd窗口,转到上述文件夹位置,运行串口助手中的那条命令:

addr2line -e CmBacktrace(此处要依据用户的工程名修改).axf -a -f 080019c6 08001ae9

如demo中工程名为printf,命令则应修改为addr2line -e printf.axf -a -f 080019c6 08001ae9

<center><img src="http://mcu.eetrend.com/files/2022-06/wen_zhang_/100560992-256558-13.png…; alt=“图13. 调用CMD运行addr2line.exe工具"></center><center><i>图13. 调用CMD运行addr2line.exe工具</i></center>

可以看到addr2line.exe工具定位出了错误相关的代码行号,查看对应行的代码:

可以看到addr2line.exe工具定位出了错误相关的代码行号,main.c的第60行,fault_test.c的第38行,查看对应行的代码:

<center><img src="http://mcu.eetrend.com/files/2022-06/wen_zhang_/100560992-256559-14.png…; alt=“图14. 确认错误代码区域"></center><center><i>图14. 确认错误代码区域</i></center>

可见,对应的行号正是出错的地方,使用这个CmBacktrace库能帮助用户有效、快速地定位到HardFault之类的错误。

<strong>案例展示</strong>

案例一 无OS除零错误

工程位置:AN0028_SourceCode_V2.0.0\utilities\AN0028_demo\non_os

测试内容:在裸机上除零错误

案例二 FreeRTOS上除零错误

工程位置:AN0028_SourceCode_V2.0.0\utilities\AN0028_demo\os\freertos

测试内容:在FreeROTOS上除零错误,需注意tasks.c中有注释/*&lt;Support For CmBacktrace>*/的三处为针对CmBacktrace做出的修改

案例三 USOCⅢ上非对齐访问错误

工程位置:AN0028_SourceCode_V2.0.0\utilities\AN0028_demo\os\ucosiii

测试内容:在UCOSⅢ上非对齐访问错误,需注意os_cfg.h中#define OS_CFG_DBG_EN为1u

来源:<a href="https://mp.weixin.qq.com/s/AuyAIv-ewEk9CMAJ7Fhogg">AT32 MCU 雅特力科技</a>
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。