HaaS100开发调试系列 之 使用AliOS Things诊断调试组件定位Bug

1、背景

在嵌入式开发中,我们经常遇到的一个问题是:写代码一个不小心,就制造了一个bug,C语言中bug的威力大家也心知肚明——可以直接把系统搞挂!

即大家常见的系统死机、系统重启等等;而问题的来源或者根因,又常常使得我们束手无策,只好采用“打印”大法,一遍遍的加printf。

而每次改代码又要经历痛苦的“编译-烧写-运行-复现”这个过程,不知不觉,一天过去了,bug还没解。

 

所以我们经常想,要是系统能直接告诉我们bug在哪、是什么错误导致的就好了,直接改代码分分钟搞定,可以节省很多开发时间!

这也是我们常用一些仿真器(如JLINK)的原因,系统挂死后可以挂上仿真器,查看PC在哪,通过bt查看调用栈等来帮助我们定位。

而这又对硬件有了一定的要求——要能支持仿真器连接,有时还要开发者折腾一下环境。

我们的HaaS100虽然也支持硬件连接仿真器(参考上一篇帖子:HaaS100开发调试系列 之 如何使用J-Link仿真器调试代码)。

这里我们告知大家一个更方便定位系统异常死机的方法——AliOS Things的诊断调试组件

 

2、诊断调试组件简介

诊断调试包含的内容很多,前面我们介绍过一些调试命令,参见文章《一文轻松入门HaaS100诊断调试系统》。

本文我们重点介绍的是AliOS Things的诊断调试组件是怎么帮助解决代码bug的。

 

诊断调试组件可以缩短bug定位时间。

如果一个bug出现导致系统异常后,用户可以不用连仿真器、不用加打印、不用打开gdb单步调试的情况下,可以快速找到bug原因。

或者帮助用户指出可能的异常点,进而修复以节省开发时间。

 

举例说明:

  • 代码中访问了非法内存(比如:在不可写的地址处写了数据,如访问了0地址)导致系统奔溃,AliOS Things诊断调试组件可以记录访问非法内存时的pc值,告诉用户挂在了哪一行;
  • 代码跑飞了(pc=0),AliOS Things诊断调试组件记录了函数调用的栈,并根据栈向上回溯可以找到A->B->C的函数调用过程,与仿真器中bt命令类似;
  • 用户内存申请时malloc 失败,AliOS Things诊断调试组件可以记录用户此时申请了多少内存、此时系统还有多少内存可以供申请、用户是在哪个任务中申请的内存、从系统启动开始内存的申请情况等信息,这些信息可以帮助开发者定位是否有内存泄漏的情况。
  • ......

AliOS Things的诊断调试组件可以干很多事,后面我们会陆续推出文章来介绍。

今天我们只看一个问题——bug产生了,系统异常挂死了,那么AliOS Things会做哪些事呢?

简单一句话回答,输出重要的log帮助大家定位问题,这也是AliOS Things的诊断调试组件最重要的部分。

 

3、AliOS Things的异常log到底是啥样

直接上HaaS100输出的log

