一段时间,发现某个机型上一些系统级APP高概率出现NE,现场如下:
pid: 20335, tid: 20335, name: m.xxx.market >>> com.xxx.market <<< signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000140 r0 75bf67f8 r1 bec44e10 r2 41526558 r3 00000000 r4 41524fa8 r5 00000000 r6 00000004 r7 6d5acbf4 r8 bec44e88 r9 6d5acbec sl 41526568 fp bec44e9c ip 6d5acbf4 sp bec44e00 lr 7057f683 pc 400cdac0 cpsr 20000010 backtrace: #00 pc 00000ac0 /system/lib/libc.so #01 pc 0001d67f /system/lib/libjavacore.so #02 pc 0001d30c /system/lib/libdvm.so (dvmPlatformInvoke+112) #03 pc 0004d8db /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+398) #04 pc 00026720 /system/lib/libdvm.so #05 pc 0002d790 /system/lib/libdvm.so (dvmMterpStd(Thread*)+76) #06 pc 0002adf4 /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*)+184) #07 pc 0005fd75 /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list)+336) #08 pc 0004c9e3 /system/lib/libdvm.so #09 pc 00002300 /data/app-lib/com.lvtech.ydserver-1/libjohn.so (_JNIEnv::NewObject(_jclass*, _jmethodID*, ...)+32) #10 pc 00002870 /data/app-lib/com.lvtech.ydserver-1/libjohn.so #11 pc 00000bd0 /data/app-lib/com.lvtech.ydserver-1/libjohn.so
每次NE的时候几乎都是相同调用栈。
从调用栈来看,com.xxx.market被注入了/data/app-lib/com.lvtech.ydserver-1/libjohn.so后发生了崩溃。
上网搜索这个libjohn.so,发现是由《永德安卓读屏》的提供的,这是一个盲人辅助软件,需要对UI做特殊处理。
再分析FC现场:
#00 pc 00000ac0 /system/lib/libc.so
用objdump查看libc.so
00020abc <stat>: 20abc: e1a0c007 mov ip, r7 20ac0: e3a070c3 mov r7, #195 ; 0xc3 20ac4: ef000000 svc 0x00000000 20ac8: e1a0700c mov r7, ip 20acc: e3700a01 cmn r0, #4096 ; 0x1000 20ad0: 912fff1e bxls lr 20ad4: e2600000 rsb r0, r0, #0 20ad8: ea0074a5 b 3dd74 <__aeabi_uidivmod+0xe0>
FC的地址0x20ac0的指令是mov指令,这里不应该出现SIGSEGV的,唯一的可能是运行时的so被篡改了。
通过tombstone中的pc around查看运行时的pc附近的字节码:
code around pc: 400cdaa0 e3a070c5 ef000000 e1a0700c e3700a01 400cdab0 912fff1e e2600000 ea0074ad e59ff000 400cdac0 75a37140 75a37140 e1a0700c e3700a01 400cdad0 912fff1e e2600000 ea0074a5 e1a0c007 400cdae0 e3a070c4 ef000000 e1a0700c e3700a01
显然,这个stat应该是已经被篡改了。
00020abc<stat>: 20abc: e59ff000 ldr pc, [pc] 20ac0: 75a37140 strvc r7, [r3, #320]! ; 0x140 20ac4: 75a37140 strvc r7, [r3, #320]! ; 0x140
这里hook代码的原意是pc直接赋值0x75a37140,也就是跳转到0x75a37140。
但处于某种原因并未执行0x20abc的代码,而是pc执行到0x20ac0,错误的将地址0x75a37140当做str指令来执行。
出问题时,r3 = 0,所以0x20ac0上的指令就相当于str r7,[0x140],而这个0x140显然是未映射的地址。
所以报了SIGSEGV,且fault addr为00000140。
那为什么0x20abc中的ldr pc, [pc]没被执行呢?
一种可能性是程序运行0x20abc时,改地址上的指令还是没被篡改前的指令,也就是mov ip, r7。
F如果假设成立,那FC时的ip值应该等于r7:
pid: 20335, tid: 20335, name: m.xxx.market >>> com.xxx.market <<< signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000140 r0 75bf67f8 r1 bec44e10 r2 41526558 r3 00000000 r4 41524fa8 r5 00000000 r6 00000004 r7 6d5acbf4 r8 bec44e88 r9 6d5acbec sl 41526568 fp bec44e9c ip 6d5acbf4 sp bec44e00 lr 7057f683 pc 400cdac0 cpsr 20000010
恩,确实是这样的,所以实际运行时的指令流为:
00020abc<stat>: 20abc: e1a0c007 mov ip, r7 20ac0: 75a37140 strvc r7, [r3, #320]! ; 0x140 20ac4: 75a37140 strvc r7, [r3, #320]! ; 0x140
这种解释和FC现场完全吻合。
如果真是这种情况,一个线程在执行stat函数的第一条指令的同时,另外一个线程刚好在改写stat函数。
这种情况是存在的,但这个时间窗口实际上是非常小的,而这个问题的概率确实特别高,而且这类问题只是一个特定机型上才会发生。
如果是hook的问题,那应该所有机型上都高概率复现才对,为什么只有一个机型上报这个问题呢?
这时突然注意到一个细节,出问题的地址0x20ac0是64字节对齐的。而其他机型上面的同样位置并不是64位对齐的。
我们知道cache页的长度是64字节,难道是因为没有刷cache导致的?
为了证明跟cache相关,在出问题的机型版本中修改了libc.so的代码,重新编译确保stat相应位置不是64字节边界。
发现原先高概率出现的FC竟然不出现了。
后来联系到永德的工程师,让他们在hook后刷cache,问题就不再出现了。
结论:
程序实际运行时,执行的可能是flash的代码,也可能是ram里的代码,也可能是cache里的代码。三者可能不一样!