JAVA代码引起的NATIVE野指针问题(小米音乐)

https://my.oschina.net/u/3168816/blog

朴英敏,小米MIUI部门。从事嵌入式开发和调试工作8年多,擅长逆向分析方法,主要负责解决安卓系统稳定性问题。

上周音乐组同事反馈了一个必现Native Crash问题,tombstone如下:

 
  1. pid: 5028, tid: 5028, name: com.miui.player  >>> com.miui.player <<< 
  2. signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 79801f28 
  3.     r0 7ac59c98  r1 00000000  r2 bea7b174  r3 400fc1b8 
  4.     r4 774c4c88  r5 79801f28  r6 bea7b478  r7 40c12bb8 
  5.     r8 7c1b68e8  r9 778781e8  sl bea7b478  fp bea7b414 
  6.     ip 00000001  sp bea7b148  lr 40c07031  pc 79801f28  cpsr 600f0010 
  7. backtrace: 
  8.     #00  pc 0000bf28  <unknown> 
  9.     #01  pc 0002302f  /system/lib/libhwui.so (android::uirenderer::OpenGLRenderer::callDrawGLFunction(android::Functor*, android::uirenderer::Rect&)+322) 
  10.     #02  pc 00015d91  /system/lib/libhwui.so (android::uirenderer::DrawFunctorOp::applyDraw(android::uirenderer::OpenGLRenderer&, android::uirenderer::Rect&)+28) 
  11.     #03  pc 00014527  /system/lib/libhwui.so (android::uirenderer::DrawBatch::replay(android::uirenderer::OpenGLRenderer&, android::uirenderer::Rect&, int)+74) 
  12.     #04  pc 00014413  /system/lib/libhwui.so (android::uirenderer::DeferredDisplayList::flush(android::uirenderer::OpenGLRenderer&, android::uirenderer::Rect&)+218) 
  13.     #05  pc 0001d1cf  /system/lib/libhwui.so (_ZN7android10uirenderer14OpenGLRenderer15drawDisplayListEPNS0_11DisplayListERNS0_4RectEi.part.47+230) 
  14.     #06  pc 0006820d  /system/lib/libandroid_runtime.so 

崩溃的原因是pc指向了一个没有可执行权限的内存地址上。

初步分析:

对应的代码如下:

 
  1. status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { 
  2.  
  3. if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone; 
  4.  
  5. detachFunctor(functor); 
  6.  
  7. ... 
  8.  
  9. interrupt(); 
  10.  
  11. => status_t result = (*functor)(DrawGlInfo::kModeDraw, &info); 

其中,Functor类重载了()操作符:

 
  1. class Functor { 
  2.  
  3. public
  4.  
  5. Functor() {} 
  6.  
  7. virtual ~Functor() {} 
  8.  
  9. => virtual status_t operator ()(int /*what*/, void* /*data*/) { return NO_ERROR; } 
  10.  
  11. }; 

因此,()操作其实就是调用了Functor类的一个虚函数,它的具体实现目前还不清楚。

对应的汇编代码如下:

 
  1. 23028: aa0b add r2, sp, #44 
  2.  
  3. 2302a: 6803 ldr r3, [r0, #0] ; r0是functor,r3 = [r0] = functor.vtlb 
  4.  
  5. 2302c: 689d ldr r5, [r3, #8] ; r5 = [r3 + 8] = [functor.vtlb + 8] = Functor.operator() 
  6.  
  7. 2302e: 47a8 blx r5 ; call Functor.operator() 

崩溃时的寄存器值如下:

 
  1. r0 7ac59c98 r1 00000000 r2 bea7b174 r3 400fc1b8 
  2.  
  3. r4 774c4c88 r5 79801f28 r6 bea7b478 r7 40c12bb8 
  4.  
  5. r8 7c1b68e8 r9 778781e8 sl bea7b478 fp bea7b414 
  6.  
  7. ip 00000001 sp bea7b148 lr 40c07031 pc 79801f28 cpsr 600f0010 

可以看到,r5和pc值是相等的,可以知道,确定是崩溃在2302e这一行汇编代码中。

而查看寄存器对应的内存值,发现有点问题:

 
  1. memory near r0: 
  2.     7ac59c78 00000018 0000001b 735a9b38 23831ef0   
  3.     7ac59c88 23831ef0 735a9b50 00000018 00000011   
  4.     7ac59c98 79822328 77768698 00000010 00000022   
  5.     7ac59ca8 00000000 00000000 00000000 00000003   
  6.  
  7. memory near r3: 
  8.     400fc198 7c74c000 00200000 00000077 0d44acd8   
  9.     400fc1a8 00000000 00000000 400fc1a8 400fc1a8   
  10.     400fc1b8 400fc1b0 400fc1b0 7c04acb8 7c78f008   
  11.     400fc1c8 7c021d98 7c78ffc0 7983bbf0 7c04bfa8 

[r0] = [7ac59c98] = 798223298,这个和r3值(400fc1b8)不一样,

同样

[r3+8] = [400fc1b8 + 8] = 7c04acb8,这个值也和r5值(79801f28)不一样。

这在平时的tombstone里是非常少见的!

乍一看非常不可思议,但仔细想想tombstone的生成过程,就能发现其中的问题。

原来寄存器信息是错位崩溃时的cpu context,保存在崩溃时的线程私有的信号栈和内核栈中,直到debuggerd去获取这个值,它是不会被修改的。

而内存是进程中的各个线程共享的,所以在发生异常到debuggerd打印内存信息这段过程中(其实是相对很长的一个过程),别的线程是有可能修改内存值的。

为了证明别的线程在改这个内存值,在callDrawGLFunction()函数中的若干处打印了Functor和它的vtbl(虚函数表地址)值:

 
  1. status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { 
  2.  
  3. AOGI("functor=%p,vtbl=%p"); 
  4.  
  5. sleep(1); 
  6.  
  7. if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone; 
  8.  
  9. AOGI("functor=%p,vtbl=%p"); 
  10.  
  11. sleep(1); 
  12.  
  13. detachFunctor(functor); 
  14.  
  15. ... 
  16.  
  17. AOGI("functor=%p,vtbl=%p"); 
  18.  
  19. sleep(1); 
  20.  
  21. interrupt(); 
  22.  
  23. AOGI("functor=%p,vtbl=%p"); 
  24.  
  25. sleep(1); 
  26.  
  27. status_t result = (*functor)(DrawGlInfo::kModeDraw, &info); 

抓到的log如下:

 
  1. 10-27 21:19:45.794 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x73648de0 
  2.  
  3. 10-27 21:19:47.801 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x73648de0 
  4.  
  5. 10-27 21:19:48.801 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x73648de0 
  6.  
  7. 10-27 21:19:49.801 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x73648de0 
  8.  
  9. 10-27 21:19:50.804 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x73648de0 
  10.  
  11. 10-27 21:19:51.804 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x400fc1b8 

可以确定确实有别的线程在修改这个值。

这里就存在两个可能性了:

1、别的线程也持有functor指针,并修改内容

2、functor是野指针,对应的内存已经还回系统,其他模块可任意使用。

而对象的vtbl一般是不会修改的,所以2的可能性更大一些。

为了查明是哪个线程在改,对functor指向的内存做了写保护操作:

 
  1. static int** s_saved_vtbl = NULL
  2. static void* s_saved_functor = NULL
  3.  
  4. static void  mprotect_local(int** p) { 
  5.     // 一旦发现vtbl有变化就将对应内存设置为只读 
  6.     if(p != s_saved_vtbl) {  
  7.         mprotect((void*)((unsigned int)s_saved_functor&0xfffff000), 4096, PROT_READ); 
  8.     } 
  9.     sleep(1); 
  10.  
  11. status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { 
  12.     int* ptr = (int*)functor; 
  13.     s_saved_functor = (void*)ptr; 
  14.     s_saved_vtbl = (int**)*ptr; 
  15.  
  16.     if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone;  
  17.  
  18.     mprotect_local((int**)*ptr); 
  19.     detachFunctor(functor); 
  20.     mprotect_local((int**)*ptr); 
  21.     ... 
  22.     mprotect_local((int**)*ptr); 
  23.     interrupt(); 
  24.   
  25.     status_t result = (*functor)(DrawGlInfo::kModeDraw, &info); 

push到手机中复现问题,很容易抓到访问权限引起的crash。

而每次的crash的线程和位置都不一样,也就是不同的线程在不同的函数中读写这个地址。

这样基本上就确定是野指针问题,进入下一阶段的分析。

关于野指针:

所谓野指针就是一个对象被释放后又被使用,可能是释放的问题,也可能是使用的问题。

我们已经知道使用的位置,接下来要找出是从哪释放的。

找到释放对象的最笨的方法,是在free()函数里打印调用栈。

但这么做有两个问题:

1、log太量多,一秒内可能会有成千上万的malloc/free函数被调用。

2、打印调用栈的函数本身会调用free函数,这样会陷入死循环。

为了解决上面两个问题,需要用到hook技术。

关于hook技术:

要了解hook技术,得先了解外部函数的调用过程。

所谓外部函数就是外部模块中定义的函数。比如,libhwui.so中的某个源文件中调用了malloc函数,而这个malloc函数是libc.so中定义的。

当编译libhwui.so的这个源文件时,对应调用malloc的地方会生成如下的汇编代码:

 
  1. blx addr 

这里blx是arm的跳转指令,addr是目标地址,也就是malloc函数的地址,那这个malloc函数的地址如何确定?

这个编译的阶段是无法确定的,只有当运行时进程加载完libc.so以后,malloc函数的地址才能被确定。

所以编译器在编译的时候会在libbinder.so中留出一部分空间作为地址表,专门用于存放外部函数的地址,这个区域叫got表。

每一个本模块调用到的外部函数都对应got表中的一项。

当然got表里面的内容是在进程启动阶段,加载动态库时被连接器linker填充的。

而编译阶段我们只需要将代码写成:

1、从got表对应位置获取外部函数地址

2、跳转到这个外部函数的地址

这个动作需要由若干的指令来完成,所以跳转指令blx addr中的addr其实指向本模块的一组指令:

 
  1. blx cb74 <malloc@plt> 

这组指令所在的区域就是elf文件结构里的plt表,plt表中每一个外部函数都对应一个表项,如:

0000cb74 <malloc@plt>:

cb74: e28fc600 add ip, pc, #0, 12

cb78: e28cca29 add ip, ip, #167936 ;

cb7c: e5bcf1e8 ldr pc, [ip, #488]! ;

0000c8bc <free@plt>:

c8bc: e28fc600 add ip, pc, #0, 12

c8c0: e28cca29 add ip, ip, #167936 ;

c8c4: e5bcf3b8 ldr pc, [ip, #952]! ;

每一个plt表项都是做相同操作:

1、先获取got表中外目标函数对应的地址(前两行);

2、从got表中获取地址目标函数的地址,并赋给pc寄存器(第三行)。

下面给出got表和plt表在so文件中的位置:

readelf -S libhwui.so

[Nr] Name Type Addr Off Size ES Flg Lk Inf Al

[ 0] NULL 00000000 000000 000000 00 0 0 0

[ 1] .interp PROGBITS 00000134 000134 000013 00 A 0 0 1

[ 2] .dynsym DYNSYM 00000148 000148 002420 10 A 3 1 4

[ 3] .dynstr STRTAB 00002568 002568 0056a4 00 A 0 0 1

[ 4] .hash HASH 00007c0c 007c0c 001134 04 A 2 0 4

[ 5] .rel.dyn REL 00008d40 008d40 002bc8 08 A 2 0 4

[ 6] .rel.plt REL 0000b908 00b908 000a78 08 A 2 7 4

=>[ 7] .plt PROGBITS 0000c380 00c380 000fc8 00 AX 0 0 4

[ 8] .text PROGBITS 0000d348 00d348 01ef30 00 AX 0 0 8

[ 9] .ARM.exidx ARM_EXIDX 0002c278 02c278 001fb8 08 AL 8 0 4

[10] .ARM.extab PROGBITS 0002e230 02e230 000930 00 A 0 0 4

[11] .rodata PROGBITS 0002eb60 02eb60 0036a4 00 A 0 0 4

[12] .fini_array FINI_ARRAY 00034010 033010 000004 00 WA 0 0 4

[13] .data.rel.ro PROGBITS 00034018 033018 001910 00 WA 0 0 8

[14] .init_array INIT_ARRAY 00035928 034928 00000c 00 WA 0 0 4

[15] .dynamic DYNAMIC 00035934 034934 000140 08 WA 3 0 4

=>[16] .got PROGBITS 00035a74 034a74 00058c 00 WA 0 0 4

[17] .data PROGBITS 00036000 035000 00025c 00 WA 0 0 4

[18] .bss NOBITS 0003625c 03525c 000068 00 WA 0 0 4

[19] .comment PROGBITS 00000000 03525c 000010 01 MS 0 0 1

[20] .note.gnu.gold-ve NOTE 00000000 03526c 00001c 00 0 0 4

[21] .ARM.attributes ARM_ATTRIBUTES 00000000 035288 00003e 00 0 0 1

[22] .gnu_debuglink PROGBITS 00000000 0352c6 000010 00 0 0 1

[23] .shstrtab STRTAB 00000000 0352d6 0000dc 00 0 0 1

我们的hook技术就是通过修改so的got表来截获so中的某些外部函数调用。

so的代码段是多个进程共享的,但它的数据段私有的,而got表就是数据段。

所以我们只修改music应用进程的libhwui.so的got表中free函数对应的项,影响范围将大大减少。

那改成什么值呢?一般是我们自己定义的函数,比如:

 
  1. void inject_free(void *ptr)   { 
  2.     ALOGI("free ptr=%p",ptr); 
  3.     dumpNativeStack(); 
  4.     dumpJavaStack(); 
  5.     free(ptr); 

为了不影响原来的逻辑,打印完debug信息,还是要调用原来被hook的函数。

有了hook技术后能完美的解决野指针中的两个问题,下面继续分析问题。


实施hook:

我们有了hook,但目前还不知道是哪个so中释放了functor。
如果无法确定是哪个so,可以多hook几个so就行了。
当然对于特定的例子,也有技巧来确定so,比如我们这个例子:
被析构的对象是Functor类的对象,由于它的vtbl地址我们能够从log中获取到,
而vtbl一般指向定义了该类的so中,所以用vtbl值(0×73648de0)去map表中找,就能确定是哪个so了。
73635000-73646000 rw-p 00000000 00:00 0

73646000-73648000 r-xp 00000000 b3:18 1287       /system/lib/libwebviewchromium_plat_support.so

=>73648000-73649000 r--p 00001000 b3:18 1287       /system/lib/libwebviewchromium_plat_support.so

73649000-7364a000 rw-p 00002000 b3:18 1287       /system/lib/libwebviewchromium_plat_support.so

7364a000-73684000 rw-p 00000000 00:00 0

73684000-73696000 r-xp 00000000 b3:18 1034       /system/lib/libjavacrypto.so

73696000-73697000 r--p 00011000 b3:18 1034       /system/lib/libjavacrypto.so

73697000-73698000 rw-p 00012000 b3:18 1034       /system/lib/libjavacrypto.so

而需要注意的是,C++对象的释放是delete函数,
libwebviewchromium_plat_support.so不会直接调用libc的free函数,而是调用libc++.so中的delete函数,再由delete函数调用free函数,
所以我们得hook libc++.so的free函数,但打印调用栈的模块也依赖libc++.so,所以如果在hook函数中打印调用栈,也会遇到死循环问题。
所以我们得hook libwebviewchromium_plat_support.so中的delete函数,这样既减少log量,也能避免死循环。

先确认libwebviewchromium_plat_support.so是否依赖了delete函数:

$ readelf -s libwebviewchromium_plat_support.so |grep UND

     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND

     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_finalize

     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_atexit

     4: 00000000     0 FUNC    GLOBAL DEFAULT  UND __aeabi_unwind_cpp_pr0

     5: 00000000     0 FUNC    GLOBAL DEFAULT  UND __aeabi_unwind_cpp_pr1

     6: 00000000     0 FUNC    GLOBAL DEFAULT  UND getrlimit

     7: 00000000     0 FUNC    GLOBAL DEFAULT  UND setrlimit

     8: 00000000     0 FUNC    GLOBAL DEFAULT  UND __errno

     9: 00000000     0 FUNC    GLOBAL DEFAULT  UND strerror

    10: 00000000     0 FUNC    GLOBAL DEFAULT  UND __android_log_print

=>  11: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Znwj

=>  12: 00000000     0 FUNC    GLOBAL DEFAULT  UND _ZdlPv

    14: 00000000     0 FUNC    GLOBAL DEFAULT  UND __android_log_assert

    ...

    51: 00000000     0 FUNC    GLOBAL DEFAULT  UND __aeabi_llsr

    52: 00000000     0 OBJECT  GLOBAL DEFAULT  UND __popcount_tab

其中11项_Znwj是new的符号,_ZdlPv是delete的符号。
接下来就用工具hook libwebviewchromium_plat_support.so的delete函数:

extern void _ZdlPv(void *);

void inject__ZdlPv(void* ptr) {

    LOGD("delete %p",ptr);

    dumpNativeStack();

    dumpJavaStack();

    _ZdlPv(ptr);

}

hook后复现问题,抓到的log如下:

10-27 21:19:52.961  8027  8027 D ObserverLayout: onStop: clz=com.miui.player.display.view.DisplayFragmentLayout{45665838 V.E..... ........ 0,0-1080,1920 #7f080039 app:id/content}

10-27 21:19:52.965  8027  8027 I MusicBaseFragment: onDestroyView  the view is still attached, delay destroy

10-27 21:19:52.966  8027  8027 D INJECT  : delete 0x7a7b8530

10-27 21:19:52.986  8027  8027 D INJECT  : #00  pc 000015f6  /system/lib/libinject.so (inject__ZdlPv+21)

10-27 21:19:52.986  8027  8027 D INJECT  : #01  pc 00001134  /system/lib/libwebviewchromium_plat_supp

10-27 21:19:52.986  8027  8027 D INJECT  : #02  pc 00001088  /system/lib/libwebviewchromium_plat_supp

10-27 21:19:52.987  8027  8027 D INJECT  : #03  pc 0001d30c  /system/lib/libdvm.so (dvmPlatformInvoke+112)

10-27 21:19:52.987  8027  8027 D INJECT  : #04  pc 0004d8da  /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JV+397)

10-27 21:19:52.987  8027  8027 D INJECT  : #05  pc 00026720  /system/lib/libdvm.so

10-27 21:19:52.987  8027  8027 D INJECT  : #06  pc 0002d790  /system/lib/libdvm.so (dvmMterpStd(Thread*)+76)

10-27 21:19:52.987  8027  8027 D INJECT  : #07  pc 0002adf4  /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JVa+184)

10-27 21:19:52.988  8027  8027 D INJECT  : #08  pc 00060058  /system/lib/libdvm.so (dvmInvokeMethod(Object*, Method const*, +391)

10-27 21:19:52.988  8027  8027 D INJECT  : #09  pc 00067ff6  /system/lib/libdvm.so

10-27 21:19:52.988  8027  8027 D INJECT  : #10  pc 00026720  /system/lib/libdvm.so

10-27 21:19:52.988  8027  8027 D INJECT  : #11  pc 0002d790  /system/lib/libdvm.so (dvmMterpStd(Thread*)+76)

10-27 21:19:52.988  8027  8027 D INJECT  : #12  pc 0002adf4  /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JVa+184)

10-27 21:19:52.988  8027  8027 D INJECT  : #13  pc 0005fd74  /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, O+335)

10-27 21:19:52.988  8027  8027 D INJECT  : #14  pc 000494c2  /system/lib/libdvm.so

10-27 21:19:52.989  8027  8027 D INJECT  :   at com.android.webview.chromium.DrawGLFunctor.nativeDestroyGLFunctor(Native Method)

10-27 21:19:52.989  8027  8027 D INJECT  :   at com.android.webview.chromium.DrawGLFunctor.access$000(DrawGLFunctor.java:31)

10-27 21:19:52.989  8027  8027 D INJECT  :   at com.android.webview.chromium.DrawGLFunctor$DestroyRunnable.run(DrawGLFunctor.java:91)

10-27 21:19:52.989  8027  8027 D INJECT  :   at com.android.org.chromium.content.common.CleanupReference.runCleanupTaskInternal(CleanupReference.java:159)

10-27 21:19:52.989  8027  8027 D INJECT  :   at com.android.org.chromium.content.common.CleanupReference.access$300(CleanupReference.java:32)

10-27 21:19:52.989  8027  8027 D INJECT  :   at com.android.org.chromium.content.common.CleanupReference$LazyHolder$1.handleMessage(CleanupReference.java:93)

10-27 21:19:52.990  8027  8027 D INJECT  :   at com.android.org.chromium.content.common.CleanupReference.handleOnUiThread(CleanupReference.java:147)

10-27 21:19:52.990  8027  8027 D INJECT  :   at com.android.org.chromium.content.common.CleanupReference.cleanupNow(CleanupReference.java:141)

10-27 21:19:52.990  8027  8027 D INJECT  :   at com.android.webview.chromium.DrawGLFunctor.destroy(DrawGLFunctor.java:46)

10-27 21:19:52.990  8027  8027 D INJECT  :   at com.android.webview.chromium.WebViewChromium.destroy(WebViewChromium.java:430)

10-27 21:19:52.990  8027  8027 D INJECT  :   at android.webkit.WebView.destroy(WebView.java:667)

10-27 21:19:52.990  8027  8027 D INJECT  :   at com.xiaomi.music.hybrid.HybridFragment.destroyHybridView(HybridFragment.java:64)

10-27 21:19:52.990  8027  8027 D INJECT  :   at com.xiaomi.music.hybrid.HybridFragment.onDestroyView(HybridFragment.java:115)

10-27 21:19:52.990  8027  8027 D INJECT  :   at com.miui.player.component.MusicBaseFragment.onDestroyView(MusicBaseFragment.java:216)

10-27 21:19:52.991  8027  8027 D INJECT  :   at android.app.Fragment.performDestroyView(Fragment.java:1898)

10-27 21:19:52.991  8027  8027 D INJECT  :   at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:954)

10-27 21:19:52.991  8027  8027 D INJECT  :   at android.app.FragmentManagerImpl.removeFragment(FragmentManager.java:1167)

10-27 21:19:52.991  8027  8027 D INJECT  :   at android.app.BackStackRecord.popFromBackStack(BackStackRecord.java:715)

10-27 21:19:52.991  8027  8027 D INJECT  :   at android.app.FragmentManagerImpl.popBackStackState(FragmentManager.java:1544)

10-27 21:19:52.992  8027  8027 D INJECT  :   at android.app.FragmentManagerImpl$3.run(FragmentManager.java:502)

10-27 21:19:52.992  8027  8027 D INJECT  :   at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1449)

10-27 21:19:52.992  8027  8027 D INJECT  :   at android.app.FragmentManagerImpl$1.run(FragmentManager.java:443)

10-27 21:19:52.992  8027  8027 D INJECT  :   at android.os.Handler.handleCallback(Handler.java:733)

10-27 21:19:52.992  8027  8027 D INJECT  :   at android.os.Handler.dispatchMessage(Handler.java:95)

10-27 21:19:52.992  8027  8027 D INJECT  :   at android.os.Looper.loop(Looper.java:136)

10-27 21:19:52.993  8027  8027 D INJECT  :   at android.app.ActivityThread.main(ActivityThread.java:5016)

10-27 21:19:52.993  8027  8027 D INJECT  :   at java.lang.reflect.Method.invokeNative(Native Method)

10-27 21:19:52.993  8027  8027 D INJECT  :   at java.lang.reflect.Method.invoke(Method.java:515)

10-27 21:19:52.993  8027  8027 D INJECT  :   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792)

10-27 21:19:52.993  8027  8027 D INJECT  :   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:608)

10-27 21:19:52.993  8027  8027 D INJECT  :   at dalvik.system.NativeStart.main(Native Method)

10-27 21:19:53.020  8027  8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x400fc1b8

从log中可以看到,确实是在distroy view的时候释放了Functor,而随后再Renderer中又使用了这个Functor。

打印崩溃时的java调用栈如下:

10-27 21:19:53.274  8027  8027 I dalvikvm: "main" prio=5 tid=1 TIMED_WAIT10-27 21:19:53.279  8027  8027 I dalvikvm:   | group="main" sCount=0 dsCount=0 obj=0x41716ca8 self=0x415344f8

10-27 21:19:53.279  8027  8027 I dalvikvm:   | sysTid=6895 nice=-6 sched=0/0 cgrp=apps handle=1074409812

10-27 21:19:53.280  8027  8027 I dalvikvm:   | state=R schedstat=( 0 0 0 ) utm=184 stm=61 core=3

10-27 21:19:53.280  8027  8027 I dalvikvm:   at android.view.GLES20Canvas.nDrawDisplayList(Native Method)

10-27 21:19:53.281  8027  8027 I dalvikvm:   at android.view.GLES20Canvas.drawDisplayList(GLES20Canvas.java:420)

10-27 21:19:53.281  8027  8027 I dalvikvm:   at android.view.HardwareRenderer$GlRenderer.drawDisplayList(HardwareRenderer.java:1709)

10-27 21:19:53.281  8027  8027 I dalvikvm:   at android.view.HardwareRenderer$GlRenderer.draw(HardwareRenderer.java:1525)

10-27 21:19:53.282  8027  8027 I dalvikvm:   at android.view.ViewRootImpl.draw(ViewRootImpl.java:2475)

10-27 21:19:53.282  8027  8027 I dalvikvm:   at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2347)

10-27 21:19:53.283  8027  8027 I dalvikvm:   at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1977)

10-27 21:19:53.284  8027  8027 I dalvikvm:   at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1094)

10-27 21:19:53.285  8027  8027 I dalvikvm:   at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5703)

10-27 21:19:53.285  8027  8027 I dalvikvm:   at android.view.Choreographer$CallbackRecord.run(Choreographer.java:764)

10-27 21:19:53.286  8027  8027 I dalvikvm:   at android.view.Choreographer.doCallbacks(Choreographer.java:577)

10-27 21:19:53.287  8027  8027 I dalvikvm:   at android.view.Choreographer.doFrame(Choreographer.java:547)

10-27 21:19:53.288  8027  8027 I dalvikvm:   at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:750)

10-27 21:19:53.289  8027  8027 I dalvikvm:   at android.os.Handler.handleCallback(Handler.java:733)

10-27 21:19:53.289  8027  8027 I dalvikvm:   at android.os.Handler.dispatchMessage(Handler.java:95)

10-27 21:19:53.290  8027  8027 I dalvikvm:   at android.os.Looper.loop(Looper.java:136)

10-27 21:19:53.291  8027  8027 I dalvikvm:   at android.app.ActivityThread.main(ActivityThread.java:5016)

10-27 21:19:53.291  8027  8027 I dalvikvm:   at java.lang.reflect.Method.invokeNative(Native Method)

10-27 21:19:53.292  8027  8027 I dalvikvm:   at java.lang.reflect.Method.invoke(Method.java:515)

10-27 21:19:53.293  8027  8027 I dalvikvm:   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792)

