CmBacktrace 应用笔记

  • CmBacktrace 应用笔记

    • 本文的目的和结构

      • 本文的目的和背景
      • 本文的结构
    • CmBacktrace 是什么
    • 准备工作
      • 准备 addr2line
      • ENV配置
      • 确认宏定义
      • 开启C99
      • 确定初始化参数
    • 使用示例
    • 常见问题
    • 参考
      • API列表

        • cm_backtrace_init
        • cm_backtrace_call_stack
        • cm_backtrace_assert
        • cm_backtrace_fault

CmBacktrace 应用笔记

本文的目的和结构

本文的目的和背景

对于从 C51 、MSP430 等简单单片机转而使用更加复杂的 ARM 新人来说,时不时出现的 "hard falut" 死机会让新人瞬间懵掉。定位错误的方法也往往是连接上仿真器,一步步 F10/F11 单步,定位到具体的错误代码,再去猜测、排除、推敲错误原因,这种过程十分痛苦,且花费的时间很长。 当然,也有部分开发者通过故障寄存器信息来定位故障原因及故障代码地址,虽然这样能解决一小部分问题,但是重复的、繁琐的分析过程也会耽误很多时间。而且对于一些复杂问题,只依靠代码地址是无法解决的,必须得还原错误现场的函数调用逻辑关系。虽然连接仿真器可以查看到的函数调用栈,但故障状态下是无法显示的,所以还是得一步步 F10/F11 单步去定位错误代码的位置。另外,很多产品真机调试时必须断开仿真器,这又使定位错误代码雪上加霜。

为了能让开发者更快的知道造成 hard falut 的原因,更快的定位到错误代码的位置,本应用笔记将一步步介绍 CmBacktrace 的相关知识和使用方法,让开发者能不费吹灰之力就找出代码中的问题所在。

本文的结构

本文首先介绍了 什么是 CmBacktrace,然后介绍了使用 CmBacktrace 要做的准备工作,接着介绍了 在RT-Thread中使用CmBacktrace的例子,最后总结了使用 CmBacktrace 时的常见问题。通过这些讲解,希望开发者能更快上手 CmBacktrace。

CmBacktrace 是什么

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

主要特性如下:

  • 支持的错误包括:

    • 断言(assert)

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

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

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

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

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

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

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

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

准备工作

准备 addr2line

https://github.com/armink/CmBacktrace/tree/master/tools/addr2line 页面中下载 addr2line(需要按照自己的系统版本下载),然后将下载下来的 addr2line 拷贝至 C:\Windows 下 ,这样就可以使用 addr2line 了。

ENV配置

RT-Thread 已经对 CmBacktrace 做了适配,直接在 ENV 使能 CmBacktrace 就可以使用了。

下面介绍如何在 ENV 中配置CmBacktrace:

  • 打开 ENV,进入相应的 bsp 目录
  • 输入 menuconfig
  • 进入 RT-Thread online packages -> tools packages
  • 使能 CmBacktrace
  • 进入 CmBacktrace 配置界面
  • 选择自己的 CPU 平台
  • 选择打印的语言
  • 选择版本,推荐使用最新版

确认宏定义

CmBacktrace 的运行需要知道存放代码的 SECTION 的开始地址和结束地址以及栈的 SECTION 的开始地址和结束地址。用户只需要查看 cmb_def.h 文件里默认定义的 CMB_CSTACK_BLOCK_NAME 和 CMB_CODE_SECTION_NAME 这两个宏是否正确即可。如不正确,用户需要根据分散加载文件和启动文件来确定这两个宏的值并在 cmb_cfg.h 里重新定义这两个宏。

这里以 rt1052 的 mdk 工程为例进行讲解如何在工程里找到这两个宏的值。首先找 CMB_CSTACK_BLOCK_NAME 的值,我们打开工程里的启动文件,可以在文件的开头看到这样一段代码

  1. AREA RESET, DATA, READONLY
  2. EXPORT __Vectors
  3. EXPORT __Vectors_End
  4. EXPORT __Vectors_Size
  5. IMPORT |Image$$ARM_LIB_STACK$$ZI$$Limit|
  6. __Vectors DCD |Image$$ARM_LIB_STACK$$ZI$$Limit|;Top of Stack

ImageARM_LIB_STACKARM​L​​IB​S​​TACKZILimit,所以 CMB_CSTACK_BLOCK_NAME 的值应该是 ImageARM_LIB_STACKZI。

CMB_CODE_SECTION_NAME 的值在分散加载文件里寻找,分散加载文件可以点击 MDK的 Options -> Linker 选项面板里的 Edit… 按纽打开

