分享一种crash问题定位的一种解决方法,仅供参考。
ARM32平台上通过错误使用内存,触发系统异常,系统崩溃。系统异常被挂起后,能在串口中看到异常调用栈打印信息和关键寄存器信息。
如下所示,excType表示异常类型,值为4表示数据终止异常,其它数值可以查看芯片手册。
通过这些信息可以基本定位到异常所在函数和其调用栈的关系,为分析系统死机崩溃提供分析,便于查找问题所在。
[11:31:32.475]收←◆--- device idle,timeout=10000,lastT=77601,curT=87612
orbSendcmd: | F5 AE 02 FE 04 00 33 00 00 00 26 FD |
data_abort fault fsr:0x29, far:0x00000001
Abort caused by a read instruction. Domain fault, section
excType = 0x4
taskName = DeviceStaThd
taskID = 29
task stackSize = 24576
system mem addr = 0x40aaecc0
excBuffAddr pc = 0x4004cf34
excBuffAddr lr = 0x400522dc
excBuffAddr sp = 0x400f8340
excBuffAddr fp = 0x41300354
R0 = 0x1
R1 = 0x0
R2 = 0x7fffffff
R3 = 0xffffffff
R4 = 0x7fffffff
R5 = 0x1
R6 = 0x0
R7 = 0x0
R8 = 0x0
R9 = 0x1
R10 = 0x0
R11 = 0x41300354
R12 = 0xffffffff
CPSR = 0x20000013
*******backtrace begin*******
traceback 0 -- lr = 0x 400537ec fp = 0x4130065c
traceback 1 -- lr = 0x40054a74 fp = 0x4130075c
traceback 2 -- lr = 0x40055ab8 fp = 0x41300824
traceback 3 -- lr = 0x40471a8c fp = 0x41300c9c
traceback 4 -- lr = 0x40145f54 fp = 0x41300d24
traceback 5 -- lr = 0x40146384 fp = 0x41300db4
traceback 6 -- lr = 0x40146930 fp = 0x41300dc4
traceback 7 -- lr = 0x40046c58 fp = 0x41300dd4
traceback 8 -- lr = 0x4005044c fp = 0xb0b0b0b
打开编译后生成的 asm 反汇编文件
搜索PC指针 0x4004cf34 在asm文件中的位置(去掉0x)。
PC地址指向发生异常时程序正在执行的指令。在当前执行的二进制文件对应的asm文件中,查找PC值 4004cf34,找到当前CPU正在执行的指令行,得到如下所示结果。
4004cf20 :
4004cf20: e3100003 tst r0, #3
4004cf24: e6ef1071 uxtb r1, r1
4004cf28: 0a000010 beq 4004cf70
4004cf2c: e3520000 cmp r2, #0
4004cf30: 0a000035 beq 4004d00c
4004cf34: e5d03000 ldrb r3, [r0]
4004cf38: e1530001 cmp r3, r1
4004cf3c: 0a000042 beq 4004d04c
4004cf40: e2803001 add r3, r0, #1
4004cf44: ea000004 b 4004cf5c
4004cf48: e3520000 cmp r2, #0
4004cf4c: 0a00002e beq 4004d00c
ldrb指令作用:LDRB指令用于从内存中将一个8位的字节数据读取到指令中的目标寄存器中。并将寄存器的高24位清零。
LDRB R0, [R2,#3] ;将内存单元(R2+3)中字节数据读取到R0中,R0中高24位设置成0
LDRB R0,[R1] ;将内存单元(R1)中的字节数据读取到R0中,R0中高24位设置成0
结合指令分析:寄存器r0读到r3,r0为0x1,r3为0xffffffff,r3超出内存范围
cpu执行该命令发生了数据终止异常。可以确认,memchr 被调用拷贝时可能因为长度问题导致异常
根据 LR链接寄存器值 查找调用栈
从异常信息的backtrace begin开始,打印的是调用栈信息。在asm文件中查找backtrace 0 400537ec 对应的LR,如下所示。
400537e4: e1a00009 mov r0, r9
400537e8: ebfffab4 bl 400522c0
400537ec: e7d93000 ldrb r3, [r9, r0]
400537f0: e0896000 add r6, r9, r0
400537f4: e3530000 cmp r3, #0
400537f8: 0affff12 beq 40053448
400537fc: eafffe12 b 4005304c
40053800: e3170b02 tst r7, #2048 ; 0x800
40053804: 1a000255 bne 40054160
40053808: e51f3c90 ldr r3, [pc, #-3216] ; 40052b80
4005380c: e2072001 and r2, r7, #1
40053810: e51b12e0 ldr r1, [fp, #-736] ; 0xfffffd20
40053814: e3520000 cmp r2, #0
40053818: e2832005 add r2, r3, #5
4005381c: 13a01001 movne r1, #1
40053820: e50b12e0 str r1, [fp, #-736] ; 0xfffffd20
40053824: 11a03002 movne r3, r2
40053828: e50b32e4 str r3, [fp, #-740] ; 0xfffffd1c
可见,是 strnlen 调用了 memchr。
我们从strnlen中的实现也可以确定此论证
但是此信息还不足以确定问题,我们可以继续从backtrace begin提供的内存地址开始找
从backtrace1中我们确定为printf_core接口导致
traceback 1 -- lr = 40054a74 fp = 0x4130075c
40054a70: ebfff7a2 bl 40052900
40054a74: e1a05000 mov r5, r0
40054a78: e3580000 cmp r8, #0
40054a7c: 0a00000d beq 40054ab8
40054a80: e5943024 ldr r3, [r4, #36] ; 0x24
通过与代码结合,底层接口实现查找,确实如图所示
继续根据traceback 2 -- lr = 40055ab8 fp = 0x41300824找到printf_core为vprintf调用
traceback 2 -- lr = 40055ab8 fp = 0x41300824
40055ab4: ebfffbbb bl 400549a8
40055ab8: e1a05000 mov r5, r0
40055abc: e30a337c movw r3, #41852 ; 0xa37c
40055ac0: e1a00005 mov r0, r5
40055ac4: e3443001 movt r3, #16385 ; 0x4001
40055ac8: e5932000 ldr r2, [r3]
40055acc: e51b3020 ldr r3, [fp, #-32] ; 0xffffffe0
40055ad0: e0332002 eors r2, r3, r2
40055ad4: 1a00001a bne 40055b44
根据底层代码实现也可验证无问题
继续往上查找,基本可以确定是打印接口导致
traceback 3 -- lr = 40471a8c fp = 0x41300c9c
40471a88: ebef8fe7 bl 40055a2c
40471a8c: e1a00005 mov r0, r5
40471a90: ebef81b7 bl 40052174
40471a94: e2402001 sub r2, r0, #1
40471a98: e7d53002 ldrb r3, [r5, r2]
40471a9c: e353000a cmp r3, #10
40471aa0: 0a000008 beq 40471ac8
继续往上查找我们是通过哪个方法的打印接口导致的crash
traceback 4 -- lr = 40145f54 fp = 0x41300d24
40145f50: eb0caea2 bl 404719e0
40145f54: e3e00000 mvn r0, #0
40145f58: eaffffe3 b 40145eec <_ZN17CDeviceStaManager25DeviceHealthStatusMonitorEPc+0x2d4>
traceback 5 -- lr = 40146384 fp = 0x41300db4
40146380: ebfffe24 bl 40145c18 <_ZN17CDeviceStaManager25DeviceHealthStatusMonitorEPc>
40146384: e51b3048 ldr r3, [fp, #-72] ; 0xffffffb8
40146388: e1530006 cmp r3, r6
4014638c: 1affffa8 bne 40146234 <_ZN17CDeviceStaManager13DeviceStaProcEv+0x1a0>
traceback 6 -- lr = 40146930 fp = 0x41300dc4
40146904 <_ZN17CDeviceStaManager12DeviceStaThdEPv>:
40146904: e30e1464 movw r1, #58468 ; 0xe464
40146908: e92d4830 push {r4, r5, fp, lr}
4014690c: e1a04000 mov r4, r0
40146910: e3441076 movt r1, #16502 ; 0x4076
40146914: e28db00c add fp, sp, #12
40146918: e3a0000f mov r0, #15
4014691c: ebfdc7d0 bl 400b8864
40146920: e3540000 cmp r4, #0
40146924: 0a000001 beq 40146930 <_ZN17CDeviceStaManager12DeviceStaThdEPv+0x2c>
40146928: e1a00004 mov r0, r4
4014692c: ebfffdd8 bl 40146094 <_ZN17CDeviceStaManager13DeviceStaProcEv>
40146930: e3a00000 mov r0, #0
40146934: e8bd8830 pop {r4, r5, fp, pc}
再根据死机前对信息最后打印可以确定问题所在了,发送完此命令后的一条打印导致的问题
[11:31:31.083]收←◆31 08:01:26.221 [wifi] wifi status check = not connect, retry = 86
[11:31:32.082]收←◆31 08:01:27.221 [wifi] wifi status check = not connect, retry = 87
[11:31:32.475]收←◆--- device idle,timeout=10000,lastT=77601,curT=87612
orbSendcmd: | F5 AE 02 FE 04 00 33 00 00 00 26 FD |
data_abort fault fsr:0x29, far:0x00000001
Abort caused by a read instruction. Domain fault, section
所以到这里,问题可以确定为
DeviceStaThd()->DeviceStaProc()->DeviceHealthStatusMonitor()
->printf("orbSendcmd")->后一条printf导致的crash
LR用途有二,一是保存子程序返回地址,当调用BL、BX、BLX等跳转指令时会自动保存返回地址到LR;二是保存异常发生的异常返回地址。
PC(Program Counter)为程序计数器,用于保存程序的执行地址,在ARM的三级流水线架构中,程序流水线包括取址、译码和执行三个阶段,PC指向的是当前取址的程序地址,所以32位ARM中,译码地址(正在解析还未执行的程序)为PC-4,执行地址(当前正在执行的程序地址)为PC-8。
当突然发生中断的时候,保存的是PC的地址。如果返回的时候返回PC,那么中间就有一个指令没有执行,所以用SUB pc lr-irq #4。
每一种异常模式都有其自己独立的r13,它通常指向异常模式所专用的堆栈,也就是说五种异常模式、非异常模式(用户模式和系统模式),都有各自独立的堆栈,用不同的堆栈指针来索引。这样当ARM进入异常模式的时候,程序就可以把一般通用寄存器压入堆栈,返回时再出栈,保证了各种模式下程序的状态的完整性。