字节码文件完整解析

Posted 战国剑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了字节码文件完整解析相关的知识,希望对你有一定的参考价值。

 

java运行的机制:.java文件,经过编译后生成.class文件。java虚拟机加载.class文件运行代码逻辑。

本文将对.class字节码文件做一次完整解析。(以姜新星老师的字节码层面分析 class 类文件结构为基础,做一次完整解析)

https://kaiwu.lagou.com/course/courseInfo.htm?courseId=67#/detail/pc?id=1857

一、class文件结构

class文件由无符合数与表组成。

无符合数以 u1、u2、u4、u8 来分别代表 1 个字节、2 个字节、4 个字节和 8 个字节的无符号数。

表是以“_info”作为结尾。整个class文件,本质上是一张表。

二、代码示例解析

以以下代码为例,对代码做javac后,生成对应的字节码文件:

public class Test implements Serializable,Cloneable 

    private int num = 1;
    public int add (int i)
        int j = 10;
        num = num +i;
        return num;
    

字节码文件:

cafe babe 0000 0034 0017 0a00 0400 1109
0003 0012 0700 1307 0014 0700 1507 0016
0100 036e 756d 0100 0149 0100 063c 696e
6974 3e01 0003 2829 5601 0004 436f 6465
0100 0f4c 696e 654e 756d 6265 7254 6162
6c65 0100 0361 6464 0100 0428 4929 4901
000a 536f 7572 6365 4669 6c65 0100 0954
6573 742e 6a61 7661 0c00 0900 0a0c 0007
0008 0100 0454 6573 7401 0010 6a61 7661
2f6c 616e 672f 4f62 6a65 6374 0100 146a
6176 612f 696f 2f53 6572 6961 6c69 7a61
626c 6501 0013 6a61 7661 2f6c 616e 672f
436c 6f6e 6561 626c 6500 2100 0300 0400
0200 0500 0600 0100 0200 0700 0800 0000
0200 0100 0900 0a00 0100 0b00 0000 2600
0200 0100 0000 0a2a b700 012a 04b5 0002
b100 0000 0100 0c00 0000 0a00 0200 0000
0600 0400 0800 0100 0d00 0e00 0100 0b00
0000 3200 0300 0300 0000 1210 0a3d 2a2a
b400 021b 60b5 0002 2ab4 0002 ac00 0000
0100 0c00 0000 0e00 0300 0000 0a00 0300
0b00 0d00 0c00 0100 0f00 0000 0200 10

再对.class字节码文件做javap后,可看到反编译后的内容:

Classfile /Users/zhanguo/Documents/JavaStructure/src/Test.class
  Last modified 2020-8-22; size 351 bytes
  MD5 checksum 9aa7ceb14bd3c46f2c177d1f5f53e6f5
  Compiled from "Test.java"
public class Test implements java.io.Serializable,java.lang.Cloneable
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#17         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#18         // Test.num:I
   #3 = Class              #19            // Test
   #4 = Class              #20            // java/lang/Object
   #5 = Class              #21            // java/io/Serializable
   #6 = Class              #22            // java/lang/Cloneable
   #7 = Utf8               num
   #8 = Utf8               I
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               add
  #14 = Utf8               (I)I
  #15 = Utf8               SourceFile
  #16 = Utf8               Test.java
  #17 = NameAndType        #9:#10         // "<init>":()V
  #18 = NameAndType        #7:#8          // num:I
  #19 = Utf8               Test
  #20 = Utf8               java/lang/Object
  #21 = Utf8               java/io/Serializable
  #22 = Utf8               java/lang/Cloneable

  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_1
         6: putfield      #2                  // Field num:I
         9: return
      LineNumberTable:
        line 6: 0
        line 8: 4

  public int add(int);
    descriptor: (I)I
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=2
         0: bipush        10
         2: istore_2
         3: aload_0
         4: aload_0
         5: getfield      #2                  // Field num:I
         8: iload_1
         9: iadd
        10: putfield      #2                  // Field num:I
        13: aload_0
        14: getfield      #2                  // Field num:I
        17: ireturn
      LineNumberTable:
        line 10: 0
        line 11: 3
        line 12: 13