!!!!!!!!!! Exception  !!!!!!!!!!
========== Regs info  ==========  异常现场寄存器信息
R0      0x00000000
R1      0x34027F20
R2      0x34027F30
R3      0x340251B4
R4      0xFFFFFFFF
R5      0x00000000
R6      0x2C0D2C72
R7      0x00000001
R8      0x2C0D2C86
R9      0x2C0D236B
R10     0x00000000
R11     0x00000000
R12     0x0000C000
LR      0x1C5D6CC3
PC      0x1C5D6CC2
xPSR    0x61000000
SP      0x34025118
EXC_RET 0xFFFFFFBC
EXC_NUM 0x00000006
PRIMASK 0x00000000
FLTMASK 0x00000000
BASEPRI 0x00000000
CFSR    0x01000000
HFSR    0x00000000
MMFAR   0xE000ED34
BFAR    0xE000ED38
AFSR    0x00000000
========== Stack info ==========  异常现场栈信息
stack(0x34025118): 0x34027D20 0x340251B4 0x00000000 0x34022F98 
stack(0x34025128): 0x00000000 0x34682380 0x1C5D6BBD 0x00000005 
stack(0x34025138): 0x00000006 0x1C5D621F 0x00000003 0x2C0D2A0C 
stack(0x34025148): 0x340230C4 0x00000013 0x34682280 0x00000000 
stack(0x34025158): 0x000000F7 0x00000005 0x00000003 0x00000000 
stack(0x34025168): 0x00000000 0x00000000 0x00000001 0x34682380 
stack(0x34025178): 0x34022F98 0x0000000B 0x34022FA8 0x00000000 
stack(0x34025188): 0x000000F6 0x00000001 0x2C0D294D 0x1C5D63D3 
stack(0x34025198): 0x00000000 0x2C0D1BD0 0x00000000 0x0D000000 
stack(0x340251A8): 0x78300070 0x66666666 0x66666666 0x00003100 
stack(0x340251B8): 0x00000000 0x00000000 0x00000000 0x00000000 
stack(0x340251C8): 0x00000000 0x00000000 0x00000000 0x00000000 
stack(0x340251D8): 0x00000000 0x00000000 0x00000000 0x00000000 
stack(0x340251E8): 0x00000000 0x00000000 0x00000000 0x00000000 
stack(0x340251F8): 0x00000000 0x00000000 0x00000000 0x00000000 
stack(0x34025208): 0x00000000 0x00000000 0x00000000 0x00000000 
========== Call stack ==========  栈回溯信息,可以得出函数调用过程
backtrace : 0x1C5D6CC2 
backtrace : 0x1C5D621C 
backtrace : 0x1C5D63CE 
backtrace : ^task entry^
========== Heap Info  ==========  系统此时的内存信息,可以看出内存申请了多少,还剩多少
---------------------------------------------------------------------------
[HEAP]| TotalSz    | FreeSz     | UsedSz     | MinFreeSz  | MaxFreeBlkSz  |
      | 0x00680000 | 0x0065A300 | 0x00025D00 | 0x00659E20 | 0x0065A300    |
---------------------------------------------------------------------------
========== Task Info  ==========   系统当前任务状态信息,可以看出任务栈是否过小
--------------------------------------------------------------------------
TaskName             State    Prio       Stack      StackSize (MinFree)
--------------------------------------------------------------------------
dyn_mem_proc_task    PEND     0x00000006 0x2004B938 0x00000400(0x0000035C)
idle_task            RDY      0x0000003D 0x2004BE0C 0x00001000(0x00000F94)
DEFAULT-WORKQUEUE    PEND     0x00000014 0x2004F1E8 0x00000C00(0x00000B7C)
timer_task           PEND     0x00000005 0x2004D0D8 0x00002000(0x00001F48)
main                 SLP      0x00000021 0x2015A000 0x00005000(0x000044C4)
transq_msg           PEND     0x0000001F 0x3469A4C4 0x00001000(0x00000680)
apps_recover         SLP      0x00000021 0x2004A588 0x00001000(0x00000F64)
temp_main            SLP      0x00000021 0x346A15D8 0x00001000(0x00000F80)
main_task            SLP      0x00000020 0x34002668 0x00020000(0x0001F6C4)
cli                  RDY      0x0000003C 0x340232D0 0x00002000(0x0000180C)
ulog                 PEND     0x0000003C 0x34026890 0x00000C00(0x00000A58)
========== Queue Info ==========   AliOS Things kernel queue使用信息
-------------------------------------------------------
QueAddr    TotalSize  PeakNum    CurrNum    TaskWaiting
-------------------------------------------------------

======== Buf Queue Info ========  AliOS Things kernel buf queue使用信息
------------------------------------------------------------------
BufQueAddr TotalSize  PeakNum    CurrNum    MinFreeSz  TaskWaiting
------------------------------------------------------------------
0x2004FDE8 0x000001E0 0x00000000 0x00000000 0x000001E0 timer_task          
0x34025420 0x00001400 0x00000000 0x00000000 0x00001400 ulog

