前言
同事发现了一个crash是来源于公司内某个闭源jar的,而负责此Jar开发的同事已经离职,难以找人修复,于是就想找我帮忙弄下。
想修改class文件,方式很多,如Javassist、ASM等
- Javassist的修改是基于源代码,因此更容易上手,可参考修改jar的.class文件,并重新打包
- ASM则是直接基于java bytecode,复杂性相对高些,但几乎无所不能。若能理解ASM的设计的话,使用也并不复杂,不懂的可参考ASM库的介绍
要实现的功能
把uilib.doraemon.model.layer.CompositionLayer类的drawLayer方法中的代码
1 | canvas.clipRect(this.originalClipRect, Op.REPLACE); |
改为
1 | if (VERSION.SDK_INT >= 26) { |
思路
1、加载Jar包,并转换成ClassNode对象列表
2、找到uilib.doraemon.model.layer.CompositionLayer类对应的ClassNode,并遍历其所有方法
3、找到drawLayer,并定位到要替换的代码
4、对比替换前后的代码的ASM实现,并替换
5、用新的ClassNode生成新的jar包
新建Java工程,并导入ASM库
这边用Android Studio直接开发
1、新建一个Java Library
2、导入ASM库,可到官网下载,也可在这直接下载asm lib
3、新建main入口类
1 | package com.noverguo.asm; |
加载Jar包并解析成List
这里用到我以前写的一个JarLoader工具类
1 | List<ClassNode> classNodes = JarLoader.loadJar(inJarPath); |
定位对应的类及其方法,并保存更改后的类和方法
1 | List<ClassNode> changeNodes = new ArrayList<>(); |
定位要替换的代码
ASM操作的是字节码bytecode,因此先安装ASM bytecode online插件
在AndroidStudio中进入要修改的Jar包里的类,会自动反编译,如果没有反编译成功,可尝试安装jd-inteIIij插件
要修改的方法的实现如下:
1 | void drawLayer(Canvas canvas, Matrix parentMatrix, int parentAlpha) { |
在此类中右键-点击【Show Bytecode outline】
定位到此方法对应的ASM实现,如下:
1 | { |
也就是说,最终要替换的代码是这个
1 | Label l15 = new Label(); |
这里我们看到一个可以非常准确定位的点:
1 | mv.visitLineNumber(100, l15); |
这行的意思是接下来的代码在源码中的行号为100,主要用于调试定位
实现定位代码,并过滤掉旧的指令
这里使用装饰器模式的实现方式,先new一个新的MethodNode,然后通过MethodVisitor拦截visitLineNumber,并标记为found。方便后续过滤掉不要的指令
这里以visitInsn作为过滤的出口,对应的是mv.visitInsn(Opcodes.POP);
这样旧的实现代码就会被清掉
1 | MethodNode newMethodNode = new MethodNode(mn.access, mn.name, mn.desc, mn.signature, mn.exceptions.toArray(new String[0])); |
新代码ASM实现
新代码的Java实现如下:
1 | if (VERSION.SDK_INT >= 26) { |
这里有个问题是,如何得到其对应的ASM实现?
我们可以把反编译出来的代码,复制一份到新的类中,如uilib.doraemon.model.layer.CompositionLayer2
由于此类用了很多安卓的类。因此建议直接在安卓项目中新建此类。当然也可人为导入android.jar到java项目中。
之后在CompositionLayer2类中右键-点击【Show Bytecode outline】,即可找到新的实现
1 | mv.visitLabel(l6); |
现在,我们更改下visitLineNumber的实现,在找到后插入新的代码
1 | @Override |
上面会显示l14找不到,因为这里的代码没定义,而l14实际上是在mv.visitInsn(Opcodes.POP);之后的旧代码里访问的,因此我们比较难拿到。
这里可以定义个新的Label解决
1 | Label newLabel = new Label() |
保存新的ClassNode到Jar中
1 | JarLoader.saveToJar(inJarPath, outJarPath, changeNodes); |