SourceFile: "Test.java"

字节码文件的解析过程,也就是:根据javac形成的16进制内容,解析得到反编译后的文件信息。

根据前面图中列的格式,开始做完全解析:

 

魔数:cafe babe  代表class文件
版本号:0000 0034  --> 52  -->代表1.8.0版本java
常量池大小:0017  --> 23(常量数量:23-1 = 22)
常量池(表结构,tag-->u1一个字节):
一、第一个常量
1:0a -->10(转为10进制索引,后续雷同)-->表示 CONSTANT_Methodref_info 结构
结构:
CONSTANT_Methodref_info 
    u1 tag = 10;
    u2 class_index;        指向此方法的所属类
    u2 name_type_index;    指向此方法的名称和类型


2: 00 04 -->指向常量4
3: 00 11 -->指向常量17 

二、第二个常量
1:09  --> 9 -->表示 CONSTANT_Fieldref_info
结构:
CONSTANT_Fieldref_info
    u1 tag;
    u2 class_index;        指向此字段的所属类
    u2 name_type_index;    指向此字段的名称和类型


2:0003 --> 指向常量3
3:0012 --> 指向常量18

三、第三个常量
1:07 --> 7 -->表示 CONSTANT_Class_info
结构:
table CONSTANT_Class_info 
    u1  tag = 7;
    u2  name_index;

2:00 13-->19 指向常量19

四、第四个常量
1:07 --> 7 -->表示 CONSTANT_Class_info
结构:
table CONSTANT_Class_info 
    u1  tag = 7;
    u2  name_index;

2:0014 -->20 指向常量20

五、第五个常量
1:07 --> 7 -->表示 CONSTANT_Class_info
结构:
table CONSTANT_Class_info 
    u1  tag = 7;
    u2  name_index;

2:0015 -->21 指向常量21

六、第六个常量
1:07 --> 7 -->表示 CONSTANT_Class_info
结构:
table CONSTANT_Class_info 
    u1  tag = 7;
    u2  name_index;

2:0016 -->22 指向常量22

七、第七个常量
1、01 --> 1 --> 表示 CONSTANT_utf8_info 
结构:
table CONSTANT_utf8_info 
    u1  tag;
    u2  length;
    u1[] bytes;

2、00 03 --> 3 --> 表示字节长度3
3、6e 75 6d --> num

八、第八个常量
1、01 --> 1 --> 表示 CONSTANT_utf8_info 
结构:
table CONSTANT_utf8_info 
    u1  tag;
    u2  length;
    u1[] bytes;

2、00 01 --> 1 --> 表示字节长度1
3、49 --> I

九、第九个常量
1、01 --> 1 --> 表示 CONSTANT_utf8_info 
结构:
table CONSTANT_utf8_info 
    u1  tag;
    u2  length;
    u1[] bytes;

2、00 06 --> 6 --> 表示字节长度6
3、3c 69 6e 69 74 3e --> <init>

十、第十个常量
1、01 --> 1 --> 表示 CONSTANT_utf8_info 
结构:
table CONSTANT_utf8_info 
    u1  tag;
    u2  length;
    u1[] bytes;

2、00 03 --> 3 --> 表示字节长度3
3、28 29 56 --> ()V

十一、第十一个常量
1、01 --> 1 --> 表示 CONSTANT_utf8_info 
结构:
table CONSTANT_utf8_info 
    u1  tag;
    u2  length;
    u1[] bytes;

2、00 04 --> 4 --> 表示字节长度4
3、43 6f 64 65 --> Code

十二、第十二个常量
1、01 --> 1 --> 表示 CONSTANT_utf8_info 
结构:
table CONSTANT_utf8_info 
    u1  tag;
    u2  length;
    u1[] bytes;

2、00 0f --> 15 --> 表示字节长度15
3、4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 ---> LineNumberTable

