结合AliOS Things谈嵌入式系统通用问题定位方法(3):问题定位思路

简介: 各种各样的错误现场,使用的手段不尽相同。

1、定位思路参考

各种各样的错误现场,使用的手段不尽相同。

正常问题有较固定的模式去一层层剥开现象,找到本质;

还有一些问题,需要定位者仔细观察发现每一处不合乎逻辑(代码逻辑、内存逻辑等)可疑点,忽略任意一点可疑点,极大概率就会导致错过真相,含有疑点地去下结论,误判的可能性极高。

还有一些复杂问题,看似毫无逻辑性,有时需要跳出固定思维,换个层面或者角度;

总之,大家在定位问题中都会有一些自己的心得,遇到过各种各样的情况。

此处从内核底层软件角度出发,提供一些基本有效的定位手段和分析方法,给大家参考。

 

1.1、找致命错误或者异常现场

从内核系统角度,在运行中会检测内存的异常状态。

首先要判断系统出现哪种问题,如下面是内核检测最常见的两种出错状态:

发现内存问题,如内存释放时检测内存踩踏,内存申请时,发现空间不足,即会报“!! Fatal Error !!”;

系统出现异常后,内核会进入异常处理流程,串口log会输出“!! Exception !!”。

 

1.2、内存释放检测致命错误问题

  • 看打印,找报错内存

WARNING, memory maybe corrupt!! 0x81a2c378

  • 串口可以输入,输入命令查看内存状态

p 0x81a2c378 32 4                  // 输入32个4字节大小数据

  • 搜 !used !free关键字,其为内核检测出被踩内存

  • 观察出问题内存的前一块,或者之前内存块,一般简单情况都是前一块尾部踩后一块内存头:

  • 通过汇编或者基本的调用栈,分析前面一块内存使用情况

重点关注前一块内存申请后的操作,是否有memset、memcpy动作,相关代码逻辑是否有合法性判断。从汇编来看是最准确的代码逻辑,不容易像C代码一样,可能存在各种宏开关之类的误导。但是逻辑复杂,看汇编则不合理,需要大家一起检视代码确认。

也会存在内存直接使用越界的情况,比如结构体强转后越界访问,此时可以通过编译、PC-lint告警之类简单排查,也可以直接检视汇编或者C代码。

检视相关后一致认为前面的内存没有异常使用的情况下,这种可能就是二次现场或者飞踩的情况。不同情况因地制宜,参考下面几种推荐方法:

  • 优先考虑加watchpoint监控内存;
  • p前后的内存值,找规律,尤其是看内存被改写成何值,有时可以关联被谁误改写;
  • 看当前的内存头状态,是否存在已经被释放即use-after-free的情况;
  • 看任务栈是否溢出:任务栈溢出会导致各种奇怪的问题,看当前可用任务栈是否已经很少;
  • 排查业务逻辑自身问题,尤其涉及模块代码规范性问题,异常状况分支处理
  • 排查多核多任务资源保护问题;
  • 加维测:在合理的位置、合适的判断条件加合适的维测,能达到让问题尽快暴露,输出更多有用信息的效果。

 

1.3、看任务栈sp是否溢出

内核中有任务栈溢出的检测,如果检测到栈溢出了,则会即使报错。但是由于不可能检测所有栈范围,用户的代码如果访问时,跨过了栈检测的魔术字位置,那系统就不能及时报错,从而导致后续奇怪的现场。

异常时,内核会自动打印出任务信息,用户也可以在串口输入tasklist来查看

  • 对比当前SP的值,和任务栈范围

如当前 SP 0x4F8097A8

查看对应任务信息:

在任务栈的范围内则正常。

另外可以查看当前任务栈的MinFree,其表示任务栈使用过程中的最小剩余值,如果发现其很小,则基本上栈已经溢出了。

 

1.4、手动解栈

偶尔会遇到各种原因,现场调用栈没有打印出来。有栈解析遇到特殊情况的问题,也有可能pc非法等造成解析不全之类的。这时候需要人工去解调用栈。

其实方法很简单:

找到当前的SP值 => 找到当前任务栈的内容 => 将大于等于SP位置的内存都过滤 =>

找到函数执行轨迹。

 

1.5、Addr2line

如在ELF结构章节中提到的,可以通过addr2line命令快速关联某汇编和对应的.c位置

localhost: arm-none-eabi-addr2line -e ./helloworld@developerkit.elf 0x80021f8

