天天看點

用一個例子來做說明位元組碼檔案的解析

位元組碼檔案解析

最近有回過頭看了一下jvm并且去看了一下他的一部分c語言的源碼。打算嘗試用Java寫一個分析位元組碼檔案的小程式。

這裡寫了一個class來手動分析一下位元組碼的解析過程

public class jvmclasstest {
    public int i = 1;
    public static Integer a = 2;
    private String s = "caohao";

    public static void main(String[] args) {
        jvmclasstest test = new jvmclasstest();
        test.test(10);
    }

    public  void test(int a){
        this.i = a;
    }
}
           

這裡選用了一個叫做Synalyze It! Pro的16進制檢視工具

一個位元組碼檔案大概是由以下幾個部分構成

  1. 魔數
  2. 版本号
  3. 常量池
  4. 這個類的access_flag
  5. this_class
  6. super_class
  7. interfaces
  8. fileds
  9. methods
  10. attributes

位元組碼檔案的内容如下:

HEX DUMP:
[00000010]   CA FE BA BE 00 00 00 34   00 2E 0A 00 0A 00 21 09   .......4 ........
[00000020]   00 05 00 22 08 00 23 09   00 05 00 24 07 00 25 0A   ........ ........
[00000030]   00 05 00 21 0A 00 05 00   26 0A 00 27 00 28 09 00   ........ ........
[00000040]   05 00 29 07 00 2A 01 00   01 69 01 00 01 49 01 00   ........ .i...I..
[00000050]   01 61 01 00 13 4C 6A 61   76 61 2F 6C 61 6E 67 2F   .a...Lja va.lang.
[00000060]   49 6E 74 65 67 65 72 3B   01 00 01 73 01 00 12 4C   Integer. ...s...L
[00000070]   6A 61 76 61 2F 6C 61 6E   67 2F 53 74 72 69 6E 67   java.lan g.String
[00000080]   3B 01 00 06 3C 69 6E 69   74 3E 01 00 03 28 29 56   .....ini t......V
[00000090]   01 00 04 43 6F 64 65 01   00 0F 4C 69 6E 65 4E 75   ...Code. ..LineNu
[000000a0]   6D 62 65 72 54 61 62 6C   65 01 00 12 4C 6F 63 61   mberTabl e...Loca
[000000b0]   6C 56 61 72 69 61 62 6C   65 54 61 62 6C 65 01 00   lVariabl eTable..
[000000c0]   04 74 68 69 73 01 00 0E   4C 6A 76 6D 63 6C 61 73   .this... Ljvmclas
[000000d0]   73 74 65 73 74 3B 01 00   04 6D 61 69 6E 01 00 16   stest... .main...
[000000e0]   28 5B 4C 6A 61 76 61 2F   6C 61 6E 67 2F 53 74 72   ..Ljava. lang.Str
[000000f0]   69 6E 67 3B 29 56 01 00   04 61 72 67 73 01 00 13   ing..V.. .args...
[00000100]   5B 4C 6A 61 76 61 2F 6C   61 6E 67 2F 53 74 72 69   .Ljava.l ang.Stri
[00000110]   6E 67 3B 01 00 04 74 65   73 74 01 00 04 28 49 29   ng....te st....I.
[00000120]   56 01 00 08 3C 63 6C 69   6E 69 74 3E 01 00 0A 53   V....cli nit....S
[00000130]   6F 75 72 63 65 46 69 6C   65 01 00 11 6A 76 6D 63   ourceFil e...jvmc
[00000140]   6C 61 73 73 74 65 73 74   2E 6A 61 76 61 0C 00 11   lasstest .java...
[00000150]   00 12 0C 00 0B 00 0C 01   00 06 63 61 6F 68 61 6F   ........ ..caohao
[00000160]   0C 00 0F 00 10 01 00 0C   6A 76 6D 63 6C 61 73 73   ........ jvmclass
[00000170]   74 65 73 74 0C 00 1C 00   1D 07 00 2B 0C 00 2C 00   test.... ........
[00000180]   2D 0C 00 0D 00 0E 01 00   10 6A 61 76 61 2F 6C 61   ........ .java.la
[00000190]   6E 67 2F 4F 62 6A 65 63   74 01 00 11 6A 61 76 61   ng.Objec t...java
[000001a0]   2F 6C 61 6E 67 2F 49 6E   74 65 67 65 72 01 00 07   .lang.In teger...
[000001b0]   76 61 6C 75 65 4F 66 01   00 16 28 49 29 4C 6A 61   valueOf. ...I.Lja
[000001c0]   76 61 2F 6C 61 6E 67 2F   49 6E 74 65 67 65 72 3B   va.lang. Integer.
[000001d0]   00 21 00 05 00 0A 00 00   00 03 00 01 00 0B 00 0C   ........ ........
[000001e0]   00 00 00 09 00 0D 00 0E   00 00 00 02 00 0F 00 10   ........ ........
[000001f0]   00 00 00 04 00 01 00 11   00 12 00 01 00 13 00 00   ........ ........
[00000200]   00 42 00 02 00 01 00 00   00 10 2A B7 00 01 2A 04   .B...... ........
[00000210]   B5 00 02 2A 12 03 B5 00   04 B1 00 00 00 02 00 14   ........ ........
[00000220]   00 00 00 0E 00 03 00 00   00 01 00 04 00 02 00 09   ........ ........
[00000230]   00 04 00 15 00 00 00 0C   00 01 00 00 00 10 00 16   ........ ........
[00000240]   00 17 00 00 00 09 00 18   00 19 00 01 00 13 00 00   ........ ........
[00000250]   00 4B 00 02 00 02 00 00   00 0F BB 00 05 59 B7 00   .K...... .....Y..
[00000260]   06 4C 2B 10 0A B6 00 07   B1 00 00 00 02 00 14 00   .L...... ........
[00000270]   00 00 0E 00 03 00 00 00   07 00 08 00 08 00 0E 00   ........ ........
[00000280]   09 00 15 00 00 00 16 00   02 00 00 00 0F 00 1A 00   ........ ........
[00000290]   1B 00 00 00 08 00 07 00   1C 00 17 00 01 00 01 00   ........ ........
[000002a0]   1C 00 1D 00 01 00 13 00   00 00 3E 00 02 00 02 00   ........ ........
[000002b0]   00 00 06 2A 1B B5 00 02   B1 00 00 00 02 00 14 00   ........ ........
[000002c0]   00 00 0A 00 02 00 00 00   0C 00 05 00 0D 00 15 00   ........ ........
[000002d0]   00 00 16 00 02 00 00 00   06 00 16 00 17 00 00 00   ........ ........
[000002e0]   00 00 06 00 0D 00 0C 00   01 00 08 00 1E 00 12 00   ........ ........
[000002f0]   01 00 13 00 00 00 20 00   01 00 00 00 00 00 08 05   ........ ........
[00000300]   B8 00 08 B3 00 09 B1 00   00 00 01 00 14 00 00 00   ........ ........
[00000310]   06 00 01 00 00 00 03 00   01 00 1F 00 00 00 02 00   ........ ........
[00000310]   20                                                  .

           