十三、第十三个常量
1、01 --> 1 --> 表示 CONSTANT_utf8_info 
结构:
table CONSTANT_utf8_info 
    u1  tag;
    u2  length;
    u1[] bytes;

2、00 03 --> 3--> 表示字节长度3
3、61 64 64 ---> add

十四、第十四个常量
1、01 --> 1 --> 表示 CONSTANT_utf8_info 
结构:
table CONSTANT_utf8_info 
    u1  tag;
    u2  length;
    u1[] bytes;

2、00 04 --> 4--> 表示字节长度4
3、28 49 29 49 01 ---> (I)I

十五、第十五个常量
1、01 --> 1 --> 表示 CONSTANT_utf8_info 
结构:
table CONSTANT_utf8_info 
    u1  tag;
    u2  length;
    u1[] bytes;

2、00 0a --> 10--> 表示字节长度10
3、53 6f 75 72 63 65 46 69 6c 65  ---> SourceFile

十六、第十六个常量
1、01 --> 1 --> 表示 CONSTANT_utf8_info 
结构:
table CONSTANT_utf8_info 
    u1  tag;
    u2  length;
    u1[] bytes;

2、00 09 --> 9--> 表示字节长度9
3、54 65 73 74 2e 6a 61 76 61  ---> Test.java

十七、第十七个常量
1、0c --> 13 --> 表示 CONSTANT_NameAndType_info
结构:
CONSTANT_NameAndType_info
    u1 tag;
    u2 name_index;    指向某字段或方法的名称字符串
    u2 type_index;    指向某字段或方法的类型字符串



2、00 09 --> 9--> 表示指向常量9
3、00 0a --> 10  ---> 表示指向常量10

十八、第十八个常量
1、0c --> 13 --> 表示 CONSTANT_NameAndType_info
结构:
CONSTANT_NameAndType_info
    u1 tag;
    u2 name_index;    指向某字段或方法的名称字符串
    u2 type_index;    指向某字段或方法的类型字符串



2、00 07--> 7--> 表示指向常量7
3、00 08 --> 8---> 表示指向常量8


十九、第十九个常量
1、01 --> 1 --> 表示 CONSTANT_utf8_info 
结构:
table CONSTANT_utf8_info 
    u1  tag;
    u2  length;
    u1[] bytes;

2、00 04 --> 4 --> 表示字节长度4
3、54 65 73 74  ---> Test


二十、第二十个常量
1、01 --> 1 --> 表示 CONSTANT_utf8_info 
结构:
table CONSTANT_utf8_info 
    u1  tag;
    u2  length;
    u1[] bytes;

2、00 10 --> 16 --> 表示字节长度16
3、6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 
  ---> java/lang/Object


二十一、第二十一个常量
1、01 --> 1 --> 表示 CONSTANT_utf8_info 
结构:
table CONSTANT_utf8_info 
    u1  tag;
    u2  length;
    u1[] bytes;

2、00 14 --> 20--> 表示字节长度20
3、6a 61 76 61 2f 69 6f 2f 53 65 72 69 61 6c 69 7a 61 62 6c 65
  ---> java/io/Serializable


二十二、第二十二个常量
1、01 --> 1 --> 表示 CONSTANT_utf8_info 
结构:
table CONSTANT_utf8_info 
    u1  tag;
    u2  length;
    u1[] bytes;

2、00 13 --> 19--> 表示字节长度19
3、6a 61 76 61 2f 6c 61 6e 67 2f 43 6c 6f 6e 65 61 62 6c 65 ---> java/lang/Cloneable


访问标志:00 21 --> public
类索引: 00 03 --> 3 -->指向常量池3
父类索引:00 04 --> 3 -->指向常量池4
接口索引计数:00 02 --> 2 -->实现的接口数量为2
接口索引集合:00 05 00 06 (计数器 * u2 )-->指向常量池5 、6
字段索引集合大小:00 01 --> 1
字段索引集合:
访问标志:00 02  
字段名称索引:00 07 
字段的描述索引:00 08 
属性计数器: 00 00 

