字節碼工具

Java變量內部名字:java/lang/String、boolean(Z)、long(J)、int(I)

方法簽名:void a(int i, float f)   —>  (IF)V

ASM提供兩組API:Core和Tree,Core是基於訪問者模式來操作類的,Tree是基於樹節點來操作類的

Core模式:主要類有ClassReader、ClassWriter、ClassAdapter、ClassVisitor、MethodVisitor、FieldVisitor,其中ClassVisitor實現的接口如下:visit [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* (visitInnerClass | visitField | visitMethod )* visitEnd,ClassReader的accept方法是流程的起點,參數接受一個具體的ClassVisitor,ClassAdapter也是ClassVisitor的實現類,ClassAdapter可以看成是事件過濾器或邏輯處理器,另外一個比較重要的知識點是CoreApi的使用,主要理解指令如何操縱java堆棧、LOAD指令將局部變量表中的變量加載到堆棧、storeLocal(Generates the instruction to store the top stack value in the given local variable.)和loadLocal(Generates the instruction to load the given local variable on the stack.)用來操作局部變量等

//int取值0~5時JVM採用iconst_0、iconst_1、iconst_2、iconst_3、iconst_4、iconst_5指令將常量壓入棧中,取值-1時採用iconst_m1指令將常量壓入棧中
public static void main(String[] args) {
   int i = 5;
   int j = -1;
}

0: iconst_5
1: istore_1
2: iconst_m1
3: istore_2
4: return

//當int取值-128~127時,JVM採用bipush指令將常量壓入棧中
public static void main(String[] args) {
  int i = 127;
}

0: bipush 127
1: istore_1
2: return

//當int取值-32768~32767時,JVM採用sipush指令將常量壓入棧中
//當int取值-2147483648~2147483647時,JVM採用ldc指令將常量壓入棧中

//load指令和store指令用於操作局部變量表和堆棧
public static int add(int a,int b){
  int c=0;
  return a+b;
}

0: iconst_0
1: istore_2
2: iload_0
3: iload_1
4: iadd
5: ireturn
ClassWriter classWriter = new ClassWriter(0); 
classWriter.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "org/victor/core/MyCls", null, "java/lang/Object", null); 
ClassAdapter classAdapter = new MyClassAdapter(classWriter);  
classAdapter.visitField(Opcodes.ACC_PRIVATE, "name", Type.getDescriptor(String.class), null, null);//定義name屬性
classAdapter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null).visitCode();//定義構造方法
byte[] classFile = classWriter.toByteArray();
public void visitCode() {
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");//調用父類的構造方法
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    mv.visitLdcInsn("zhangzhuo");//將常量池中的字符串常量加載到棧頂   mv.visitFieldInsn(Opcodes.PUTFIELD, "org/victor/core/MyCls", "name", Type.getDescriptor(String.class));//對屬性賦值
    mv.visitInsn(Opcodes.RETURN);//設置返回值
    mv.visitMaxs(2, 1);//設置方法的棧和本地變量表的大小
}
public void visitInsn(int opcode) {     
    //此方法可以獲取方法中每一條指令的操作類型,被訪問多次,如應在方法結尾處添加新指令,則應判斷:
    if(opcode == Opcodes.RETURN)
    {
          mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
          mv.visitLdcInsn("this is a modify method!");
          mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
          mv.visitInsn(RETURN);
    }
    super.visitInsn(opcode);
}

總結:我們可以通過代理ClassVisitor和MethodVisitor來截獲調用,最終來判斷是否執行真正的visitor邏輯

Javassist主要類有ClassPool、CtClass、CtMethod,其中,ClassPool 是一個存儲 CtClass 的 Hash 表,類的名稱作爲 Hash 表的 key。ClassPool 的 get() 函數用於從 Hash 表中查找 key 對應的 CtClass 對象。如果沒有找到,get() 函數會創建並返回一個新的 CtClass 對象,這個新對象會保存在 Hash 表中

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("test.Rectangle");
cc.setSuperclass(pool.get("test.Point"));
cc.writeFile();
byte[] b = cc.toBytecode();
Class clazz = cc.toClass(); //請求當前線程的 ClassLoader 加載 CtClass 所代表的類文件
//定義新類,類的成員方法可以通過 CtNewMethod 類的工廠方法來創建,
//然後使用 CtClass 的 addMethod() 方法將其添加到類中
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Point"); 
//如果一個 CtClass 對象通過 writeFile(), toClass(), toBytecode() 被轉換成一個類文件,此 CtClass 對象會被凍結起來,
//不允許再修改,因爲一個類只能被 JVM 加載一次。但是,一個冷凍的 CtClass 也可以被解凍
CtClass cc = ...;
cc.writeFile();
cc.defrost();
cc.setSuperclass(...);//因爲類已經被解凍,所以這裏可以調用成功
//CtClass 對象所代表的類的名稱 Point 被修改爲 Pair,因此,如果後續在 ClassPool 對象上再次調用 get("Point"),
//則它不會返回變量 cc 所指的 CtClass 對象。 而是再次讀取類文件 Point.class,併爲類 Point 構造一個新的 CtClass 對象
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
cc.setName("Pair"); 
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
CtClass cc1 = pool.get("Point");   // cc1 is identical to cc.
cc.setName("Pair");                // cc1 is not identical to cc.
CtClass cc2 = pool.get("Pair");    // cc2 is identical to cc.
CtClass cc3 = pool.get("Point");   // cc3 is identical to cc1.

Javassist 不允許刪除方法或字段,但它允許更改名稱,所以,如果一個方法是沒有必要的,可以通過調用 CtMethod 的 setName() 和 setModifiers() 中將其改爲一個私有方法。