10-27 21:19:53.293  8027  8027 I dalvikvm:   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:608)

10-27 21:19:53.293  8027  8027 I dalvikvm:   at dalvik.system.NativeStart.main(Native Method)

正常情况下,view在被destroy后不应该再被绘制,通过跟孙念沟通,得知这种情况可能是view在destroy前没有remove导致的。

分析代码:
上面delete时的调用栈中有特别的两行:

10-27 21:19:52.990  8027  8027 D INJECT  :   at com.xiaomi.music.hybrid.HybridFragment.destroyHybridView(HybridFragment.java:64)

10-27 21:19:52.990  8027  8027 D INJECT  :   at com.xiaomi.music.hybrid.HybridFragment.onDestroyView(HybridFragment.java:115)

这个是应用的代码,而这个问题只有在这个应用上出现过,所以很可能是应用的代码引起的,
所以查了下opengrok中的代码,发现有两处destroyHybridView()的实现:

@v8-kk-pisces-alpha/packages/apps/MiuiMusic/common/music_sdk/hybrid/src/com/xiaomi/music/hybrid/HybridFragment.java

    private void destroyHybridView() {

        for (HybridView view : mHybridViews) {

            if (view != null) {

                view.destroy();

            }

        }

        mHybridViews.clear();

    }



@v8-kk-pisces-alpha/packages/apps/MiuiSdk/library/src/java/miui/hybrid/HybridFragment.java

    private void destroyHybridView() {

        for (HybridView view : mHybridViews) {

            if (view != null) {

=>              if (view.getParent() != null) {

=>                  ((ViewGroup) view.getParent()).removeView(view);

=>              }

                view.destroy();

            }

        }

        mHybridViews.clear();

    }

