Overview
ASM是一个用来读写,转化,分析Java class的库,它的优势在于
1. API使用简单
2. 文档详细,有很大的用户群体,开源
3. 支持最新Java
4. 整个库小而快
ASM有两种API: 1. event based core API 2. object base tree API
为什么ASM要使用Java class而不是Java源码?
1. 能够在运行时动态读写转化分析java
2. 可以不必处理stub compiler和aspect weavers
NOTE
1. 无法改变final int这种compiled constant,或者说,也可以改变,但是不能保证其行为。
Core API
Classes
编译之后的class的结构十分简单,甚至还能保持源码的结构和大部分基本符号,一般来说,一个编译好的class包含以下部分:
1. modifiers(例如public, private), class name, super class, interfaces, annotation of the class
2. 该class中声明的所有变量域:
a) 每个域都有对应的modifiers, name, type, annotations
3. 该class中的所有成员方法, constructor, deconstructor。
a) modifiers, name, return type, parameter type, annotation
b) compiled code(java bytecode)
source class 和compiled class之间的异同
1. source class可以嵌套inner class,但是compiled class一定是一个文件描述一个class,inner class中会有嵌套这个inner类的方法的引用,而main class会有对这个inner class的引用。
2. compiled class不包含注释
3. compiled class不包含import和package section
4. compiled class包含一个constant pool section,里面有所有在这个类内声明的常量
type descriptor
这里编译后的文件中type(interface或者class)采用internal name方式标识,比如String的internal name是java/lang/String。
只有在类名和接口名处能够使用internal name,在域的类型名上,或者在其他情况下,java types使用的是type descriptors,例如,String的type descriptor是Ljava/lang/String;(后面接了一个分号)。
Method descriptors
Interfaces and componenets
生成和转化compiled classes的ASM API是基于class Visitor抽象类的。
Parser示例如下:
package com.thufuzz; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; public class ClassPrinter extends ClassVisitor { public ClassPrinter() { super(org.objectweb.asm.Opcodes.ASM7); } public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { System.out.println(name + " extends " + superName + " {"); } public void visitSource(String source, String debug) { } public void visitOuterClass(String owner, String name, String desc) { } public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return null; } public void visitAttribute(org.objectweb.asm.Attribute attr) { } public void visitInnerClass(String name, String outerName, String innerName, int access) { } public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { System.out.println(" " + desc + " " + name); return null; } public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { System.out.println(" " + name + desc); return null; } public void visitEnd() { System.out.println("}"); } }
结果示例:
org/apache/iotdb/db/writelog/recover/TsFileRecoverPerformer extends java/lang/Object { Lorg/slf4j/Logger; logger Ljava/lang/String; filePath Ljava/lang/String; logNodePrefix Lorg/apache/iotdb/db/engine/version/VersionController; versionController Lorg/apache/iotdb/db/engine/storagegroup/TsFileResource; resource Z acceptUnseq Z isLastFile <init>(Ljava/lang/String;Lorg/apache/iotdb/db/engine/version/VersionController;Lorg/apache/iotdb/db/engine/storagegroup/TsFileResource;ZZ)V recover()Lorg/apache/iotdb/tsfile/write/writer/RestorableTsFileIOWriter; recoverResourceFromFile()V recoverResourceFromReader()V recoverResourceFromWriter(Lorg/apache/iotdb/tsfile/write/writer/RestorableTsFileIOWriter;)V redoLogs(Lorg/apache/iotdb/tsfile/write/writer/RestorableTsFileIOWriter;)V <clinit>()V }
ClassWriter cw = new ClassWriter(0); cw.visit(org.objectweb.asm.Opcodes.V14, org.objectweb.asm.Opcodes.ACC_PUBLIC + org.objectweb.asm.Opcodes.ACC_ABSTRACT + org.objectweb.asm.Opcodes.ACC_INTERFACE, "com.thufuzz/Comparable", null, "java/lang/Object", new String[0] ); cw.visitField(org.objectweb.asm.Opcodes.ACC_PUBLIC + org.objectweb.asm.Opcodes.ACC_FINAL + org.objectweb.asm.Opcodes.ACC_STATIC, "LESS", "I", null, -1).visitEnd(); cw.visitField(org.objectweb.asm.Opcodes.ACC_PUBLIC + org.objectweb.asm.Opcodes.ACC_FINAL + org.objectweb.asm.Opcodes.ACC_STATIC, "EQUAL", "I", null, 0).visitEnd(); cw.visitField(org.objectweb.asm.Opcodes.ACC_PUBLIC + org.objectweb.asm.Opcodes.ACC_FINAL + org.objectweb.asm.Opcodes.ACC_STATIC, "GREATER", "I", null, 1).visitEnd(); cw.visitMethod(org.objectweb.asm.Opcodes.ACC_PUBLIC + org.objectweb.asm.Opcodes.ACC_ABSTRACT, "compareTo", "(Ljava/lang/Object;)I", null, null).visitEnd(); cw.visitEnd(); byte[] b = cw.toByteArray(); MyJarFileUtils.addFileToCache("com/thufuzz/Comparable.class", tmpDir, b);
结果相当于如下代码编译后的文件:
package com.thufuzz; public interface Comparable { int LESS = -1; int EQUAL = 0; int GREATER = 1; int compareTo(Object o); }
这些字节码可以通过ClassLoader的方式直接转化为有潜力执行的Class。
class MyClassLoader extends ClassLoader { public Class defineClass(String name, byte[] b) { return defineClass(name, b, 0, b.length); } } Class c = myClassLoader.defineClass("pkg.Comparable", b);
class StubClassLoader extends ClassLoader { @Override protected Class findClass(String name) throws ClassNotFoundException { if (name.endsWith("_Stub")) { ClassWriter cw = new ClassWriter(0); ... byte[] b = cw.toByteArray(); return defineClass(name, b, 0, b.length); } return super.findClass(name); } }
Transforming classes
基础pipeline:
对于返回void方法,如果不传递visitXXX调用,后方handler就无法看到对应信息.对于非void,如果返回null,后方handler就无法看到对应信息
Methods
3.1 Structure
Java在多个线程内执行,每个线程都有各自的执行栈execution stack,由frames组成。每个frame标识一次方法调用。每个frame由两部分组成: 1. local variables 2. operand stack。每个frame都有一个独立的operand stack。
一个bytecode instruction由opcode(每个opcode都和一个mnemonic symbol结合在一起)和对应参数组成。参数就在opcode后面紧跟。bytecode instruction可以分成两类:1. 将值从本地转移到operand stack,或者相反 2. 只在operand stack上进行操作,比如加减
ILOAD, LLOAD, FLOAD, DLOAD, ALOAD这些指令都是从本地读一个变量再推进operand stack。它们的参数是local variables的序号i。这其中ILOAD读入一个boolean, byte, char, short或者int。LLOAD, FLOAD和DLOAD则是分别用来读long, float或者double的。LLOAD和DLOAD都是读两个slots。ALOAD用来读任何非原始类型,比如object或者array。对应地,ISTORE,LSTORE,FSTORE,DSTORE, ASTORE操作都pop一个value出来。
ISTORE 1 ALOAD 1 这种操作不被允许,因为如果允许,就能将任何内存地址存储在index为1的本地变量中。然而在一个local variable中存一个与当前本地变量值的类型不同的新值则是完全合法的。这就是说local variable的类型 能够在执行中动态改变。
opcode能够粗略地被分为以下几类:
a) Stack pop:出栈 dup: 复制栈顶 swap: pop两个,然后再按照相反的顺序push进去
b) Constants: 把常量推进operand stack。ACONST_NULL: push null;ICONST_0: push int 0; FCONST_0, DCONST_0, BIPUSH, SIPUSH类似。LDC cst: 推入一个int, float, long, double, string或者class常量cst。
c) Arithmetic & Logic: 没有操作数,直接从operand stack中pop一些值再进行操作。如xAdd, xSub, xMul, xDiv, xRem分别对应加减乘除余操作。x可以是ILFD。类似的,还有<<, >>, >>>(无符号右移,例如15>>>2==3), | & 和^。
d) Casts: 先把一定操作数从stack中pop出来,转化为另一个type再push进去。I2F, F2D, L2D。CheckCast t: 将ref value转换为类型t。
e)Objects: 用于对object做create, lock, test type等操作的。
f)Feilds: 读或者写单个filed。GETField owner name desc: 从owner中取出名为name的类型为desc的域。PutField则是修改。GetStatic和PutStatic则是做静态的。
g) Methods: invoke a method or a constructor。最后pop多个arg,push returnvalue。InvokeVirtual owner name desc 为invoke名为name的method(在类owner中定义, 且method descriptor是desc)。InvokeStatic则是对static method的,InvokeSpecial是对private的方法和构造函数。InvokeInterface是用来调用被定义在interface中的方法。InvokeDynamic是用来调用动态方法。
h)Arrays: 用来读写数组中数值的指令。例如xALOAD和xASTORE。这里x可能是I,L,F,D,A,B,C,S。
i)Jumps:: 在满足一定条件下,能够跳转到任意指令。这些指令用来编译if, for, do, while, break和continue。例如: IFEQ label从stack中pop一个int值,如果这个值是0,然后跳到label所对应的指令。还有IFNE或者IFGE这种类似的指令。此外TableSwitch和LookupSwitch-则对应switch指令
j)Return: xReturn和Return用来停止函数执行并且返回。Return是返回void。
例子: 对于如下代码:
package pkg1; public class Bean{ private int f; public int getF(){ return this.f; } public void setF(int f){ this.f = f; } }
其中的getter方法的字节码对应的助记指令序列是:
ALOAD 0//读第0个本地变量,也就是this
GETFIELD pkg/Bean f I//读刚才load进去的this的f这个域,这个域是属于pkg/Bean这个类的,而且是个int。读到之后,放进stack
IRETURN//return并且返回stack中的int
其中的setter方法的指令序列则是:
ALOAD 0
ILOAD 1
PUTFIELD pkg/Bean f I//pop两个值-this引用和常数1,不再push
RETURN
其中default public constructor,即Bean(){super();}的指令序列:
ALOAD 0
InvokeSpecial java/lang/Object <init> ()V
RETURN
对一个稍显复杂的setter:
public void checkAndSetF(int f){ if(f>=0){ this.f = f; } else{ throw new IllegalArgumentException(); } }
对应的byte code:
ILOAD 1
IFLT label//pop一个,如果小于0,跳转到label
ALOAD 0
ILOAD 1
PUTFIELD pkg/Bean f I
GOTO end
label:
NEW java/lang/IllegalArgumentException
DUP//2个exception ref
InvokeSpecial java/lang/IllegalArgumentException <init> ()V//pop第一个ref,在第一个ref上做初始化
ATHROW//throw 第二个ref
end:
RETURN
对于带catch的代码,例如:
public static void sleep(long d){ try{ Thread.sleep(d); }catch(InterruptedException e){ e.printStackTrace(); } }
对应的指令序列是:
TRYCATCHBLOCK try catch catch java/lang/InterruptedException
//exception handler处理从try这个标签开始到catch这个标签为止的代码。handler本体从catch这个标签开始。处理的是InterruptedException或者子类
//如果try到catch之间有异常抛出,就把stack清理干净,然后把这个exception放入stack中,从handler对应的catch开始运行
try:
LLOAD 0
InvokeStatic java/lang/Thread (J)V
Return
catch:
InvokeVirtual java/lang/InterruptedException printStackTrace ()V
Return
3.1.5 Frames
Java6以上的变异后的代码都会有stack map frames用来加速JVM中的class verification。stack map frame中有一个method的execution frame在一些时刻的状态,或者说,它包含的是对应的值在不同的local variable slot或者operand stack slot里面的类型。
以getF为例,指令序列为:(格式 序列 //[local variable slots][operand stack slot])
//[pkg/Bean][]
ALOAD 0;
//[pkg/Bean][pkg/Bean]
GETFIELD;
//[pkg/Bean][I]
IRETURN;
再以checkAndSetF为例:
[pkg/Bean I][]
ILOAD 1
[pkg/Bean I][I]
IFLT label
[pkg/Bean I][]
ALOAD 0
[pkg/Bean I][pkg/Bean]
ILOAD 1
[pkg/Bean I][pkg/Bean I]
PUTFIELD pkg/Bean f I
[pkg/Bean I][]
GOTO end
label:
[pkg/Bean I][]
NEW java/lang/IllegalArgumentException
[pkg/Bean I][Uninitialized(label)]
DUP
[pkg/Bean I][Uninitialized(label) Uninitialized(label)]
InvokeSpecial java/lang/IllegalArgumentException <init> ()V
[pkg/Bean I][java/lang/IllegalArgumentException]
ATHROW
[][java/lang/IllegalArgumentException]//或者是[pkg/Bean] [java/lang/IllegalArgumentException]
end:
[pkg/Bean I][]
RETURN
这里Uninitialized(label)]是只用在stack map frames中的special type,此时内存已经分配,但是还没有调用构造函数。该类型只能调用构造函数。当它被调用之后,这个stack map frame中是该类型的所有occurrences都会被真实的类型替换。除了Uninitalized,还有Uninitalized_this用来作为构造函数中的local variable 0的初始类型,TOP用来指代undefined value,还有NULL指代null。
为了节省空间,只有用来jump target,exception handler的指令前或者无条件跳转的指令后面会有stack map frame。其他frame可以直接按这些信息推出来。(这些打乱了从上到下扫描的顺序)
这里stack map frames记录的都是指令执行前的状态,例如无条件跳转的指令后面,在checkAndSetF中,比如ATHROW; RETURN;这里,就需要记录RETURN执行前的stack map frame。例如end: RETURN; 因为有GOTO end;所以要记录作为jump target的end:后面的RETURN这条指令在执行前的stack map frame。
为了进一步压缩空间,实际上记录的也不是stack map frames本身,而是只记initial frame和接下来每个frame之间的不同。
3.2 对应接口
ASM API主要基于MethodVisitor抽象类,而这个抽象类又是被ClassVisitor的visitMethod调用。
具体调用顺序为:
这里:
1. annotations和attributes如果存在就一定要先visit
2. 源码按顺序来读,需要先调用一个VisitCode,再调用VisitMaxs,对应一个bytecode指令的开始和结束。
3. 不需要把一个method完全读完,也可以进入下一个method读-
在ClassWriter初始化的时候有选项,决定ASM是否计算local variable的大小、operand stack parts大小或者stack map,
new ClassWriter(0)不自动计算,用户手动计算。new ClassWriter(ClassWriter.COMPUTE_MAXS)计算local variables/operand stack parts大小。new ClassWriter(ClassWriter.COMPUTE_FRAMES)以此类推。不过如果让ASM自动计算,那么用户仍然需要调用对应方法,可是对应方法的参数会被自动忽视然后重新计算。
Note:
有时为了自动计算frames是需要了解两个给定的类的公共祖先类的,这个时候需要用getCommonSuperClass方法,这个方法会将对应的两个类放进JVM并使用reflection API。这个问题在用户需要写多个相互引用的类的时候尤为麻烦,因为此时被引用的类还没有被写出来。此时,用户需要自己重载getCommonSuperClass方法。
e.g: 试着使用ASM API向类中添加一个getF函数(这里为了方便,返回Comparable.LESS这样一个静态变量)
package com.iotfuzz.asms; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class ClassTransformer extends ClassVisitor { public ClassTransformer(ClassVisitor cv) { super(Opcodes.ASM9, cv); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { System.out.println(" " + name + desc); MethodVisitor mv; mv = cv.visitMethod(access, name, desc, signature, exceptions); if (mv != null) { mv = new MethodTransformer(mv); } return mv; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { System.out.println(name + " extends " + superName); cv.visit(version, access, name, signature, superName, interfaces); var mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "getF", "()I", null, null); mv.visitCode(); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitFieldInsn(Opcodes.GETSTATIC, "com/thufuzz/Comparable", "LESS", "I"); mv.visitInsn(Opcodes.IRETURN); mv.visitMaxs(1, 0); mv.visitEnd(); } }
试着用javap查看jar包,得到:
JAR=simpleEg-1.0-SNAPSHOT_iofuzzed.jar && javap --class-path $JAR $(jar -tf $JAR | grep "class$" | sed s/.class$//) Compiled from "App.java" public class com.thufuzz.App { public int getF(); public com.thufuzz.App(); public static void main(java.lang.String[]); } Compiled from "Comparable.java" public interface com.thufuzz.Comparable { public static final int LESS; public static final int EQUAL; public static final int GREATER; public default int getF(); public abstract int compareTo(java.lang.Object); }
e.g2: 试着用ASM删除方法中的NOP。
public class RemoveNopClassAdapter extends ClassVisitor { public RemoveNopClassAdapter(ClassVisitor cv) { super(ASM4, cv); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv; mv = cv.visitMethod(access, name, desc, signature, exceptions); if (mv != null) { mv = new RemoveNopAdapter(mv); } return mv; } }
e.g3 Stateless transformations
目标: 为每个class加一个static timer field,然后对每个方法计时。
可行方法: 写一个示例的class,加上对应逻辑,然后把相关代码打印出来看看。
打印的一种可行方法:
package com.iotfuzz.asms; import java.io.PrintWriter; import java.io.StringWriter; import org.objectweb.asm.ClassReader; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.util.Printer; import org.objectweb.asm.util.Textifier; import org.objectweb.asm.util.TraceMethodVisitor; public class ASMUtils { public static void viewByteCode(byte[] bytecode) { ClassReader cr = new ClassReader(bytecode); ClassNode cn = new ClassNode(); cr.accept(cn, 0); var mns = cn.methods; Printer printer = new Textifier(); TraceMethodVisitor mp = new TraceMethodVisitor(printer); for (MethodNode mn : mns) { InsnList inList = mn.instructions; System.out.println(mn.name); for (int i = 0; i < inList.size(); i++) { inList.get(i).accept(mp); StringWriter sw = new StringWriter(); printer.print(new PrintWriter(sw)); printer.getText().clear(); System.out.print(sw.toString()); } } } }
这个例子的逻辑可以是:
public class C { public static long timer; public void m() throws Exception { timer -= System.currentTimeMillis(); //do sth timer += System.currentTimeMillis(); } }
例如:
package com.thufuzz; /** * Hello world! * */ public class App { private static long timer; public static void main( String[] args ) { timer -= System.currentTimeMillis(); System.out.println( String.format("Hello World!%d", Comparable.EQUAL) ); timer += System.currentTimeMillis();
System.out.println(timer); } }
用viewByteCode得到:
<init> L0 LINENUMBER 7 L0 ALOAD 0 INVOKESPECIAL java/lang/Object.<init> ()V RETURN L1 main L2 LINENUMBER 12 L2 GETSTATIC com/thufuzz/App.timer : J INVOKESTATIC java/lang/System.currentTimeMillis ()J LSUB PUTSTATIC com/thufuzz/App.timer : J L3 LINENUMBER 13 L3 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; LDC "Hello World!%d" ICONST_1 ANEWARRAY java/lang/Object DUP ICONST_0 ICONST_0 INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; AASTORE INVOKESTATIC java/lang/String.format (Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V L4 LINENUMBER 14 L4 GETSTATIC com/thufuzz/App.timer : J INVOKESTATIC java/lang/System.currentTimeMillis ()J LADD PUTSTATIC com/thufuzz/App.timer : J L5 LINENUMBER 15 L5 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; GETSTATIC com/thufuzz/App.timer : J INVOKEVIRTUAL java/io/PrintStream.println (J)V L6 LINENUMBER 16 L6 RETURN L7
可见是需要在方法开始时加入:
GETSTATIC com/thufuzz/App.timer : J INVOKESTATIC java/lang/System.currentTimeMillis ()J LSUB PUTSTATIC com/thufuzz/App.timer : J
在return前加入:
GETSTATIC com/thufuzz/App.timer : J INVOKESTATIC java/lang/System.currentTimeMillis ()J LADD PUTSTATIC com/thufuzz/App.timer : J GETSTATIC java/lang/System.out : Ljava/io/PrintStream; GETSTATIC com/thufuzz/App.timer : J INVOKEVIRTUAL java/io/PrintStream.println (J)V
文档试着排除了interface类和<init>方法,所以ClassVisitor需要记录一些visit得到的消息,比如通过access&Opcodes.ACC_INTERFACE得到目前的类是不是interface。
package com.iotfuzz.asms; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class ClassTransformer extends ClassVisitor { String className; boolean isInterface; public ClassTransformer(ClassVisitor cv) { super(Opcodes.ASM9, cv); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { System.out.println("visitMethod:" + name + " " + desc); MethodVisitor mv; mv = cv.visitMethod(access, name, desc, signature, exceptions); if (mv != null && name != " <init>" && !isInterface) { mv = new AddTimerMethodTransformer(mv, className); } return mv; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.className = name; this.isInterface = (access & Opcodes.ACC_INTERFACE) != 0; cv.visit(version, access, name, signature, superName, interfaces); if(!isInterface) { FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "timer", "J", null, null); if(fv!=null)fv.visitEnd(); } } }
重点在methodvisitor:
package com.iotfuzz.asms; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.util.HashSet; import java.util.Random; import static org.objectweb.asm.Opcodes.*; public class AddTimerMethodTransformer extends org.objectweb.asm.MethodVisitor { private String ownerClassName; private static final Logger logger = LogManager.getLogger("MethodTransformer"); public AddTimerMethodTransformer(MethodVisitor mv, String ownerClassName) { super(Opcodes.ASM9, mv); this.ownerClassName = ownerClassName; } @Override public void visitCode() { mv.visitCode(); mv.visitFieldInsn(GETSTATIC, ownerClassName, "timer", "J"); mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false); mv.visitInsn(LSUB); mv.visitFieldInsn(PUTSTATIC, ownerClassName, "timer", "J"); } @Override public void visitInsn(int opcode) { if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) { mv.visitFieldInsn(GETSTATIC, ownerClassName, "timer", "J"); mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false); mv.visitInsn(LADD); mv.visitFieldInsn(PUTSTATIC, ownerClassName, "timer", "J"); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitFieldInsn(GETSTATIC, ownerClassName, "timer", "J"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V", false); } mv.visitInsn(opcode); } }
运行修改的jar包之后,输出结果:
Hello World!0
21
e.g4 statefull transformations
本节则要根据已经读到的code进行交互。这里做一个最简单的事情-删除掉+0这种指令,也就是说,当ICONST_0后面紧跟着IADD的时候,把这两个操作符都删除,这样就直接把上面一个放进栈的弹出栈。
我们这里改一下,改成要删除x *= 20这个,比较能看出区别。
package com.thufuzz; /** * Hello world! * */ public class App { public static void main( String[] args ) { int x = 10; x *= 20; System.out.println( String.format("Hello World!%d", x) ); } }
示例采用了在visitIntInsn和visitInsn中都插入一个释放函数visitInsn()为主要方法
package com.iotfuzz.asms; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class RemoveMULAdapter extends PatternMethodAdapter { private static int SEEN_BIPUSH20 = 1; public RemoveMULAdapter(MethodVisitor mv) { super(Opcodes.ASM9, mv); } @Override public void visitIntInsn(int opcode, int operand) { visitInsn(); if (opcode == Opcodes.BIPUSH && operand == 20) { state = SEEN_BIPUSH20; return; } mv.visitIntInsn(opcode, operand); } @Override public void visitInsn(int opcode) { if (state == SEEN_BIPUSH20) { if (opcode == Opcodes.IMUL) { state = SEEN_NOTHING; return; } } visitInsn(); mv.visitInsn(opcode); } @Override protected void visitInsn() { if (state == SEEN_BIPUSH20) { mv.visitIntInsn(Opcodes.BIPUSH, 20); } state = SEEN_NOTHING; } }
//这种拆分我不常用,所以记一下
package com.iotfuzz.asms; import org.objectweb.asm.MethodVisitor; public abstract class PatternMethodAdapter extends MethodVisitor { protected final static int SEEN_NOTHING = 0; protected int state; public PatternMethodAdapter(int api, MethodVisitor mv) { super(api, mv); } protected abstract void visitInsn(); }
package com.iotfuzz.asms; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class ClassTransformer extends ClassVisitor { String className; boolean isInterface; public ClassTransformer(ClassVisitor cv) { super(Opcodes.ASM9, cv); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { System.out.println("visitMethod:" + name + " " + desc); MethodVisitor mv; mv = cv.visitMethod(access, name, desc, signature, exceptions); if (mv != null && name != " <init>" && !isInterface) { mv = new RemoveMULAdapter(mv); } return mv; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.className = name; this.isInterface = (access & Opcodes.ACC_INTERFACE) != 0; cv.visit(version, access, name, signature, superName, interfaces); } }
PatternMethodAdaptor
至此,我们还需要注意以下两个问题:
1. 如果我们删除的恰好是the target of jump instruction?
2. 如果我们删除的指令恰好带着stack map frame?
文中给出了一种解决方案,那就是在visitLabel和visitFrame之前处理这些逻辑
public abstract class PatternMethodAdapter extends MethodVisitor { // dosth @Override public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { visitInsnLogic(); mv.visitFrame(type, nLocal, local, nStack, stack); } @Override public void visitLabel(Label label) { visitInsnLogic(); mv.visitLabel(label); } @Override public void visitMaxs(int maxStack, int maxLocals) { visitInsnLogic(); mv.visitMaxs(maxStack, maxLocals); } }
Tools
org.objectweb.asm.commons包中有一些预先定义好的adapters,能够协助我们来写adapter。
Type
对xLOAD, xADD或者xRETURN这类的转换操作数类型。Type.FLOAT_TYPE.getOpCode(IRETURN)就能返回FRETURN
TraceClassVisitor
能打印opcode等信息。
例如: java -classpath asm.jar:asm-util.jar org.objectweb.asm.util.TraceClassVisitor java.lang.Void
可以打印出
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); if (debug && mv != null) { // if this method must be traced Printer p = new Textifier(ASM4) { @Override public void visitMethodEnd() { print(aPrintWriter); // print it after it has been visited } }; mv = new TraceMethodVisitor(mv, p); } return new MyMethodAdapter(mv); }
AnalyzerAdapter
能够自动在必须要计算frame的地方计算frame,比COMPUTE_MAXS更高效
LocalVariableSorter
如果没有这个Adapter,新变量的插入一般要在最后,以免打乱之前的local variable调用顺序。
AdviceAdapter
抽象类,用来在RETURN或者ATHROW之前插入代码。其主要的用途在于它对<init>也能用,而init一般是不应该直接在code入口就插入新代码的,毕竟还需要对super类做初始化。