库 keil 编译很慢_CmBacktrace: ARM CortexM 系列 MCU 错误追踪库

说明:本文来自armink大佬的CmBacktrace库文档。

CmBacktrace: ARM Cortex-M 系列 MCU 错误追踪库

0、CmBacktrace 是什么

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

  • 支持的错误包括:

    • 断言(assert)

    • 故障(Hard Fault, Memory Management Fault, Bus Fault, Usage Fault, Debug Fault)

  • 故障原因 自动诊断 :可在故障发生时,自动分析出故障的原因,定位发生故障的代码位置,而无需再手动分析繁杂的故障寄存器;

  • 输出错误现场的 函数调用栈(需配合 addr2line 工具进行精确定位),还原发生错误时的现场信息,定位问题代码位置、逻辑更加快捷、精准。也可以在正常状态下使用该库,获取当前的函数调用栈;

  • 支持 裸机 及以下操作系统平台:

    • RT-Thread:http://www.rt-thread.org/

    • UCOS

    • FreeRTOS(需修改源码)

  • 根据错误现场状态,输出对应的 线程栈 或 C 主栈;

  • 故障诊断信息支持多国语言(目前:简体中文、英文);

  • 适配 Cortex-M0/M3/M4/M7 MCU;

  • 支持 IAR、KEIL、GCC 编译器;

1、为什么选择 CmBacktrace

入门新人 :对于从 C51 、MSP430 等简单单片机转而使用更加复杂的 ARM  新人来说,时不时出现的 "hard falut" 死机会让新人瞬间懵掉。定位错误的方法也往往是连接上仿真器,一步步 F10/F11 单步,定位到具体的错误代码,再去猜测、排除、推敲错误原因,这种过程十分痛苦。

熟练老手 :慢慢的大家知道可以通过故障寄存器信息来定位故障原因及故障代码地址,虽然这样能解决一小部分问题,但是重复的、繁琐的分析过程也会耽误很多时间。而且对于一些复杂问题,只依靠代码地址是无法解决的,必须得还原错误现场的函数调用逻辑关系。虽然连接仿真器可以查看到的函数调用栈,但故障状态下是无法显示的,所以还是得一步步 F10/F11 单步去定位错误代码的位置。另外,还有两种场景,

  • 1、很多产品真机调试时必须断开仿真器

  • 2、问题确实存在,但是极难被重现

所以定位这类问题就显得难上加难。

使用本库 :上述所有问题都迎刃而解,可以将错误信息输出到控制台上,还可以将错误信息使用 EasyFlash 的 Log 功能保存至 Flash 中,设备死机后重启依然能够读取上次的错误信息。CmBacktrace 输出的信息包括函数调用栈、故障诊断结果、堆栈、故障寄存器及产品固件信息,极大的提升了错误定位的效率及准确性。

俗话说,工欲善其事,必先利其器。所以有时候做事效率低的原因也许是,你会用的工具种类太少。

合作、贡献 :开源软件的发展离不开大家的支持,欢迎大家多提建议,也希望更多的人一起参与进来,共同提高  。如果觉得这个开源项目很赞,可以点击 项目主页:https://github.com/armink/CmBacktrace (Github|OSChina|Coding)  右上角的 Star ,同时把它推荐给更多有需要的朋友。

2、CmBacktrace 如何使用

2.1 演示

