java之解讀class字節碼文件,打開JAVA虛擬機(JVM)大門

1.JVM體系結構以及class文件位置:

  • 簡述:衆所周知,我們編寫的java文件會被編譯成class文件,然後交給JVM來處理。如圖:
    在這裏插入圖片描述
    那麼,我們需要知道,class文件裏有什麼東西呢?(這不是廢話麼,當然是我們寫的java程序),話雖如此,他的本質是什麼呢?下面我們一起去探索!!!

2.編寫程序、編譯、打開class文件:

  • 編寫一段程序代碼:TopicString.java

    public class TopicString {
        public static void main(String[] args) {
            String s1 = "1";
            String s2 = new String("1");
            String s3 = "1" + "2" + "3";
            String s4 = "123";
            String s5 = "1" + "3" + new String("1") + "4";
    
            System.out.println(s1 == s2);
            System.out.println(s1.equals(s2));
            System.out.println(s3 == s4);
            System.out.println(s3.equals(s4));
        }
    }
    
  • 編譯代碼:cmd命令輸入javac TopicString.java得到一個TopicString.class文件
    注意:如果編寫的代碼中有中文,防止編譯亂碼需要:javac -encoding utf-8 xxx.java

  • 打開class文件:編譯後的class內容是十六進制,使用winhex工具打開讀者也可以用其它的工具打開

    • 方式一.安裝nodepad++32位或者64位戳我下載,提取碼: ptbr 在插件管理安裝Hex-Editor,打開class文件,選擇:插件 – HEX-EditorView in HEX
    • 方式二.下載winhex戳我下載, 提取碼: 2nuk
    • 打開文件,筆者截取部分字節碼信息
      在這裏插入圖片描述
      這是什麼玩意?這就是十六進制碼唄,怎麼看?我能看的懂麼?當然可以,它就像解讀摩斯電碼好玩,例如下面:發生在電臺甲(s1)和電臺乙(s2)之間的通訊
      s1:CQ CQ CQ de s1 K
      s2:s1 de s2 K
      s1:SK
      s2:SK
      他們在說什麼呢?
      CQ == 呼叫任何人,de == 這是,k = 結束,SK == 再見;
      所以第一句就是:
      s1:“呼叫,呼叫,呼叫,這裏是s1,結束!”。同理:
      s2:“s1,這裏是s2,結束!”。
      s1:“再見”。
      s2:“再見”。
      

3.怎麼解讀class文件中十六進制信息?

3.1.初步理解解讀class文件組成圖[官方圖]:

  • 解讀class文件的規則:官方地址這裏給個截圖,官方對每個屬性都有解讀,but!對新手不太友好:
    在這裏插入圖片描述
    截圖中內容什麼意思呢?又該怎麼看呢?

    u4 magic; 		 	// u4代表4個字節,magic是魔數值;魔數值理解,例如:寫信以‘親愛的xxx’開頭
                  		   對於class文件來說,文件內容的開頭須是【CA FE BA BE】 開頭,這不是
                  		   咖啡寶貝?對的,要不java怎麼會是冒熱氣的咖啡杯??
    
    u2  minor_version	// u2代表兩2個字節,minor_version是JDK次要版本;意思就是:
    					   往下數2個字節【00 00】,這2個字節表示的是JDK次要版本
    					   
    u2  major_version	// u2代表兩2個字節,major_version是JDK主要版本;意思就是:
    				       再往下數2個字節【00 34】,這2個字節表示的是JDK主要版本。
    				       十六進制的【00 34】轉爲十進制值等於:52。根據下圖得知52對應JDK8
    同理......
    下面就繼續解讀class文件其它部分咯!!
    

    在這裏插入圖片描述

