关于android: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 crash
10-12 19:03:21.464  1038  2723 E WebViewEntry: java.lang.NoClassDefFoundError: com.uc.webkit.impl.WebViewChromiumFactoryProvider
10-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, #+0
0:[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@7982
0:[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@7347
0:[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 v0
0:[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@17017
0:[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 v0
0:[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, +4
0:[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, v1
0:[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 v0
0:[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@26
0:[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 v0
0:[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, +4
0:[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, v1
0:[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 -8
0:[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@1459
0:[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 v0
0:[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, +4
0:[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, v1
0:[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 -16
0:[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@1318
0:[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 v0
0:[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 -21
0:[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],  MERGE
0:[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 v0
0:[Reference: android.os.IBinder],1:[Conflict],2:[Conflict],
Rejecting opcode return-object v0
Register 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.IBinder
Dumping 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 -21
Setting 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-loader
Bug: 185088679
Change-Id: Idd39eabe00faa017aa5254f7188e7adbcaa23c74

diff --git a/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
index 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 篇挪动技术实际&干货给你思考!

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理