写点什么

Android 12 “致命”崩溃解决之路

  • 2022 年 2 月 16 日
  • 本文字数:8851 字

    阅读完需:约 29 分钟

Android 12 “致命”崩溃解决之路


作者:林作健


UC 内核在 Android 12 上发现一个致命的崩溃。约有 10%的用户在冷启动的时候会遇到这个问题,严重影响了 UC 内核的发布。它的调用栈是这样的:


10-12 19:03:21.461  1038  2723 I id.AlipayGphon: Rejecting re-init on previously-failed class java.lang.Class<com.uc.webkit.impl.WebViewChromiumFactoryProvider>: java.lang.VerifyError: Verifier rejected class com.uc.webkit.impl.WebViewChromiumFactoryProvider: com.uc.webkit.an com.uc.webkit.impl.WebViewChromiumFactoryProvider.g() failed to verify: com.uc.webkit.an com.uc.webkit.impl.WebViewChromiumFactoryProvider.g(): [0x15]  can't resolve returned type 'Unresolved Reference: com.uc.webkit.an' or 'Unresolved Reference: com.uc.webkit.impl.ak' (declaration of 'com.uc.webkit.impl.WebViewChromiumFactoryProvider' appears in /data/user/0/com.eg.android.AlipayGphone/app_h5container/uc/3.22.2.28.21092218119_64/so/core.jar)10-12 19:03:21.461  1038  2723 I id.AlipayGphon: (Throwable with empty stack trace)10-12 19:03:21.464  1038  2723 E WebViewEntry: init error and prepare native crash10-12 19:03:21.464  1038  2723 E WebViewEntry: java.lang.NoClassDefFoundError: com.uc.webkit.impl.WebViewChromiumFactoryProvider10-12 19:03:21.464  1038  2723 E WebViewEntry:     at com.uc.webkit.impl.WebViewChromiumFactoryProvider.i(Unknown Source:0)10-12 19:03:21.464  1038  2723 E WebViewEntry:     at com.uc.webkit.WebViewEntry.p(U4Source:193)10-12 19:03:21.464  1038  2723 E WebViewEntry:     at com.uc.webkit.bg.run(Unknown Source:0)10-12 19:03:21.464  1038  2723 E WebViewEntry:     at android.os.Handler.handleCallback(Handler.java:938)10-12 19:03:21.464  1038  2723 E WebViewEntry:     at android.os.Handler.dispatchMessage(Handler.java:99)10-12 19:03:21.464  1038  2723 E WebViewEntry:     at android.os.Looper.loopOnce(Looper.java:201)10-12 19:03:21.464  1038  2723 E WebViewEntry:     at android.os.Looper.loop(Looper.java:288)10-12 19:03:21.464  1038  2723 E WebViewEntry:     at android.os.HandlerThread.run(HandlerThread.java:67)10-12 19:03:21.464  1038  2723 E WebViewEntry: Caused by: java.lang.VerifyError: Verifier rejected class com.uc.webkit.impl.WebViewChromiumFactoryProvider: com.uc.webkit.an com.uc.webkit.impl.WebViewChromiumFactoryProvider.g() failed to verify: com.uc.webkit.an com.uc.webkit.impl.WebViewChromiumFactoryProvider.g(): [0x15]  can't resolve returned type 'Unresolved Reference: com.uc.webkit.an' or 'Unresolved Reference: com.uc.webkit.impl.ak' (declaration of 'com.uc.webkit.impl.WebViewChromiumFactoryProvider' appears in /data/user/0/com.eg.android.AlipayGphone/app_h5container/uc/3.22.2.28.21092218119_64/so/core.jar)
复制代码


不解决这个问题我们的内核可能无法在 Android 12 上启用了,对于内核来说又是一个生死攸关的问题。这个问题正常操作无法重现,只能通过 monkey 疯狂冷启动才能偶现。


另外一个背景是 UC 浏览器把 sdk level 提高到了 30 才引发这个问题。

调用栈分析

从调用栈的信息我们看到最顶层的 Error 是 NoClassDefFoundError,但他是由下面的 VerifyError 引起的。这个调用栈显示正在进行正常的启动过程。


Rejecting re-init on previously-failed class 显示com.uc.webkit.impl.WebViewChromiumFactoryProvider应该已经尝试过 Verify,但是 Error 了。按照常理应该还有一个 VerifyError 的抛出。但找了多个崩溃日志都没有发现第一次 VerifyError 抛出的位置。