我们可以找到这样一段代码

  1. ER_IROM1 m_text_start m_text_size ; load address = execution address
  2. {
  3. *(RESET,+FIRST)
  4. *(InRoot$$Sections)
  5. .ANY (+RO)
  6. }

保存有 .ANY (+RO) 的 SECTION 名字就是我们要找的值,所以,CMB_CODE_SECTION_NAME 的值为 ER_IROM1

开启C99

CmBacktrace 的使用需要 C99 的支持,使用 MDK 的开发者可以在 Options -> C/C++面板中勾选 C99 Mode选项。

使用IAR的开发者,可以在 Options -> C/C++ Compiler 中选择 C99。

使用 GCC 进行编译的用户,在编译配置中增加 -std=c99 即可 。

确定初始化参数

在使用 CmBacktrace 之前需要先调用下初始化函数,函数原型如下:

  1. void cm_backtrace_init(constchar*firmware_name,constchar*hardware_ver,constchar*software_ver)

CmBacktrace 的初始化函数需要 3 个参数,第一个参数是固件名字,第二个参数是硬件版本,第三个参数是软件版本。这三个参数会在发生 hard fault时打印出来,firmware_name需要填写生成的固件名称,错误填写会导致在使用 addr2line 时无法找到文件。hardware_ver和software_ver建议填写真实的软硬件版本号,方便后期调试和维护。在 cmb_port.c 文件中,我们可以看到 RT-Thread 已经将 rt_cm_backtrace_init 函数进行了自动初始化,默认的三个参数分别是rtthread,1.0,1.0,开发者需要按照实际情况进行更改。

使用示例

CmBacktrace 提供了一个测试函数,提供除零测试和执行非对齐访问的测试。当做完上面的准备工作后,开发者可以直接将工程编译,下载进板子里,进行测试,判断 CmBacktrace 是否正常工作。

CmBacktrace 导出到 finsh shell 中的测试函数命令为cmb_test,输入 cmb_test DIVBYZERO 就是进行除零测试,输入 cmb_test UNALIGNED 就是执行非对齐访问的测试。

我们看下运行完除零测试的结果

  1. msh />cmb_test DIVBYZERO
  2. thread pri status sp stack size max used left tick error
  3. ---------------------------------------------------------
  4. tshell 20 ready 0x000001000x0000100023%0x00000009000
  5. phy 30 suspend 0x0000006c0x0000020030%0x00000001000
  6. tcpip 10 suspend 0x000000b40x0000080017%0x00000014000
  7. etx 12 suspend 0x000000880x0000040013%0x00000010000
  8. erx 12 suspend 0x000000880x0000040013%0x00000010000
  9. mmcsd_de 22 suspend 0x000000900x0000040049%0x00000013000
  10. tidle 31 ready 0x000000540x0000010032%0x00000018000
  11. main 10 suspend 0x000000640x0000080035%0x00000012000
  12. Firmware name: rtthread-imxrt, hardware version:1.0, software version:1.0
  13. Fault on thread tshell
  14. =====Thread stack information =====
  15. addr:80002ad0 data:00000012
  16. addr:80002ad4 data:6002ae58
  17. addr:80002ad8 data:80001a40
  18. addr:80002adc data:6000b575
  19. addr:80002ae0 data:80001a40
  20. addr:80002ae4 data:80001a49
  21. addr:80002ae8 data:00000000
  22. addr:80002aec data:00000000
  23. addr:80002af0 data:00000000
  24. addr:80002af4 data:00000000
  25. addr:80002af8 data:00000000
  26. addr:80002afc data:00000000
  27. addr:80002b00 data:00000000
  28. addr:80002b04 data:00000000
  29. addr:80002b08 data:20000c7c
  30. addr:80002b0c data:00000012
  31. addr:80002b10 data:80001a40
  32. addr:80002b14 data:20000c7c
  33. addr:80002b18 data:00000001
  34. addr:80002b1c data: deadbeef
  35. addr:80002b20 data: deadbeef
  36. addr:80002b24 data: deadbeef
  37. addr:80002b28 data: deadbeef
  38. addr:80002b2c data:60019ffb
  39. addr:80002b30 data:00000001
  40. addr:80002b34 data:0000000d
  41. addr:80002b38 data:00000000
  42. addr:80002b3c data:60015a7b
  43. addr:80002b40 data:23232323
  44. ====================================
  45. ===================Registers information ====================
  46. R0 :0000000a R1 :00000000 R2 :0000004f R3 :80808000
  47. R12:01010101 LR :6000c5ad PC :6000c5c8 PSR:41000000
  48. ==============================================================
  49. Usage fault is caused byIndicates a divide by zero has taken place (can be set only if DIV_0_TRP isset)
  50. Show more call stack info by run: addr2line -e rtthread-imxrt.axf -a -f 6000c5c86000c5a96002ae546000b57160019ff760015a77

