结合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关键字,其为内核检测出被踩内存

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

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

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

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

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

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

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

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

 

1.3、看任务栈sp是否溢出

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

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

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

如当前 SP 0x4F8097A8

查看对应任务信息:

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

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

另外可以查看当前任务栈的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查看当前任务状态

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

 

1.7、查看某任务调用栈

taskbt 任务ID

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

 

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

debug

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

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

 

1.9、查看任务CPU占有率

cpuusage

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

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

 

1.10、查看内存值

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

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

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

 

1.11、Dumpsys导内存

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

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

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

 

1.12、强制异常 (fiq)

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

$#@!

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

 

1.13、异常定位

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

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

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

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

 

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

异常PC:
 


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

对应汇编:

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

查看当前r4值:


R4      0x4FFFFFF4

查看数据异常寄存器:


DFAR    0x50000003

从上面看出,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:   e2842f6b    add r2, r4, #428    ; 0x1ac                    //
  2. 405fbf98:   e5d43001    ldrb    r3, [r4, #1]
  3. 405fbf9c:   e1a00005    mov r0, r5
  4. 405fbfa0:   e58d7000    str r7, [sp]
  5. 405fbfa4:   e59411a8    ldr r1, [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:   e92d43f0    push    {r4, r5, r6, r7, r8, r9, lr}              //

 

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


  1. stack(0x4F8097E8) : 0x00043753 0x8184D561 0x4F7FEE98 0x4F7FEE98 (R4)     //
  2. stack(0x4F8097F8) : 0x4F769498 0x4F7FEFA0 0x4F80FD88 0x00000000
  3. stack(0x4F809808) : 0x4F78FF8C 0x405FBFB8(LR) 0x4F80FD88 0x4071B904

 

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

 

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

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

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

观察到当前LR 0x4060E18C

对应汇编:

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

从这一点看出,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:   e92d43f0    push    {r4, r5, r6, r7, r8, r9, lr}
  3. 4060e09c:   e24dd04c    sub sp, sp, #76 ; 0x4c
  4. 4060e0a0:   e1a07001    mov r7, r1                                    //

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

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

 

最后一公里:

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

 

2、质量工作

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

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

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

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

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

 

3、参考文档

《ARM相关架构文档》

《ELF格式解析》

 

4、传送门

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

 

开发者技术支持

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

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

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

上一篇:在ASP.NET MVC3 中利用JSONP跨域登“.NET技术”录WEB系统


下一篇:hudson无法访问问题,linux防火墙问题