另外,这个 VerifyError 的Caused by: java.lang.VerifyError位置应该后面还跟着它第一次 Verify 的调用栈,但它却显示(Throwable with empty stack trace)

黑科技分析:手段一

带着上述的诸多疑问,我们发现目前的数据不足以我们进行分析,我们需要更多的和 Verify 有关的信息才能处理问题。


Android 的 art 虚拟机是带着 verbose log 的。它是按照模块分类的,平时不会打开。需要启动 art 的时候通过传参让它打开。


我们尝试了 wrapper 技术,即在 lib 目录加上文件 wrapper.sh,系统就会用 wrapper.sh 启动虚拟机,而不是通过 Zygote。很遗憾这个手段没有作用,分析了 AndroidRuntime.cpp 里面的源码后,我们发现 wrapper 传入的虚拟机参赛会被它过滤掉,完全无视。


我们只能使用正经途径之外的方法了。



上图是 Verbose log 的结构,我们看到有个全局变量 gLogVerbosity 控制这它们的开关。我们能不能通过修改 gLogVerbosity 达到启动 verbose log 的目的?


UC 内核有着一系列强大的黑科技组合。适应这种需求的黑科技是 symbol_resolver 模块。这个技术能够从/proc/self/maps文件里面分析指名的 so 映射的位置,并通过 elf 解析拿到所有的符号,然后我们就能够从 Key-Value 对里面找到想要的符号的位置。


用这个技术我们很快定位了 libart.so 里面的gLogVerbosity位置,并且当作一个 bool 数组把 verifier 和 verifier_debug 项置为 true。于是我们有了新的 log:


