跳至主要內容

JVM (五) 手动解析.class 文件

安图新大约 9 分钟

JVM (五) 手动解析.class 文件

一:不同进制之间的转换

二进制:逢 2 进 1,数字 0-1。

八进制:逢 8 进 1,数字 0-7。三位二进制表示一位八进制。三位二进制最大为 111,最大为 7。

十进制:逢 10 进 1,数字 0-9。四位二进制表示一位十进制数,四位二进制组合有 16 种数字,取其中 10 个作为十进制的 0-9 数字的表示。称为 BCD 编码。8-4-2-1 编码。

十六进制:逢 16 进 1,数字 0-9,A,B,C,D,E,F。四位二进制表示一位 16 进制。最大的二进制 1111 刚好是 F。

二:小端和大端

大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。

小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

 
 

网络传输是大端模式。主机存储是小端模式。

三:Class 文件组成

一个 class 文件是由多部分组成的:

u1 占一个字节。u2 占两个字节。u4 占四个字节。

 
 

注意:

1)字节码文件中,如果实现的接口个数=0,下面的实现的接口部分是不存在的。

2)常量池有三种:(1)class 中的常量池--静态的 (2)运行时常量池--动态的(3)字符串常量池 StringTable

 
 

常量池中的具体的数据项的数据结构如下:

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;              //CONSTANT_Utf8_info
}
CONSTANT_Fieldref_info {
    u1 tag;
    u2 class_index;             //CONSTANT_Class_info
    u2 name_and_type_index;     //CONSTANT_NameAndType_info
}
CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;             //CONSTANT_Class_info
    u2 name_and_type_index;     //CONSTANT_NameAndType_info
}
CONSTANT_InterfaceMethodref_info {
    u1 tag;
    u2 class_index;             //CONSTANT_Class_info
    u2 name_and_type_index;     //CONSTANT_NameAndType_info
}
CONSTANT_String_info {
    u1 tag;
    u2 string_index;            //CONSTANT_Utf8_info
}
CONSTANT_Integer_info {
    u1 tag;
    u4 bytes;                   //直接数
}
CONSTANT_Float_info {
    u1 tag;
    u4 bytes;                   //直接数
}
CONSTANT_Long_info {
    u1 tag;
    u4 high_bytes;              //直接数
    u4 low_bytes;               //直接数
}
CONSTANT_Double_info {
    u1 tag;
    u4 high_bytes;              //直接数
    u4 low_bytes;               //直接数
}
CONSTANT_NameAndType_info {
    u1 tag;
    u2 name_index;              //CONSTANT_Utf8_info
    u2 descriptor_index;        //CONSTANT_Utf8_info
}
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;                  //直接数
    u1 bytes[length];           //改进版的UTF8
}

与标准 UTF8 不同的地方:

1、 null 字符使用 2 字节格式,而不是标准的 1 字节;

  • 避免内嵌 null 时分隔问题 2、 JVM 仅使用 1/2/3 字节的 UTF8,不使用标准中 4 字节格式;

  • 使用 2 个 3 字节 UTF8 来表示 3、 字符串结尾不会追加 null 终止符;

  • 紧凑

class 文件结构中的成员属性的数据结构

 

attributes_count 是指成员属性的属性个数,比如类属性是 final 的,就会有。如果这个数为 0,也就没有下面的 attribute_info 了。

class 文件结构中的成员方法的数据结构

 
 

上面提到的 attribute_info 所说的数据结构

 
 
 
 
 
 
 
 
 
 
 
 

描述符补充:

void 的描述符是:v。我们经常会看到()v,表示返回值为空的一个方法。

LClassName; 表示一个类的实例。例如一个 String 的实例:Ljava/lang/String;

也可以组合使用。[B 表示一个字节数组。

上面是数值的描述符。方法的描述符是: (数据类型的描述符)返回值的描述符。比如 main 函数的描述符是:([Ljava/lang/String;)V

 
 

不同 JDK 版本有不同的主版本号:

 
 

四:解析 Class 文件

在 idea 中可以通过 Jclasslib ByteCode viewer 插件查看字节码里面内容。

我们以下面的 java 类为例:

 
 

安装好插件之后,选中这个类点击 View,选择

 
 

在右侧就可以看到 class 里面都有那些内容:

 
 

一般信息是 class 文件信息的汇总。这个信息就是解析.class 文件得来的。我们尝试解析.class 文件来得到里面的内容。

我们这里使用 UltraEdit 来打开.class 文件,可以看到最原始的十六进制的数据。

 
 
 
 

我们按照上面 class 文件的结构来一行一行解析。

1:魔数

这里指用来判断文件类型的魔数,就是上面的 CA FE BA BE。如果开头不是这个魔数,这个 java 的 class 文件就不合法。

2:次版本号

占两个字节这里是 00 00 换算成十进制:0

3:主版本号

占两个字节,这里是 00 34 换算成十进制:52。 JDK 版本不对的时候经常看到这个数字,这是 1.8 版本的

4:常量池的大小

占两个字节,这里就是 00 29 换算成十进制:41。

目前我们按照 class 的结构解析了四个内容,和插件解析出来的内容是一致的。接下来就是常量池里面的内容了,但是这里有个注意的点常量池是从 01 开始计数的,常量池里面实际数据项的大小是计算出来的常量池大小-1

插件解析出来的常量池也是如此。

 
 
 
 

5:常量池里面数据项的解析

我们就解析前几项数据,其它的解析都是一样的按字节大小往后算。常量池数据项的数据结构按上面列出的十一项常量池数据结构来。

第一项是 tag 占一个字节。看 tag 的数值是多少来确定是哪一种数据类型。

1)常量池大小后面的第一个字节是:0A 换算成十进制是 10. tag 是 10 的是数据结构是:CONSTANT_Methodref_info

有两个描述符的索引值 index 分别占两个字节,分别是:00 07,00 1A,换算成十进制分别是:7,26.因此常量池第一项数据结构就是:

CONSTANT_Methodref_info {
    u1 tag;                       //10
    u2 class_index;             //CONSTANT_Class_info  7 指的就是符号引用
    u2 name_and_type_index;     //CONSTANT_NameAndType_info 26 指的就是符号引用
 }

2)第二项数据结构的 tag 是:08,换算成十进制是:8,对应的数据结构是:  CONSTANT_String_info

