大家在用STM32的时候有没有遇到过HardFault的问题呢:
下面针对这个问题做个小总结。
现象还原:在debug模式下进行仿真调试,全速运行再停止运行,程序会跑到 HardFault_Handler函数中,产生 HardFault,即硬错。其产生的原因大概有如下几类:
(1)数组越界操作;
(2)内存溢出,访问越界;
(3)堆栈溢出,程序跑飞;
(4)中断处理错误;
针对HardFault问题的定位,网上有几种方法,大概都是围绕着:在debug模式下,查看一些地址,分析寄存器、函数调用栈等,这是很让人头疼的事情。这里分享一种简单的、直观的HardFault错误定位的方法,使用开源库:CmBacktrace 。这个库之前已经有介绍过了,这篇笔记我们来实践一下。
CmBacktrace 源码地址:
https://github.com/armink/CmBacktrace
下面说下使用方法,具体使用方法如下:
一、把cm_backtrace文件夹复制到我们的工程目录下,并添加至keil工程中,并添加头文件、勾选C99模式
此时,编译会产生几个错误:
那是因为有些预处理宏没有找到,打开、修改cmb_cfg.h
文件的内容。cmb_cfg.h
文件默认内容为:(我是使用RT-Thread 带系统的)
#ifndef _CMB_CFG_H_
#define _CMB_CFG_H_
/* print line, must config by user */
#define cmb_println(...) rt_kprintf(__VA_ARGS__);rt_kprintf("\r\n") /* e.g., printf(__VA_ARGS__);printf("\r\n") */
/* enable bare metal(no OS) platform */
/* #define CMB_USING_BARE_METAL_PLATFORM */
/* enable OS platform */
#define CMB_USING_OS_PLATFORM
/* OS platform type, must config when CMB_USING_OS_PLATFORM is enable */
#define CMB_OS_PLATFORM_TYPE CMB_OS_PLATFORM_RTT //or CMB_OS_PLATFORM_UCOSII or CMB_OS_PLATFORM_UCOSIII or CMB_OS_PLATFORM_FREERTOS */
/* cpu platform type, must config by user */
#define CMB_CPU_PLATFORM_TYPE CMB_CPU_ARM_CORTEX_M4 /* CMB_CPU_ARM_CORTEX_M0 or CMB_CPU_ARM_CORTEX_M3 or CMB_CPU_ARM_CORTEX_M4 or CMB_CPU_ARM_CORTEX_M7 */
/* enable dump stack information */
/* #define CMB_USING_DUMP_STACK_INFO */
/* language of print information */
#define CMB_PRINT_LANGUAGE CMB_PRINT_LANUUAGE_ENGLISH // CMB_PRINT_LANGUAGE_ENGLISH(default) or CMB_PRINT_LANGUAGE_CHINESE */
#endif /* _CMB_CFG_H_ */
二、在使用了本库提供的 cmb_fault.s 汇编文件时,因为该汇编文件内部已经定义了 HardFault_Handler ,所以如果项目中还有其他地方定义了该函数,则会提示 HardFault_Handler 被重复定义的错误。此时有两种解决方法:
- 1、注释/删除其他文件中定义的
HardFault_Handler
函数,仅保留 cmb_fault.s 中的;(我用的是stm32f302,注释代码如下)将context_rvds.S中最后的HardFault_Handler
注释掉,注意保留最后的END -
; compatible with old version rt_hw_interrupt_thread_switch PROC EXPORT rt_hw_interrupt_thread_switch BX lr ENDP IMPORT rt_hw_hard_fault_exception ;EXPORT HardFault_Handler ;HardFault_Handler PROC ;; get current context ;MRS r0, psp ; get fault thread stack pointer ;PUSH {lr} ;BL rt_hw_hard_fault_exception ;POP {lr} ;ORR lr, lr, #0x04 ;BX lr ;ENDP ;ALIGN 4 END
- 2、将 cmb_fault.s 移除工程,手动添加
cm_backtrace_fault
函数至现有的故障处理函数,但需要注意的是,务必 保证该函数数入参的准备性 ,否则可能会导致故障诊断功能及堆栈打印功能无法正常运行。所以如果是新手,不推荐第二种解决方法。
三、可能会有提示uint32_t重复定义的错误,这个问题可能不太好找。原因是cmb_def.h中引用了arm中的stdint.h和工程里的stdint.h中同时定义了uint32_t,解决方法为:将#include <stdint.h>改为#include "stdint.h"即可,具体可以研究下include<>与include“”的区别。
四、将一下两行代码 放在void init_thread_entry(void* parameter)中进行初始化。
cm_backtrace_init("CmBacktrace", HARDWARE_VERSION, SOFTWARE_VERSION);
/* set exception hook */
rt_hw_exception_install(exception_hook); ‘
同时需要hook函数,如下:
#define HARDWARE_VERSION "V1.0.0"
#define SOFTWARE_VERSION "V0.1.0"
static rt_err_t exception_hook(void *context) {
extern long list_thread(void);
uint8_t _continue = 1;
rt_enter_critical();
#ifdef RT_USING_FINSH
list_thread();
#endif
cm_backtrace_fault(*((uint32_t *)(cmb_get_sp() + sizeof(uint32_t) * 8)), cmb_get_sp() + sizeof(uint32_t) * 9);
while (_continue == 1);
return RT_EOK;
}
至此工程已经配置完毕并且可以编译通过了,我们可以人为制造一个HardFault,比如使用一个没有初始化的信号量。这时候运行程序会有如下的串口打印出来:
Firmware name: CmBacktrace, hardware version: V1.0.0, software version: V0.1.0
Fault on thread uart1_rx€
=================== Registers information ====================
R0 : 200027ac R1 : 20001a2c R2 : 00000000 R3 : 00008000
R12: 00000000 LR : 0800438f PC : 080056f8 PSR: 41000000
==============================================================
Bus fault is caused by imprecise data access violation
Show more call stack info by run: addr2line -e CmBacktrace.axf -a -f 080056f8 0800438e 0800456a 08000a0e
五、
可以看到,列出的信息很详细,包括出错原因。按照它的提示,我们运行命令:
addr2line -e CmBacktrace.axf -a -f 080056f8 0800438e 0800456a 08000a0e
运行这个命令需要用到addr2line.exe工具,这个工具在CmBacktrace源码目录下的tools文件夹中:
在这个文件中进入到cmd窗口,方法:按下Shift键的同时点击鼠标右键:
运行上面那条命令:
但是win10按住shift右键打开控制台运行这个会有问题,因为我们可以写一个bat脚本来运行:
addr2line.exe -e STM32F302_B0.axf -a -f 080056f8 0800438e 0800456a 08000a0e
pause