下面來依次分析内容

魔數與版本号

第一個看到的就是傳說中的cafebaby他占據了開始的四個位元組并且内容一定是0xCAFEBABY

接下來就是主版本号和次版本号了,JVM的版本是向下相容的也就是說如果目前我們的JDK是1.8的話運作起來的JVM程序會相容1.7版本JDK所寫的位元組碼檔案而這裡的檢查就是看魔數後面的這兩個版本号來檢查的

版本号依舊占據了四個位元組前兩個是主版本号後兩個位元組是次版本号,咱們這裡是00 00 00 34對應的數字就是52也就是JDK1.8 Java的版本号是從45開始計算作為JDK1.1随後依次加1,比如說JDK1.2就是46這樣的

常量池

接下來就是重中之重常量池了,其實JVM内部使用C的一系列oop-klass模型的執行個體對象來儲存這些資訊,不過這裡不做介紹就簡單的來分析一下常量池的這部分位元組碼

首先一定是一個常量池中的常量個數随後跟着一系列的常量元素的資訊而每一個元素都是由一個tag和一個date組成

tag作為标簽标定了目前元素是那種類型的常量,JVM定義了11中常量如下

這裡的tag占據了一個位元組

名稱 tag标記 含義
1 CONSTANT_utf8_Info utf8編碼的字元串
3 CONSTANT_Integer_Info 整形字面量
4 CONSTANT_Float_Info 浮點型
5 CONSTANT_Long_Info 長整型
6 CONSTANT_Double_Info 雙精度型
7 CONSTANT_Class_Info 類或者接口的引用
8 CONSTANT_String_Info 字元串類型
9 CONSTANT_Fieldref_Info 字段的引用
10 CONSTANT_Methodref_Info 方法的引用
11 CONSTANT_InterfaceMrthodref_Info 接口中方法的引用
12 CONSTANT_NameAndType_Info 字段和方法名稱以及類型的符号引用

