1
头图

Author: Lin Zuojian

UC kernel found a fatal crash on Android 12. About 10% of users will encounter this problem during cold boot, which seriously affects the release of the UC kernel. Its call stack looks like this:

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)

Without solving this problem, our kernel may not be enabled on Android 12, and it is a matter of life and death for the kernel. This problem cannot be reproduced in normal operation, and can only occur occasionally through a crazy cold start of monkey.

Another background is that the UC browser raised the sdk level to 30 to cause this problem.

call stack analysis

From the call stack information we see that the topmost Error is NoClassDefFoundError, but it is caused by VerifyError below. This call stack shows that the normal startup process is going on.

Rejecting re-init on previously-failed class shows that com.uc.webkit.impl.WebViewChromiumFactoryProvider should have tried Verify, but got Error. According to common sense, there should be a VerifyError thrown. But after looking for multiple crash logs, I didn't find the location where VerifyError was thrown for the first time.

In addition, the Caused by: java.lang.VerifyError position of this VerifyError should be followed by the call stack of its first Verify, but it shows (Throwable with empty stack trace) .

Black Technology Analysis: Means One

With the above questions, we found that the current data is not enough for us to analyze, and we need more information about Verify to deal with the problem.

Android's art virtual machine is with verbose log. It is classified according to the module, usually will not open. When you need to start art, let it open by passing parameters.

We tried the wrapper technology, that is, adding the file wrapper.sh to the lib directory, and the system will start the virtual machine with wrapper.sh instead of Zygote. Unfortunately, this method has no effect. After analyzing the source code in AndroidRuntime.cpp, we found that the virtual machine competition imported by the wrapper will be filtered out by it and completely ignored.

We can only use methods outside the normal way.

The above picture is the structure of Verbose log. We see that there is a global variable gLogVerbosity that controls their on/off. Can we achieve the purpose of starting verbose log by modifying gLogVerbosity?

The UC core has a series of powerful black technology combinations. The black technology to meet this need is the symbol_resolver module. This technology can analyze the location of the named so mapping from the /proc/self/maps file, and get all the symbols through elf parsing, and then we can find the location of the desired symbol from the Key-Value pair.

Using this technique we quickly locate the gLogVerbosity location in libart.so and set the verifier and verifier_debug items to true as a bool array. So we have a new 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.

There are two things worth noting about this 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

[]()

According to the log code, we see that return_type corresponds to 'Unresolved Reference: android.os.IBinder' .

But the source of return_type is:

And GetMethodReturnType :

will call FromDescriptor :

will call ResolveClass , ResolveClass will call ClassLinker::FindClass , FindClass has an obvious failure premise:

That is, when the current thread is RuntimeThread, FindClass will be rejected. Because this may cause the class to enter the initialization process, causing it to call the class initialization function in the static block inside the class. The lack of an environment that allows java functions in RuntimeThread cannot allow it to do so.

Is it because the current thread is Runtime Thread? If yes, which Runtime Thread is this Thread? Is it a gc thread?

2. Analyze the Verify action before and after the log. It is found that the threads that can be verified normally have logs of the load class. But the thread in question did not have a log of a load class, and it also failed to verify several classes for the same reason. This is more certain that the failed thread is a Runtime Thread. In addition, the aforementioned phenomenon that VerifyError has no call stack record also confirms that this is a Runtime Thread. Because Runtime Thread has no Java environment and cannot call Java functions, there is no record. But we still need to find out what this thread is. To this end, we used the second black technology.

Black Technology Analysis: Method 2

By observing the code, we found that VerifyError is thrown through the same function:

[]()

We can also find its global symbol, so we just need to add the code that executes the crash immediately in the place of the symbol, and then let the monkey trigger the problem to deal with it.

There's a problem here: Android forbids us to change the permissions of the code segment to writable for security reasons.

How to safely change the code segment? We used the /prof/self/mem technique: open the /proc/self/mem file, and then use the pwrite api to write the crash code to the symbol position.

So we found the thread where Verify failed:

[]()

Root Cause Analysis

We got the thread name Verification th . Also get the call stack that the thread started. He was started from ThreadPool. Threads in ThreadPool are all RuntimeThreads, which confirms the previous guess. The task that the thread runs is the BackgroundVerificationTask. You can quickly find where it starts:

[]()

Look again for this submitted question:

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

I searched for it with the git tag --contain command and found that it was indeed brought by the android 12 beta version.

solution

In addition to reporting the problem to Google and complaining, we still have to find a solution. Google says their next android 12 update will fix this issue, but many older machines don't update at all, so they can't count on it.

We must find a way to force it not to start the background verification thread from the OatFileManager::RunBackgroundVerification function. Our eyes quickly fell on:

above. Because we still have control over the filename. The previous logic also judges the sdk level. As long as the sdk level is <= 29, this thread will not be started, but the UC browser has already opened the sdk level to 30 (this also confirms the background that the UC browser has raised the sdk level to 30). appeared).

Observed the function DexLocationToOdexFilename and found one line helpful:

// 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;
  }

As long as we make it not find the suffix separator "." we can force it to quit.

result

After using the method of soft link core.jar to corejar for android 12, this problem disappeared. The monster that threatened the core of UC was defeated, and the world returned to its former peace.

pay attention to [Alibaba Mobile Technology] WeChat public account, 3 mobile technology practice & dry goods every week for you to think about!


阿里巴巴终端技术
336 声望1.3k 粉丝

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