官术网_书友最值得收藏!

項目框架搭建

分析class文件結構的工具有很多,可分為兩類。一類是基于十六進制的分析工具,如010editer、UE;另一類是可視化的class文件結構分析工具,如開源的classpy[1]。為能讓讀者更好的理解class文件結構,本書將介紹如何使用java語言編寫一個解析class文件的項目,通過該項目實現class文件的解析,并從編寫項目的過程中掌握class文件結構。

根據表2-1 class文件結構以及class文件的解析流程設計解析項目的技術架構圖,如圖2.1所示。

圖2.1 class文件解析項目的架構設計

根據技術架構圖搭建項目的框架。先定義對應class文件結構中各項的類型,如常量池、字段表、方法表、屬性表、U2、U4,再定義各項的解析器,并使用責任鏈模式完成class文件結構各項的解析工作。框架搭建如圖2.2所示。

圖2.2 class文件解析項目的整體框架

如圖2.2所示,handler包用于存放class文件結構各項的解析器,如魔數解析器、版本號解析器;type包存放對應class文件結構各項的類型;util包存放工具類,如根據類的訪問標志的字節表示轉為字符串表示的工具類。

根據表2-1 class文件結構創建ClassFile類,ClassFile類如代碼清單2-2所示。

代碼清單2-2 ClassFile類

public class ClassFile {

    private U4 magic; // 魔數
    private U2 minor_version; // 副版本號
    private U2 magor_version; // 主版本號
    private U2 constant_pool_count; // 常量池計數器
    private CpInfo[] constant_pool; // 常量池
    private U2 access_flags; // 訪問標志
    private U2 this_class; // 類索引
    private U2 super_class; // 父類索引
    private U2 interfaces_count; // 接口總數
    private U2[] interfaces; // 接口數組
    private U2 fields_count; // 字段總數
    private FieldInfo[] fields; // 字段表
    private U2 methods_count; // 方法總數
    private MethodInfo[] methods; // 方法表
    private U2 attributes_count; // 屬性總數
private AttributeInfo[] attributes; // 屬性表

}

ClassFile類中的每個字段是按照class文件結構中各項的順序聲明的,其中CpInfo、FieldInfo、MethodInfo、AttributeInfo這幾個類目前并未添加任何字段,只是一個空的類,代碼如下。

public class CpInfo {
}
public class FieldInfo {
}
public class MethodInfo {
}
public class AttributeInfo {
}

U2和U4是基本單位,長度分別為兩個字節和四個字節。為了便于理解,我們需要為這兩種基本單位創建對應的Java類,這兩個類都只需要一個字段,類型為Byte數組,長度在構造方法中控制,要求構造方法必須傳入數組每個元素的值。為驗證解析結果是否正確,以及解析結果的可讀性,還需要為這兩個類添加一個byte[]轉int的方法,以及byte[]轉16進制字符串的方法。如代碼清單2-3所示。

代碼清單2-3 U2和U4

public class U2 {

    private byte[] value;

    public U2(byte b1, byte b2) {
        value = new byte[]{b1, b2};
    }

    public Integer toInt() {
        return (value[0] & 0xff) << 8 | (value[1] & 0xff);
    }

