共计 14907 个字符,预计需要花费 38 分钟才能阅读完成。
作者:林作健
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
:
会调用 ResolveClass
,ResolveClass
会调用 ClassLinker::FindClass
,FindClass
有个不言而喻的失败前提是:
也就是在以后线程是 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 篇挪动技术实际 & 干货给你思考!