Div0是指的除零错误,一般在Android上会直接crash,报SIG_FPE异常,能看到div0的关键字。这里讲述一个比较极品的div0错误。
先看堆栈:
02-07 08:46:47.113 179 179 I DEBUG : backtrace:
02-07 08:46:47.113 179 179 I DEBUG : #00 pc 00021f90 /system/lib/libc.so (tgkill+12)
02-07 08:46:47.113 179 179 I DEBUG : #01 pc 00012fe1 /system/lib/libc.so (pthread_kill+48)
02-07 08:46:47.113 179 179 I DEBUG : #02 pc 000131f5 /system/lib/libc.so (raise+10)
02-07 08:46:47.113 179 179 I DEBUG : #03 pc 000aa9e4 /data/app-lib/com.xxx.example/libmyclient.so (__aeabi_ldiv0+8)
02-07 08:46:47.113 179 179 I DEBUG : #04 pc 0004aff4 /data/app-lib/com.xxx.example/libmyclient.so (CMyExample::OnTimer(CMyTimer*)+860)
能看到div0这个关键字,就明白应该就是除零错误了。而且能看到错误发生在OnTimer这个函数里。如果分析到此为止,我们理应直接奔OnTImer而去,查找里面的除法,问题应该迎刃而解。可以我遇到的实际情况是,除法在OnTimer函数里出现了3次(也不算多),每次做除法之前都有对分母的保护(确保分母非零)。于是经典的矛盾就来了,为什么问题推测是除零错误,可以实际代码却不可能会除零?我徘徊了半天,苦恼的很。终于在别人的建议之下,直接去看看汇编,看看发生了什么。
于是故事就开始了。注意到860这个数字,像是一个偏移量。我先用"objdump -D"把CMyExample的汇编代码拿到,找到OnTimer函数的定义,
0004ac98 <_ZN22CWseVideoSourceChannel7OnTimerEP9CWseTimer>:
尝试做一下860(0x35c)的偏移,即0004ac98+0x35c=4aff4,然后去看看4aff4这行是什么,
4aff4: eb017e54 bl aa94c <__aeabi_ldivmod>
太好了,就是divmod。这里说一下ldivmod是64bit除法,分子是(r1, r0),分母是(r3, r2),商是(r1, r0),余数是(r3, r2)。前面的是高32bit,后面的是低32bit。每个寄存器是32bit。
有了这个ldivmod如果定位是源码里面的哪个除号呢?型号源码中除号前有个常量(8000),而且有if else,这就简单了,因为常量在汇编里容易找到(#8000),if else实质就是cmp指令+跳转指令。
问题逐渐清晰了,看关键汇编:
4afa0: e5942628 ldr r2, [r4, #1576] ; 0x628
4afa4: e3520000 cmp r2, #0
4afa8: 03a03e63 moveq r3, #1584 ; 0x630
4afac: e1a06000 mov r6, r0
4afb0: 05840628 streq r0, [r4, #1576] ; 0x628
4afb4: 0184a0f3 strdeq sl, [r4, r3]
4afb8: 0a000023 beq 4b04c <_ZN22CMyExample7OnTimerEP9CMyTimer+0x3b4>
4afbc: e2823ffa add r3, r2, #1000 ; 0x3e8
4afc0: e1500003 cmp r0, r3
4afc4: 3a000020 bcc 4b04c <_ZN22CMyExample7OnTimerEP9CMyTimer+0x3b4>
4afc8: e3a03e63 mov r3, #1584 ; 0x630
4afcc: e1a0800a mov r8, sl
4afd0: e18400d3 ldrd r0, [r4, r3]
4afd4: e1a0900b mov r9, fp
4afd8: e3a07d7d mov r7, #8000 ; 0x1f40
4afdc: e0622006 rsb r2, r2, r6
4afe0: e0588000 subs r8, r8, r0
4afe4: e0c99001 sbc r9, r9, r1
4afe8: e3a03000 mov r3, #0
4afec: e0810798 umull r0, r1, r8, r7
4aff0: e0211997 mla r1, r7, r9, r1
4aff4: eb017e54 bl aa94c <__aeabi_ldivmod>
64bit除法的分母是两个32bit寄存器,r3和r2。r3是高32bit,r2是低32bit。可以看到r2做了一个减法(rsb)之后,r3直接赋零。如果r2有什么溢出的可能性,r3并没有保存溢出的部分。于是只要r2在减法后是零,那么分母就是零。
回归代码:
unsigned long a = xxx;
unsigned long b = xxx;
if (a >= b + 1000)
unsigned long c = xx64 / (a - b);
“xx64”表示一个64bit变量,分母是a-b。之前我判断了a超过b至少1000,所以a不可能等于b,所以分母不可能为零。可实际情况呢?从汇编看出来,的确是除零错误。回归汇编,r6大于等于r3,r3=r2+1000,如果r6真的等于r2,只有一种情况,那就是r3溢出了!所以案发现场应该是,r2和r6都很大而且相等,r3=r2+1000,r3溢出,r6自然大于一个很小的正数,于是r6减去r2得到的分母为零。也就是说,源码里面的a等于b,而且都是很大的正数,b+1000后溢出变成一个很小的正数,小于a,于是进入除法。crash发生!
通过这个bug,我至少学会了:看简单的汇编;通过crash stack定位汇编代码,进而定位源码;
阅读(4251) | 评论(0) | 转发(0) |