使用jclasslib修改字節碼/源碼

  查看源碼很簡單,一些常用IDE裏如idea、eclipse都提供了查看class文件源碼的功能,雖然跟源碼有些出入(解語法糖等),但功能實現上是一致的,且比源碼更貼近於JVM運行時的情況。
  有時候我們需要修改源碼以滿足使用要求,對於java代碼生成的字節碼重新源碼就比較簡單了,一種方式是繼承然後重寫待修改的功能,另一種方式是直接創建一個同名類文件,把反編譯的源碼複製進去,修改後,將新生成的class文件替換原jar包中的class文件,但有些字節碼是由其它語言生成的,反編譯後的文件並不能滿足java編譯語法,也就無法編譯成新的class文件,對於這種情況,有種通用的方式就是直接修改字節碼來實現。
  網上有很多修改字節碼的文章,但大都是修改常量池來輸出不同的值,本文使用jclasslib直接修改字節碼中的源碼邏輯,爲方便演示,本文的demo可能比較簡單,但是這種方法可以用到更復雜的類中,如有需要歡迎留言探討。

1、java源碼

package com.zhanghao.test.jclasslib;

public class JclasslibTest {
    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        printMin(a, b);
    }

    private static void printMin(int a, int b) {
        int min = a <= b ? a : b;
        System.out.println(min);
    }
}

輸出兩個參數中的較小值:1
目標:通過修改字節碼的方式使printMin方法輸出較大值:2

2、class文件反編譯結果

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.zhanghao.test.jclasslib;

public class JclasslibTest {
    public JclasslibTest() {
    }

    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        printMin(a, b);
    }

    private static void printMin(int a, int b) {
        int min = a <= b ? a : b;
        System.out.println(min);
    }
}

  可以看出,生成的class文件只是比java源碼多加了一個默認的無參構造方法(因爲demo比較簡單,所以未體現編譯優化的多種策略)

3、下載安裝jclasslib

https://github.com/ingokegel/jclasslib/releases

4、使用jclasslib打開class文件

在這裏插入圖片描述
字節碼各部分名稱已經很明確類,這裏不在過多解釋,看我們需要修改的位置Methods->printMin
在這裏插入圖片描述
printMin方法對應的字節碼一共爲19行
0:將第一個參數入棧(參數a=1)
1:將第二個參數入棧(參數b=2)
2:比較棧頂兩int型數值的大小,當結果大於0時跳轉到第9行字節碼指令(判斷條件進入相應邏輯塊)
5:將第一個參數入棧(選擇a)
6:無條件跳轉到第10行字節碼指令
9:將第二個參數入棧(選擇b)
10:將棧頂int型數值存入第三個本地變量(存儲選擇結果c)
11:獲取指定類的靜態域,並將其壓入棧頂(獲取待運行的實例及方法)
14:將第三個參數入棧(將c入棧以供11中的實例方法運行參數)
15:調用實例方法
18:從當前方法返回void
更多字節碼指令可參考 虛擬機字節碼指令表
參照虛擬機字節碼指令表,如要實現將pringMin輸出參數中的較大值,只要把第三條指令if_icmpgt修改成if_icmple即可。

助記符 if_icmpgt if_icmple
指令含義 比較棧頂兩int型數值的大小,當結果大於0時跳轉 比較棧頂兩int型數值的大小,當結果小於或等於0時跳轉
字節碼 0xa3 0xa4
有符號型十進制數 -93 -92

5、修改字節碼

package com.zhanghao.test.jclasslib;

import com.alibaba.fastjson.JSON;
import java.io.*;
import org.gjt.jclasslib.io.ClassFileWriter;
import org.gjt.jclasslib.structures.AttributeInfo;
import org.gjt.jclasslib.structures.ClassFile;
import org.gjt.jclasslib.structures.MethodInfo;
import org.gjt.jclasslib.structures.attributes.CodeAttribute;

public class JclasslibModify {
    public static void main(String[] args) throws Exception {
        String filePath = "/Users/zhanghao/Desktop/jclasslib/JclasslibTest.class";
        FileInputStream fis = new FileInputStream(filePath);

        DataInput di = new DataInputStream(fis);
        ClassFile cf = new ClassFile();
        cf.read(di);
        System.out.println(JSON.toJSONString(cf));
        MethodInfo[] methodInfos = cf.getMethods();
        MethodInfo methodInfo = methodInfos[2];
        AttributeInfo[] attributeInfos = methodInfo.getAttributes();
        CodeAttribute codeAttribute = (CodeAttribute) attributeInfos[0];
        byte[] bytes = codeAttribute.getCode();
        bytes[2] = -92;

        fis.close();
        File f = new File(filePath);
        ClassFileWriter.writeToFile(f, cf);
    }
}

讀取class文件,可通過debug或者輸出json的方式查看ClassFile的類結構,修改類結構中字節碼然後重寫文件。
需要用到jclasslib.jar,下載鏈接:jclasslib.jar

6、查看修改後的結果

package com.zhanghao.test;

public class JclasslibTest {
    public JclasslibTest() {
    }

    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        printMin(a, b);
    }

    private static void printMin(int a, int b) {
        int min = a > b ? a : b;
        System.out.println(min);
    }
}

在這裏插入圖片描述

本文只是簡單的闡述修改字節碼的方法,對於實際項目中需要修改字節碼時,情況會更爲複雜,但是換湯不換藥,可以先用java生成待修改部分的字節碼,然後替換掉原字節碼中相應的字節碼塊,其它文件也需要相應修改,比如常量池等

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