- Java虛擬機字節碼:從入門到實戰
- 吳就業
- 1937字
- 2021-01-08 19:08:24
項目框架搭建
分析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文件的大小。
- Web前端開發技術:HTML、CSS、JavaScript(第3版)
- Spring Cloud、Nginx高并發核心編程
- TypeScript實戰指南
- 網絡爬蟲原理與實踐:基于C#語言
- Extending Puppet(Second Edition)
- 微服務架構深度解析:原理、實踐與進階
- Go語言精進之路:從新手到高手的編程思想、方法和技巧(2)
- 動手學數據結構與算法
- 大學計算機基礎
- SQL Server 入門很輕松(微課超值版)
- IBM Cognos TM1 Developer's Certification guide
- 跟戴銘學iOS編程:理順核心知識點
- 數據分析與挖掘算法:Python實戰
- OpenCV 3.0 Computer Vision with Java
- 程序員必會的40種算法