CmBacktrace 首先打印出了发生 hard falut 时的所有线程信息,接着打印了固件名字和软硬件版本号,再打印了错误是发生在 tshell 这个线程里面的(因为我们是在 tshell 这个线程里调用的除零测试函数),紧接着打印的是线程的栈信息和寄存器信息。最后两行信息是最重要的,倒数第二行介绍了发生故障的原因,是因为除零造成的。最后一行提示如果需要获取函数调用栈,需要在 addr2line 中运行 CmBacktrace 给出的参数。

在使用addr2line 之前我们要先确认保存了工程对象文件的文件夹位置。使用mdk的开发者,可以在 Options -> Output 面板中查看,设置对象文件的保存路径。

使用IAR的开发者,可以在 Options -> General Options 面板的Output选项中查看和设置。

我们将 run:后面的所有内容都复制下来,然后进入保存了工程生成的对象文件的文件夹,打开env,将刚刚复制的内容粘贴上去,按下回车,错误现场的函数调用栈就会输出出来,我们看下刚刚进行除零测试时的函数调用栈的信息

  1. > addr2line -e rtthread-imxrt.axf -a -f 6000c5c86000c5a96002ae546000b57160019ff760015a77
  2. 0x6000c5c8
  3. cmb_test
  4. D:\rt-thread\bsp\imxrt1052-evk/packages\CmBacktrace-v1.2.0\/cmb_port.c:87
  5. 0x6000c5a9
  6. cmb_test
  7. D:\rt-thread\bsp\imxrt1052-evk/packages\CmBacktrace-v1.2.0\/cmb_port.c:82
  8. 0x6002ae54
  9. FSymTab$$Base
  10. ??:?
  11. 0x6000b571
  12. msh_get_cmd
  13. D:\rt-thread\bsp\imxrt1052-evk/..\..\components\finsh\/msh.c:312
  14. 0x60019ff7
  15. msh_exec
  16. D:\rt-thread\bsp\imxrt1052-evk/..\..\components\finsh\/msh.c:335
  17. 0x60015a77
  18. finsh_thread_entry
  19. D:\rt-thread\bsp\imxrt1052-evk/..\..\components\finsh\/shell.c:613

我们可以看到,CmBacktrace 不仅仅定位出了是 cmb_port.c里第87行产生的问题,还打印出了函数调用逻辑关系,方便开发者进行 BUG 修复。

常见问题

  • 使用前必须确认自己的 MCU 是 ARM Cortex-M0(+)/M3/M4/M7 架构,其他架构暂不支持。
  • 使用前要确定 CMB_CSTACK_BLOCK_NAME 和 CMB_CODE_SECTION_NAME 两个宏的宏定义,如果定义错误,CmBacktrace会无法正确使用。
  • 初始化时要输入正确的固件名称,不然使用 addr2line 时会提示找不到文件
  • 当线程的栈被写穿时,CmBacktrace 无法正常使用。

    参考

API列表

cm_backtrace_init

CmBacktrace 库初始化

函数原型:

  1. void cm_backtrace_init(constchar*firmware_name,constchar*hardware_ver,constchar*software_ver)
参数 描述
firmware_name 固件名称,需与编译器生成的固件名称对应
hardware_ver 固件对应的硬件版本号
software_ver 固件的软件版本号

函数返回:无

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

cm_backtrace_call_stack

获取函数调用栈

函数原型:

  1. size_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) */
  2. uint32_t call_stack[16]={0};
  3. size_t i, depth =0;
  4. /* 获取当前环境下的函数调用栈,每个元素将会以 32 位地址形式存储, depth 为函数调用栈实际深度 */
  5. depth = cm_backtrace_call_stack(call_stack,sizeof(call_stack), cmb_get_sp());
  6. /* 输出当前函数调用栈信息
  7. * 注意:查看函数名称及具体行号时,需要使用 addr2line 工具转换
  8. */
  9. for(i =0; i < depth; i++){
  10. printf("%08x ", call_stack[i]);
  11. }

cm_backtrace_assert

追踪断言错误信息

函数原型:

  1. void cm_backtrace_assert(uint32_t sp)
参数 描述
sp 断言环境时的堆栈指针

函数返回:无

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

cm_backtrace_fault

追踪故障错误信息

