Java代碼引起的NATIVE野指針問題(下)
樸英敏,小米MIUI部門。從事嵌入式開發(fā)和調(diào)試工作8年多,擅長(zhǎng)逆向分析方法,主要負(fù)責(zé)解決安卓系統(tǒng)穩(wěn)定性問題。
實(shí)施hook:
我們有了hook,但目前還不知道是哪個(gè)so中釋放了functor。
如果無(wú)法確定是哪個(gè)so,可以多hook幾個(gè)so就行了。
當(dāng)然對(duì)于特定的例子,也有技巧來確定so,比如我們這個(gè)例子:
被析構(gòu)的對(duì)象是Functor類的對(duì)象,由于它的vtbl地址我們能夠從log中獲取到,
而vtbl一般指向定義了該類的so中,所以用vtbl值(0×73648de0)去map表中找,就能確定是哪個(gè)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++對(duì)象的釋放是delete函數(shù),
libwebviewchromium_plat_support.so不會(huì)直接調(diào)用libc的free函數(shù),而是調(diào)用libc++.so中的delete函數(shù),再由delete函數(shù)調(diào)用free函數(shù),
所以我們得hook libc++.so的free函數(shù),但打印調(diào)用棧的模塊也依賴libc++.so,所以如果在hook函數(shù)中打印調(diào)用棧,也會(huì)遇到死循環(huán)問題。
所以我們得hook libwebviewchromium_plat_support.so中的delete函數(shù),這樣既減少log量,也能避免死循環(huán)。
先確認(rèn)libwebviewchromium_plat_support.so是否依賴了delete函數(shù):
- $ 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項(xiàng)_Znwj是new的符號(hào),_ZdlPv是delete的符號(hào)。
接下來就用工具h(yuǎn)ook libwebviewchromium_plat_support.so的delete函數(shù):
- extern void _ZdlPv(void *);
- void inject__ZdlPv(void* ptr) {
- LOGD("delete %p",ptr);
- dumpNativeStack();
- dumpJavaStack();
- _ZdlPv(ptr);
- }
hook后復(fù)現(xiàn)問題,抓到的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中可以看到,確實(shí)是在distroy view的時(shí)候釋放了Functor,而隨后再Renderer中又使用了這個(gè)Functor。
打印崩潰時(shí)的java調(diào)用棧如下:
- 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后不應(yīng)該再被繪制,通過跟孫念溝通,得知這種情況可能是view在destroy前沒有remove導(dǎo)致的。
分析代碼:
上面delete時(shí)的調(diào)用棧中有特別的兩行:
- 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)
這個(gè)是應(yīng)用的代碼,而這個(gè)問題只有在這個(gè)應(yīng)用上出現(xiàn)過,所以很可能是應(yīng)用的代碼引起的,
所以查了下opengrok中的代碼,發(fā)現(xiàn)有兩處destroyHybridView()的實(shí)現(xiàn):
- @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();
- }
跟應(yīng)用的同事溝通后得知,音樂應(yīng)用是用上面的代碼,也就是沒有removeView的代碼。
將上面代碼中添加removeView的邏輯后不再?gòu)?fù)現(xiàn)問題。
雖然問題得到解決,但還不清楚為什么沒有removeView會(huì)導(dǎo)致野指針。
為了找到根源仔細(xì)閱讀了相關(guān)代碼,發(fā)現(xiàn)代碼中Render中有detachFunctor的代碼:
- class GLES20Canvas extends HardwareCanvas {
- ...
- public void detachFunctor(int functor) {
- nDetachFunctor(mRenderer, functor);
- }
用studio在這個(gè)代碼中設(shè)置斷點(diǎn),得到如下調(diào)用棧:
- 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后,會(huì)從Render中刪除Functor,這樣Render在繪制時(shí),不再調(diào)用這個(gè)Functor。
這個(gè)問題只會(huì)在KK上有,L以后對(duì)Render做的很大改動(dòng),即使不做removeView,也不會(huì)存在野指針問題。
【本文是51CTO專欄“小米開放平臺(tái)”的原創(chuàng)文章,“小米開放平臺(tái)”微信公眾號(hào):xiaomideveloper】