java虛擬機Class格式與指令

前幾天看《深入理解java虛擬機》中關於.class文件的詳細解析,實際上Class文件裏面就是包含着運行時候的指令,以及數據等內容。如果想要能夠讀懂Class文件,那一定需要對Class的文件結構以及Java虛擬機指令集有一定的瞭解

JVM指令集

java虛擬機也有自己的指令集(字節碼指令集),指令佔一個字節長度,也就是說JVM中的指令最多也就256個。一常用的指令如:iload,iadd,isub,imul…,這裏舉的例子只是對應於int類型的運算操作碼,如果float類型,對應的指令爲:fload,fadd,fsub,fmul…。由指令加上對應的數據就是一次運算了。

與指令緊密相關的就是操作數了,JVM裏面有操作數棧,本地變量表。

Class文件結構

對一個非常簡單的java文件:

public class Test{
    private int a;

    public int value(){
       return a;
    }
}

使用javac Test.java得到Test.class.

使用javap -verbose Test.class可以獲得Class文件可視化結果.其中包括常量池,函數字節碼代碼

    Classfile /Users/houzhi/Test.class
    Last modified Sep 8, 2015; size 265 bytes
      MD5 checksum 7a6cc538f28ef4dc4978891008b41ed8
      Compiled from "Test.java"
    public class Test
      SourceFile: "Test.java"
      minor version: 0
      major version: 51
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #4.#15         //  java/lang/Object."<init>":()V
       #2 = Fieldref           #3.#16         //  Test.a:I
       #3 = Class              #17            //  Test
       #4 = Class              #18            //  java/lang/Object
       #5 = Utf8               a
       #6 = Utf8               I
       #7 = Utf8               <init>
       #8 = Utf8               ()V
       #9 = Utf8               Code
      #10 = Utf8               LineNumberTable
      #11 = Utf8               value
      #12 = Utf8               ()I
      #13 = Utf8               SourceFile
      #14 = Utf8               Test.java
      #15 = NameAndType        #7:#8          //  "<init>":()V
      #16 = NameAndType        #5:#6          //  a:I
      #17 = Utf8               Test
      #18 = Utf8               java/lang/Object
    {
      public Test();
        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 2: 0

      public int value();
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0       
             1: getfield      #2                  // Field a:I
             4: ireturn       
          LineNumberTable:
            line 7: 0
    }

從上面的結果可以大概看出一些內容,其中major version是java的版本,java7對應是51.然後是類的訪問標誌,public。然後是常量表,記錄字符的表,包括函數名稱,變了名稱,類路徑名稱等等。
再之後是函數,每個函數其實是方法表,方法表裏面再包括了訪問標誌,代碼表,LineNumberTable是指函數是在第幾行。

代碼段裏面的內容主要是剛剛操作碼的指令,從這些字節代碼是完全可以查看函數的功能的。

利用查看Class解決bug

這裏順便提一下我昨天干的一件事,因爲部署的服務器應用的源代碼在另外一個開發者那裏,暫時他去有事了。但是我重新部署他的代碼發現有問題,保存圖片的位置一直不對,經過測試,感覺他可能是在程序裏面寫了一個固定的地址,導致換了服務器後,路徑總是不太對。正巧這段時間有空的時候就在虛擬機的相關東西,而且基本能確定很大可能是路徑問題,所以直接嘗試去查看class的內容,就使用了javap -verbose來查看對應的class文件的內容,查看引用的地方最終找到了保存的代碼,發現確實是他把保存路徑寫死了。

找到了問題,一下子沒源碼,就嘗試修改class文件去解決這個問題,畢竟class文件是編譯後,按照規範的,只要class是符合規範,程序驗證通過的話,就會被JVM接受。使用修改的方式如下

  1. 使用vim -b Upload.class,這個時候是打開二進制文件,一堆亂碼
  2. 輸入vim的命令 :%!xxd 把文件轉換成二進制的讀入,轉化後內容會變成這樣

“`
0000010: 1400 1508 0016 0a00 1700 1807 0016 0700 …………….

0000020: 1901 0004 5445 5354 0100 124c 6a61 7661  ....TEST...Ljava

0000030: 2f6c 616e 672f 5374 7269 6e67 3b01 000d  /lang/String;...
```
  1. 然後看着右邊那裏的解釋修改。我是直接改了路徑,所以相對比較簡單,如果太複雜就最好先編譯。輸入 :%!xxd -r 可以切換回原來的模式。修改後保存。修改可以直接改那些數字。
  2. 一次非常好的class文件學習實踐體驗。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章