在嵌入式开发中,我们经常遇到的一个问题是:写代码一个不小心,就制造了一个bug,C语言中bug的威力大家也心知肚明——可以直接把系统搞挂!
即大家常见的系统死机、系统重启等等;而问题的来源或者根因,又常常使得我们束手无策,只好采用“打印”大法,一遍遍的加printf。
而每次改代码又要经历痛苦的“编译-烧写-运行-复现”这个过程,不知不觉,一天过去了,bug还没解。
所以我们经常想,要是系统能直接告诉我们bug在哪、是什么错误导致的就好了,直接改代码分分钟搞定,可以节省很多开发时间!
这也是我们常用一些仿真器(如JLINK)的原因,系统挂死后可以挂上仿真器,查看PC在哪,通过bt查看调用栈等来帮助我们定位。
而这又对硬件有了一定的要求——要能支持仿真器连接,有时还要开发者折腾一下环境。
我们的HaaS100虽然也支持硬件连接仿真器(参考上一篇帖子:HaaS100开发调试系列 之 如何使用J-Link仿真器调试代码)。
这里我们告知大家一个更方便定位系统异常死机的方法——AliOS Things的诊断调试组件。
诊断调试包含的内容很多,前面我们介绍过一些调试命令,参见文章《一文轻松入门HaaS100诊断调试系统》。
本文我们重点介绍的是AliOS Things的诊断调试组件是怎么帮助解决代码bug的。
诊断调试组件可以缩短bug定位时间。
如果一个bug出现导致系统异常后,用户可以不用连仿真器、不用加打印、不用打开gdb单步调试的情况下,可以快速找到bug原因。
或者帮助用户指出可能的异常点,进而修复以节省开发时间。
举例说明:
AliOS Things的诊断调试组件可以干很多事,后面我们会陆续推出文章来介绍。
今天我们只看一个问题——bug产生了,系统异常挂死了,那么AliOS Things会做哪些事呢?
简单一句话回答,输出重要的log帮助大家定位问题,这也是AliOS Things的诊断调试组件最重要的部分。
直接上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 !!!!!!!!!!
上面的log是在HaaS100上产生系统异常后,由AliOS Things输出的log。log可以分为:
log中所示的内存包含了很多内核相关的内容,后续我们也会推出文章来介绍AliOS Things的内核。
用户只需要在aos.mk里包含debug组件,重新编译烧录上电即可。
$(NAME)_COMPONENTS += debug
理论上任何一个系统异常后,都会出现类似上面的log,如果开发者对产生系统异常感兴趣,可以使用下面的简单方法:
m 0xffffffff 1
即使用系统提供的cli 命令,改写系统位于0xfffffff出的内存值为1,地址0xfffffff在HaaS100上为不可写的区域,改写这个值可以触发系统异常,打印出上面的log。
使用cli命令的方法可以参考另外一篇文章《一文轻松入门HaaS100诊断调试系统》
调用栈的信息输出是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
大家有没有觉得,通过这个方法定位Bug,让异常发生的位置一目了然,我们快速找到这行代码后修改,分分钟解决了这个Bug。又可以开心的继续干活了!
不过,AliOS Things诊断调试组件只是尽可能的帮助大家节省解Bug的时间,而有些Bug的产生并不会导致系统异常,但会给系统埋下不稳定的伏笔,这个时候再好的诊断工具也没用了。
大家还是要多修炼写代码内功,不产生bug才是我们的追求!
如需更多技术支持,可加入钉钉开发者群
更多技术与解决方案介绍,请访问阿里云AIoT首页https://iot.aliyun.com/