3.2.繼續解讀代碼示例class文件內容【難點】:

  • 解讀class文件需要有耐心哦,咬牙堅持吧

    u2 constant_pool_count; // u2代表兩2個字節,constant_pool_count常量池數據總計數 
    						   往下數2字節【00 3C】轉爲十進制值等於:60。
    						   
    【理解難點】:
    cp_info constant_pool[constant_pool_count-1]; // 常量池‘表結構’總數,怎麼理解?
    
    // 因爲constant_pool_count = 60;所以這裏等價:cp_info constant_pool[59];
    // 怎麼理解?官方的ClassFile結構其實就好像一個JSON對象一樣:
    {
     	"u4": "magic",					 	// 魔數值
     	"u2": "minor_version",				// JDK次要版本
    	"u2": "major_version",				// JDK主要版本
    	"u2": "constant_pool_count",		// 常量池數據總計數
    	"cp_info": [{						// cp_info表結構對象,裏面有59個表結構對象
    		"cp_info1":[
    		],
    		......
    		......
    		"cp_info59":[
    		]
    	}],
    	"u2":"access_flags",
    	...
    	...
    	省略其它...
    }
    
    
  • 官方cp_info表結構對象長這樣戳我直達
    在這裏插入圖片描述
    因此ClassFile現在變成這樣:

    {
     	"u4": "magic",					 	// 魔數值
     	"u2": "minor_version",				// JDK次要版本
    	"u2": "major_version",				// JDK主要版本
    	"u2": "constant_pool_count",		// 常量池數據總計數
    	"cp_info": [{						// cp_info表結構對象,裏面有59個表結構對象
    		"cp_info1":[{
    	        "u1":"tag",
    	        "u1":"info[]"
    	      }],
    		......
    		......
    		"cp_info59":[{
    	        "u1":"tag",
    	        "u1":"info[]"
    	      }],
    	}],
    	"u2":"access_flags",
    	...
    	...
    	省略其它...
    }
    
  • 理解cp_info結構內容:

    u1 tag:標籤;不同的標籤對應的不同表結構
    u1 info[]:表結構;這個表結構需要根據tag值去查看對應表結構;鏈接:查看標籤值對應的表結構

    常量池中表結構都以tag開頭,佔1個字節,解讀第一個tag:
    在這裏插入圖片描述

    u1 tag;  // 常量池表結構標籤值,往下數1個字節,得到:
    			十六進制【0a】對應十進制值等於:10
    
  • 根據tag標籤值,找到對應的常量池表結構,戳我查看官方tag值對應的表結構:有以下表類型
    在這裏插入圖片描述

  • 查看tag標籤值=10對應的CONSTANT_Methodref_info表類型結構鏈接:戳我查看

    CONSTANT_Methodref_info { 		// 方法引用表結構
        u1 tag;						// 這個tag等價上面的tag
        u2 class_index;				// 所屬類下標索引
        u2 name_and_type_index;		// 這裏指初始化方法類型索引(見官方解釋)
    }
    
    得知第一個tag後,ClassFile變成如下:
    {
     	"u4": "magic",					 	// 魔數值 [ca fe ba be]
     	"u2": "minor_version",				// JDK次要版本 [00 00]
    	"u2": "major_version",				// JDK主要版本 [00 34]
    	"u2": "constant_pool_count",		// 常量池數據總計數 [00 3c]
    	"cp_info": [{						// cp_info表結構對象,裏面有59個表結構對象
    		"cp_info1":[{
    	        "u1":"tag",					// 標籤值 [0a]
    	        "u2":"class_index",			// 所屬類下標索引 [00 10]
    	        "u2":"name_and_type_index"	// 初始化方法類型索引 [00 1d]
    	      }],
    		......
    		......
    		"cp_info59":[{
    	        "u1":"tag",
    	        "u1":"info[]"
    	      }],
    	}],
    	"u2":"access_flags",
    	...
    	...
    	省略其它...
    }
    
