JVM技术专题针对于Class字节码的文件分析和研究指南 「 进阶篇」

Posted 浩宇の天尚

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM技术专题针对于Class字节码的文件分析和研究指南 「 进阶篇」相关的知识,希望对你有一定的参考价值。

任何足够先进的科技,都与魔法无异

承接上篇:

【JVM技术专题】针对于Class字节码的文件分析和研究指南 「 入门篇」

字段表集合

字段表集合用于描述接口或者类中声明的变量,这里的数据为:0000。

fields_count 字段计数器

fields_count 的值表示当前 Class 文件 fields[]数组的成员个数。 fields[]数组中每一项都是一个 field_info 结构的数据项,它用于表示该类或接口声明的类字段或者实例字段。

fields[] 字段表

fields[]数组中的每个成员都必须是一个 fields_info 结构的数 据项,用于表示当前类或接口中某个字段的完整描述。fields[]数组描述当前类或接口 声明的所有字段,但不包括从父类或父接口继承的部分。

field_info 结构格式如下:

field_info  
    u2 access_flags;
    u2 name_index;
    u2 descriptor_index;
    u2 attributes_count;
    attribute_info attributes[attributes_count];

属性集合

attributes_count 属性计数器

attributes_count 的值表示当前 Class 文件 attributes 表的成员个 数。attributes 表中每一项都是一个 attribute_info 结构的数据项。

attributes[]属性表

属性表,attributes 表的每个项的值必须是 attribute_info 结构(§4.7)。

属性(Attributes)在 Class 文件格式中的 ClassFile 结构、field_info 结构,method_info 结构和 Code_attribute 结构都有使用,所有属性的通用格式如下:


对于任意属性,attribute_name_index 必须是对当前 Class 文件的常量池的有效16 位无符号索引。常量池在该索引处的项必须是 CONSTANT_Utf8_info 结构,表示当前属性的名字。attribute_length 项的值给出了跟随其后的字节的长度,这个长度不包括 attribute_name_index 和 attribute_name_index 项的 6 个字节。

有些属性因 Class 文件格式规范所需,已被预先定义好。这些属性在表 4.6 中列出,同时,被列出的信息还包括它们首次出现的Class文件版本和 Java SE 版本号。在本规范定义的环境 中,也就是已包含这些预定义属性的 Class 文件中,它们的属性名称被保留,不能再被属性表中其他的自定义属性所使用。 下表是 class 文件的属性:

方法表集合

在字段表后的 2 个字节是一个方法计数器,表示类中总有有几个方法,在字段计数器后,才是具体的方法数据。这里数据为:0002 。

methods_count 方法计数器

methods[] 数组中的每个成员都必须是一个 method_info 结构的 数据项,用于表示当前类或接口中某个方法的完整描述。如果某个 method_info 结构 的 access_flags 项既没有设置 ACC_NATIVE 标志也没有设置 ACC_ABSTRACT 标志, 那么它所对应的方法体就应当可以被 Java 虚拟机直接从当前类加载,而不需要引用其它 类。method_info 结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、 实例初始化方法方法和类或接口初始化方法方法。methods[]数组 只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。

method_info  
    u2 access_flags;
    u2 name_index;
    u2 descriptor_index;
    u2 attributes_count;
    attribute_info attributes[attributes_count];

Log 类的字节码文件中,方法计数器的值为 00 02,表示一共有 2 个方法。

第一个方法:

0001 000700 0800 0100 0900 0000 1d00 0100 0100 0000 052a b700 01b1 0000 0001 000a 0000 0006 0001 0000 0003

  • 方法计数器后 2 个字节表示方法访问标识,这里是 0001,表示其实 ACC_PUBLIC 标识,对比上面的图表可知其表示 public 访问标识。
  • 紧接着 2 个字节表示方法名称的索引,这里是 00 07 表示指向了常量池第 7 个常量,查阅可知其指向了。
  • 紧接着的 2 个字节表示方法描述符索引项,这里是 00 08 表示指向了常量池第 8 个常量,查阅可知其指向了()V。
  • 紧接着 2 个字节表示属性表计数器,这里是 00 01 表示该方法的属性表一共有 1 个属性。属性表的表结构如下:

attribute_info 
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];

前两个字节是名字索引、接着 4 个字节是属性长度、接着是属性的值。这里前两个字节为 0009,指向了常量池第 9 个常量,查询可知其值为 Code,说明此属性是方法的字节码描述。


Code 属性是一个变长属性,位于method_info结构的属性表。一个 Code 属性只为唯一一个方法、实例类初始化方法或类初始化方法保存 Java 虚拟机指令及相关辅助信息。** 所有 Java 虚拟机实现都必须能够识别 Code 属性。如果方法被声明为 native 或者 abstract 类型,那么对应的 method_info 结构不能有明确的 Code 属性,其它情况下, method_info 有必须有明确的 Code 属性。**

Code 属性的格式如下:

Code_attribute 
    u2 attribute_name_index; 
 u4 attribute_length;
    u2 max_stack;
    u2 max_locals;
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;  u2 start_pc;
            u2 end_pc;
            u2 handler_pc;
            u2 catch_type;
     exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];

  • 根据 Code 属性对应表结构知道,前 2 个字节为 0009,即常量池第 9 个常量,查询知道是字符串常量 Code。
  • 接着 4 个字节表示属性长度,这里值为 0000 001d,即 29 的长度。下面我们继续分析 Code 属性的数据内容。
  • 紧接着 2 个字节为 max_stack 属性。这里数据为 00 01,表示操作数栈深度的最大值。
  • 紧接着 2 个字节为 max_locals 属性。这里是数据为 00 01,表示局部变量表所需的存储空间为 1 个 Slot。在这里 max_locals 的单位是 Slot,Slot 是虚拟机为局部变量分配内存所使用的最小单位。
  • 接着 4 个字节为 code_length,表示生成字节码这里给的长度。
  • 这里数据为 00 00 00 05,表示生成字节码长度为 5 个字节。

那么紧接着 5 个自己就是对应的数据,这里数据为 2a b7 00 01 b1,这一串数据其实就是字节码指令。通过查询字节码指令表,可知其对应的字节码指令:


读入 2A,查表得 0x2A 对应的指令为 aload_0,这个指令的含义是将第 0 个 Slot 中为 reference 类型的本地变量推送到操作数栈顶。


读入 B7,查表得0xB7对应的指令为 invokespecial,这条指令的作用是以栈顶的 reference 类型的数据所指向的对象作为方法接收者,调用此对象的实例构造器方法、private 方法或者它的父类的方法。这个方法有一个 u2 类型的参数说明具体调用哪一个方法,它指向常量池中的一个 CONSTANT_Methodref_info 类型常量,即此方法的方法符号引用。

读入 00 01,这是 invokespecial 的参数,查常量池得 0x0001 对应的常量为实例构造器“”方法的符号引用。

读入 B1,查表得0xB1对应的指令为 return,含义是返回此方法,并且返回值为void。这条指令执行后,当前方法结束。

着 2 个字节为异常表长度,这里数据为 00 00,表示没有异常表数据。那么接下来也就不会有异常表的值。

紧接着 2 个字节是属性表的长度,这里数据为 00 01,表示有一个属性。该属性长度为一个 attribute_info 那么长。

首先,前两个字节表示属性名称索引,这里数据为:00 0A。指向了第 10 个常量,查阅可知值为:LineNumberTable。

LineNumberTable 属性是可选变长属性,位于 Code(§4.7.3)结构的属性表。它被调试 器用于确定源文件中行号表示的内容在 Java 虚拟机的 code[]数组中对应的部分。在 Code 属性 的属性表中,LineNumberTable 属性可以按照任意顺序出现,此外,多个 LineNumberTable 属性可以共同表示一个行号在源文件中表示的内容,即 LineNumberTable 属性不需要与源文件 的行一一对应。

LineNumberTable 属性格式如下:

LineNumberTable_attribute 
    u2 attribute_name_index;
    u4 attribute_length;
    u2 line_number_table_length; 
     
        u2 start_pc;
        u2 line_number;
     line_number_table[line_number_table_length];

其前两个字节是属性名称索引,就是上面已经分析过的 00 0A。

接着 4 个字节是属性长度,这里数据为 00 00 00 06,表示有 6 个字节的数据。接着 2 个字节是 LineNumberTable 的长度,这里数据是 00 01,表示长度为 1。接着跟着 1 个 line_number_info 类型的数据,下面是 line_number_info 表的结构,其包含了 start_pc 和 line_number 两个 u2 类型的数据项。前者是字节码行号,后者是 Java 源码行号。

那么接下来 2 个字节为 00 00,即 start_pc 表示的字节码行号为第 0 行。接着 00 03,即 line_number 表示 Java 源码行号为第 3 行。

从上面的反编译来看,上面的分析也是对的:


  public com.hello.test.Log();
    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 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello world!
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8

第二个方法这里就不详细分析了,大家可以自己对着上面的反编译结果进行分析。
第二个方法开头是 0009,表示的是 ACC_PUBLIC 与 ACC_STATIC 合在一起的结果。

以上是关于JVM技术专题针对于Class字节码的文件分析和研究指南 「 进阶篇」的主要内容,如果未能解决你的问题,请参考以下文章

JVM技术专题针对于ASM库生成和修改class文件开发指南 「 入门篇」

JVM技术专题 class字节码指令操作介绍「上篇」

JVM技术专题针对于HotSpot虚拟机对象学习和分析指南 「 入门篇」

JVM技术专题针对于加载器与双亲委派机制分析和研究指南 「 入门篇」

JVM技术专题 深入分析class字节码指令方法调用详解「原理篇」

JVM技术专题 虚拟机字节码执行引擎方法调用「 分析篇」