Verification failed on class org.chromium.ui.base.WindowAndroid in /data/user/0/com.eg.android.AlipayGphone/app_h5container/uc/3.22.2.31.10191532_64/so/core.jar because: Verifier rejected class org.chromium.ui.base.WindowAndroid: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken() failed to verify: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken(): [0x10]  can't resolve returned type 'Unresolved Reference: android.os.IBinder' or 'Reference: android.os.IBinder'VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x0] : Processing const/4 v1, #+00:[Undefined],1:[Undefined],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x1] : Processing iget-object v0, v2, Ljava/lang/ref/WeakReference; org.chromium.ui.base.WindowAndroid.e // field@79820:[Undefined],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x3] : Processing invoke-virtual {v0}, java.lang.Object java.lang.ref.WeakReference.get() // method@73470:[Reference: java.lang.ref.WeakReference],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x6] : Processing move-result-object v00:[Reference: java.lang.ref.WeakReference],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x7] : Processing check-cast v0, android.content.Context // type@TypeIndex[61]0:[Reference: java.lang.Object],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x9] : Processing invoke-static {v0}, android.app.Activity org.chromium.ui.base.WindowAndroid.a(android.content.Context) // method@170170:[Reference: android.content.Context],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0xc] : Processing move-result-object v00:[Reference: android.content.Context],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0xd] : Processing if-nez v0, +40:[Reference: android.app.Activity],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0xf] : Processing move-object v0, v10:[Reference: android.app.Activity],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x10] : Processing return-object v00:[Zero/null],1:[Conflict],2:[Conflict],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x11] : Processing invoke-virtual {v0}, android.view.Window android.app.Activity.getWindow() // method@260:[Reference: android.app.Activity],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x14] : Processing move-result-object v00:[Reference: android.app.Activity],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x15] : Processing if-nez v0, +40:[Reference: android.view.Window],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x17] : Processing move-object v0, v10:[Reference: android.view.Window],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x18] : Processing goto -80:[Zero/null],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x19] : Processing invoke-virtual {v0}, android.view.View android.view.Window.peekDecorView() // method@14590:[Reference: android.view.Window],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x1c] : Processing move-result-object v00:[Reference: android.view.Window],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x1d] : Processing if-nez v0, +40:[Reference: android.view.View],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x1f] : Processing move-object v0, v10:[Reference: android.view.View],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x20] : Processing goto -160:[Zero/null],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x21] : Processing invoke-virtual {v0}, android.os.IBinder android.view.View.getWindowToken() // method@13180:[Reference: android.view.View],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x24] : Processing move-result-object v00:[Reference: android.view.View],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x25] : Processing goto -210:[Reference: android.os.IBinder],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x25] : Merging at [0x25] to [0x10]: 0:[Zero/null],1:[Conflict],2:[Conflict],  MERGE0:[Reference: android.os.IBinder],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],  ==0:[Reference: android.os.IBinder],1:[Conflict],2:[Conflict],VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x10] : Processing return-object v00:[Reference: android.os.IBinder],1:[Conflict],2:[Conflict],Rejecting opcode return-object v0Register Types:  0: Undefined  1: Conflict  2: null  3: Boolean  4: Byte  5: Short  6: Char  7: Integer  8: Long (Low Half)  9: Long (High Half)  10: Float  11: Double (Low Half)  12: Double (High Half)  13: Precise Constant: -1  14: Zero/null  15: Precise Constant: 1  16: Precise Constant: 2  17: Precise Constant: 3  18: Precise Constant: 4  19: Reference: org.chromium.ui.base.WindowAndroid  20: Reference: java.lang.Object  21: Reference: java.lang.ref.WeakReference  22: Reference: java.lang.ref.Reference  23: Reference: android.content.Context  24: Reference: android.app.Activity  25: Unresolved Reference: android.os.IBinder  26: Reference: android.view.Window  27: Reference: android.view.View  28: Reference: android.os.IBinderDumping instructions and register lines:  0:[Undefined],1:[Undefined],2:[Reference: org.chromium.ui.base.WindowAndroid],  0x0000: V-O-B-- const/4 v1, #+0  0x0001: V-O---- iget-object v0, v2, Ljava/lang/ref/WeakReference; org.chromium.ui.base.WindowAndroid.e // field@7982  0x0003: V-O---- invoke-virtual {v0}, java.lang.Object java.lang.ref.WeakReference.get() // method@7347  0x0006: V-O---- move-result-object v0  0x0007: V-O--G- check-cast v0, android.content.Context // type@TypeIndex[61]  0x0009: V-O---- invoke-static {v0}, android.app.Activity org.chromium.ui.base.WindowAndroid.a(android.content.Context) // method@17017  0x000c: V-O---- move-result-object v0  0x000d: V-O---- if-nez v0, +4  0x000f: V-O---- move-object v0, v1  0:[Reference: android.os.IBinder],1:[Conflict],2:[Conflict],  0x0010: VCO-B-R return-object v0  0:[Reference: android.app.Activity],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],  0x0011: V-O-B-- invoke-virtual {v0}, android.view.Window android.app.Activity.getWindow() // method@26  0x0014: V-O---- move-result-object v0  0x0015: V-O---- if-nez v0, +4  0x0017: V-O---- move-object v0, v1  0x0018: V-O---- goto -8  0:[Reference: android.view.Window],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],  0x0019: V-O-B-- invoke-virtual {v0}, android.view.View android.view.Window.peekDecorView() // method@1459  0x001c: V-O---- move-result-object v0  0x001d: V-O---- if-nez v0, +4  0x001f: V-O---- move-object v0, v1  0x0020: V-O---- goto -16  0:[Reference: android.view.View],1:[Zero/null],2:[Reference: org.chromium.ui.base.WindowAndroid],  0x0021: V-O-B-- invoke-virtual {v0}, android.os.IBinder android.view.View.getWindowToken() // method@1318  0x0024: V-O---- move-result-object v0  0x0025: V-O---- goto -21Setting org.chromium.ui.base.WindowAndroid to erroneous.
复制代码


这个 log 最值得关注的有两点:


1、[0x10] can't resolve returned type 'Unresolved Reference: android.os.IBinder' or 'Reference: android.os.IBinder' VFY: android.os.IBinder org.chromium.ui.base.WindowAndroid.getWindowToken()[0x0] : Processing const/4 v1, #+0



根据打 log 的代码,我们看到 return_type 对应着'Unresolved Reference: android.os.IBinder'


但 return_type 的来源是:



GetMethodReturnType:



会调用FromDescriptor



会调用ResolveClassResolveClass会调用ClassLinker::FindClassFindClass有个显而易见的失败前提是:



也就是在当前线程是 RuntimeThread 的时候,会拒绝FindClass。因为这可能会导致 class 进入初始化过程,导致它调用 class 里面 static block 中的 class 初始化函数。在 RuntimeThread 缺少允许 java 函数的环境,不能允许它这么做。


难道由于当前线程是 Runtime Thread 吗?是的话这个 Thread 是哪个 Runtime Thread?难道是 gc thread 吗?