该演示分如下几个步骤:

  • 1、制造除零异常(IAR 工程,点击查看源码:https://github.com/armink/CmBacktrace/tree/master/demos/non_os/stm32f10x/app/src))

  • 2、查看错误诊断信息

  • 3、查看函数调用栈基本信息

  • 4、通过命令行工具进入项目工程存放可执行文件的路径

  • 5、使用 addr2line 命令,查看函数调用栈详细信息,并定位错误代码

afc71348429e8ac7cfb6f45de4b0b431.gif
此处是GIF动态图

2.2 Demo

目录平台链接
\demos\non_os\stm32f10x裸机 STM32 Cortex-M3点击查看
\demos\os\rtthread\stm32f4xxRT-Thread STM32 Cortex-M4点击查看
\demos\os\ucosii\stm32f10xUCOSII STM32 Cortex-M3点击查看
\demos\os\freertos\stm32f10xFreeRTOS STM32 Cortex-M3点击查看

2.3 移植说明

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 说明

2.3.2 配置说明

配置文件名: 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_TYPECPU平台M0/M3/M4/M7
CMB_USING_DUMP_STACK_INFO是否使用 Dump 堆栈的功能使用则定义该宏
CMB_PRINT_LANGUAGE输出信息时的语言CHINESE/ENGLISH

注意:以上部分配置的内容可以在 cmb_def.h 中选择,更多灵活的配置请阅读源码

2.4 API 说明

2.4.1 库初始化
1void cm_backtrace_init(const char *firmware_name, const char *hardware_ver, const char *software_ver)2
参数描述
firmware_name固件名称,需与编译器生成的固件名称对应
hardware_ver固件对应的硬件版本号
software_ver固件的软件版本号

注意 :以上入参将会在断言或故障时输出,主要起了追溯的作用

2.4.2 获取函数调用栈
1size_t cm_backtrace_call_stack(uint32_t *buffer, size_t size, uint32_t sp)
参数描述
buffer存储函数调用栈的缓冲区
size缓冲区大小
sp待获取的堆栈指针

示例:

 1/* 建立深度为 16 的函数调用栈缓冲区,深度大小不应该超过 CMB_CALL_STACK_MAX_DEPTH(默认16) */
2uint32_t call_stack[16] = {0};
3size_t i, depth = 0;
4/* 获取当前环境下的函数调用栈,每个元素将会以 32 位地址形式存储, depth 为函数调用栈实际深度 */
5depth = cm_backtrace_call_stack(call_stack, sizeof(call_stack), cmb_get_sp());
6/* 输出当前函数调用栈信息 7 * 注意:查看函数名称及具体行号时,需要使用 addr2line 工具转换 8 */
9for (i = 0; i 10    printf("%08x ", call_stack[i]);
11}
2.4.3 追踪断言错误信息
1void cm_backtrace_assert(uint32_t sp)2
参数描述
sp断言环境时的堆栈指针

注意 :入参 SP 尽量在断言函数内部获取,而且尽可能靠近断言函数开始的位置。当在断言函数的子函数中(例如:在 RT-Thread 的断言钩子方法中)使用时,由于函数嵌套会存在寄存器入栈的操作,此时再获取 SP 将发生变化,就需要人为调整(加减固定的偏差值)入参值,所以作为新手 不建议在断言的子函数 中使用该函数。

2.4.4 追踪故障错误信息
1void cm_backtrace_fault(uint32_t fault_handler_lr, uint32_t fault_handler_sp)2
参数描述
fault_handler_lr故障处理函数环境下的 LR 寄存器值
fault_handler_sp故障处理函数环境下的 SP 寄存器值

该函数可以在故障处理函数(例如: HardFault_Handler)中调用。另外,库本身提供了 HardFault 处理的汇编文件(点击查看,需根据自己编译器进行选择),会在故障时自动调用 cm_backtrace_fault 方法。所以移植时,最简单的方式就是直接使用该汇编文件。

2.5 常见问题

2.5.1 编译出错,提示需要 C99 支持

点击查看教程:一步开启 Keil/IAR/GCC 的 C99 支持:https://github.com/armink/CmBacktrace/blob/master/docs/zh/enable_c99_for_keil_iar_gcc.md

一步开启 Keil/IAR/GCC 的 C99 支持

背景

C99 标准于 1999 年发布,至今(2016-12-28)将近 20 年的时间,相比早期的 C89 ,C99 有如下新特性(摘自 维基百科):

  • 增加了对编译器的限制,比如源程序每行要求至少支持到 4095 字节,变量名函数名的要求支持到 63 字节(extern 要求支持到 31)。

  • 增强了预处理功能。例如:

    • 宏支持取可变参数 #define Macro(…) `VA_ARGS`

    • 使用宏的时候,允许省略参数,被省略的参数会被扩展成空串。

    • 支持 // 开头的单行注释(这个特性实际上在C89的很多编译器上已经被支持了)

  • 增加了新关键字 restrict, inline, _Complex, _Imaginary, _Bool

    • 支持 long long, long double _Complex, float _Complex 等类型

  • 支持不定长的数组,即数组长度可以在运行时决定,比如利用变量作为数组长度。声明时使用 int a[var] 的形式。不过考虑到效率和实现,不定长数组不能用在全局,或 struct 与 union 里。

  • 变量声明不必放在语句块的开头,for 语句提倡写成 for(int i=0;i<100;++i) 的形式,即i 只在 for 语句块内部有效。

  • 允许采用(type_name){xx,xx,xx} 类似于 C++ 的构造函数的形式构造匿名的结构体。

  • 初始化结构的时候允许对特定的元素赋值,形式为:

    • `struct test{int a[3],b;} foo[] =  { [0].a = {1}, [1].a = 2 };`

    • `struct test{int a, b, c, d;} foo =  { .a = 1, .c = 3, 4, .b = 5 };  // 3,4 是对 .c,.d 赋值的`

  • 格式化字符串中,利用 \u 支持 unicode 的字符。

  • 支持 16 进制的浮点数的描述。

  • printf scanf 的格式化串增加了对 long long int 类型的支持。

  • 浮点数的内部数据描述支持了新标准,可以使用 #pragma 编译器指令指定。

  • 除了已有的 line__` `_file_` 以外,增加了 `__func 得到当前的函数名。

  • 允许编译器化简非常数的表达式。

  • 修改了 / % 处理负数时的定义,这样可以给出明确的结果,例如在C89中-22 / 7 = -3, -22 % 7 = -1,也可以-22 / 7= -4, -22 % 7 = 6。 而C99中明确为 -22 / 7 = -3, -22 % 7 = -1,只有一种结果。

  • 取消了函数返回类型默认为 int 的规定。

  • 允许在 struct 的最后定义的数组不指定其长度,写做 [](flexible array member)。

  • const const int i 将被当作 const int i 处理。

  • 增加和修改了一些标准头文件,比如定义 bool 的

    ,定义一些标准长度的 int 的,定义复数的,定义宽字符的,类似于泛型的数学函数, 浮点数相关的。 在增加了 va_copy 用于复制 … 的参数。里增加了 struct tmx ,对 struct tm 做了扩展。
  • 输入输出对宽字符以及长整数等做了相应的支持。

C99 提供了众多的便利,也提高了程序开发的效率,但是一些嵌入式工具链并不是默认开启 C99,接下来将会针对不同的工具链,介绍如何开启 C99 模式。

Keil 4

923dc62d0c2724a3b5c2ba7605758100.png
keil4_enable_c99

Keil 5

88b2416bbc2b169900a17bdb70d872ec.png
keil5_enable_c99

IAR

IAR 新建完的工程,默认开启 C99 ,如果工程没有开启,请使用下面的方法

0f7e274c6664c81e6426df6c8ca70f2f.png
iar_enable_c99

GCC

在编译配置中增加 -std=c99 即可

2.5.2 如何查看到函数调用栈中函数的具体名称及代码行号

点击查看教程:如何使用 addr2line 工具获取函数调用栈详细信息:https://github.com/armink/CmBacktrace/blob/master/docs/zh/how_to_use_addr2line_for_call_stack.md

如何使用 addr2line 工具获取函数调用栈详细信息

addr2line 是什么

addr2line (它是标准的 GNU Binutils 中的一部分)是一个可以将指令的地址和可执行映像转换成文件名、函数名和源代码行数的工具。

如何获得 addr2line

Linux 系统一般会集成这个工具,本文重点介绍 Windows 系统下如何获取该工具。方法很多,我这里仅介绍两种方式

  • 第一种:安装 MinGW(网上教程很多,自行搜索),安装后在其安装目录的 bin 文件夹里会包含 addr2line.exe ,此时只用保证环境变量 path 中包含该路径即可;

  • 第二种(XP 平台除外):在本项目的 tools 文件夹中已存放 addr2line.exe ,可以将其直接拷贝至 C:\Windows 下,或者将 CmBacktrace 仓库的 tools 文件夹路径添加至到环境变量 path 中,这样都能保证命令行工具能正常使用 addr2line 命令。

addr2line 如何使用

使用 addr2line --help 可以看到如下介绍:

 1$addr2line --help
2Usage: addr2line [option(s)] [addr(s)]
3 Convert addresses into line number/file name pairs.
4 If no addresses are specified on the command line, they will be read from stdin
5 The options are:
6  @<file>                Read options from <file>
7  -a --addresses         Show addresses
8  -b --target=  Set the binary file format 9  -e --exe=  Set the input file name (default is a.out)10  -i --inlines           Unwind inlined functions11  -j --section=<name>    Read section-relative offsets instead of addresses12  -p --pretty-print      Make the output easier to read for humans13  -s --basenames         Strip directory names14  -f --functions         Show function names15  -C --demangle[=style]  Demangle function names16  -h --help              Display this information17  -v --version           Display the program's version1819addr2line: supported targets: pe-x86-64 pei-x86-64 pe-bigobj-x86-64 elf64-x86-64 elf64-l1om elf64-k1om pe-i386 pei-i386 elf32-i386 elf64-little elf64-big elf32-little elf32-big plugin srec symbolsrec verilog tekhex binary ihex20Report bugs to //www.sourceware.org/bugzilla/>

这里常用的是以下参数

  • -e :指定可执行映像名称

  • -a :显示函数地址

  • -f :显示函数名称

例如命令 addr2line -e CmBacktrace.out -f 08000a60 08000141 0800313f 将会显示名称为 CmBacktrace.out 的可执行映像,在地址为 08000a60 08000141 0800313f 对应的函数名称及源代码信息。执行结果如下:

1$addr2line -e CmBacktrace.out -a -f 08000a60 08000141 0800313f
2fault_test_by_div0
3D:\Program\STM32\CmBacktrace\demo\non_os\stm32f10x\app\src/fault_test.c:38
4main
5D:\Program\STM32\CmBacktrace\demo\non_os\stm32f10x\app\src/app.c:20
6_call_main
7??:?

更多使用指南,请 参考官方说明文档:https://sourceware.org/binutils/docs-2.27/binutils/addr2line.html#addr2line 。

2.5.3 故障处理函数:HardFault_Handler 重复定义

在使用了本库提供的 cmb_fault.s 汇编文件时,因为该汇编文件内部已经定义了 HardFault_Handler ,所以如果项目中还有其他地方定义了该函数,则会提示 HardFault_Handler 被重复定义的错误。此时有两种解决方法:

  • 1、注释/删除其他文件中定义的 HardFault_Handler 函数,仅保留 cmb_fault.s 中的;

  • 2、将 cmb_fault.s 移除工程,手动添加 cm_backtrace_fault 函数至现有的故障处理函数,但需要注意的是,务必 保证该函数数入参的准备性 ,否则可能会导致故障诊断功能及堆栈打印功能无法正常运行。所以如果是新手,不推荐第二种解决方法。

2.6 许可

采用 MIT 开源协议,细节请阅读项目中的 LICENSE 文件内容。

本文经原作者授权,我本想写个使用教程的,但是朱天龙大佬的教程太详细了,我就直接转载过来了,更多相关信息请点击下方阅读原文,直达朱天龙大佬的开源库:https://github.com/armink/CmBacktrace

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值