结构:
CONSTANT_Fieldref_info
    u2  access_flags    字段的访问标志
    u2  name_index          字段的名称索引(也就是变量名)
    u2  descriptor_index    字段的描述索引(也就是变量的类型)
    u2  attributes_count    属性计数器
    attribute_info

访问标志:00 01 
方法名索引:00 09
方法类型索引:00 0a 
方法属性计数器:00 01
    属性表结构:
    CONSTANT_Attribute_info
    u2 name_index;
    u2 attribute_length length;
    u1[] info;
    

    属性的索引指向Code结构:

    类型  名称                数量
    u2  attribute_name_index  1
    u4  attribute_length      1
    u2  max_stack             1
    u2  max_locals            1
    u4  code_length           1
    u1  code               code_length
    u2  exception_table_length  1
    exception_info  exception_table exception_table_length
    u2  attributes_count       1
    attribute_info  attributes  attributes_count

    
    属性索引:00 0b --> Code
    Code长度:00 00 00 26 --> 38
    max_stack:00 02 -->2
    max_locals:00 01 -->1
    code_length:00 00 00 0a -->10
    code:2a b7 00 01 2a 04 b5 00 02 b1 --> aload_0 invokespecial #1  aload_0 iconst_1 putfield #2 return
    exception_table_length:--> 00 00 (exception数量0)
    attributes_count: --> 00 01 (属性数量) 
    attribute_info: --> 指向常量表12(LineNumberTable),

  
    LineNumberTable结构:
    类型  名称                数量
    u2  attribute_name_index  1
    u4  attribute_length      1
    u2  line_number_table_lentgh   1
    line_number_info  line_number_tagle   line_number_table_lentgh

    00 00 00 0a ---> LineNumberTable length ---> 10
    00 02 ---> line_number_table_lentgh  ---> 2

    line_number_info 结构
    类型  名称  数量
    u2   start_pc 1
    u2   line_number 1

    00 00 00 06  ---> 0  6
    00 04 00 08  ---> 4  8
访问标志:00 01 
方法名索引:00 0d
方法类型索引:00 0e 
方法属性计数器:00 01

    属性表结构(根据索引,仍指向Code结构):
    CONSTANT_Attribute_info
    u2 name_index;
    u2 attribute_length length;
    u1[] info;
    
    
    属性索引:00 0b --> Code
    Code长度:00 00 00 32 --> 52
    max_stack:00 03 -->3
    max_locals:00 03 -->3
    code_length:00 00 00 12 -->18
    code:10 0a 3d 2a 2a b4 00 02 1b 60 b5 00 02 2a b4 00 02 ac ---> bipush 10 istore_2 aload_0 aload_0 getfield #2 iload_1 istore_1 putfield #2 aload_0 getfield #2 ireturn
    exception_table_length:00 00(exception数量0)
    attributes_count:00 01 (属性数量)
    00 0c ---> 指向常量表12(LineNumberTable),
    
    结构:
    类型  名称                数量
    u2  attribute_name_index  1
    u4  attribute_length      1
    u2  line_number_table_lentgh   1
    line_number_info  line_number_tagle   line_number_table_lentgh


    00 00 00 0e ---> attribute_length ---> 15
    00 03 line_number_table_lentgh ---> 3
    00 00 00 0a ---> 0  10
    00 03 00 0b ---> 3  11
    00 0d 00 0c ---> 12 13

    00 01
    00 0f  --> 指向常量15
    00 00 00 02  --> 长度2
    00 10  --> 指向常量16

自此,完成所有解析。

Code解析对应命令内容,可参考:https://wds.gitee.io/jvm-code/codeByNo.html 

 

 

以上是关于字节码文件完整解析的主要内容,如果未能解决你的问题,请参考以下文章

深入理解JVM—— HelloWorld字节码完整解析

深入理解JVM—— HelloWorld字节码完整解析

python字节码(转)

Hermes源码分析(二)——解析字节码

COREJAVA核心概述-反射

COREJAVA核心概述-反射