Android热补丁之Robust(二)自动化补丁原理解析
在 Android 热补丁框架 Robust 中,几个重要的流程包括:
- 补丁加载过程
- 基础包插桩过程
- 补丁包生成过程
在上一篇文章Android热补丁之Robust原理解析(一)中,我们分析了前两个,补丁加载过程和基础包插桩过程,分析的版本为 0.3.2
。
该篇文章为该系列的第二篇文章,主要分析补丁自动化生成的过程,分析的版本为0.4.82
。
时间跨度有点大…
系列文章:
整体流程
首先在 Gradle 插件中注册了一个名为 AutoPatchTranform 的 Tranform
|
|
该类为插件的入口,实现了 GradlePlugin 并继承自 Transform,在入口处初始化配置并注册 Transform。配置主要是读取 Robust xml 配置、混淆优化后的 mapping 文件、插庄过程中生成的 methodsMap.robust 文件、初始化内联工厂类等等。
然后最主要的是transform
方法
|
|
在 transform
方法中,使用 javassist API 把所有需要处理的类加载到待扫描队列中,然后调用autoPatch
方法自动生成补丁。
在 autoPatch
方法中,主要做了这么几件事情:
- 读取被 @Add、@Modify、RobustModify.modify() 标注的方法或类并记录
- 解析 mapping 文件并记录每个类和类中方法混淆前后对应的信息,其中方法存储的信息有:返回值,方法名,参数列表,混淆后的名字;字段存储的信息有:字段名,混淆后的名字
- 根据得到的信息,
generatPatch
方法实际生成补丁 - 将生成的补丁class打包成jar包
- jar -> dex
- dex -> smali
- 处理 smali,主要是处理 super 方法和处理混淆关系
- smali -> dex
- dex -> jar
1、2 比较好懂就不逐步分析了,主要看3;后面的5、6、7、8、9 都是为了 7 中处理 smali,所以只要搞懂smali处理就好了。下面我们分步来看。
生成补丁
主要逻辑在 generatPatch
方法
|
|
分为两个部分
逐步翻译
首先调用InlineClassFactory.dealInLineClass(patchPath, Config.newlyAddedClassNameList)
识别被优化过的方法,这里的优化是泛指,包括被优化、内联、新增过的类和方法,具体的逻辑为扫描修改后的所有类和类中的方法,如果这些类和方法不在 mapping 文件中存在,那么可以定义为被优化过,其中包括@Add
新增的类或方法。
然后调用initSuperMethodInClass
方法识别修改后的所有类和类中的方法中,分析是否如包含 super
方法,如果有那么缓存下来。
然后调用 PatchesFactory.createPatch
反射翻译修改的类和方法,具体的实现在
|
|
这段代码其实是这个插件的核心部分,总体来说就是将修改后的代码全部翻译成反射调用生成 xxxPatch 类。
我们先只关注method.instrument()
这个方法,这个Javassist的API,作用是遍历方法中的代码逻辑,包括:
- FieldAccess,字段访问操作。分为字段读和写两种,分别调用
ReflectUtils.getFieldString
方法,将代码逻辑使用Javassist翻译成反射调用,然后替换。 - NewExpr,new 对象操作。也分为两种
- 非静态内部类,调用
ReflectUtils.getNewInnerClassString
翻译成反射,然后替换 - 外部类,调用
ReflectUtils.getCreateClassString
翻译成反射,然后替换
- 非静态内部类,调用
- Cast,强转操作。调用
ReflectUtils.getCastString
翻译成反射,然后替换 - MethodCall,方法调用操作。情况比较复杂,以下几种情形
- lamda表达式,调用
ReflectUtils.getNewInnerClassString
生成内部类的方法并翻译成反射,然后替换 - 修改的方法是内联方法,调用
ReflectUtils.getInLineMemberString
方法生成占位内联类xxInLinePatch
,并在改类中把修改的方法翻译成反射,然后替换调用,这方法中又有一些其他情况判断,感兴趣的读者可以自行阅读 - 如果是super方法,这个情况后面单独拎出来说
- 正常方法,调用
ReflectUtils.getMethodCallString
方法翻译成反射,然后替换
- lamda表达式,调用
- 生成补丁类并增加构造方法
请注意,以上所有方法和需要处理的方法都需要特别注意方法签名!
控制补丁行为
最后调用 createControlClass(patchPath, ctClass)
、createPatchesInfoClass(patchPath);
生成 PatchesInfoImpl、xxxPatchControl 写入补丁信息和控制补丁行为。
其中,PatchesInfoImpl
中包含所有补丁类的一一对应关系,比如 MainActivity -> MainActivityPatch
,不清楚的可以参考该系列的上一篇文章。生成的xxxPatchControl
类用于生成xxPatch
类,并判断补丁中的方法是否和methods.robust中的方法id匹配,如果匹配才会去调用补丁中方法。
至此整体流程基本梳理完成。后面会针对具体的复杂情况加以解析。
this 如何处理
首先,在补丁类中 xxPatch 中,this
指代的是xxPatch类的对象,而我们是想要的对象是被补丁的类的对象。
在PatchesFactory.createPatchClass()
方法中
|
|
这段的作用是,在每个xxPatch补丁类中都插入一个getRealParameter()
方法,反编译出来最终的结果:
|
|
它的作用是,在每个xxPatch中使用的this
,都转换成xx被补丁类的对象。其中的originClass
就是补丁类的对象。
super 如何处理
同this
类似,xxPatch中调用 super
方法同样需要转为调用被补丁类中相关方法的super
调用。
还是在PatchesFactory.createPatchClass()
方法中有dealWithSuperMethod(temPatchClass, modifiedClass, patchPath);
调用
|
|
保留主要代码,根据方法签名生成了一个新的方法,以staticRobust+methodName
命名,方法中调用以RobustAssist
结尾的类中的同名方法,并调用 PatchesAssistFactory.createAssistClass
方法生成该类,这个类的父类是被补丁类的父类。
|
|
然后在遍历MethodCall
过程中,处理方法调用
|
|
传递的参数,patch、originClass(被补丁类对象)、方法实际参数列表。
反编译出的结果实际是这样的:
|
|
参数是按照实际的方法参数传进去的,最后调用了xxPatch.superMethod方法。但是这样也并没有实现super
方法的转义啊,再往下看。
在处理smali过程中,有这么一段:
|
|
在处理之前,smali是长这样的:invoke-invoke-virtual {p0, p2}, Lcom/meituan/robust/patch/SecondActivityPatch;->onCreate(Landroid/os/Bundle;)V
,
处理之后是这样的:invoke-super {p1, p2}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V
,意思就是本来是调用正常方法,现在转为调用super方法,并且把参数换了一下,把p0(补丁类对象)换成了p1(被补丁类对象),这样就完成了super
的处理。反编译后最终结果:
|
|
内联怎么处理
内联是个广义的概念,包括了混淆过程中的优化(修改方法签名、删除方法等)、内联。在上面的分析中处理zi方法基本也提到了,缺啥补啥:就是把内联掉的方法再补回来。
对于内联的方法,不能用@Modify
注解标注,只能使用RobustModify.modify()
标注,因为在基础包中方法都没了,打了l补丁方法也没用。
主要逻辑在遍历MethodCall
-> repalceInlineMethod()
-> ReflectUtils.getInLineMemberString()
|
|
作用就是把内联掉的方法调用替换为InLine类中的新增方法。
结果就是这样的:
总结
Robust 的核心其实就是自动化生成补丁这块,至于插庄、补丁加载这些都是很好实现的,因为没有很多的特殊情况需要处理。
这篇文章主要分析了自动化补丁插件的主要工作流程,和一些特殊情况的处理,文章有限,当然还有很多特殊情况没有分析,这里只是提供一些分析源码的思路,碰到特殊情况可以按照这个思路排查解决问题。
就像代码中有一行注释,我觉得特别能概括自动化生成补丁的团队的心里路程,在此也再次感谢美团团队对开源做出的贡献。
总体来说,Robust 坑是有的,但是它也是高可用性、高兼容性的热修复框架,尤其是在Android 系统开放性越来越收紧的趋势下,Robust 作为不 hook 系统 API 的热修复框架优势更加突出。虽然其中可能有一些坑,只要我们对原理熟悉掌握,才有信心能搞定这些问题。
下一篇文章,主要就讲下Robust与其他框架搭配使用出现的一些坑。
参考
本文链接: http://w4lle.com/2018/05/28/robust-1/
版权声明:本文为 w4lle 原创文章,可以随意转载,但必须在明确位置注明出处!
本文链接: http://w4lle.com/2018/05/28/robust-1/