CmBacktrace开源库应用指南
文档编号:AN0007
序号 |
版本 |
修改日期 |
修改说明 |
1 |
1.0 |
2024/08/27 |
初始发布 |
模块简介
一个新的量产产品开发包含多个流程,在产品开发过程中,越早期发现产品缺陷,公司的改进成本越低,改进难度也越低。一般公司的产品开发流程,它包括六个产品开发阶段如下:
产品规划(阶段1)
方案预研(阶段2)
产品设计(阶段3)
开发调试(阶段4)
工厂测试(阶段5)
试产扩量(阶段6)
开发者在1 - 4流程中,可以复现和debug多数场景,对于功能性缺陷和系统性缺陷,可以稳定复现的故障可以迅速排除。对于一些偶发场景的故障,需要在第5或第6阶段才能被发现。但一旦进入到5 - 6阶段,产品已经成型之后,再想排查BUG就比较麻烦了。例如工厂测试阶段,有可能连续运行好几天或者好几个星期才能复现的问题,排查起来就十分的复杂。对于这种情况,backtrace是十分必要的。可以在离线的状态下分析系统的关键信息,通过函数的栈回溯,从而找到出错的对应的执行函数,然后结合程序设计,基本 上可以找到大部分bug。
栈回溯技术原理
当程序发生异常并进入hardfault时,程序通常不能从异常中返回,只能保存异常状态并等待复位。但是幸运的是,栈空间并不会因此被清除,可以反向分析栈空间,定位异常代码的函数。同时,Cortex-M7的System control block (SCB)等寄存器可以提供异常原因的参考,综合起来开发者就可以获取到更多信息,找到缺陷原因。开发者需要参考以下手册:
《Procedure Call Standard for the Arm® Architecture》(AAPCS32),ARM32程序调用流程文档。
《Arm® Cortex®-M7 Processor User Guide Reference Material》(CM7UGRM),Cortex-M7用户指导参考手册。
《 Arm® v7-M Architecture Reference Manual》,Arm®v7-M架构参考手册。
从这些手册中,我们可以获取到以下几项有用的信息:
(1)从AAPCS32手册中,arm32系列芯片的Application Binary Interface (ABI) 规则,并获取到arm32芯片的函数调用栈规则,可以帮助开发者从栈中找到每一个栈帧,如下表所示:
(2)从CM7UGRM手册中,获取到Cortex-M7 CPU的微架构,并且获取到系统寄存器,如下图所示:
同时,获取辅助定位异常原因,部分寄存器如下图所示:
(3)Arm®v7-M架构参考手册提供了ARMV7-M结构体系中规定的寄存器和指令集等,如下图所示:
CmBacktrace开源库
CmBacktrace (Cortex Microcontroller Backtrace)开源库是一款针对 ARM Cortex-M 系列 MCU 的错误代码自动追踪、定位,错误原因自动分析的开源库。可用于多种Cortex-M内核的MCU。主要优点如下:
支持的错误包括:
断言(assert)
故障(Hard Fault, Memory Management Fault, Bus Fault, Usage Fault, Debug Fault)
故障原因 自动诊断 :可在故障发生时,自动分析出故障的原因,定位发生故障的代码位置,而无需再手动分析繁杂的故障寄存器;
输出错误现场的 函数调用栈,还原发生错误时的现场信息,定位问题代码位置、逻辑更加快捷、精准;
支持裸机及RTOS操作系统平台:
根据错误现场状态,输出对应的 线程栈 或 C主栈;
故障诊断信息支持中文;
不足点如下:
使用AC6编译器存在warning,存在print安全问题的提示;
不支持对分散加载到RAM中运行的函数;
MDK编译器不使用FP栈帧,CmBacktrace通过寻找栈中的跳转指令获取到跳转函数,因此调用栈不一定准确,仅能提供参考;
移植步骤
ET 6001 MCU内置双核Cortex-M7,在SDK发布的示例工程中,ETMCU团队移植了CmBacktrace开源库并进行了相关的配置,并进行一些优化。示例工程下载地址是https://www.etmcu.com/product/detail/76。以下就以示例工程为例,移植示例工程的CmBacktrace库到现有工程中。现有工程需要提前准备的工作有以下2点:
默认使用micro C标准库,使用printf向串口输出log,用户需要准备printf重定向接口;
该开源库模式使用了HardFault_Handler,因此可能造成HardFault_Handler重复定义,可以将用户的HardFault_Handler函数作为子函数由cm_backtrace_fault调用。
移植步骤如下:
第一步:复制示例工程中的CmBacktrace文件夹到目标工程文件夹中;
第二步:将工程中添加CmBacktrace文件的源文件和头文件;
第三步:将CmBacktrace头文件路径加入到include路径下
第四步:配置编译器选型,支持C99规范,AC5编译器的配置在此处:
AC6编译器的C99配置在此处:
AC6不支持中文GB18030的编码规范,因此编译时产生大量warning。如果用户需要清除warning信息在Misc Control栏目添加-Wno-invalid-source-encoding,或者使用UTF-8格式语言的头文件(需要支持UTF-8的串口助手)。
第五步:加入头文件并进行CmBacktrace库初始化,如下代码所示
#include "cm_backtrace.h"
int main(void)
{
xxx
cm_backtrace_init("Project", "1.0", "1.0");
xxx
}
其中,cm_backtrace_init函数的入参分别是固件名,软件版本,硬件版本,可有用户任意指定。
使用示例
下面将演示在ET6001 SDK CmBacktrace示例工程,运行并且获取到log,并进行分析的过程。
总线错误示例
源代码如下:
int main(void)
{
UART0_printf_init();
cm_backtrace_init("Project", "1.0", "1.0");
func_1(1, 2, 3, 4, 5, 6);
while(1);
}
void func_1(int a, int b, int c, int d, int e, int f)
{
func_2(a, b, c, d, e, f);
}
void func_2(int a, int b, int c, int d, int e, int f)
{
func_3(a, b, c, d, e, f);
}
void func_3(int a, int b, int c, int d, int e, int f)
{
*(volatile uint32_t *)0xffffffff = a + b + c + d + e + f;
}
其中,函数的调用顺序为main -> func_1 -> func_2 -> func_3,并在func_3中发生总线错误,输出的log如下:
Cortex-M7 backtrace 1.0 test on ET6001 固件名称:Project,硬件版本号:1.0,软件版本号:1.0 在中断或裸机环境下发生错误异常
=========== 线程堆栈信息 ===========
addr: 200005c8 data: 00000004
addr: 200005cc data: 00000003
addr: 200005d0 data: 00000002
addr: 200005d4 data: 00000001
addr: 200005d8 data: 00000005
addr: 200005dc data: 00000006
addr: 200005e0 data: 00000004
addr: 200005e4 data: 00000003
addr: 200005e8 data: 00000002
addr: 200005ec data: 00000001
addr: 200005f0 data: 00000000
addr: 200005f4 data: 00002fe1
addr: 200005f8 data: 00000005
addr: 200005fc data: 00000006
addr: 20000600 data: 00000006
addr: 20000604 data: 00000005
====================== 发生异常前寄存器信息 ======================
R0 : 00000015 R1 : ffffffff R2 : 00000003 R3 : 00000004
R12: 00000005 LR : 00003019 PC : 0000304c PSR: 21000000
发生用法错误,原因:企图执行非对齐访问 查看更多函数调用栈信息,请运行:
.\addr2line -e Project.axf -afpiC 0x0000304c 0x00003018 0x00002fe0 0x0000307a
或直接进入debug界面,查看Disassembly栏目并跳转到以上地址查看源码
分析:
错误情况:发生用法错误。错误原因是:企图执行非对齐访问。
发生异常前的寄存器信息,最有用的是PC和LR寄存器,其他寄存器信息仅供参考。PC寄存器提供了发生错误时的代码位置。而LR寄存器则提供了当前出错函数的上层调用。PC寄存器和LR寄存器也被获取到了调用栈中,可以直接对函数调用栈进行分析。
分析:.\addr2line -e Project.axf -afpiC 0x0000304c 0x00003018 0x00002fe0 0x0000307a
0x0000304c 表示出错的程序指针(PC)地址
0x00003018 表示当前错误函数的调用者
0x00002fe0 和0x0000307a 表示更上层的调用关系。
使用addr2line工具,获取到以上4个地址的对应源码值,步骤如下所示:
拷贝CmBacktrace目录下的addr2line.exe到工程Objects目录下
(2) 调用CMD工具,并执行.\addr2line -e Project.axf -afpiC 0x0000304c 0x00003018 0x00002fe0 0x0000307a。注意:Project.axf文件可能和生成的不一致,可以将cm_backtrace_init函数的固件名入参函数改为实际工程固件名。
执行结果如下
xxx\\Project\\Objects> .\\addr2line -e Project.axf -afpiC 0x0000304c 0x00003018 0x00002fe0 0x0000307a
0x0000304c: func_3 at xxx\\Project/../Source/main.c:143
0x00003018: func_2 at xxx\\Project/../Source/main.c:139
0x00002fe0: func_1 at xxx\\Project/../Source/main.c:134
0x0000307a: main at C:xxx\\Project/../Source/main.c:128
程序源码对应的函数代码行数为:
说明:在ARM32结构体系下,发生函数调用时,LR寄存器由CPU硬件自动更新为当前跳转函数 + 4的地址。
结合以上信息反向查询源码的调用关系,并得到以下结论:
函数调用栈为main -> func_1 -> func_2 -> func_3,并在func_3中发生总线错误
当开发者有debug环境时,也可以直接进入Disassembly目录下,进行直接查找反汇编以及对应的源码,如下图所示:
步骤1:进入debug模式并右键进入Show Disassembly at Address…
步骤2:输入log中提示的地址值,如下图所示:
结合汇编就可以知道具体出错的指令,是由于STR指令触发了异常中断,而发生异常时的寄存器也被记录了下来,如下:
可以得出结论,向0xffffffff地址写入0x15时,出现了总线错误,触发中断。












