Java代码引起的NATIVE野指针问题(下)
朴英敏,小米MIUI部门。从事嵌入式开发和调试工作8年多,擅长逆向分析方法,主要负责解决安卓系统稳定性问题。 实施hook: 我们有了hook,但目前还不知道是哪个so中释放了functor。 如果无法确定是哪个so,可以多hook几个so就行了。 当然对于特定的例子,也有技巧来确定so,比如我们这个例子: 被析构的对象是Functor类的对象,由于它的vtbl地址我们能够从log中获取到, 而vtbl一般指向定义了该类的so中,所以用vtbl值(0×73648de0)去map表中找,就能确定是哪个so了。 ... 73635000-73646000rw-p0000000000:000 73646000-73648000r-xp00000000b3:181287/system/lib/libwebviewchromium_plat_support.so =>73648000-73649000r--p00001000b3:181287/system/lib/libwebviewchromium_plat_support.so 73649000-7364a000rw-p00002000b3:181287/system/lib/libwebviewchromium_plat_support.so 7364a000-73684000rw-p0000000000:000 73684000-73696000r-xp00000000b3:181034/system/lib/libjavacrypto.so 73696000-73697000r--p00011000b3:181034/system/lib/libjavacrypto.so 73697000-73698000rw-p00012000b3:181034/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-slibwebviewchromium_plat_support.so|grepUND 0:000000000NOTYPELOCALDEFAULTUND 1:000000000FUNCGLOBALDEFAULTUND__cxa_finalize 2:000000000FUNCGLOBALDEFAULTUND__cxa_atexit 4:000000000FUNCGLOBALDEFAULTUND__aeabi_unwind_cpp_pr0 5:000000000FUNCGLOBALDEFAULTUND__aeabi_unwind_cpp_pr1 6:000000000FUNCGLOBALDEFAULTUNDgetrlimit 7:000000000FUNCGLOBALDEFAULTUNDsetrlimit 8:000000000FUNCGLOBALDEFAULTUND__errno 9:000000000FUNCGLOBALDEFAULTUNDstrerror 10:000000000FUNCGLOBALDEFAULTUND__android_log_print =>11:000000000FUNCGLOBALDEFAULTUND_Znwj =>12:000000000FUNCGLOBALDEFAULTUND_ZdlPv 14:000000000FUNCGLOBALDEFAULTUND__android_log_assert ... 51:000000000FUNCGLOBALDEFAULTUND__aeabi_llsr 52:000000000OBJECTGLOBALDEFAULTUND__popcount_tab 其中11项_Znwj是new的符号,_ZdlPv是delete的符号。 接下来就用工具hook libwebviewchromium_plat_support.so的delete函数: externvoid_ZdlPv(void*); voidinject__ZdlPv(void*ptr){ LOGD("delete%p",ptr); dumpNativeStack(); dumpJavaStack(); _ZdlPv(ptr); } hook后复现问题,抓到的log如下: 10-2721:19:52.96180278027DObserverLayout:onStop:clz=com.miui.player.display.view.DisplayFragmentLayout{45665838V.E.............0,0-1080,1920#7f080039app:id/content} 10-2721:19:52.96580278027IMusicBaseFragment:onDestroyViewtheviewisstillattached,delaydestroy 10-2721:19:52.96680278027DINJECT:delete0x7a7b8530 10-2721:19:52.98680278027DINJECT:#00pc000015f6/system/lib/libinject.so(inject__ZdlPv+21) 10-2721:19:52.98680278027DINJECT:#01pc00001134/system/lib/libwebviewchromium_plat_supp 10-2721:19:52.98680278027DINJECT:#02pc00001088/system/lib/libwebviewchromium_plat_supp 10-2721:19:52.98780278027DINJECT:#03pc0001d30c/system/lib/libdvm.so(dvmPlatformInvoke+112) 10-2721:19:52.98780278027DINJECT:#04pc0004d8da/system/lib/libdvm.so(dvmCallJNIMethod(unsignedintconst*,JV+397) 10-2721:19:52.98780278027DINJECT:#05pc00026720/system/lib/libdvm.so 10-2721:19:52.98780278027DINJECT:#06pc0002d790/system/lib/libdvm.so(dvmMterpStd(Thread*)+76) 10-2721:19:52.98780278027DINJECT:#07pc0002adf4/system/lib/libdvm.so(dvmInterpret(Thread*,Methodconst*,JVa+184) 10-2721:19:52.98880278027DINJECT:#08pc00060058/system/lib/libdvm.so(dvmInvokeMethod(Object*,Methodconst*,+391) 10-2721:19:52.98880278027DINJECT:#09pc00067ff6/system/lib/libdvm.so 10-2721:19:52.98880278027DINJECT:#10pc00026720/system/lib/libdvm.so 10-2721:19:52.98880278027DINJECT:#11pc0002d790/system/lib/libdvm.so(dvmMterpStd(Thread*)+76) 10-2721:19:52.98880278027DINJECT:#12pc0002adf4/system/lib/libdvm.so(dvmInterpret(Thread*,Methodconst*,JVa+184) 10-2721:19:52.98880278027DINJECT:#13pc0005fd74/system/lib/libdvm.so(dvmCallMethodV(Thread*,Methodconst*,O+335) 10-2721:19:52.98880278027DINJECT:#14pc000494c2/system/lib/libdvm.so 10-2721:19:52.98980278027DINJECT:atcom.android.webview.chromium.DrawGLFunctor.nativeDestroyGLFunctor(NativeMethod) 10-2721:19:52.98980278027DINJECT:atcom.android.webview.chromium.DrawGLFunctor.access$000(DrawGLFunctor.java:31) 10-2721:19:52.98980278027DINJECT:atcom.android.webview.chromium.DrawGLFunctor$DestroyRunnable.run(DrawGLFunctor.java:91) 10-2721:19:52.98980278027DINJECT:atcom.android.org.chromium.content.common.CleanupReference.runCleanupTaskInternal(CleanupReference.java:159) 10-2721:19:52.98980278027DINJECT:atcom.android.org.chromium.content.common.CleanupReference.access$300(CleanupReference.java:32) 10-2721:19:52.98980278027DINJECT:atcom.android.org.chromium.content.common.CleanupReference$LazyHolder$1.handleMessage(CleanupReference.java:93) 10-2721:19:52.99080278027DINJECT:atcom.android.org.chromium.content.common.CleanupReference.handleOnUiThread(CleanupReference.java:147) 10-2721:19:52.99080278027DINJECT:atcom.android.org.chromium.content.common.CleanupReference.cleanupNow(CleanupReference.java:141) 10-2721:19:52.99080278027DINJECT:atcom.android.webview.chromium.DrawGLFunctor.destroy(DrawGLFunctor.java:46) 10-2721:19:52.99080278027DINJECT:atcom.android.webview.chromium.WebViewChromium.destroy(WebViewChromium.java:430) 10-2721:19:52.99080278027DINJECT:atandroid.webkit.WebView.destroy(WebView.java:667) 10-2721:19:52.99080278027DINJECT:atcom.xiaomi.music.hybrid.HybridFragment.destroyHybridView(HybridFragment.java:64) 10-2721:19:52.99080278027DINJECT:atcom.xiaomi.music.hybrid.HybridFragment.onDestroyView(HybridFragment.java:115) 10-2721:19:52.99080278027DINJECT:atcom.miui.player.component.MusicBaseFragment.onDestroyView(MusicBaseFragment.java:216) 10-2721:19:52.99180278027DINJECT:atandroid.app.Fragment.performDestroyView(Fragment.java:1898) 10-2721:19:52.99180278027DINJECT:atandroid.app.FragmentManagerImpl.moveToState(FragmentManager.java:954) 10-2721:19:52.99180278027DINJECT:atandroid.app.FragmentManagerImpl.removeFragment(FragmentManager.java:1167) 10-2721:19:52.99180278027DINJECT:atandroid.app.BackStackRecord.popFromBackStack(BackStackRecord.java:715) 10-2721:19:52.99180278027DINJECT:atandroid.app.FragmentManagerImpl.popBackStackState(FragmentManager.java:1544) 10-2721:19:52.99280278027DINJECT:atandroid.app.FragmentManagerImpl$3.run(FragmentManager.java:502) 10-2721:19:52.99280278027DINJECT:atandroid.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1449) 10-2721:19:52.99280278027DINJECT:atandroid.app.FragmentManagerImpl$1.run(FragmentManager.java:443) 10-2721:19:52.99280278027DINJECT:atandroid.os.Handler.handleCallback(Handler.java:733) 10-2721:19:52.99280278027DINJECT:atandroid.os.Handler.dispatchMessage(Handler.java:95) 10-2721:19:52.99280278027DINJECT:atandroid.os.Looper.loop(Looper.java:136) 10-2721:19:52.99380278027DINJECT:atandroid.app.ActivityThread.main(ActivityThread.java:5016) 10-2721:19:52.99380278027DINJECT:atjava.lang.reflect.Method.invokeNative(NativeMethod) 10-2721:19:52.99380278027DINJECT:atjava.lang.reflect.Method.invoke(Method.java:515) 10-2721:19:52.99380278027DINJECT:atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792) 10-2721:19:52.99380278027DINJECT:atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:608) 10-2721:19:52.99380278027DINJECT:atdalvik.system.NativeStart.main(NativeMethod) 10-2721:19:53.02080278027IOpenGLRenderer:functor=0x7a7b8530,vtbl=0x400fc1b8 从log中可以看到,确实是在distroy view的时候释放了Functor,而随后再Renderer中又使用了这个Functor。 打印崩溃时的java调用栈如下: 10-2721:19:53.27480278027Idalvikvm:"main"prio=5tid=1TIMED_WAIT10-2721:19:53.27980278027Idalvikvm:|group="main"sCount=0dsCount=0obj=0x41716ca8self=0x415344f8 10-2721:19:53.27980278027Idalvikvm:|sysTid=6895nice=-6sched=0/0cgrp=appshandle=1074409812 10-2721:19:53.28080278027Idalvikvm:|state=Rschedstat=(000)utm=184stm=61core=3 10-2721:19:53.28080278027Idalvikvm:atandroid.view.GLES20Canvas.nDrawDisplayList(NativeMethod) 10-2721:19:53.28180278027Idalvikvm:atandroid.view.GLES20Canvas.drawDisplayList(GLES20Canvas.java:420) 10-2721:19:53.28180278027Idalvikvm:atandroid.view.HardwareRenderer$GlRenderer.drawDisplayList(HardwareRenderer.java:1709) 10-2721:19:53.28180278027Idalvikvm:atandroid.view.HardwareRenderer$GlRenderer.draw(HardwareRenderer.java:1525) 10-2721:19:53.28280278027Idalvikvm:atandroid.view.ViewRootImpl.draw(ViewRootImpl.java:2475) 10-2721:19:53.28280278027Idalvikvm:atandroid.view.ViewRootImpl.performDraw(ViewRootImpl.java:2347) 10-2721:19:53.28380278027Idalvikvm:atandroid.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1977) 10-2721:19:53.28480278027Idalvikvm:atandroid.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1094) 10-2721:19:53.28580278027Idalvikvm:atandroid.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5703) 10-2721:19:53.28580278027Idalvikvm:atandroid.view.Choreographer$CallbackRecord.run(Choreographer.java:764) 10-2721:19:53.28680278027Idalvikvm:atandroid.view.Choreographer.doCallbacks(Choreographer.java:577) 10-2721:19:53.28780278027Idalvikvm:atandroid.view.Choreographer.doFrame(Choreographer.java:547) 10-2721:19:53.28880278027Idalvikvm:atandroid.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:750) 10-2721:19:53.28980278027Idalvikvm:atandroid.os.Handler.handleCallback(Handler.java:733) 10-2721:19:53.28980278027Idalvikvm:atandroid.os.Handler.dispatchMessage(Handler.java:95) 10-2721:19:53.29080278027Idalvikvm:atandroid.os.Looper.loop(Looper.java:136) 10-2721:19:53.29180278027Idalvikvm:atandroid.app.ActivityThread.main(ActivityThread.java:5016) 10-2721:19:53.29180278027Idalvikvm:atjava.lang.reflect.Method.invokeNative(NativeMethod) 10-2721:19:53.29280278027Idalvikvm:atjava.lang.reflect.Method.invoke(Method.java:515) 10-2721:19:53.29380278027Idalvikvm:atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792) 10-2721:19:53.29380278027Idalvikvm:atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:608) 10-2721:19:53.29380278027Idalvikvm:atdalvik.system.NativeStart.main(NativeMethod) 正常情况下,view在被destroy后不应该再被绘制,通过跟孙念沟通,得知这种情况可能是view在destroy前没有remove导致的。 分析代码: 上面delete时的调用栈中有特别的两行: 10-2721:19:52.99080278027DINJECT:atcom.xiaomi.music.hybrid.HybridFragment.destroyHybridView(HybridFragment.java:64) 10-2721:19:52.99080278027DINJECT:atcom.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 privatevoiddestroyHybridView(){ for(HybridViewview:mHybridViews){ if(view!=null){ view.destroy(); } } mHybridViews.clear(); } @v8-kk-pisces-alpha/packages/apps/MiuiSdk/library/src/java/miui/hybrid/HybridFragment.java privatevoiddestroyHybridView(){ for(HybridViewview:mHybridViews){ if(view!=null){ =>if(view.getParent()!=null){ =>((ViewGroup)view.getParent()).removeView(view); =>} view.destroy(); } } mHybridViews.clear(); } 跟应用的同事沟通后得知,音乐应用是用上面的代码,也就是没有removeView的代码。 将上面代码中添加removeView的逻辑后不再复现问题。 虽然问题得到解决,但还不清楚为什么没有removeView会导致野指针。 为了找到根源仔细阅读了相关代码,发现代码中Render中有detachFunctor的代码: classGLES20CanvasextendsHardwareCanvas{ ... publicvoiddetachFunctor(intfunctor){ nDetachFunctor(mRenderer,functor); } 用studio在这个代码中设置断点,得到如下调用栈: java.lang.Thread.State:RUNNABLE atandroid.view.GLES20Canvas.detachFunctor(GLES20Canvas.java:321) atandroid.view.HardwareRenderer$GlRenderer.detachFunctor(HardwareRenderer.java:1791) atandroid.view.ViewRootImpl.detachFunctor(ViewRootImpl.java:744) atcom.android.webview.chromium.DrawGLFunctor$DestroyRunnable.detachNativeFunctor(DrawGLFunctor.java:97) atcom.android.webview.chromium.DrawGLFunctor.detach(DrawGLFunctor.java:53) atcom.android.webview.chromium.WebViewChromium.onDetachedFromWindow(WebViewChromium.java:1718) atandroid.webkit.WebView.onDetachedFromWindow(WebView.java:2108) atandroid.view.View.dispatchDetachedFromWindow(View.java:12631) atandroid.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2587) atandroid.view.ViewGroup.removeViewInternal(ViewGroup.java:3845) atandroid.view.ViewGroup.removeViewInternal(ViewGroup.java:3818) atandroid.view.ViewGroup.removeView(ViewGroup.java:3750) atcom.xiaomi.music.hybrid.HybridFragment.destroyHybridView(HybridFragment.java:66) atcom.xiaomi.music.hybrid.HybridFragment.onDestroyView(HybridFragment.java:119) atcom.miui.player.component.MusicBaseFragment.onDestroyView(MusicBaseFragment.java:216) atandroid.app.Fragment.performDestroyView(Fragment.java:1898) atandroid.app.FragmentManagerImpl.moveToState(FragmentManager.java:954) atandroid.app.FragmentManagerImpl.removeFragment(FragmentManager.java:1167) atandroid.app.BackStackRecord.popFromBackStack(BackStackRecord.java:715) atandroid.app.FragmentManagerImpl.popBackStackState(FragmentManager.java:1544) atandroid.app.FragmentManagerImpl$3.run(FragmentManager.java:502) atandroid.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1449) atandroid.app.FragmentManagerImpl$1.run(FragmentManager.java:443) atandroid.os.Handler.handleCallback(Handler.java:733) atandroid.os.Handler.dispatchMessage(Handler.java:95) atandroid.os.Looper.loop(Looper.java:136) atandroid.app.ActivityThread.main(ActivityThread.java:5016) atjava.lang.reflect.Method.invokeNative(Method.java:-1) atjava.lang.reflect.Method.invoke(Method.java:515) atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792) atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:608) atdalvik.system.NativeStart.main(NativeStart.java:-1) 加了removeView后,会从Render中删除Functor,这样Render在绘制时,不再调用这个Functor。 这个问题只会在KK上有,L以后对Render做的很大改动,即使不做removeView,也不会存在野指针问题。 作者:朴英敏 来源:51CTO