首先感谢大神提供的开源库 CmBacktrace
开源地址:
https://github.com/armink/CmBacktrace/releases/latest
https://gitee.com/Armink/CmBacktrace
CmBacktrace是什么
-
CmBacktrace一款针对 ARM Cortex-M 系列 MCU 的错误代码自动追踪、定位,错误原因自动分析的开源库
-
CmBacktrace 输出的信息包括函数调用栈、故障诊断结果、堆栈、故障寄存器及产品固件信息,极大的提升了错误定位的效率及准确性
主要特性如下:
- 支持的错误包括:
- 断言(assert)
- 故障(Hard Fault, Memory Management Fault, Bus Fault, Usage Fault, Debug Fault)
- 故障原因 自动诊断 :可在故障发生时,自动分析出故障的原因,定位发生故障的代码位置,而无需再手动分析繁杂的故障寄存器;
- 输出错误现场的 函数调用栈(需配合 addr2line 工具进行精确定位),还原发生错误时的现场信息,定位问题代码位置、逻辑更加快捷、精准。也可以在正常状态下使用该库,获取当前的函数调用栈;
- 支持 裸机 及以下操作系统平台:
- RT-Thread
- UCOS
- FreeRTOS(需修改源码)
- 根据错误现场状态,输出对应的 线程栈 或 C 主栈;
- 故障诊断信息支持多国语言(目前:简体中文、英文);
- 适配 Cortex-M0/M3/M4/M7 MCU;
- 支持 IAR、KEIL、GCC 编译器;
移植说明
2.3.1 准备工作
- 1、查看
\demos
目录下有没有合适自己的 Demo ,如有类似,则建议在其基础上修改 - 2、明确操作系统/裸机平台及 CPU 平台
- 3、将
\src
下的全部源文件添加至产品工程中,并保证源码目录被添加至头文件路径 - 4、cmb_fault.s 汇编文件(点击查看)可以选择性添加至工程,添加后需要把项目原有的
HardFault_Handler
注释掉 - 5、把
cm_backtrace_init
函数放在项目初始化地方执行 - 6、将
cm_backtrace_assert
放在项目的断言函数中执行,具体使用方法参照下面的 API 说明 - 7、如果第 4 步骤没有将 cmb_fault.s 汇编文件启用,则需要将
cm_backtrace_fault
放到故障处理函数(例如:HardFault_Handler
)中执行,具体使用方法参照下面的 API 说明
配置文件
配置文件名: cmb_cfg.h
,针对不同的平台和场景,用户需要自自行手动配置,常用配置如下:
配置名称 | 功能 | 备注 |
---|---|---|
cmb_println(…) | 错误及诊断信息输出 | 必须配置 |
CMB_USING_BARE_METAL_PLATFORM | 是否使用在裸机平台 | 使用则定义该宏 |
CMB_USING_OS_PLATFORM | 是否使用在操作系统平台 | 操作系统与裸机必须二选一 |
CMB_OS_PLATFORM_TYPE | 操作系统平台 | RTT/UCOSII/UCOSIII/FREERTOS |
CMB_CPU_PLATFORM_TYPE | CPU平台 | M0/M3/M4/M7 |
CMB_USING_DUMP_STACK_INFO | 是否使用 Dump 堆栈的功能 | 使用则定义该宏 |
CMB_PRINT_LANGUAGE | 输出信息时的语言 | CHINESE/ENGLISH |
2.4 API 说明
2.4.1 库初始化
void cm_backtrace_init(const char *firmware_name, const char *hardware_ver, const char *software_ver)
参数 | 描述 |
---|---|
firmware_name | 固件名称,需与编译器生成的固件名称对应 |
hardware_ver | 固件对应的硬件版本号 |
software_ver | 固件的软件版本号 |
注意 :以上入参将会在断言或故障时输出,主要起了追溯的作用
2.4.2 获取函数调用栈
size_t cm_backtrace_call_stack(uint32_t *buffer, size_t size, uint32_t sp)
参数 | 描述 |
---|---|
buffer | 存储函数调用栈的缓冲区 |
size | 缓冲区大小 |
sp | 待获取的堆栈指针 |
示例:
/* 建立深度为 16 的函数调用栈缓冲区,深度大小不应该超过 CMB_CALL_STACK_MAX_DEPTH(默认16) */
uint32_t call_stack[16] = {0};
size_t i, depth = 0;
/* 获取当前环境下的函数调用栈,每个元素将会以 32 位地址形式存储, depth 为函数调用栈实际深度 */
depth = cm_backtrace_call_stack(call_stack, sizeof(call_stack), cmb_get_sp());
/* 输出当前函数调用栈信息
* 注意:查看函数名称及具体行号时,需要使用 addr2line 工具转换
*/
for (i = 0; i < depth; i++) {
printf("%08x ", call_stack[i]);
}
2.4.3 追踪断言错误信息
void cm_backtrace_assert(uint32_t sp)
参数 | 描述 |
---|---|
sp | 断言环境时的堆栈指针 |
注意 :入参 SP 尽量在断言函数内部获取,而且尽可能靠近断言函数开始的位置。当在断言函数的子函数中(例如:在 RT-Thread 的断言钩子方法中)使用时,由于函数嵌套会存在寄存器入栈的操作,此时再获取 SP 将发生变化,就需要人为调整(加减固定的偏差值)入参值,所以作为新手 不建议在断言的子函数 中使用该函数。
2.4.4 追踪故障错误信息
void cm_backtrace_fault(uint32_t fault_handler_lr, uint32_t fault_handler_sp)
参数 | 描述 |
---|---|
fault_handler_lr | 故障处理函数环境下的 LR 寄存器值 |
fault_handler_sp | 故障处理函数环境下的 SP 寄存器值 |
该函数可以在故障处理函数(例如: HardFault_Handler
)中调用。另外,库本身提供了 HardFault
处理的汇编文件(点击查看,需根据自己编译器进行选择),会在故障时自动调用 cm_backtrace_fault
方法。所以移植时,最简单的方式就是直接使用该汇编文件。
移植
1.复制文件到工程
2.添加文件
注意这里的cmb_fault.s
是\middle\cm_backtrace\fault_handler\keil
下的文件
3.添加头文件路径
…\middle\cm_backtrace
…\middle\cm_backtrace\Languages\en-US
4.屏蔽原有的HardFault_Handler
函数实现
5.配置文件设置cmb_cfg.h
裸机配置
#ifndef _CMB_CFG_H_
#define _CMB_CFG_H_
/* 打印函数配置 */
#define cmb_println(...) printf(__VA_ARGS__);printf("\r\n")
/* 平台选择 enable bare metal(no OS) platform
裸机:CMB_USING_BARE_METAL_PLATFORM
操作系统:CMB_USING_OS_PLATFORM
*/
#define CMB_USING_BARE_METAL_PLATFORM
/* cpu内核选择 cpu platform type, must config by user */
#define CMB_CPU_PLATFORM_TYPE CMB_CPU_ARM_CORTEX_M4
/* 使能栈的dump功能 enable dump stack information */
#define CMB_USING_DUMP_STACK_INFO
/* 语言选择 默认选择英语language of print information */
#define CMB_PRINT_LANGUAGE CMB_PRINT_LANUUAGE_ENGLISH
#endif /* _CMB_CFG_H_ */
FreeRTOS 操作系统配置
#ifndef _CMB_CFG_H_
#define _CMB_CFG_H_
/* 打印函数配置 */
#define cmb_println(...) 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_FREERTOS
/* cpu内核选择 cpu platform type, must config by user */
#define CMB_CPU_PLATFORM_TYPE CMB_CPU_ARM_CORTEX_M4
/* 使能栈的dump功能 enable dump stack information */
#define CMB_USING_DUMP_STACK_INFO
/* 语言选择 默认选择英语language of print information */
#define CMB_PRINT_LANGUAGE CMB_PRINT_LANUUAGE_ENGLISH
#endif /* _CMB_CFG_H_ */
**默认支持的ARMCortex-M 系列 CPU平台可选 **
#define CMB_CPU_ARM_CORTEX_M0 0
#define CMB_CPU_ARM_CORTEX_M3 1
#define CMB_CPU_ARM_CORTEX_M4 2
#define CMB_CPU_ARM_CORTEX_M7 3
#define CMB_CPU_ARM_CORTEX_M33 4
可选 RTOS 操作系统
#define CMB_OS_PLATFORM_RTT 0
#define CMB_OS_PLATFORM_UCOSII 1
#define CMB_OS_PLATFORM_UCOSIII 2
#define CMB_OS_PLATFORM_FREERTOS 3
语言的选择
#define CMB_PRINT_LANGUAGE_ENGLISH 0//英语
#define CMB_PRINT_LANGUAGE_CHINESE 1//中文
#define CMB_PRINT_LANGUAGE_CHINESE_UTF8 2//中文utf-8
注意freertos作者做了修改
因为 FreeRTOS 的 TCB 中没有 StackSize 信息,所以修改了其源码(基于 V9.0.0),在
FreeRTOS/tasks.c
中增加了uxSizeOfStack
字段, 以及vTaskStackAddr()
、vTaskStackSize()
、vTaskName()
函数。
源码内部修改了3个地方
6.我们的assert_failed调用cm_backtrace_assert可以输出一些信息
这一步并非关键
7.CmBacktrace初始化
#include <cm_backtrace.h>
//cm_backtrace 初始化,这里的初始化并非必要,仅仅设置一些非必要参数
cm_backtrace_init(“N32L40x”,“HV1.0”,“SV1.0”);
8.添加测试函数
void fault_test_by_unalign(void) {
volatile int * SCB_CCR = (volatile int *) 0xE000ED14; // SCB->CCR
volatile int * p;
volatile int value;
*SCB_CCR |= (1 << 3); /* bit3: UNALIGN_TRP. */
p = (int *) 0x00;
value = *p;
printf("addr:0x%02X value:0x%08X\r\n", (int) p, value);
p = (int *) 0x04;
value = *p;
printf("addr:0x%02X value:0x%08X\r\n", (int) p, value);
p = (int *) 0x03;
value = *p;
printf("addr:0x%02X value:0x%08X\r\n", (int) p, value);
}
void fault_test_by_div0(void) {
volatile int * SCB_CCR = (volatile int *) 0xE000ED14; // SCB->CCR
int x, y, z;
*SCB_CCR |= (1 << 4); /* bit4: DIV_0_TRP. */
x = 10;
y = 0;
z = x / y;
printf("z:%d\n", z);
}
裸机示例1:内存地址异常访问异常
将N32L40x.axf 复制到Linux系统中既可以得到问题所在
addr2line -e N32L40x.axf -a -f 0800350e 08003552 080080c2
根据addr2line工具分析找到问题所在,在函数fault_test_by_unalign
中,在文件fault_test.c 的27行
裸机示例2:除数为零分析
addr2lin分析结果
freertos示例3:
线程内部调用除数为零的异常函数,CmBacktrace明确说明出现问题的线程名称和发生错误的原因
标签:cm,N32L40x,CmBacktrace,backtrace,CMB,PLATFORM,mdk5,OS,define From: https://blog.csdn.net/u010261063/article/details/140788568addr2line -e N32L40x.axf -a -f 080035b6 080008de