LoadedApk Crash的探索之路
Bug成因分析,问题定位
Crash 现场
起因是:在工作中遇到两个奔溃堆栈日志,如下图:
初步分析
这个Crash发生在第三方SDK中,是由SDK内部触发,不是通过直接调用产生的,并且难以复现。本来这个问题应该由SDK提供方来解决,但是用户不管这些,需要我们APP自己来解决。
我在华为手机上也遇到这个问题了,没有页面发生奔溃,但是也没有从根本上避免这个问题。
代码定位
这两个Crash分别发生在ConnectionTracker类的unbindService方法和zzaz类的onReceive方法。
通过查看LoadedApk的源码,定位到了抛出异常的地方。通过分析代码发现,是没有注册Receiver或Service,就进行相应的解注册而抛出的异常。
Crash模拟
自己写了一个demo,做了一个试验,比较顺利地复现这一情况。
手动修复尝试
反射替换实例
思路:使用反射,替换成一个带有try-catch方法ConnectionTracker的子类。
报错:
Class创建实例
先通过反射创建一个ConnectionTracker的一个实例,也报错。
public void reflection1() throws Exception {
ConnectionTracker instance = ConnectionTracker.getInstance();
Field zzfa = instance.getClass().getDeclaredField("zzfa");
zzfa.setAccessible(true);
ConnectionTracker tracker = ConnectionTracker.class.newInstance();
zzfa.set(instance, tracker);
}
java.lang.IllegalAccessException: void com.google.android.gms.common.stats.ConnectionTracker.<init>() is not accessible from java.lang.Class<zhangls.me.myapplication12.MainActivity>
Constructor创建实例
换一种方法用反射创建一个ConnectionTracker的一个实例,成功了,没有报错。
public void reflection2() throws Exception {
ConnectionTracker instance = ConnectionTracker.getInstance();
Field zzfa = instance.getClass().getDeclaredField("zzfa");
zzfa.setAccessible(true);
Constructor<ConnectionTracker> constructor
= ConnectionTracker.class.getDeclaredConstructor();
constructor.setAccessible(true);
ConnectionTracker tracker = constructor.newInstance();
zzfa.set(instance, tracker);
}
使用CGlib
但是还是没有办法去创建ConnectionTracker它的子类,现在尝试使用cglib动态代理的方式来做。
//implementation 'cglib:cglib:3.3.0'
public void reflection3() throws Exception {
ConnectionTracker instance = ConnectionTracker.getInstance();
Field zzfa = instance.getClass().getDeclaredField("zzfa");
zzfa.setAccessible(true);
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ConnectionTracker.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws Throwable {
Object result = null;
try {
result = proxy.invokeSuper(obj, args);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
});
zzfa.set(instance, enhancer.create());
}
也报错,原来是Android不适用标准的cglib.
2019-10-07 09:39:45.663 20306-20306/zhangls.me.myapplication12 E/AndroidRuntime: FATAL EXCEPTION: main
Process: zhangls.me.myapplication12, PID: 20306
java.lang.ExceptionInInitializerError
at zhangls.me.myapplication12.MainActivity.reflection3(MainActivity.java:55)
at zhangls.me.myapplication12.MainActivity.onCreate(MainActivity.java:30)
at android.app.Activity.performCreate(Activity.java:7136)
at android.app.Activity.performCreate(Activity.java:7127)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
使用CGlib-for-Android
找了一个CGLib-for-Android,地址:https://github.com/leo-ouyang/CGLib-for-Android
public void reflection4() throws Exception {
ConnectionTracker instance = ConnectionTracker.getInstance();
Field zzfa = instance.getClass().getDeclaredField("zzfa");
zzfa.setAccessible(true);
leo.android.cglib.proxy.Enhancer enhancer = new leo.android.cglib.proxy.Enhancer(this);
enhancer.setSuperclass(ConnectionTracker.class);
enhancer.setInterceptor(new leo.android.cglib.proxy.MethodInterceptor() {
@Override
public Object intercept(Object o, Object[] args,
leo.android.cglib.proxy.MethodProxy methodProxy) throws Exception {
return methodProxy.invokeSuper(o, args);
}
});
zzfa.set(instance, enhancer.create());
}
还是报错,Java.lang.IllegalStateException: static methods cannot access ‘this’
W: java.lang.IllegalStateException: static methods cannot access 'this'
W: at leo.android.cglib.dx.Code.getThis(Code.java:256)
W: at leo.android.cglib.proxy.Enhancer.generateFieldsAndMethods(Enhancer.java:154)
W: at leo.android.cglib.proxy.Enhancer.create(Enhancer.java:49)
W: at zhangls.me.myapplication12.MainActivity.reflection4(MainActivity.java:87)
W: at zhangls.me.myapplication12.MainActivity.onCreate(MainActivity.java:30)
W: at android.app.Activity.performCreate(Activity.java:7136)
W: at android.app.Activity.performCreate(Activity.java:7127)
W: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
W: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
W: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
W: at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
W: at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
W: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
W: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
W: at android.os.Handler.dispatchMessage(Handler.java:106)
W: at android.os.Looper.loop(Looper.java:193)
W: at android.app.ActivityThread.main(ActivityThread.java:6669)
W: at java.lang.reflect.Method.invoke(Native Method)
W: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
W: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
遗留的那些线索
- Extends private class
- 动态修改字节码,重新加载
- app 编译覆盖library的class
- 构建Apk时进行dex分包
- JavaAgent premain
DexClassLoader动态加载
基本原理
安卓App热补丁动态修复技术介绍
(https://zhuanlan.zhihu.com/p/20308548)
该方案的缺点
- 兼容性不好
- 维护成本较高
- 容易出问题
手动修改SDK的aar方案
操作步骤
- 准备old.aar 放在app/libs目录下
- unzip old.aar -d tempFolder
- unzip classes.jar -d tempFolderClasses
- 修改对应的class
- jar cvf newClasses.jar -C tempFolderClasses/ .
- jar cvf new.aar -C tempFolder/ .
https://github.com/dinuscxj/ClassPlugin
(A flexible class replacement plugin for gradle)
遇到的问题
该方案的缺点
- 操作步骤复杂,繁琐
- 依赖传递失效,引起不必要的麻烦
- Git无法记录,不利于以后的跟踪和协作
- 无法复用,或者成本很高
构建Apk编码时修改class
基本原理
插件架构
该方案的缺点
- 增加打包时间
- 学习成本较高
- 注意混淆
行业内其他方案开源
1.https://github.com/didi/booster
简介:🚀Optimizer for mobile applications
Booster 示例:使用 ASM 对字节码进行操作
https://github.com/boostersamples/transformer-with-asm
2.https://github.com/tiann/epic
简介:Epic 是一个在虚拟机层面、以 Java Method 为粒度的 运行时 AOP Hook 框架。简单来说,Epic 就是 ART 上的 Dexposed(支持 Android 4.0 ~ 10.0)。它可以拦截本进程内部几乎任意的 Java 方法调用,可用于实现 AOP 编程、运行时插桩、性能分析、安全审计等。
监控 Java 线程的创建和销毁
class ThreadMethodHook extends XC_MethodHook{
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Thread t = (Thread) param.thisObject;
Log.i(TAG, "thread:" + t + ", started..");
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Thread t = (Thread) param.thisObject;
Log.i(TAG, "thread:" + t + ", exit..");
}
}
DexposedBridge.hookAllConstructors(Thread.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Thread thread = (Thread) param.thisObject;
Class<?> clazz = thread.getClass();
if (clazz != Thread.class) {
Log.d(TAG, "found class extend Thread:" + clazz);
DexposedBridge.findAndHookMethod(clazz, "run", new ThreadMethodHook());
}
Log.d(TAG, "Thread: " + thread.getName() + " class:" + thread.getClass() + " is created.");
}
});
DexposedBridge.findAndHookMethod(Thread.class, "run", new ThreadMethodHook());
3.https://github.com/eleme/lancet
简介:
Lancet 是一个轻量级Android AOP框架。
编译速度快, 并且支持增量编译.
简洁的 API, 几行 Java 代码完成注入需求.
没有任何多余代码插入 apk.
支持用于 SDK, 可以在SDK编写注入代码来修改依赖SDK的App.
@Proxy("i")
@TargetClass("android.util.Log")
public static int anyName(String tag, String msg){
msg = msg + "lancet";
return (int) Origin.call();
}
参考资料
1. Android修改第三方.aar后重新打包(https://www.jianshu.com/p/f0a267551493)
2. Java字节码增强探秘(https://juejin.im/post/5d773ae1518825058772843c)
3. ASM实战统计方法耗时(http://www.wangyuwei.me/2017/03/05/ASM实战统计方法耗时/)
4. Android 跟我一起用 ASM 实现编译期字节码插桩(http://blog.n0texpecterr0r.cn/?p=752)
5. Gradle插件与ASM入门(https://www.jianshu.com/p/83a360c9a3d7)
6. CSDN ASM 6篇文章专栏(https://blog.csdn.net/lijingyao8206/article/category/3276863)
7. How to edit jar files with ASM(https://riptutorial.com/java/example/12965/how-to-edit-jar-files-with-asm)
转载:https://blog.csdn.net/ihrthk/article/details/102535584