目录
2.5 注释原生HardFault_Handler(void)函数
由于手上的一个项目出现了不易复现的问题(板卡直接挂掉,无数据输出),所以打算移植一下CmBacktrace。
因为回溯信息原生只能串口打印出来,但是产品实际运行环境比较独立,无法记录打印的信息,产品实际上是有SD卡且带fatfs的,但是系统崩溃后进hardfaule_handler再重新挂载文件系统,写日志?这个没试过感觉不咋靠谱,所以打算以写flash的形式保存,网上找了一圈没有makefile的都是keil的版本所以就根据收集到的信息自己搞了下,最后也成功移植,踩了一些坑,在这记录一下。
stm32cubemx pack版本:FW_F4 V1.28.1
1.CmBacktrace下载
CmBacktrace就不过多介绍了,github地址:https://github.com/armink/CmBacktrace,我当前的版本是1.4.1
2.移植
把代码库拉下来之后直接复制进我们的项目
2.1 修改makefile
增加源文件,包含头文件
添加.s汇编文件,原始文件路径是CmBacktrace\cm_backtrace\fault_handler\gcc\cmb_fault.s 用于CmBacktrace中断处理,后面会提到,这里要把文件后缀改成小写的s 方便编译, 大写的S后缀stm32cubemx生成得makefile识别不了,编译会报错
添加生成.axf shell命令, .axf文件在错误回溯的时候需要用到,本质和.elf文件一样都是可执行文件,所以只要利用cp命令进行复制改后缀就行
$(BUILD_DIR)/%.axf: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(CP) $< $@
2.2 初始化配置
cm_backtrace_init("CmBacktrace", HARDWARE_VERSION, SOFTWARE_VERSION);
cmb_cfg.h文件
我的配置是:
/*
* This file is part of the CmBacktrace Library.
*
* Copyright (c) 2016, Armink, <armink.ztl@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* 'Software'), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Function: It is the configure head file for this library.
* Created on: 2016-12-15
*/
#ifndef _CMB_CFG_H_
#define _CMB_CFG_H_
#ifdef CMB_USER_CFG
#include "cmb_user_cfg.h"
#else
/* print line, must config by user */
#define cmb_println(...) printf(__VA_ARGS__);printf("\r\n")/* e.g., printf(__VA_ARGS__);printf("\r\n") or SEGGER_RTT_printf(0, __VA_ARGS__);SEGGER_RTT_WriteString(0, "\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_FREERTOS //CMB_OS_PLATFORM_RTT or CMB_OS_PLATFORM_UCOSII or CMB_OS_PLATFORM_UCOSIII or CMB_OS_PLATFORM_FREERTOS or CMB_OS_PLATFORM_RTX5 or CMB_OS_PLATFORM_THREADX */
/* 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 or CMB_CPU_ARM_CORTEX_M33 */
/* enable dump stack information */
#define CMB_USING_DUMP_STACK_INFO
/* language of print information */
#define CMB_PRINT_LANGUAGE CMB_PRINT_LANGUAGE_ENGLISH //CMB_PRINT_LANGUAGE_ENGLISH(default) or CMB_PRINT_LANGUAGE_CHINESE or CMB_PRINT_LANGUAGE_CHINESE_UTF8 */
#endif
#endif /* _CMB_CFG_H_ */
2.3 FreeRTOS修改
tasks.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;
}
/*-----------------------------------------------------------*/
static void prvInitialiseNewTask()函数中添加这一行代码
pxNewTCB->uxSizeOfStack = ulStackDepth; /*< Support For CmBacktrace >*/
typedef struct tskTaskControlBlock 结构体中添加
FreeRTOS.h文件
在 typedef struct xSTATIC_TCB 结构体中添加
2.4 修改ld文件
.text段添加_stext = ., 字段
用户堆栈段添加
2.5 注释原生HardFault_Handler(void)函数
因为我们已经将CmBacktrace的.s文件通过makefile文件链接到我们的工程中了,CmBacktrace有自己的HardFault_Handler中断函数,我们可以直接调用使用,如果不链接cmb_fault.s,就需要我们在HardFault_Handler错误中断中去调用CmBacktrace的错误中断处理函数void cm_backtrace_fault(uint32_t fault_handler_lr, uint32_t fault_handler_sp),但是调用时需要填lr , sp这两个指针参数,为了方便我们直接用CmBacktrace错误处理就好了,把stm32原生的注释掉就行(两种方法本质上是一样的),具体可以看作者的说明
stm32f4xx_it.c文件中注释掉
.h文件中的也注释掉
make编译一下,没有错误就是可以的
2.6 添加错误回溯信息flash写入
分析了一下源码 ,简单来说就是进错误中断后,根据osTCB获取当前执行线程的状态包括堆栈信息,psp指针等,然后获取一些堆栈相关寄存器,根据寄存器值判断错误类型,结合之前的错误地址然后将其打印出来;然后利用addline工具,结合cm_backtrace信息和.axf可执行文件解析出具体哪一个文件哪个函数哪一行出现了问题,官方demo的打印信息分析,所以我们只要把错误类型和追溯地址写入flash就行,本文章就只保存这些,至于堆栈信息和相关寄存器信息看个人需要吧,看具体情况。
也就是fault_diagnosis()函数中的打印信息
还有print_call_stack()函数中的打印信息
fault_diagnosis()函数修改
把所有的错误类型输出利用snprintf()函数拼接起来,加上一个偏移量,后面只需要判断偏移量就可以,flash写入函数会在下面给出
/*add flash save log*/
#if (CMB_CPU_PLATFORM_TYPE != CMB_CPU_ARM_CORTEX_M0)
/**
* fault diagnosis then print cause of fault
*/
static void fault_diagnosis(void) {
char diagnosis_info[512] = {0};
int offset = 0;
if (regs.hfsr.bits.VECTBL) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_HFSR_VECTBL]);
cmb_println(print_info[PRINT_HFSR_VECTBL]);
}
if (regs.hfsr.bits.FORCED) {
/* Memory Management Fault */
if (regs.mfsr.value) {
if (regs.mfsr.bits.IACCVIOL) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_MFSR_IACCVIOL]);
cmb_println(print_info[PRINT_MFSR_IACCVIOL]);
}
if (regs.mfsr.bits.DACCVIOL) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_MFSR_DACCVIOL]);
cmb_println(print_info[PRINT_MFSR_DACCVIOL]);
}
if (regs.mfsr.bits.MUNSTKERR) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_MFSR_MUNSTKERR]);
cmb_println(print_info[PRINT_MFSR_MUNSTKERR]);
}
if (regs.mfsr.bits.MSTKERR) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_MFSR_MSTKERR]);
cmb_println(print_info[PRINT_MFSR_MSTKERR]);
}
#if (CMB_CPU_PLATFORM_TYPE == CMB_CPU_ARM_CORTEX_M4) || (CMB_CPU_PLATFORM_TYPE == CMB_CPU_ARM_CORTEX_M7) || \
(CMB_CPU_PLATFORM_TYPE == CMB_CPU_ARM_CORTEX_M33)
if (regs.mfsr.bits.MLSPERR) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_MFSR_MLSPERR]);
cmb_println(print_info[PRINT_MFSR_MLSPERR]);
}
#endif
if (regs.mfsr.bits.MMARVALID) {
if (regs.mfsr.bits.IACCVIOL || regs.mfsr.bits.DACCVIOL) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s: 0x%08x\n", print_info[PRINT_MMAR], regs.mmar);
cmb_println(print_info[PRINT_MMAR], regs.mmar);
}
}
}
/* Bus Fault */
if (regs.bfsr.value) {
if (regs.bfsr.bits.IBUSERR) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_BFSR_IBUSERR]);
cmb_println(print_info[PRINT_BFSR_IBUSERR]);
}
if (regs.bfsr.bits.PRECISERR) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_BFSR_PRECISERR]);
cmb_println(print_info[PRINT_BFSR_PRECISERR]);
}
if (regs.bfsr.bits.IMPREISERR) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_BFSR_IMPREISERR]);
cmb_println(print_info[PRINT_BFSR_IMPREISERR]);
}
if (regs.bfsr.bits.UNSTKERR) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_BFSR_UNSTKERR]);
cmb_println(print_info[PRINT_BFSR_UNSTKERR]);
}
if (regs.bfsr.bits.STKERR) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_BFSR_STKERR]);
cmb_println(print_info[PRINT_BFSR_STKERR]);
}
#if (CMB_CPU_PLATFORM_TYPE == CMB_CPU_ARM_CORTEX_M4) || (CMB_CPU_PLATFORM_TYPE == CMB_CPU_ARM_CORTEX_M7) || \
(CMB_CPU_PLATFORM_TYPE == CMB_CPU_ARM_CORTEX_M33)
if (regs.bfsr.bits.LSPERR) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_BFSR_LSPERR]);
cmb_println(print_info[PRINT_BFSR_LSPERR]);
}
#endif
if (regs.bfsr.bits.BFARVALID) {
if (regs.bfsr.bits.PRECISERR) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s: 0x%08x\n", print_info[PRINT_BFAR], regs.bfar);
cmb_println(print_info[PRINT_BFAR], regs.bfar);
}
}
}
/* Usage Fault */
if (regs.ufsr.value) {
if (regs.ufsr.bits.UNDEFINSTR) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_UFSR_UNDEFINSTR]);
cmb_println(print_info[PRINT_UFSR_UNDEFINSTR]);
}
if (regs.ufsr.bits.INVSTATE) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_UFSR_INVSTATE]);
cmb_println(print_info[PRINT_UFSR_INVSTATE]);
}
if (regs.ufsr.bits.INVPC) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_UFSR_INVPC]);
cmb_println(print_info[PRINT_UFSR_INVPC]);
}
if (regs.ufsr.bits.NOCP) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_UFSR_NOCP]);
cmb_println(print_info[PRINT_UFSR_NOCP]);
}
#if (CMB_CPU_PLATFORM_TYPE == CMB_CPU_ARM_CORTEX_M33)
if (regs.ufsr.bits.STKOF) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_UFSR_STKOF]);
cmb_println(print_info[PRINT_UFSR_STKOF]);
}
#endif
if (regs.ufsr.bits.UNALIGNED) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_UFSR_UNALIGNED]);
cmb_println(print_info[PRINT_UFSR_UNALIGNED]);
}
if (regs.ufsr.bits.DIVBYZERO0) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_UFSR_DIVBYZERO0]);
cmb_println(print_info[PRINT_UFSR_DIVBYZERO0]);
}
}
}
/* Debug Fault */
if (regs.hfsr.bits.DEBUGEVT) {
if (regs.dfsr.value) {
if (regs.dfsr.bits.HALTED) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_DFSR_HALTED]);
cmb_println(print_info[PRINT_DFSR_HALTED]);
}
if (regs.dfsr.bits.BKPT) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_DFSR_BKPT]);
cmb_println(print_info[PRINT_DFSR_BKPT]);
}
if (regs.dfsr.bits.DWTTRAP) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_DFSR_DWTTRAP]);
cmb_println(print_info[PRINT_DFSR_DWTTRAP]);
}
if (regs.dfsr.bits.VCATCH) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_DFSR_VCATCH]);
cmb_println(print_info[PRINT_DFSR_VCATCH]);
}
if (regs.dfsr.bits.EXTERNAL) {
offset += snprintf(diagnosis_info + offset, sizeof(diagnosis_info) - offset,
"%s\n", print_info[PRINT_DFSR_EXTERNAL]);
cmb_println(print_info[PRINT_DFSR_EXTERNAL]);
}
}
}
// 将收集的诊断信息写入 flash
if (offset > 0) {
save_fault1_to_flash(diagnosis_info, strlen(diagnosis_info));
}
}
#endif /* (CMB_CPU_PLATFORM_TYPE != CMB_CPU_ARM_CORTEX_M0) */
print_call_stack()函数修改
/**
* dump function call stack
*
* @param sp stack pointer
*/
static void print_call_stack(uint32_t sp) {
size_t i, cur_depth = 0;
uint32_t call_stack_buf[CMB_CALL_STACK_MAX_DEPTH] = {0};
cur_depth = cm_backtrace_call_stack(call_stack_buf, CMB_CALL_STACK_MAX_DEPTH, sp);
for (i = 0; i < cur_depth; i++) {
sprintf(call_stack_info + i * (8 + 1), "%08lx", (unsigned long)call_stack_buf[i]);
call_stack_info[i * (8 + 1) + 8] = ' ';
}
if (cur_depth) {
cmb_println(print_info[PRINT_CALL_STACK_INFO], fw_name, CMB_ELF_FILE_EXTENSION_NAME, cur_depth * (8 + 1),
call_stack_info);
save_fault2_to_flash(call_stack_info, 288); // 保存调用栈信息到flash ,288这个值可以自己定义,就看追溯的深度
} else { // 这里288就足够了
cmb_println(print_info[PRINT_CALL_STACK_ERR]);
save_fault2_to_flash(call_stack_info, 50); //这里的意思是如果追溯深度为0,则保存当前的错误信息
}
}
Flash 写入函数
也就是上面出现的save_fault1_to_flash() save_fault2_to_flash() ,其实大家再加上两个入参的话就可以合并成一个函数,我这里偷了个懒。。。。。。。。。。。
/*在hardfault()中写flash一定要先把全部中断关了再进行写入,以防遇到写入失败的情况*/
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
/*flash先擦除再写入*/
#define FAULT_LOG_ADDR (0x080A0000) // 错误类型写入地址
#define FAULT_LOG_ADDR2 (0x080A0160) // 错误追溯信息写入地址
/*这个地址可以自己定义在空闲flash段就好*/
//arr为要写入的信息 ,size为要写入的长度
void save_fault1_to_flash(char* arr, uint32_t size)
{
// 关中断
__disable_irq();
// 解锁Flash
HAL_FLASH_Unlock();
FlashErase(9, 1);
FlashWrite(FAULT_LOG_ADDR, arr, size);
// 锁定Flash
HAL_FLASH_Lock();
// 开中断
__enable_irq();
}
void save_fault2_to_flash(char* arr, uint32_t size)
{
// 关中断
__disable_irq();
// 解锁Flash
HAL_FLASH_Unlock();
FlashErase(10, 1);
FlashWrite(FAULT_LOG_ADDR2, arr, size);
// 锁定Flash
HAL_FLASH_Lock();
// 开中断
__enable_irq();
}
//sector 要擦除的起始扇区的扇区号, num_of_sectors为要擦除的扇区个数
uint8_t FlashErase(uint32_t sector, uint8_t num_of_sectors)
{
FLASH_EraseInitTypeDef EraseInitStruct;
uint32_t sectorError = 0;
HAL_FLASH_Unlock();
EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;
// EraseInitStruct.Banks = FLASH_BANK_1;
EraseInitStruct.Sector = sector;
EraseInitStruct.NbSectors = num_of_sectors;
EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3;
int ret = HAL_FLASHEx_Erase(&EraseInitStruct, §orError);
if (ret != HAL_OK)
{
// #ifdef DEBUG_BL
// debug_print_string(DEBUG_BL , " unable to erase sectors");
// #endif
return false;
}
else
{
// #ifdef DEBUG_BL
// debug_print_string(DEBUG_BL , " sector erased successfull");
// #endif
}
HAL_FLASH_Lock();
return true;
}
// addr为要写入数据的起始地址 ,buffer为要写入的数据, len为要写入数据的长度
bool FlashWrite(uint32_t addr, uint8_t *buffer, uint32_t len)
{
HAL_FLASH_Unlock();
for (int i = 0; i < len; i++)
{
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, addr + i, buffer[i]) !=
HAL_OK)
{
// #ifdef DEBUG_BL
// debug_print_string(DEBUG_BL , " flash writting error");
// #endif
}
else
{
// #ifdef DEBUG_BL
// debug_print_string(DEBUG_BL , " flash writting sucesssfull");
// #endif
}
}
HAL_FLASH_Lock();
return true;
}
这里的扇区号就根据具体的单片机还有你自己定义的写入地址决定的,就比如我现在项目用的是stm32F405RG,对应的扇区划分就是
而我定义的FAULT_LOG_ADDR 0x080A0000 就在第九扇区,所以函数中擦写的就是9扇区
这样整体就移植完成了,接下来进行测试,看是否可以正常追溯错误
3.测试
我们将官方的测试函数放进我们的代码中,人为制造HardFault
编译烧写固件,上电启动,发现我们的单片机啥也不输出了 ,然后我们通过j-flash工具读取单片机的flash ,有错误信息和错误追溯地址并且写入地址和我们之前定义的一致说明代码没问题
之后我们通过addr2line工具,这个工具的介绍和使用方法官方有介绍,这里不过多赘述https://github.com/armink/CmBacktrace/blob/master/README_ZH.md
在.axf文件的路径中执行 addr2line -e (文件名.axf) -afpiC (回溯信息)
对应到我们的代码文件中,完全一致,移植完成!