2、对这个日志前后的 Verify 动作进行分析。发现正常能 Verify 过的线程,都有 load class 的日志。但出问题的这条线程一条 load class 的日志都没有,后面它还因为同样的原因 Verify 失败了好几个 class。这更加肯定失败的线程是一个 Runtime Thread。另外前面提到的 VerifyError 没有调用栈记录的现象也在侧面印证这是个 Runtime Thread。因为 Runtime Thread 没有 Java 环境,不能调用 Java 函数,所以没有记录。但我们还是需要找到这个线程是什么。为此我们动用了第二个黑科技。

黑科技分析:手段二

通过观察代码,我们发现 VerifyError 都是通过同一个函数抛出的:



我们也能找到它的全局符号,所以我们只需要在这个符号的位置加上执行马上崩溃的代码,然后让 monkey 触发这个问题就能处理它了。


这里有个问题:android 为了安全的原因禁止我们把代码段的权限改为可写。


如何安全的把代码段改了呢?我们使用了/prof/self/mem技术:打开/proc/self/mem文件,然后用 pwrite api 往符号的位置写入必崩代码。


这样我们就发现了 Verify 失败的那个线程:



根本原因分析

我们拿到了线程名Verification th。也拿到了线程启动的调用栈。他是从 ThreadPool 启动的,ThreadPool 中的 Thread 都是 RuntimeThread,坐实了之前的猜测。线程运行的任务是 BackgroundVerificationTask。可以迅速找到它启动的位置:



再找一下是这个提交出的问题:


commit 0d5f6402ff925ac1385ccb349f8a2798a4816458 Author: Nicolas Geoffray ngeoffray@google.com Date: Tue Apr 13 13:05:36 2021 +0100
Only run background verification when dexPathList is set.
Otherwise, the runtime will not be able to find the classes.
Test: 692-vdex-secondary-loaderBug: 185088679Change-Id: Idd39eabe00faa017aa5254f7188e7adbcaa23c74
diff --git a/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.javaindex 710a88cc6d0..afbc9ec9de7 100644--- a/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java+++ b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java@@ -128,6 +128,9 @@ public class BaseDexClassLoader extends ClassLoader { : Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length); this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted); + // Run background verification after having set 'pathList'.+ this.pathList.maybeRunBackgroundVerification(this);+ reportClassLoaderChain(); } @@ -186,6 +189,8 @@ public class BaseDexClassLoader extends ClassLoader { this.sharedLibraryLoaders = null; this.pathList = new DexPathList(this, librarySearchPath); this.pathList.initByteBufferDexPath(dexFiles);+ // Run background verification after having set 'pathList'.+ this.pathList.maybeRunBackgroundVerification(this); } @Override
复制代码


git tag --contain命令找了下,发现确实是 android 12 beta 版开始带上的。

解决方案

除了向谷歌报告问题,抱怨一通之外我们还是要找到解决方案。谷歌说他们下一版 android 12 的 12 月更新就会解决这个问题,但很多老机器根本不更新,所以他们是指望不上的了。


我们必须从OatFileManager::RunBackgroundVerification函数里面找到逼迫它不要启动后台验证线程的方法。我们的目光很快落在了:



上面。因为我们还是能控制文件名的。前面的逻辑也有判断 sdk level,只要 sdk level<=29 也不会启动这个线程,但 UC 浏览器已经把 sdk level 打开到 30 了(这也印证了背景提到 UC 浏览器把 sdk level 提高到 30 才出现)。


观察了函数DexLocationToOdexFilename,发现一行很有帮助:


// Get the base part of the file without the extension.  std::string file = location.substr(pos+1);  pos = file.rfind('.');  if (pos == std::string::npos) {    *error_msg = "Dex location " + location + " has no extension.";    return false;  }
复制代码


只要我们让它找不到 suffix separator "."就能迫使它退出了。

结果

对 android 12 使用了软链接 core.jar 为 corejar 的方法后, 这个问题就消失了。威胁 UC 内核的怪兽被打败了,世界又恢复往日的和平。


关注【阿里巴巴移动技术】微信公众号,每周 3 篇移动技术实践 &干货给你思考!

发布于: 刚刚阅读数: 2
用户头像

还未添加个人签名 2018.07.07 加入

阿里巴巴移动&终端技术官方账号。

评论

发布
暂无评论
Android 12 “致命”崩溃解决之路