Javassist 不允許向現有方法添加額外的參數。你可以通過新建一個方法達到同樣的效果。

CtMethod 和 CtConstructor 提供了 insertBefore(),insertAfter() 和 addCatch() 方法。 它們可以將用 Java 編寫的代碼片段插入到現有方法中。Javassist 包括一個用於處理源代碼的簡單編譯器,它接收用 Java 編寫的源代碼,並將其編譯成 Java 字節碼,並內聯方法體中。

方法 insertBefore() ,insertAfter(),addCatch() 和 insertAt() 接收一個表示語句或語句塊的 String 對象。一個語句是一個單一的控制結構,比如 if 和 while 或者以分號結尾的表達式。語句塊是一組用大括號 {} 包圍的語句。因此,以下每行都是有效語句或塊的示例:

System.out.println("Hello");
{ System.out.println("Hello"); }
if (i < 0) { i = -i; }

由於編譯器支持語言擴展,以 $ 開頭的幾個標識符有特殊的含義:

符號 含義
$0$1$2, ... this and 方法的參數
$args 方法參數數組.它的類型爲 Object[]
$$ 所有實參。例如, m($$) 等價於 m($1,$2,...)
$cflow(...) cflow 變量
$r 返回結果的類型,用於強制類型轉換
$w 返回結果的類型,用於強制類型轉換
$_ 返回值

下面有一些使用這些特殊變量的例子。假設一個類 Point,要在調用方法 move() 時打印 dx 和 dy 的值,請執行以下程序:

class Point {
    int x, y;
    void move(int dx, int dy) { x += dx; y += dy; }
}


ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
CtMethod m = cc.getDeclaredMethod("move");
m.insertBefore("{ System.out.println($1); System.out.println($2); }");
cc.writeFile();
exMove($$, context); //這個表達式等價於
exMove($1, $2, $3, context);

$cflow表示控制流,此只讀變量返回特定方法的遞歸調用的深度。

int fact(int n) {
    if (n <= 1)
        return n;
    else
        return n * fact(n - 1);
}

CtMethod cm = ...;
cm.useCflow("fact");
cm.insertBefore("if ($cflow(fact) == 0)   System.out.println(\"fact \" + $1);");
return ($r)result;

Integer i = ($w)5;

javassist.expr.ExprEditor 是一個用於替換方法體中的表達式的類。用戶可以定義 ExprEditor 的子類來指定修改表達式的方式。

CtMethod cm = ... ;
cm.instrument(
    new ExprEditor() {
        public void edit(MethodCall m) throws CannotCompileException {
            if (m.getClassName().equals("Point") && m.getMethodName().equals("move"))
                m.replace("{ $1 = 0; $_ = $proceed($$); }");
        }
    });

//javassist.expr.MethodCall表示方法調用
//javassist.expr.ConstructorCall表示構造函數調用
//javassist.expr.FieldAccess表示字段訪問
//javassist.expr.NewExpr表示使用new運算符(不包括數組創建)創建對象的表達式
//javassist.expr.NewArray表示使用new運算符創建數組
//javassist.expr.Instanceof表示一個instanceof表達式
//javassist.expr.Cast表示一個cast表達式
//javassist.expr.Handler表示try-catch語句的catch子句

添加一個新方法:

//向類 Point 添加了一個公共方法 xmove()
CtClass point = ClassPool.getDefault().get("Point");
CtMethod m = CtNewMethod.make("public int xmove(int dx) { x += dx; }", point);
point.addMethod(m);

//如果目標對象和目標方法名也被傳遞給 make() 方法,源文本中也可以包括 $proceed
CtClass point = ClassPool.getDefault().get("Point");
CtMethod m = CtNewMethod.make("public int ymove(int dy) { $proceed(0, dy); }", point, "this", "move");
//這個程序創建一個 ymove() 方法,定義如下:
public int ymove(int dy) { this.move(0, dy); }

//Javassist 還提供了另一種添加新方法的方式。你可以先創建一個抽象方法,然後給它一個方法體:
CtClass cc = ... ;
CtMethod m = new CtMethod(CtClass.intType, "move", new CtClass[] { CtClass.intType }, cc);
cc.addMethod(m);
m.setBody("{ x += $1; }");
cc.setModifiers(cc.getModifiers() & ~Modifier.ABSTRACT);
//因爲Javassist在類中添加了的方法是抽象的,所以在調用 setBody() 之後,必須將類顯式地改回非抽象類

添加一個字段:

//向類Point添加一個名爲z的字段
CtClass point = ClassPool.getDefault().get("Point");
CtField f = new CtField(CtClass.intType, "z", point);
point.addField(f);
//指定添加字段的初始值
CtClass point = ClassPool.getDefault().get("Point");
CtField f = new CtField(CtClass.intType, "z", point);
point.addField(f, "0");  // initial value is 0

//上述代碼可以重寫爲更簡單代碼:
CtClass point = ClassPool.getDefault().get("Point");
CtField f = CtField.make("public int z = 0;", point);
point.addField(f);

要刪除字段或方法,請在 CtClass 的 removeField() 或 removeMethod() 方法。

 

https://www.jianshu.com/p/43424242846b

https://www.jianshu.com/p/b9b3ff0e1bf8

https://www.jianshu.com/p/7803ffcc81c8

https://blog.csdn.net/mbugatti/article/details/53410506

https://www.diycode.cc/topics/581

https://www.ibm.com/developerworks/cn/java/j-lo-asm30/index.html

https://blog.csdn.net/conquer0715/article/details/51283610

https://asm.ow2.io

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章