/workspace/aos_gerrit_new/core/osal/aos/rhino.c:815

 

1.6、查看当前任务状态

tasklist

异常现场会自动打印当前任务状态,用户可以直接输入tasklist查看当前任务状态

 

1.7、查看某任务调用栈

taskbt 任务ID

 

1.9、查看任务/信号量/mutex状态

debug

如从上图可以看到,dyn_mem_proc_task任务在等待某个sem,处于pend状态。

 

1.9、查看任务CPU占有率

cpuusage

以此定位是否有任务超负荷占用CPU

 

1.10、查看内存值

关键时候,需要查看内存,通过p命令

下面命令打印32个4字节内存:

 

1.11、Dumpsys导内存

怀疑内存堆有问题,可以通过dumpsys

mm_info命令查看所有内存blk状态,然后再结合p内存命令定位。

 

1.12、强制异常 (fiq)

如果系统停止运行,怀疑死锁之类的问题,可以通过

$#@!

命令通过让CPU进入fiq打断现场,内核会将当前的各种状态快照输出来分子当前的状态,包括:通用寄存器现场、任务状态、内存状态等。

 

1.13、异常定位

非法的内存、指令访问会导致异常。内存被踩,如果没有及时检测魔术字错误,用户访问的时候也会触发异常。

下面以某一实际遇到的异常log为例:

  • 看异常类型,最普遍的区分数据异常还是指令异常

 

  • 看异常位置,核对异常状态寄存器和通用寄存器

异常PC:
 


  1. kernel space exception
  2. ========== Regs info  ==========
  3. PC      0x4060E160

对应汇编:

4060e160:   e5d4900f    ldrb    r9, [r4, #15]

查看当前r4值:


 
R4      0x 4FFFFFF 4

查看数据异常寄存器:


  
DFAR    0x 50000003

从上面看出,R4 = 0x4FFFFFF4,导致访问了 0x4FFFFFF4 + 15位置的内存异常。

那问题就变为R4为何不对。

  • 分析导致异常寄存器的前后赋值关系在汇编中(此时C代码也可),查看r4的赋值:

(下面出错位置代码较长,有截取汇编上下文,关注下面加“//”部分)
 


   
  1. 4060e098 <scanSetRequestChannel>:
  2. 4060e098 :   e92d43f0    push    { r4, r5, r6, r7, r8, r9, lr}
  3. 4060e09c:   e24dd04c    sub sp, sp, #76 ; 0x4c
  4. 4060e0a0:   e1a07001    mov r7, r1
  5. 4060e0a4:   e1a04002    mov r4, r2                                              //
  6. ***********省略*****************
  7. 4060e160 :   e5d4900f    ldrb    r9, [ r4, #15]                                   //
  8. 4060e164 :   e3a02010    mov r2, #16
  9. 4060e168 :   e0880206     add r0, r8, r6, lsl #4
  10. 4060e16c:   e1a01004    mov r1, r4
  11. 4060e170 :   e2800028    add r0, r0, #40 ; 0x28
  12. 4060e174 :   e2866001    add r6, r6, #1
  13. 4060e178 :   ebfef704    bl  405cbd90 <memcpy>
  14. 4060e17c:   e3a02020    mov r2, #32
  15. 4060e180 :   e28d1028    add r1, sp, #40 ; 0x28
  16. 4060e184 :   e1a00009    mov r0, r9
  17. 4060e188 :   ebffff87    bl  4060dfac <scanSetBit>
  18. 4060e18c:   e2855001    add r5, r5, #1                                            //

指令4060e160位置为异常位置;

4060e0a4位置R4为从R2赋值,R2为scanSetRequestChannel的第三个参数;

  • 看压栈现场

此时怀疑scanSetRequestChannel入参有问题,那么可以看这个函数调用过来的现场,从Call

stack找出上一层位置:


    
  1. ========== Call stack ==========
  2. backtrace : 0x4060E160
  3. backtrace : 0x405FBFB4                       //
  4. backtrace : 0x405F5558
  5. backtrace : 0x40628B60
  6. backtrace : ^task entry^

 

查看0x405FBFB4汇编
 


     
  1. 405fbf94:   e 2842f 6b    add r 2, r 4, # 428    ; 0x 1ac                    //
  2. 405fbf98:   e 5d 43001    ldrb    r 3, [r4, #1]
  3. 405fbf9c:   e 1a 00005    mov r 0, r 5
  4. 405fbfa0:   e 58d 7000    str r 7, [sp]
  5. 405fbfa4:   e 59411a 8    ldr r 1, [r4, #424]  ; 0x1a8
  6. 405fbfa8:   e2433003    sub r3, r3, #3
  7. 405fbfac:   e16f3f13    clz r3, r3
  8. 405fbfb0:   e1a032a3    lsr r3, r3, #5
  9. 405fbfb4:      eb004837     bl  4060e098 <scanSetRequestChannel>      //
  10. 405fbfb8:      eaffffab     b   405fbe6c <aisFsmSteps+0x794>          //

指令位置405fbf94显示R2由R4 + 0x1ac所得,那么就需要找到R4,而我们知道R4一般会在被调用函数scanSetRequestChannel压栈处理,那么回头看scanSetRequestChannel的汇编开头


      
  1. 4060e098 <scanSetRequestChannel>:
  2. 4060e098:   e 92d 43f 0    push    {r 4, r 5, r 6, r 7, r 8, r 9, lr}              //

 

那么找到压栈位置,就可以找到R4的值,一般通过被压栈的LR位置来搜索,当前进入scanSetRequestChannel时LR的值是405fbfb8,可以在栈的上下文搜索(或者通过当前SP)405fbfb8,找到下面栈位置,并往前搜索7个单元,找到R4压栈的位置,显示R4=0x4F7FEE98。


       
  1. stack( 0x 4F 8097E 8) : 0x 00043753 0x 8184D 561 0x 4F 7FEE 98 0x 4F 7FEE 98 (R 4)     //
  2. stack( 0x 4F 8097F 8) : 0x 4F 769498 0x 4F 7FEFA 0 0x 4F 80FD 88 0x 00000000
  3. stack( 0x 4F 809808) : 0x 4F 78FF 8C 0x 405FBFB 8(LR) 0x 4F 80FD 88 0x 4071B 904

 

到目前为止,我们找到R4 = 0x4F7FEE98,这个值看着挺正常,而且和出错时R4 0x4FFFFFF4地址相去甚远。

 

这里我们可以推测一个结论,至少R4在传入的时候还是好的,是在scanSetRequestChannel函数内部运行时发生了问题,怀疑大量循环之后或者不合理的数据运算造成了问题。

  • 观测当前LR(这一步可以在前面观察)

当前异常时LR一般是函数调用时候当前PC跳转前的下一条指令,会随着函数调用不断变化,因此从LR我们能定位当前处于哪个函数范围内。

观察到当前LR 0x4060E18C

对应汇编:

从这一点看出,LR已经在发生异常指令0x4060e160之后,推测确实是在进入了scanSetRequestChannel之后,在进行来回的循环之后才导致的异常。

那么此处有理由怀疑scanSetRequestChannel的R2入参正确的情况下,可能其他参数有问题,造成了R2循环累加的溢出。

同样按照分析R4在栈位置的原理,以及参考对应C代码,可以继续发现下面问题:

arChannel 是出问题的指针源头,u4ScanChannelNum是其索引,有极大的嫌疑有问题。


        
  1. void scanSetRequestChannel(IN struct ADAPTER *prAdapter,
  2.         IN uint32_t u4ScanChannelNum,
  3.         IN struct RF_CHANNEL_INFO arChannel[],
  4.         IN uint8_t fgIsOnlineScan,
  5.         OUT struct MSG_SCN_SCAN_REQ_V2 *prScanReqMsg)

 

再次分析汇编

u4ScanChannelNum作为参数被保存在R7,根据上下文R7没有被修改。
 


         
  1. 4060e098 <scanSetRequestChannel>:
  2. 4060e098:   e 92d 43f 0    push    {r 4, r 5, r 6, r 7, r 8, r 9, lr}
  3. 4060e09c:   e 24dd 04c    sub sp, sp, # 76 ; 0x 4c
  4. 4060e0a0:   e 1a 07001    mov r 7, r 1                                    //

再次查看异常寄存器现场,发现R7果然是一个很大的值。

 

最后一公里:

至此只能说找到出问题的点,scanSetRequestChannel的参数u4ScanChannelNum有问题,至于为什么R7有问题,代码是否有合法性判断,还是被其他引起的,此为后话,最后一公里,往往会产生很多变化,需要在实践中继续积累经验,才能最终定位根因,而不能仅停留在表明的错误。

 

2、质量工作

大部分时候,由于项目周期较短等原因,很多时候忽略了基本的代码质量检查。这种做法,对于商用产品来讲基本上可能是致命的,也是极度危险的。

除了少部分由代码逻辑之类引起的,剩余的还有一大部分问题都是由于代码的不规范引起的。解决不规范的代码所消耗的精力,往往比通过实际联调定位解决问题来的高效得多。

而且在某段时间实际运行中能发现的问题总归是有限的,远不如彻底根治不规范代码引起的风险来的有效。

下面列出的质量工作能够极大提高代码和产品质量:

  • 编译告警
  • PC-LINT检查
  • Coverity检查
  • Fortify检查
  • 详细覆盖全面的单元测试
  • 完善的逻辑组合测试
  • 顶层的黑盒测试
  • 其他

 

3、参考文档

《ARM相关架构文档》

《ELF格式解析》

 

4、传送门

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

 

开发者技术支持

如需更多技术支持,可加入钉钉开发者群,或者关注微信公众号

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

相关文章
|
物联网 Linux C语言
3_1_2 AliOS Things 编译工具及编译配置系统|学习笔记
快速学习3_1_2 AliOS Things 编译工具及编译配置系统。
384 0
3_1_2 AliOS Things 编译工具及编译配置系统|学习笔记
|
监控 IDE 物联网
结合AliOS Things谈嵌入式系统通用问题定位方法(2):内核相关基础
内核提供的任务创建接口,会存在参数指定当前任务创建完立即运行还是需要显示调用start运行,需要注意。如果在创建任务时指定了立即执行,而在创建任务后去设置任务参数,可能是不生效的。(尤其posix的pthread接口经常遇到这种问题)
379 15
结合AliOS Things谈嵌入式系统通用问题定位方法(2):内核相关基础
|
NoSQL 安全 IDE
结合AliOS Things谈嵌入式系统通用问题定位方法(1):CPU相关基础
本文着重从问题定位的角度来介绍如何定位嵌入式软件系统中的问题,并结合AliOS Things提供的部分维测手段来介绍。
结合AliOS Things谈嵌入式系统通用问题定位方法(1):CPU相关基础
|
物联网 调度
AliOS Things SMP系统及其在esp32上实现示例
AliOS Things实现了基本的SMP调度框架,支持多CPU体系的系统运行和调度机制。任务可以动态在多核间进行切换或者绑定运行;高优先级任务可以最大化利用空闲资源核运行。基于此框架,可以快速实现AliOS Things在各种不同多核CPU架构下的移植。
5332 0
|
物联网 测试技术 持续交付
AliOS Things 持续集成(CI)系统介绍
AliOS Things在快速的迭代进化之中,如何保证提交的代码质量,并保证在各个硬件平台上的稳定性,是一个非常大的挑战。同时物联网硬件碎片化,资源紧张,对持续集成(CI)系统也提出了特殊的要求。本文介绍AliOS Things的CI系统的实现方式及思考。
5606 0
|
安全 物联网 Java
AliOS Things 组件系统(uCube)
AliOS Things 是阿里巴巴提供的物联网操作系统,可以在不同的设备上运行不同的功能,甚至相同的设备运行不同的功能,AliOS Things 基于组件管理: 1、 组件功能单一,复用组件提供的功能,比如通道、升级等功能。
4384 0
|
传感器 消息中间件 物联网
HCIA物联网初级考试-第五章物联网操作系统及感知层开发介绍
HCIA物联网初级考试-第五章物联网操作系统及感知层开发介绍
HCIA物联网初级考试-第五章物联网操作系统及感知层开发介绍
|
算法 AliOS-Things 物联网
HaaS100开发调试系列 之 定位AliOS Things内存及Crash问题
本文主要说开发调试过程中经常遇到的内存问题。
341 15
HaaS100开发调试系列 之 定位AliOS Things内存及Crash问题
|
AliOS-Things 物联网 编译器
使用HaaS Studio开发AliOS Things C/C++应用
本文章将介绍使用HaaS Studio 进行AliOS-Things C/C++应用开发。
使用HaaS Studio开发AliOS Things C/C++应用
|
NoSQL 物联网
使用线上的开发板做开发调试 |《AliOS Things快速开发指南》
当您手上没有现成可用的开发板时,也可以使用线上的开发板来调试验证您的程序。本文主要介绍线上开发板的使用流程。
使用线上的开发板做开发调试 |《AliOS Things快速开发指南》
下一篇
DataWorks