上面的11種元素類型都在JVM内部以某種C類的結構來儲存資訊

除了utf8以外的結構都沒有length這個屬性,其他的都是tag+index而這個是tag+byte+length

length的含義就是表示bytes的長度而bytes則是他的内容資料是以說他的長度是1+2+length

而其他的都是1+2或者1+2+2

在JVM内用U2來描述contentpool的元素個數也就是兩個位元組,我們這裡他是0x00 2E也就是46個元素JVM規定0這個位置不能使用可能是為了友善吧。

這裡不會全部解析拿出一兩個看一下就行

第一個元素

根據上面的分析首先會是一個占據一個位元組大小的tag 0X0A這裡的tag是10也就是CONSTANT_Methodref_Info

他的結構是 tag index index這樣的結構

随後就是兩個U2的索引分别是00 0A和 00 21也就是10和33分别代表了常量池中的第十個和第33個常量

第二個元素

随後就是第二個元素了首先tag是09代表了CONSTANT_Fieldref_Info字段資訊

他的結構是tag加兩個索引是以還是往後讀兩個U2是00 05和 00 22

其他的也都是這樣的元素

通路标記和繼承資訊

接下來就是access_flag,this_class,super_class和interface了

首先就是U2的access_flag

他也是在JVM内部規定好了的格式

名稱 标記 含義
ACC_PUBLIC 0x0001 是否為public
ACC_FINAL 0x0010 是否被聲明了final
ACC_SUPER 0x0020 是否允許invokespecial,JDK1.2後編譯出來的類這個都為真
ACC_INTERFACE 0x0200 标記是一個接口
ACC_ABSTRACT 0x0400 标記抽象類或者接口
ACC_SYNTHETIC 0x1000 标記這個類并非是使用者代碼所産生的
ACC_ANNOTATION 0x2000 标記是一個注解
ACC_ENUM 0x4000 一個枚舉

我們這裡這一項是0x00 21也就是super+public是一個public修飾的class

然後是this_class标記,是一個U2的索引指向常量池中˙的一個元素,我們這裡是0x00 05也就是常量池中的第5個元素這個元素是一個classinfo他的index是37在去看37元素是一個Utf8他的bytes是jvmclasstest也就是我們的類名

随後就是superclass了這個和thisclass基本一緻的解析也是一個index,我們這裡是0x00 0A也就是索引10這裡的10是一個classinfo他的index是42我們的42是utf8他的bytes是java/lang/Object

在後面就是我們的interface了我們的類是可以實作很多接口的是以,不出意外和constantpool一樣會先有一個interface的count這個byte之後就是一系列的interface了這裡的count也是一個u2的資料我們這裡是0x00 00因為我們并沒有實作接口是以是這樣的不過就算有後面的也都是一些指向常量池的u2索引而已。

字段資訊

接下來就是我們的field了。其實上面分析了這麼多應該有經驗了像是這種多個元素的都會是數量+元素數組的形式存在。

那麼首先就是一個u2的count,我們這裡是0x00 03也就是三個field,和我們的類是完全一緻的。

随後就是一系列的field_info了這個fieldinfo也是JVM内部定義的一種結構它由以下幾種内容組成

  1. u2的accessflag這個内容有一個
  2. u2的fieldname的索引指向constantpool
  3. u2的描述資訊也是一個index
  4. u2的field的attribute數量
  5. 一系列的attribute_info

這就是field的結構,下面來分析一下我們的例子類

我們拿出個field來看,我們在類裡定義的第一個field是 public int i = 1;

首先是accessflag:00 01是一個public

fieldname:00 0B index11->utf8的i

描述:00 0C。index12是一個utf8的大寫的I代表了int

count:00 00也就是沒有内部attribute

方法資訊

method和field的内容基本上是一樣的首先也是一個u2的count

随後就是一系列的method_info了甚至methodinfo的結構和fieldinfo也是一樣的感興趣的可以自己分析一下這個16進制檔案

繼續閱讀