假如我们现在有这样一个文件,代码如下:
public class Hello {
public String name = "hello";
public static void main(String[] args){
System.out.println("Hello, world");
}
}
我们想要知道它的哪些指令被执行了,因此我们需要对其编译过后的class文件中的指令进行插桩。下面讲解具体做法。
首先,我们需要读取要被插桩的文件:
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("Hello.class");
ClassWriter cw = new ClassWriter(0);
ClassReader reader = new ClassReader(is);
reader.accept(new ClassAdapter(ASM9, cw), 0);
byte[] code = cw.toByteArray();
同时,我们为了能够插桩还需要重写ClassVisitor和MethodVisitor的方法。
为了能够对class文件插桩,我们需要访问class文件的method部分,因此我们需要重写methodVisitor的方法。为此我们使用一个名为ClassAdapter的类继承ClassVisitor,代码如下:
class ClassAdapter extends ClassVisitor implements Opcodes {
public ClassAdapter(int api, ClassVisitor cv) {
super(api, cv);
}
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
return new MethodAdaptor(this.api, mv);
}
}
因为我们是要在每一条指令执行之前插桩,所以我们还需要重写visitXXXInsn,所以我们还需要新建一个继承于MethodVisitor的类。假如我们需要对所有zero operand类型的指令进行插桩,那么MethodVisitor的写法如下:
class MethodAdaptor extends MethodVisitor implements Opcodes {
public MethodAdaptor(int api, MethodVisitor mv) {
super(api, mv);
}
@Override
public void visitInsn(int opcode) {
mv.visitIntInsn(SIPUSH, opcode);
mv.visitMethodInsn(INVOKESTATIC, "insert", "beforeInvoke", "(I)V", false);
super.visitInsn(opcode);
}
}
其它类型指令的插桩方法大同小异。
其中:
mv.visitIntInsn(SIPUSH, opcode);
mv.visitMethodInsn(INVOKESTATIC, "insert", "beforeInvoke", "(I)V", false);
这段代码的作用是掉用另一个类的静态方法来打印即将被执行的指令对应的opcode,另一个类的写法如下:
public class insert {
public static void beforeInvoke(int opcode) {
System.out.println("before "+opcode);
};
}