函数原型:

  1. 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 方法。所以移植时,最简单的方式就是直接使用该汇编文件。

原文: https://www.rt-thread.org/document/site/rtthread-application-note/debug/cmbacktrace/an0013-rtthread-debug-CmBacktrace/

(0)

相关推荐

  • 一种编写回调函数的简单方法

    繁琐的重复性工作 在之前的一篇文章中,我们讨论了这样一个主题:如果回调函数是一个类的成员函数,则这个回调函数必须被声明为静态的. 编写一个静态的回调函数,其实并不复杂,但是还是比较繁琐,下图中展示了一 ...

  • 系统故障转储怎么消除?消除系统故障转储的方法

    **** 故障转储是当系统出现错误时,故障转储用于保存环境代码,内存映像和错误代码,是一个特殊的文件,一般保存在C分区中.那么当我们的系统发生了故障转储,我们该怎么消除呢?不会的小伙伴一起来看看吧,希 ...

  • Ace Admin 使用教程

    (原) 公司项目要换框架,然后丢了一套国外的给我,ace admin,本想着拿来改改,翻翻百度就能用的,可它是国外的啊,国内普及率又不高,没办法,硬着头皮一点点啃英文文档吧. 最近留邮箱要文档的太多拉 ...

  • 为XBox开发者提供的ASan早期发布版本

    本文来自XBox Advanced Technology Group (ATG)的首席软件工程师Tad Switf. 介绍 Address Sanitizer (ASan) 是一款用来查找难以追踪的内 ...

  • Lua 5.3 参考手册

    作者 Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes 译者 云风 Lua.org, PUC-Rio 版权所有 © ...

  • 超级干货:高瓴资本张磊的投资笔记和思维导图

    超级干货:高瓴资本张磊的投资笔记和思维导图

  • 一则公报案例学习笔记:对修改股东出资期限应否适用资本多数决规则的思考|审判研究

    一.问题的提出 2021年第3期<最高人民法院公报案例>刊登了鸿大(上海)投资管理有限公司与姚锦城公司决议纠纷上诉案,裁判要旨为:"公司股东滥用控股地位,以多数决方式通过修改出资 ...

  • 725分高考状元唐楚玥记笔记的“三字”方法,以及里面藏着的学习秘诀

    你背不下来的书,总有人能背下来:你做不出来的题,总有人能做出来:你不爱做的笔记,总有人把它做到了极致. 不要小看记笔记,同样是一手漂亮的笔记,效果可能千差万别. 湖北725分高考状元唐楚玥的笔记就做到 ...

  • 72条经方笔记,助你精细辩证(建议收藏)

    导读:前人总结的经方运用思路,供大家参考. 1.温病不能发汗,又不能吃泻药,更不能用火攻,只能用白虎汤.真正的温病实证(表现为说胡话,大便干,下同时用强壮滋阴解热药活不用生地用人参也可),可用大量的麦 ...

  • JAVA多线程学习笔记整理

    多线程: 三种创建方法 继承Thread类,以线程运行内容重写run方法,创建Thread对象并用start方法启动该线程. (匿名内部类) (Lambda表达式) 实现Runable接口,以线程运行 ...

  • 高建忠.读方用方笔记(十)临证谈理中丸

    我们上一节谈了理中丸的第一功效,就是止吐泻的,那么这一节我们来谈理中丸方中何为君药?我们先来看看原方: 理中丸方:人参,干姜,炙甘草,白术各三两.上四味,捣筛,蜜和为丸,如鸡子黄许大,以沸汤数合,和一 ...

  • 周哥学习笔记(2021.5.8)

    心理界限存在的意义,正是为了帮助人们控制情绪进入的量,不至于太过冷漠或太过投入,让我们保持一个合适的距离与外界互动. 人没有办法只通过吸收变得更美好和丰富,它必须通过大胆的碰撞和创造.如果不能保持足够 ...

  • 皮囊读书笔记,当没有倾听者时,它就是伙伴

    当没有倾听者时,你就与书对话,它就是你的伙伴. 书的前半段,我一直在思考,为什么叫皮囊,皮囊应该写的是, 我们的生命本来多轻盈,都是被这肉体和各种欲望的污浊给拖住. 从前面几章阿太和父亲的皮囊中似乎领 ...

  • 《大江东区》杂谈,读书笔记

    雷东宝出监狱,对小雷家重新安排,为了得到政府的支持,把小雷家镇政府所有制.对于人物的安排,士跟是不敢再用了,让红伟远走,隔了一大领军人物,提了一个后期新秀,让背后的一堆后期新秀站到了他队伍中,这就是权 ...