跟应用的同事沟通后得知,音乐应用是用上面的代码,也就是没有removeView的代码。
将上面代码中添加removeView的逻辑后不再复现问题。

虽然问题得到解决,但还不清楚为什么没有removeView会导致野指针。
为了找到根源仔细阅读了相关代码,发现代码中Render中有detachFunctor的代码:

class GLES20Canvas extends HardwareCanvas {

    ...

    public void detachFunctor(int functor) {

        nDetachFunctor(mRenderer, functor);

    }

用studio在这个代码中设置断点,得到如下调用栈:

java.lang.Thread.State: RUNNABLE

      at android.view.GLES20Canvas.detachFunctor(GLES20Canvas.java:321)

      at android.view.HardwareRenderer$GlRenderer.detachFunctor(HardwareRenderer.java:1791)

      at android.view.ViewRootImpl.detachFunctor(ViewRootImpl.java:744)

      at com.android.webview.chromium.DrawGLFunctor$DestroyRunnable.detachNativeFunctor(DrawGLFunctor.java:97)

      at com.android.webview.chromium.DrawGLFunctor.detach(DrawGLFunctor.java:53)

      at com.android.webview.chromium.WebViewChromium.onDetachedFromWindow(WebViewChromium.java:1718)

      at android.webkit.WebView.onDetachedFromWindow(WebView.java:2108)