有一个指向字符串字面量的索引 index 占两个字节: 00 1B 对应的十进制:27

第二项数据结构:

CONSTANT_String_info {
    u1 tag;                      // 8
    u2 string_index;            //CONSTANT_Utf8_info  27 表示是对常量池中第27 个 CONSTANT_Utf8_info的引用
 }

3)第三项数据结构的 tag: 09 对应的数据结构是:CONSTANT_Fieldref_info

class_index 是 00 1C 十进制是 28

name_and_type_index 00 1D 十进制是 29

CONSTANT_Fieldref_info {
    u1 tag;
    u2 class_index;             //CONSTANT_Class_info
    u2 name_and_type_index;     //CONSTANT_NameAndType_info
}

4)第四项数据结构的 tag:08 对应 对应的数据结构是:  CONSTANT_String_info

有一个指向字符串字面量的索引 index 占两个字节: 00 1E 对应的十进制:30

数据结构项:

CONSTANT_String_info {
    u1 tag;                      // 8
    u2 string_index;            //CONSTANT_Utf8_info  30 表示是对常量池中第30 个 CONSTANT_Utf8_info的引用
 }

常量池中数据我们先解析到这里,下面都是按照这种套路来的,首先根据 tag 来确定数据结构然后看此数据结构下有几个子属性占几个字节,依次类推。

我们通过计算常量池到如下这行的位置就结束了。

 
 

按照 class 文件的结构下面就是 access flags-类的访问控制权限

6:access flags-类的访问控制权限

占两个字节,00 21。这个表示什么呢?从类访问和属性修饰符标识表 中没有找到这个数字,其实这是两个修饰符共同作用的结果。

0021 是 0x0001 | 0x0020 的结果,也就是 ACC_PUBLIC 和 ACC_SUPER 共同作用的。

7:类名

占两个字节,00 06 指向常量池中的第六项数据。就是我们的全限定类名。

 
 

8:父类名

占两个字节,00 07,常量池中的第七项数据如下:

 
 

9:实现接口个数

占两个字节,是 00 00,我们这个类没有实现接口,那么它下面哪一项实现的接口就在 class 文件中不存在了。

这里我们也可以得出一个结论 Java 中一个可以实现的最多的接口数量就是 FFFF。

10:成员属性

占两个字节,00 00,这里是没有的。所以下面的值也是没有的。

11:成员方法。

占两个字节是 00 02,有两个成员方法。按照上面说的成员方法的数据结构来解析。

第一个方法:

u2access_flag: 00 01 ACC_PUBLIC

u2name_index: 00 08 常量池中第八项数据。<init>方法

 
 

u2descriptor_index : 00 09 常量池中第九项数据。就是个描述符()V,具体意思下面再讲。

 
 

u2attributes_count: 00 01。有一个 attribute_info 属性。

接下来就是 attribute_info 的数据结构。

u2attribute_name_index: 00 0A 常量池的第十项数据。

 
 

当前是 Code_attribute 的数据结构。

u4attribute_length: 00 00 00 2F 十进制是 47.表示接下来的属性一共占用 47 个字节。

u2max_stack:操作数栈大小。00 01

u2max_locals: 局部表大小。00 01

u4code_length: 00 00 00 05

u1code(方法体,字节码指令):(连续 5 个)2A B7 00 01 B1.可以从 jclasslib 中看到他们代表什么字节码。

 
 

aload_0 可以点击进去看到。

   

u2exception_table_length 长度为 00 00.下面紧挨着部分就没有内容了。

u2attributes_cout: 00 02 。 Code 属性的属性。

再接下来两个字节是 00 0B.在常量池中是:第 11 个数据项:

 
 

接下来这个是 LineNumberTable 的数据结构:
u2attribute_name_index: 就是 00 0B。

u4attribute_length: 00 00 00 06(属性的总长度)

u2line_number_table_length:有多少个下面的数据结构: 00 01 就一个

u2start_pc: 00 00

u2line_number 00 03

从 jclasslib 中也可以看到:

 
 

再接下来就是:局部变量表了。

u2attribute_name_index : 00 0C 是 12

 
 

u4attribute_length:属性的总长度:00 00 00 0C 一共占 12 个字节

u2local_varibale_table_length:一共有多少个下面的数据结构:00 01

u2start_pc: 00 00

u2length: 00 05

u2name_index: 00 0D

u2descriptor_index: 00 0E

u2index : 00 00

和 jclasslib 翻译出来的是一致的。

 
 

12:除了方法,就剩下属性了

 
 

就按照上面列出的数据结构解析。

上次编辑于:
贡献者: Andy