Webview onRenderProcessGone返回值导致的崩溃处理****
· 问题
· 排查步骤
问题****
2022年2月份开始以来,bugly上这条崩溃变多,vivo机型占多数
main
SIGTRAP
#00 pc 00000000018ab394 /system/product/app/WebViewGoogle/WebViewGoogle.apk
#01 pc 00000000018ab190 /system/product/app/WebViewGoogle/WebViewGoogle.apk
java:
android.os.MessageQueue.next(MessageQueue.java:340)
android.os.Looper.loop(Looper.java:183)
android.app.ActivityThread.main(ActivityThread.java:8087)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:526) com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1034)
排查步骤****
1. 为啥2月份以来崩溃会变多,诱因暂时未知,猜测如下:
1. vivo有推出系统更新,导致更多人有这个崩溃
2. h5有上新功能,新功能导致出现这个崩溃的概率变大
1. 根据bugly线上抓到的一些跟踪日志,发现了如下日志记录:
1. FATAL:crashpad_client_linux.cc(404)] Render process (28925)'s crash wasn't handled by all associated webviews, triggering application crash.直译大致的意思是渲染层发生了异常,但是没有被相关的webview进行处理,触发了应用程序闪退。
2. 通过分析源码和搜索发现可以通过loadUrl("chrome://crash")来完整复现这个问题,并确认了如下两点
1. 根据这个日志打印搜索相关C++层源码,发现这个crash直接与webview的回调返回值强相关,如果webview的 onRenderProcessGone 方法是使用默认返回值,则直接触发异常,如果返回true,则捕获这个异常,不会造成APP crash
2. webview没有复写onRenderProcessGone方法,导致webview渲染异常时直接崩溃
3. 接下来重点排查所有使用WebView的地方哪里没有设置WebViewClient,首先将X5内核相关的webview,复写了 onRenderProcessGone方法,直接返回true,以及通过字节码插桩找到apk中所有使用webview的地方,确保都正确的设置了WebViewClient,
4. 上线观察没效果
5. 根据bugly页面路径发现都展示过广告,怀疑和广告有关系,特别看到很多崩溃显示穿山甲的广告Activity展示过,首先怀疑穿山甲,询问了他们那边的开发,他们否认,说自己的SDK都正确设置了WebViewClient
6. 分析webview相关源码:source.chromium.org/chromium/ch…
1. 使用pine hook框架hook org.chromium.android_webview.AwContents#onRenderProcessGone 方法,以及webview的构造方法(具体hook代码见底部),目的是webview创建时记录创建堆栈,检测到AwContents#onRenderProcessGone返回false时(意味着将会崩溃),收集对应webview包含创建堆栈在内的的相关信息,埋点上报
7. 方案上线后分析埋点,结果如下,结论是穿山甲SDK中webview没有设置WebViewClient导致的,因为穿山甲这块代码是动态下载的,所以apk字节码插桩没发现
8 最终反馈穿山甲修复处理
·
pine hook代码:
@Override
public void hookWebViewClient() {
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
return;
}
if (done) {
return;
}
done = true;
// 先只修复vivo手机
if (!Build.FINGERPRINT.contains("vivo")) {
return;
}
try {
ClassLoader loader;
loader = WebView.getWebViewClassLoader();
Class<?> targetClass = Class.forName(
"org.chromium.android_webview.AwContents", false, loader);
Method m;
try {
m = targetClass.getDeclaredMethod("onRenderProcessGone", int.class,
boolean.class);
} catch (Throwable throwable) {
m = targetClass.getDeclaredMethod("onRenderProcessGoneDetail", int.class,
boolean.class);
}
PineConfig.debug = true;
PineConfig.debuggable = true;
if (m == null) {
return;
}
Pine.hook(m, new MethodHook() {
@Override
public void beforeCall(Pine.CallFrame callFrame) throws Throwable {
super.beforeCall(callFrame);
Object result = callFrame.invokeOriginalMethod();
if ((Boolean) result) {
return;
}
try {
Field[] fields = callFrame.thisObject.getClass().getDeclaredFields();
for (Field f : fields) {
try {
f.setAccessible(true);
Object o = f.get(callFrame.thisObject);
Field[] fields1 = o.getClass().getDeclaredFields();
for (Field f1 : fields1) {
try {
f1.setAccessible(true);
Object o1 = f1.get(o);
if (o1 instanceof WebView) {
String trace = map.get(o1.hashCode());
HashMap<String, Object> map = new HashMap<>();
if (trace == null) {
trace = "";
}
StringBuilder info = new StringBuilder(trace);
WebView webView = (WebView) o1;
ViewParent parent = webView.getParent();
while (parent != null) {
info.append(parent);
parent = parent.getParent();
}
Context context = webView.getContext();
if (context != null) {
info.append(context);
}
map.put("Result", "found" + info.toString());
callFrame.setResult(true);
return;
}
} catch (Throwable throwable) {
HashMap<String, Object> map = new HashMap<>();
map.put("Result", ExceptionUtils.getStackTrace(throwable));
}
}
} catch (Throwable throwable) {
HashMap<String, Object> map = new HashMap<>();
map.put("Result", ExceptionUtils.getStackTrace(throwable));
}
}
} catch (Throwable throwable) {
HashMap<String, Object> map = new HashMap<>();
map.put("Result", ExceptionUtils.getStackTrace(throwable));
}
callFrame.setResult(true);
}
});
Pine.hook(WebView.class.getDeclaredConstructor(Context.class,
AttributeSet.class, int.class, int.class),
new MethodReplacement() {
@Override
protected Object replaceCall(Pine.CallFrame callFrame) throws Throwable {
callFrame.invokeOriginalMethod();
map.put(callFrame.thisObject.hashCode(),
ExceptionUtils.getStackTrace(new Throwable()));
return null;
}
});
HashMap<String, Object> map = new HashMap<>();
map.put("Result", "start");
} catch (Throwable e) {
e.printStackTrace();
HashMap<String, Object> map = new HashMap<>();
map.put("Result", ExceptionUtils.getStackTrace(e));
}
}