      at android.view.View.dispatchDetachedFromWindow(View.java:12631)

      at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2587)

      at android.view.ViewGroup.removeViewInternal(ViewGroup.java:3845)

      at android.view.ViewGroup.removeViewInternal(ViewGroup.java:3818)

      at android.view.ViewGroup.removeView(ViewGroup.java:3750)

      at com.xiaomi.music.hybrid.HybridFragment.destroyHybridView(HybridFragment.java:66)

      at com.xiaomi.music.hybrid.HybridFragment.onDestroyView(HybridFragment.java:119)

      at com.miui.player.component.MusicBaseFragment.onDestroyView(MusicBaseFragment.java:216)

      at android.app.Fragment.performDestroyView(Fragment.java:1898)

      at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:954)

      at android.app.FragmentManagerImpl.removeFragment(FragmentManager.java:1167)

      at android.app.BackStackRecord.popFromBackStack(BackStackRecord.java:715)

      at android.app.FragmentManagerImpl.popBackStackState(FragmentManager.java:1544)

      at android.app.FragmentManagerImpl$3.run(FragmentManager.java:502)

      at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1449)

      at android.app.FragmentManagerImpl$1.run(FragmentManager.java:443)

      at android.os.Handler.handleCallback(Handler.java:733)

      at android.os.Handler.dispatchMessage(Handler.java:95)

      at android.os.Looper.loop(Looper.java:136)

      at android.app.ActivityThread.main(ActivityThread.java:5016)

      at java.lang.reflect.Method.invokeNative(Method.java:-1)

      at java.lang.reflect.Method.invoke(Method.java:515)

      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792)

      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:608)

      at dalvik.system.NativeStart.main(NativeStart.java:-1)

加了removeView后,会从Render中删除Functor,这样Render在绘制时,不再调用这个Functor。

这个问题只会在KK上有,L以后对Render做的很大改动,即使不做removeView,也不会存在野指针问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值