3.2.1 解讀注意問題:
  1. 常量池CONSTANT_Utf8_info 類型的表結構:

    CONSTANT_Utf8_info {
        u1 tag;					// 常量池表結構標籤
        u2 length;				// 往下數length個字節,十六進制[00 06] 對應十進制值:6
        						   往下數6個字節,得到:[3c 69 6e 69 74 3e]
        u1 bytes[length];		// bytes[6],將則6個十六進制值轉成字符串,怎麼轉換呢?
    }
    

    在這裏插入圖片描述

    • 對照碼錶:戳我查看碼錶,依次搜索3c、69、6e、69、74、3e組裝結果爲:<init>
    • java程序轉換:將十六進制複製運行即可:
      public class EncodeConversionUtils {
      	    /**
      	     * 字符UTF8串轉16進制字符串
      	     *
      	     * @param strPart 字符
      	     * @return 16進制字符串
      	     */
      	    public static String string2Hexit8(String strPart) {
      	        return string2HexString(strPart, "UTF-8");
      	    }
      	
      	    public static String string2HexString(String strPart, String teletype) {
      	        try {
      	            return bytes2HexString(strPart.getBytes(teletype));
      	        } catch (Exception e) {
      	            return "";
      	        }
      	    }
      	
      	    /**
      	     * 字節處理
      	     *
      	     * @param b 字節信息
      	     * @return 字符串
      	     */
      	    public static String bytes2HexString(byte[] b) {
      	        StringBuilder result = new StringBuilder();
      	        for (byte value : b) {
      	            result.append(String.format("%02X", value));
      	        }
      	        return result.toString();
      	    }
      	
      	
      	    /**
      	     * @param src 16進制字符串
      	     * @return 字節數組
      	     */
      	    public static byte[] hexString2Bytes(String src) {
      	        int l = src.length() / 2;
      	        byte[] ret = new byte[l];
      	        for (int i = 0; i < l; i++) {
      	            ret[i] = Integer.valueOf(src.substring(i * 2, i * 2 + 2), 16).byteValue();
      	        }
      	        return ret;
      	    }
      	
      	    /**
      	     * 16進制字符串轉字符串
      	     *
      	     * @param src 16進制字符串
      	     * @return 字節數組
      	     */
      	    public static String hexString2String(String src, String oldCharType, String charType) {
      	        byte[] bts = hexString2Bytes(src);
      	        try {
      	            if (oldCharType.equals(charType)) {
      	                return new String(bts, oldCharType);
      	            } else {
      	                return new String(new String(bts, oldCharType).getBytes(), charType);
      	            }
      	        } catch (Exception e) {
      	            return "";
      	        }
      	    }
      	
      	    /**
      	     * 16進制UTF-8字符串轉字符串
      	     *
      	     * @param src 16進制字符串
      	     * @return 字節數組
      	     */
      	    public static String hexConvertUtf8(String src) {
      	        // 去除中間的空格
      	        return hexString2String(src.replaceAll(" ", ""), "UTF-8", "UTF-8");
      	    }
      	
      	    public static void main(String[] args) {
      	        System.out.println(EncodeConversionUtils.hexConvertUtf8("3c 69 6e 69 74 3e"));
      	        // 輸出結果:<init>
      	    }
      	}
      
  2. 筆者在解讀文件中也有說明:截圖表示一下吧
    在這裏插入圖片描述

4.解讀文件下載:

5.使用官方命令反編譯class文件:哦!原來是這樣的!

  • 他哥的!解讀熱情過去了,也知道怎麼解讀了,看看官方給的反編譯:

  • 命令javap -c或者javap -v讀者自己輸出看看結果

  • 小編結果展示:

    Classfile /C:/Users/Administrator/Desktop/TopicString.class
      Last modified 2020-5-9; size 947 bytes
      MD5 checksum 23a3a4ca6f73d56aa128c3713038f48f
      Compiled from "TopicString.java"
    public class TopicString
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #16.#29        // java/lang/Object."<init>":()V
       #2 = String             #30            // 1
       #3 = Class              #31            // java/lang/String
       #4 = Methodref          #3.#32         // java/lang/String."<init>":(Ljava/lang/String;)V
       #5 = String             #33            // 123
       #6 = Class              #34            // java/lang/StringBuilder
       #7 = Methodref          #6.#29         // java/lang/StringBuilder."<init>":()V
       #8 = String             #35            // 13
       #9 = Methodref          #6.#36         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      #10 = String             #37            // 4
      #11 = Methodref          #6.#38         // java/lang/StringBuilder.toString:()Ljava/lang/String;
      #12 = Fieldref           #39.#40        // java/lang/System.out:Ljava/io/PrintStream;
      #13 = Methodref          #41.#42        // java/io/PrintStream.println:(Z)V
      #14 = Methodref          #3.#43         // java/lang/String.equals:(Ljava/lang/Object;)Z
      #15 = Class              #44            // TopicString
      #16 = Class              #45            // java/lang/Object
      #17 = Utf8               <init>
      #18 = Utf8               ()V
      #19 = Utf8               Code
      #20 = Utf8               LineNumberTable
      #21 = Utf8               main
      #22 = Utf8               ([Ljava/lang/String;)V
      #23 = Utf8               StackMapTable
      #24 = Class              #46            // "[Ljava/lang/String;"
      #25 = Class              #31            // java/lang/String
      #26 = Class              #47            // java/io/PrintStream
      #27 = Utf8               SourceFile
      #28 = Utf8               TopicString.java
      #29 = NameAndType        #17:#18        // "<init>":()V
      #30 = Utf8               1
      #31 = Utf8               java/lang/String
      #32 = NameAndType        #17:#48        // "<init>":(Ljava/lang/String;)V
      #33 = Utf8               123
      #34 = Utf8               java/lang/StringBuilder
      #35 = Utf8               13
      #36 = NameAndType        #49:#50        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      #37 = Utf8               4
      #38 = NameAndType        #51:#52        // toString:()Ljava/lang/String;
      #39 = Class              #53            // java/lang/System
      #40 = NameAndType        #54:#55        // out:Ljava/io/PrintStream;
      #41 = Class              #47            // java/io/PrintStream
      #42 = NameAndType        #56:#57        // println:(Z)V
      #43 = NameAndType        #58:#59        // equals:(Ljava/lang/Object;)Z
      #44 = Utf8               TopicString
      #45 = Utf8               java/lang/Object
      #46 = Utf8               [Ljava/lang/String;
      #47 = Utf8               java/io/PrintStream
      #48 = Utf8               (Ljava/lang/String;)V
      #49 = Utf8               append
      #50 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
      #51 = Utf8               toString
      #52 = Utf8               ()Ljava/lang/String;
      #53 = Utf8               java/lang/System
      #54 = Utf8               out
      #55 = Utf8               Ljava/io/PrintStream;
      #56 = Utf8               println
      #57 = Utf8               (Z)V
      #58 = Utf8               equals
      #59 = Utf8               (Ljava/lang/Object;)Z
    {
      public TopicString();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 1: 0
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=4, locals=6, args_size=1
             0: ldc           #2                  // String 1
             2: astore_1
             3: new           #3                  // class java/lang/String
             6: dup
             7: ldc           #2                  // String 1
             9: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
            12: astore_2
            13: ldc           #5                  // String 123
            15: astore_3
            16: ldc           #5                  // String 123
            18: astore        4
            20: new           #6                  // class java/lang/StringBuilder
            23: dup
            24: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
            27: ldc           #8                  // String 13
            29: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            32: new           #3                  // class java/lang/String
            35: dup
            36: ldc           #2                  // String 1
            38: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
            41: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            44: ldc           #10                 // String 4
            46: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            49: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
            52: astore        5
            54: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
            57: aload_1
            58: aload_2
            59: if_acmpne     66
            62: iconst_1
            63: goto          67
            66: iconst_0
            67: invokevirtual #13                 // Method java/io/PrintStream.println:(Z)V
            70: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
            73: aload_1
            74: aload_2
            75: invokevirtual #14                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
            78: invokevirtual #13                 // Method java/io/PrintStream.println:(Z)V
            81: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
            84: aload_3
            85: aload         4
            87: if_acmpne     94
            90: iconst_1
            91: goto          95
            94: iconst_0
            95: invokevirtual #13                 // Method java/io/PrintStream.println:(Z)V
            98: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
           101: aload_3
           102: aload         4
           104: invokevirtual #14                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
           107: invokevirtual #13                 // Method java/io/PrintStream.println:(Z)V
           110: return
          LineNumberTable:
            line 3: 0
            line 4: 3
            line 5: 13
            line 6: 16
            line 7: 20
            line 9: 54
            line 10: 70
            line 11: 81
            line 12: 98
            line 13: 110
          StackMapTable: number_of_entries = 4
            frame_type = 255 /* full_frame */
              offset_delta = 66
              locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
              stack = [ class java/io/PrintStream ]
            frame_type = 255 /* full_frame */
              offset_delta = 0
              locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
              stack = [ class java/io/PrintStream, int ]
            frame_type = 90 /* same_locals_1_stack_item */
              stack = [ class java/io/PrintStream ]
            frame_type = 255 /* full_frame */
              offset_delta = 0
              locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
              stack = [ class java/io/PrintStream, int ]
    }
    SourceFile: "TopicString.java"
    
    

6.本文的案例對應的虛擬機指令解讀請見筆者文章:

一定,一定,一定要學會自己解讀class文件,很重要!
祝生活愉快!

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