    public String toHexString() {
        char[] hexChar = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        StringBuilder hexStr = new StringBuilder();
        for (int i = 1; i >= 0; i--) {
            int v = value[i] & 0xff;
            while (v > 0) {
public class U2 {

    private byte[] value;

    public U2(byte b1, byte b2) {
        value = new byte[]{b1, b2};
    }

    public Integer toInt() {
        return (value[0] & 0xff) << 8 | (value[1] & 0xff);
    }

    public String toHexString() {
        char[] hexChar = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        StringBuilder hexStr = new StringBuilder();
        for (int i = 1; i >= 0; i--) {
            int v = value[i] & 0xff;
            while (v > 0) {
                int c = v % 16;
                v = v >>> 4;
                hexStr.insert(0, hexChar[c]);
            }
            if (((hexStr.length() & 0x01) == 1)) {
                hexStr.insert(0, '0');
            }
        }
        return "0x" + (hexStr.length() == 0 ? "00" : hexStr.toString());
    }

}

public class U4 {

    private byte[] value;

    public U4(byte b1, byte b2, byte b3, byte b4) {
        value = new byte[]{b1, b2, b3, b4};
    }

    public int toInt() {
        int a = (value[0] & 0xff) << 24;
        a |= (value[1] & 0xff) << 16;
        a |= (value[2] & 0xff) << 8;
        return a | (value[3] & 0xff);
    }

    public String toHexString() {
        char[] hexChar = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        StringBuilder hexStr = new StringBuilder();
        for (int i = 3; i >= 0; i--) {
            int v = value[i] & 0xff;
            while (v > 0) {
                int c = v % 16;
                v = v >>> 4;
                hexStr.insert(0, hexChar[c]);
            }
            if (((hexStr.length() & 0x01) == 1)) {
                hexStr.insert(0, '0');
            }
        }
        return "0x" + hexStr.toString();
    }

}

接著我們需要創建一個接口BaseByteCodeHandler,抽象出class文件結構各項的解析器行為。每個解析器應該只負責完成class文件結構中某一項的解析工作,如常量池解析器就只負責解析常量池。BaseByteCodeHandler接口如代碼清單2-4所示。

代碼清單2-4 class文件結構各項的解釋器接口

public interface BaseByteCodeHandler {
    /**
     * 解釋器的排序值
     * @return
     */
    int order();

    /**
     * 讀取
     * @param codeBuf
     * @param classFile
     */
    void read(ByteBuffer codeBuf, ClassFile classFile) throws Exception;
}

如代碼清單2-4所示,BaseByteCodeHandler接口只定義了一個read方法,該方法要求傳入class文件的字節緩存[2]和ClassFile對象。read方法將從字節緩存中讀取相應的字節數據寫入ClassFile對象。由于解析是按順序解析的,因此BaseByteCodeHandler接口還定義了一個返回排序值的方法,用于實現解析器排序,比如版本號解析器排在魔數解析器的后面。

有了解析器之后,我們還需要實現一個管理和調度解析器工作的總指揮ClassFileAnalysiser,如代碼清單2-5所示。

代碼清單2-5 ClassFileAnalysiser類

public class ClassFileAnalysiser {

    private final static List<BaseByteCodeHandler> handlers = new ArrayList<>();

    static {
        // 添加各項的解析器
        handlers.add(new MagicHandler());
        handlers.add(new VersionHandler());
         ......
        // 解析器排序,要按順序調用
        handlers.sort((Comparator.comparingInt(BaseByteCodeHandler::order)));
    }

  // 將傳入的從class文件讀取的字節緩存,解析生成一個ClassFile對象
public static ClassFile analysis(ByteBuffer codeBuf) throws Exception {
  // 重置ByteBuffer的讀指針,從頭開始
       codeBuf.position(0);
        ClassFile classFile = new ClassFile();
        // 遍歷解析器,調用每個解析器的解析方法
        for (BaseByteCodeHandler handler : handlers) {
            handler.read(codeBuf, classFile);
        }
        return classFile;
    }

}

ClassFileAnalysiser的靜態代碼塊負責實例化各個解釋器并排好序。ClassFileAnalysiser暴露analysis方法給外部調用,由analysis方法根據解析器的排序順序去調用各個解析器的read方法完成class文件結構各項的解析工作,由各項解析器將解析結果賦值給ClassFile對象的對應字段。

analysis方法的入參是class文件內容的字節緩存,從class文件中讀取而來。使用ByteBuffer而不直接使用byte[]緩存讀取的class文件內容是因為ByteBuffer能更好的控制順序讀取。

現在我們只需要實現將class文件讀取到內存中,再調用ClassFileAnalysiser的analysis方法,就能實現將一個class文件解析為一個ClassFile對象了,如代碼清單2-6所示。

代碼清單2-6 將class文件讀取到ByteBuffer并解析

public class ClassFileAnalysisMain {

    public static ByteBuffer readFile(String classFilePath) throws Exception {
        File file = new File(classFilePath);
        if (!file.exists()) {
            throw new Exception("file not exists!");
        }
        byte[] byteCodeBuf = new byte[4096];
        int lenght;
        try (InputStream in = new FileInputStream(file)) {
            lenght = in.read(byteCodeBuf);
        }
        if (lenght < 1) {
            throw new Exception("not read byte code.");
        }
    // 將字節數組包裝為ByteBuffer
        return ByteBuffer.wrap(byteCodeBuf, 0, lenght).asReadOnlyBuffer();
    }

public static void main(String[] args) throws Exception {
  // 讀取class文件
        ByteBuffer codeBuf = readFile("xxx.class");
    // 解析class文件
        ClassFile classFile = ClassFileAnalysiser.analysis(codeBuf);
    // 打印魔數解析器解析出來的Magic
        System.out.println(classFile.getMagic().toHexString());
    }

}

當然,這只是整體的框架搭建,class文件結構各項的解釋器還沒有實現。接下來,我們就按照class文件結構的解析順序實現各項解析器。


注釋:

[1] classpy是一個開源的class文件結構分析工具:https://github.com/zxh0/classpy

[2] class文件字節緩存是指從class文件讀入內存的字節緩存,這是一個數組,大小即為class文件的大小。

主站蜘蛛池模板: 鄂托克旗| 凤城市| 剑河县| 昌黎县| 新丰县| 达日县| 龙井市| 新乐市| 溧阳市| 繁昌县| 霍林郭勒市| 桃园市| 林甸县| 胶州市| 文登市| 阿拉善右旗| 安岳县| 航空| 仙居县| 伊川县| 砀山县| 宁城县| 澜沧| 清镇市| 崇仁县| 利辛县| 太仆寺旗| 靖边县| 平舆县| 宣化县| 桂平市| 霍邱县| 韩城市| 乳山市| 普陀区| 丹巴县| 新野县| 汾阳市| 庆安县| 安顺市| 鄄城县|