=========== Sem Info ===========  AliOS Things kernel semphore使用信息
--------------------------------------------
SemAddr    Count      PeakCount  TaskWaiting
--------------------------------------------
0x2004CF60 0x00000000 0x00000000 dyn_mem_proc_task   
0x2004F1B8 0x00000000 0x00000000 DEFAULT-WORKQUEUE   
0x340023A0 0x00000001 0x00000001                     
0x34002478 0x00000000 0x00000000                     
0x340025D0 0x00000001 0x00000001                     
0x34682C34 0x00000000 0x00000000                     
0x34682C58 0x00000000 0x00000000                     
0x340275B0 0x00000000 0x00000000                     
!!!!!!!!!! dump end   !!!!!!!!!!

3.1、Log分析

上面的log是在HaaS100上产生系统异常后,由AliOS  Things输出的log。log可以分为:

  • 异常现场寄存器:跟arch相关的通用寄存器和一些特殊寄存器信息;
  • 异常栈信息:产生异常的任务的栈信息;
  • 栈回溯信息:产生异常的调用栈,类似仿真器中的bt命令,这个是异常log中最重要的部分;
  • 内存信息:系统此时的内存状态,对于定位一些内存泄漏问题比较有用;
  • 任务信息:系统当前的任务状态信息,对于定位任务栈溢出问题比较有用;
  • 内核信息:包含了kernel 中的queue、buf_queue 和 sem状态。

 

log中所示的内存包含了很多内核相关的内容,后续我们也会推出文章来介绍AliOS Things的内核。

 

3.2、如何打开诊断调试组件

用户只需要在aos.mk里包含debug组件,重新编译烧录上电即可。

$(NAME)_COMPONENTS += debug

 

3.3、如何产生一个系统异常

理论上任何一个系统异常后,都会出现类似上面的log,如果开发者对产生系统异常感兴趣,可以使用下面的简单方法:

m 0xffffffff 1

即使用系统提供的cli 命令,改写系统位于0xfffffff出的内存值为1,地址0xfffffff在HaaS100上为不可写的区域,改写这个值可以触发系统异常,打印出上面的log。

使用cli命令的方法可以参考另外一篇文章《一文轻松入门HaaS100诊断调试系统》

 

3.4、调用栈的价值

调用栈的信息输出是AliOS Things诊断调试组件的核心,我们通过上面的命令产生异常后,使用toolchain自带的arm-none-eabi-addr2line 命令对上面log中的call stack调用栈中的地址进行解析,使用方法是:

 arm-none-eabi-addr2line -pfiCe xxx.elf addr

以log中输出的call stack地址为例

./build/compiler/gcc-arm-none-eabi/Linux64/bin/arm-none-eabi-addr2line -pfiCe out/debug_demo@haas100/binary/[email protected] 0x1C5D6CC2 0x1C5D621C 0x1C5D63CE

可以解析出调用栈所对应的代码位置,如:

pmem_cmd at /workspace/hass/AliOS-Things/core/cli/cli_default_command.c:224

proc_onecmd at /workspace/hass/AliOS-Things/core/cli/cli.c:173
 (inlined by) cli_handle_input at /workspace/hass/AliOS-Things/core/cli/cli.c:290

cli_main at /workspace/hass/AliOS-Things/core/cli/cli.c:781

我们可以清楚看到发生异常的函数调用过程,并且指出了函数代码的路径和行号。

cli_main  -- >  proc_onecmd ---> pmem_cmd

 

4、笔者的话

大家有没有觉得,通过这个方法定位Bug,让异常发生的位置一目了然,我们快速找到这行代码后修改,分分钟解决了这个Bug。又可以开心的继续干活了!

不过,AliOS Things诊断调试组件只是尽可能的帮助大家节省解Bug的时间,而有些Bug的产生并不会导致系统异常,但会给系统埋下不稳定的伏笔,这个时候再好的诊断工具也没用了。

大家还是要多修炼写代码内功,不产生bug才是我们的追求!

 

5、开发者技术支持

如需更多技术支持,可加入钉钉开发者群

更多技术与解决方案介绍,请访问阿里云AIoT首页https://iot.aliyun.com/

你可能感兴趣的:(AliOS,Things物联网操作系统,嵌入式,实时操作系统,debugger,物联网,HaaS)