修改现有方法(优化-删除-复杂变换)
┌─── stateless transformations │ │ │ ┌─── a finite number of states ASM Transformation ───┤ │ │ ┌─── state machine ───┼─── current state │ │ │ │ │ └─── state transitions └─── stateful transformations ────┤ │ ┌─── iconst_0 iadd │ │ └─── examples ────────┼─── aload_0 aload_0 getfield putfield │ └─── getstatic ldc invokevirtual
1. 复杂的变换
1.1. stateless transformations
The stateless transformation does not depend on the instructions that have been visited before the current one.
举几个关于stateless transformation的例子:
- 添加说明:打印方法的参数- - 以及返回值和计算方法的运行时间。
- 删除说明:删除NOP、清空方法体。
- 修改说明:替换调用方法。
这种stateless transformation很容易实现,所以也叫simple transformations。
1.2. stateful transformations
The stateful transformation require memorizing some state about the instructions that have been visited before the current one. This requires storing state inside the method adapter.
举几个关于stateful transformation的例子:
- 删除指令:移除ICONST_0 IADD。例如,int d = c 0;与int d = c;两者的效果是一样的,所以 可删除0部分。
- 删除说明:删除ALOAD_0 ALOAD_0 GETFIELD PUTFIELD。例如,this.val = this.val;,赋值字段本身没有实质意义。
- 删除说明:删除GETSTATIC LDC INVOKEVIRTUAL。例如,System.out.println(“Hello World),删除打印信息。
这种stateful transformation很难实现,所以也叫complex transformations。
那为什么呢?stateless transformation很容易实现,但是stateful transformation会更难实现吗?做个类比,stateless transformation类似于一人吃饱,全家不饿,不用想太多,所以实现起来比较简单;而且stateful transformation类似于结婚后,要考虑一家人的生活状况,要多考虑一点,所以很难实现。难归难,但还是要想办法实现。
那么,stateful transformation如何开始实现?stateful transformation在这个过程中,通常涉及多个指令(Instruction)同时,判断这些指令是一个组合,不能轻易。我们通过三个步骤来实现它:
- 第一步是将问题本身转化为Instruction然后总结多个指令组合的特征或遵循的模式。
- 第二步是用这个总结的特征或模式来识别指令。在识别过程中,每一个Instruction加入会导致原状态(state)变化对应stateful的部分;
- 第三步是识别成功后Class文件进行转换,这就对应着transformation部分。谈到transformation,无非就是对Instruction添加、删除和修改内容。
在这里,有一个新的问题:如何记录第二步的状态(state)变化呢?我们的回答是,在帮助下state machine。
1.3. state machine
首先,让我们回答一个问题:什么是state machine?
A state machine is a behavior model. It consists of a finite number of states and is therefore also called finite-state machine (FSM). Based on the current state and a given input the machine performs state transitions and produces outputs.
对于state machine,我想到了这句话:我的生命是有限的,我的知识是无限的。有限的生命追求无限的知识,势必身无神伤。state machine聪明就是把无限的操作步骤限制在有限的状态下思考。
接下来,给出一个具体的state machine。也就是说,下面的MethodPatternAdapter类,是原始的state machine,我们从三个层面把握它:
- 第一层,class info。MethodPatternAdapter类,继承自MethodVisitor类,本身也是抽象类。
- 第二层,fields。MethodPatternAdapter类,定义了两个字段,其中SEEN_NOTHING字段是常量值,表示初始状态state字段用于记录不断变化的状态。
- 第三层,methods。MethodPatternAdapter类定义visitXxxInsn()方法会调用自定义visitInsn()方法。visitInsn()方法是一种抽象方法,其作用是使所有其他状态(state)都回到了初始状态。
那该怎么用呢?MethodPatternAdapter类别呢?我们只是写一个。MethodPatternAdapter这个子类是一个更先进的子类state machine,做以下三件事:
- 第一件事情,从字段层面,根据处理的问题,来定义更多的状态;也就是,类似于SEEN_NOTHING字段。这是对的a finite number of states进行定义。
- 第二件事,从方法层面,处理好visitXxxInsn()调用,对state字段状态的影响。也就是说,输入新的指令(Instruction),都会对state影响字段。这是构建状态。(state)变化机制。
- 第三件事,从方法层面实现visitInsn()方法,依据state如何将字段值回到初始状态。这里增加了恢复出厂设置的功能,使状态(state)归零,回到初始状态。让状态(state)归零是一种状态(state)变化机制是一个特殊的环节。结合生活,生活中有不好的地方,从新开始从零开始。
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
public abstract class MethodPatternAdapter extends MethodVisitor {
protected final static int SEEN_NOTHING = 0;
protected int state;
public MethodPatternAdapter(int api, MethodVisitor methodVisitor) {
super(api, methodVisitor);
}
@Override
public void visitInsn(int opcode) {
visitInsn();
super.visitInsn(opcode);
}
@Override
public void visitIntInsn(int opcode, int operand) {
visitInsn();
super.visitIntInsn(opcode, operand);
}
@Override
public void visitVarInsn(int opcode, int var) {
visitInsn();
super.visitVarInsn(opcode, var);
}
@Override
public void visitTypeInsn(int opcode, String type) {
visitInsn();
super.visitTypeInsn(opcode, type);
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
visitInsn();
super.visitFieldInsn(opcode, owner, name, descriptor);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor) {
visitInsn();
super.visitMethodInsn(opcode, owner, name, descriptor);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
visitInsn();
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
@Override
public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
visitInsn();
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
}
@Override
public void visitJumpInsn(int opcode, Label label) {
visitInsn();
super.visitJumpInsn(opcode, label);
}
@Override
public void visitLdcInsn(Object value) {
visitInsn();
super.visitLdcInsn(value);
}
@Override
public void visitIincInsn(int var, int increment) {
visitInsn();
super.visitIincInsn(var, increment);
}
@Override
public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
visitInsn();
super.visitTableSwitchInsn(min, max, dflt, labels);
}
@Override
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
visitInsn();
super.visitLookupSwitchInsn(dflt, keys, labels);
}
@Override
public void visitMultiANewArrayInsn(String descriptor, int numDimensions) {
visitInsn();
super.visitMultiANewArrayInsn(descriptor, numDimensions);
}
@Override
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
visitInsn();
super.visitTryCatchBlock(start, end, handler, type);
}
@Override
public void visitLabel(Label label) {
visitInsn();
super.visitLabel(label);
}
@Override
public void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) {
visitInsn();
super.visitFrame(type, numLocal, local, numStack, stack);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
visitInsn();
super.visitMaxs(maxStack, maxLocals);
}
protected abstract void visitInsn();
}
2. 示例一:加零
2.1. 预期目标
假如有一个HelloWorld类,代码如下:
public class HelloWorld {
public void test(int a, int b) {
int c = a + b;
int d = c + 0;
System.out.println(d);
}
}
我们想要实现的预期目标:将int d = c + 0;转换成int d = c;。
$ javap -c sample.HelloWorld
Compiled from "HelloWorld.java"
public class sample.HelloWorld {
...
public void test(int, int);
Code:
0: iload_1
1: iload_2
2: iadd
3: istore_3
4: iload_3
5: iconst_0
6: iadd
7: istore 4
9: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
12: iload 4
14: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
17: return
}
2.2. 编码实现
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import static org.objectweb.asm.Opcodes.*;
public class MethodRemoveAddZeroVisitor extends ClassVisitor {
public MethodRemoveAddZeroVisitor(int api, ClassVisitor classVisitor) {
super(api, classVisitor);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
if (mv != null && !"<init>".equals(name) && !"<clinit>".equals(name)) {
boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
boolean isNativeMethod = (access & ACC_NATIVE) != 0;
if (!isAbstractMethod && !isNativeMethod) {
mv = new MethodRemoveAddZeroAdapter(api, mv);
}
}
return mv;
}
private class MethodRemoveAddZeroAdapter extends MethodPatternAdapter {
private static final int SEEN_ICONST_0 = 1;
public MethodRemoveAddZeroAdapter(int api, MethodVisitor methodVisitor) {
super(api, methodVisitor);
}
@Override
public void visitInsn(int opcode) {
// 第一,对于感兴趣的状态进行处理
switch (state) {
case SEEN_NOTHING:
if (opcode == ICONST_0) {
state = SEEN_ICONST_0;
return;
}
break;
case SEEN_ICONST_0:
if (opcode == IADD) {
state = SEEN_NOTHING;
return;
}
else if (opcode == ICONST_0) {
mv.visitInsn(ICONST_0);
return;
}
break;
}
// 第二,对于不感兴趣的状态,交给父类进行处理
super.visitInsn(opcode);
}
@Override
protected void visitInsn() {
if (state == SEEN_ICONST_0) {
mv.visitInsn(ICONST_0);
}
state = SEEN_NOTHING;
}
}
}
2.3. 进行转换
import lsieun.utils.FileUtils;
import org.objectweb.asm.*;
public class HelloWorldTransformCore {
public static void main(String[] args) {
String relative_path = "sample/HelloWorld.class";
String filepath = FileUtils.getFilePath(relative_path);
byte[] bytes1 = FileUtils.readBytes(filepath);
//(1)构建ClassReader
ClassReader cr = new ClassReader(bytes1);
//(2)构建ClassWriter
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
//(3)串连ClassVisitor
int api = Opcodes.ASM9;
ClassVisitor cv = new MethodRemoveAddZeroVisitor(api, cw);
//(4)结合ClassReader和ClassVisitor
int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
cr.accept(cv, parsingOptions);
//(5)生成byte[]
byte[] bytes2 = cw.toByteArray();
FileUtils.writeBytes(filepath, bytes2);
}
}
2.4. 验证结果
$ javap -c sample.HelloWorld
public class sample.HelloWorld {
...
public void test(int, int);
Code:
0: iload_1
1: iload_2
2: iadd
3: istore_3
4: iload_3
5: istore 4
7: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
10: iload 4
12: invokevirtual #22 // Method java/io/PrintStream.println:(I)V
15: return
}
3. 示例二:字段赋值
3.1. 预期目标
假如有一个HelloWorld类,代码如下:
public class HelloWorld {
public int val;
public void test(int a, int b) {
int c = a + b;
this.val = this.val;
System.out.println(c);
}
}
我们想要实现的预期目标:删除掉this.val = this.val;语句。
$ javap -c sample.HelloWorld
Compiled from "HelloWorld.java"
public class sample.HelloWorld {
public int val;
...
public void test(int, int);
Code:
0: iload_1
1: iload_2
2: iadd
3: istore_3
4: aload_0
5: aload_0
6: getfield #2 // Field val:I
9: putfield #2 // Field val:I
12: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
15: iload_3
16: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
19: return
}
3.2. 编码实现
import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import static org.objectweb.asm.Opcodes.*; public class MethodRemoveGetFieldPutFieldVisitor extends ClassVisitor { public MethodRemoveGetFieldPutFieldVisitor(int api, ClassVisitor classVisitor) { super(api, classVisitor); } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions); if (mv != null && !"<init>".equals(name) && !"<clinit>".equals(name)) { boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0; boolean isNativeMethod = (access & ACC_NATIVE) != 0; if (!isAbstractMethod && !isNativeMethod) {