一起来读字节码

什么是.class

      Java源文件被编译后被Java虚拟机所执行的代码使用了一种平台中立(不依赖于特定硬件及操作系统)的二进制格式来表示,并且经常(但并非绝对)以文件的形式存储,因此这种格式成为class文件格式。class文件格式中精确地定义了类与接口的表示形式。
      每个class文件都由字节流组成,每个字节含有8个二进制位,所有16位,32位和64位长度的数据将通过构造2个、4个和8个连续的8位字节来表示。在Java SDK中,可以使用java.io.DataInput、java.io.DataOutput等接口和java.io.DataInputStream和java.io.DataOutputStream等类来访问这种格式的数据。

ClassFile结构

      每个class文件对应一个如下所示的ClassFile结构:

ClassFile {
    u4                magic;
    u2                minor_version;
    u2                major_version;
    u2                constant_pool_count;
    cp_info           constant_pool[constant_pool_count-1];
    u2                access_flags;
    u2                this_class;
    u2                super_class;
    u2                interfaces_count;
    u2                interfaces[interfaces_count];
    u2                fields_count;
    field_info        fields[fields_count];
    u2                methods_count;
    method_info       methods[methods_count];
    u2                attributes_count;
    attribute_info    attributes[attributes_count];
}

      《Optimizing Java》的作者编了一句顺口溜帮忙记住上面这十部分:“My Very Cute Animal Turns Savage In Full Moon Areas.”
在这里插入图片描述

      JVM规范专门定义了一组专用的数据类型来表示class文件的内容,它们包括u1、u2和u4,分别代表1、2和4个字节的无符号数。对于其他类型如cp_info、field_info等都是复合结构,介绍到再详细讲。

准备

      在本次分析字节码的过程中以下代码将一直陪伴我们,我们将分析它的class文件:

package bytecode;

public class Test1 {
    private int a = 1;

    public int getA() {
        return a;
    }

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

      class文件是16进制的数据,需要使用WinHex打开,先下载安装吧!打开class文件如下所示(看不懂没关系,有规则的):
Test1.class文件

      为了判断我们的分析结果我们可以使用javap -verbose <filename>命令让Java告诉我们class文件的详细信息。

魔数

      所有的.class字节码文件的前4个字节都是魔数,魔数值为固定值:0xCAFEBABE。它的作用是确定这个文件是否为一个能被虚拟机所接受的class文件。

版本号

      继魔数之后的4个字节是版本信息,其中前2个字节为minor_version(副版本号),后2个字节为(主版本号)。如这次使用的Test1.class文件中的版本信息为00 00 00 34,转换为十进制则副版本号为0,主版本号为52。

常量池计数器

      紧接着版本号的2个字节为constant_pool_count(常量池计数器),此数值表示常量池中成员的数目。如这次使用的Test1.class文件中的版本信息为00 18,转换为十进制为24,说明常量池数目为24个。但是,我们查看javap给我们的信息可以发现实际上常量池数目为23。
常量池数目

      注意:常量池数组中元素的个数 = 常量池数 - 1(其中0暂时不使用),目的是满足某些常量池索引值的数据在特定情况下需要表达【不引用任何一个常量池】的含义;根本原因在于,索引为0也是一个常量(保留常量),只不过它不位于常量表中,这个常量就对应null值,所以常量池的索引从1而非0开始。

常量池

      一个Java类中定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是Class文件的资源仓库,比如说Java类中定义的方法与变量信息,都是存储在常量池中。常量池中主要存储两类常量:字面量与符号引用。字面量如文本字符串,Java中声明为final的常量值等,而符号引用如类和接口的全限定名,字段的名称和描述符,方法的名称和描述符等。
      常量池中的每一项都具备相同的特征——第1个字节作为类型标记,用于确定该项的格式,这个字节成为tag byte(标记字节、标签字节)
      常量池表中的所有项都具有如下通用格式。

cp_info {
    u1    tag;
    u1    info[];
}

      在常量池表中,每个cp_info项都必须表示cp_info类型的单字节"tag"项开头。后面info[]数组的内容由tag的值所决定。有效的tag和对应的值如下表所示。每个tag字节之后必须有两个或更多的字节,这些字节用于指定这个常量的信息,附加信息的格式由tag的值来决定。

名称 项目 类型 描述
CONSTANT_Utf8_info
tag u1 值为1
length u2 UTF-8编码的字符串长度
bytes u1 长度为length的UTF-8编码的字符串
CONSTANT_Integer_info
tag u1 值为3
bytes u4 按照高位在前存储的int值
CONSTANT_Float_info
tag u1 值为4
bytes u4 按照高位在前存储的float值
CONSTANT_Float_info
tag u1 值为5
bytes u8 按照高位在前存储的long值
CONSTANT_Double_info
tag u1 值为6
bytes u8 按照高位在前存储的double值
CONSTANT_Class_info
tag u1 值为7
index u2 指向全限定名常量项的索引。
CONSTANT_String_info
tag u1 值为8
index u2 指向字符串字面量的索引。
CONSTANT_Fieldref_info
tag u1 值为9
index u2 指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项
index u2 指向字段描述符CONSTANT_NameAndType_info的索引项
CONSTANT_Methodref_info
tag u1 值为10
index u2 指向声明方法的类描述符CONSTANT_Class_info的索引项
index u2 指向名称及类型描述符CONSTANT_NameAndType_info的索引项
CONSTANT_InterfaceMethodref_info
tag u1 值为11
index u2 指向声明方法的接口描述符CONSTANT_Class_info的索引项
index u2 指向名称及类型描述符CONSTANT_NameAndType_info的索引项
CONSTANT_NameAndType_info
tag u1 值为12
index u2 指向改字段或方法名称常量项的索引
index u2 指向该字段或方法描述符常量项的索引
CONSTANT_MethodHandle_info
tag u1 值为15
kind u1 值必须在1~9之间,表示方法句柄的类型
index u2 指向该方法 常量项的索引
CONSTANT_MethodType_info
tag u1 值为16
index u2 指向该方法类型描述符常量项的索引
CONSTANT_InvokeDynamic_info
tag u1 值为18
index u2 对当前class文件中引导方法表的bootstrap_methods数组的有效索引
index u2 对常量项的索引,必须为CONSTANT_NameAndType_info结构

      上表记不住?没关系,不必要去记,会查就行。看不懂,没关系,马上教!

      继Constant_pool_count后就是常量池了,我们知道常量池中的每一项都具备相同的特征——第1个字节作为类型标记。查看Test1.class字节码,tag为0x0A,十进制为10,查表得类型为CONSTANT_Methodref_info,该类型除tag外还有两个u2类型的index,所以接着数4个字节,前2个字节(0x0004表示4),后2个字节(0x0014表示20),OK!我们得到了第一个常量:#1=Methodref #4.#20。由于我们还没分析完所以暂时不知道引用的具体常量项含义。
#2=Fieldref   #3.#21

#3=Class	#22

#4=Class	#23

      接下来的类型是Constant_Utf8,除tag外length和bytes分别代表不同含义,length表示给Utf8字符串的占多少个字节,比如占5个字节,那么我们往后数5个字节的数据就是该字符串表示的具体内容了(就是bytes)。
#4=Class	#23

      按照上述方法分析出索引5~19之间的Utf8类型的数据如下:

#5 = Utf8			a
#6 = Utf8			I
#7 = Utf8			<init>
#8 = Utf8			()V
#9 = Utf8			Code
#10= Utf8			LineNumberTable
#11= Utf8			LocalVariableTable
#12= Utf8			this
#13= Utf8			Lbytecode/Test1;
#14= Utf8			getA
#15= Utf8			()I
#16= Utf8			setA
#17= Utf8			(I)V
#18= Utf8			SourceFile
#19= Utf8			Test1.java

5~19Utf8

#20= NameAndType	#7.#8

#21= NameAndType	#5.#6

      最后的两个常量项都是Utf8类型,值为:

#22= Utf8           bytecode/Test1
#23= Utf8           java/lang/Object

      至此常量池中的所有项都分析完了,我们可以根据引用的索引项写上注释,结果如下所示:

Constant_pool:
	#1 = Methodref	   #4.#20				 //java/lang/Object.<init>:()V
	#2 = Fieldref		#3.#21				 //bytecode/Test1.a:I
	#3 = Class		   #22					//bytecode/Test1
	#4 = Class		   #23					//java/lang/Object
	#5 = Utf8			a
	#6 = Utf8			I
	#7 = Utf8			<init>
	#8 = Utf8			()V
	#9 = Utf8			Code
	#10= Utf8			LineNumberTable
	#11= Utf8			LocalVariableTable
	#12= Utf8			this
	#13= Utf8			Lbytecode/Test1;
	#14= Utf8			getA
	#15= Utf8			()I
	#16= Utf8			setA
	#17= Utf8			(I)V
	#18= Utf8			SourceFile
	#19= Utf8			Test1.java
	#20= NameAndType	 #7:#8				  //<init>:()V	
	#21= NameAndType	 #5:#6				  //a:I
	#22= Utf8            bytecode/Test1
	#23= Utf8            java/lang/Object

      也许您会疑惑诸如()V,Lbytecode/Test1;等书写方式,读完下面这三段就知道了:

  1. 在JVM规范中,每个变量/字段都有描述信息,描述信息主要的作用是描述字段的数据类型、方法的参数列表(包括数量、类型与顺序)与返回值。根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符来表示,对象类型则使用字符L加对象的全限定名来表示。为了压缩字节码文件的体积,对于基本数据类型,JVM都只使用一个大写字母来表示,B - byte,C - char,D - double,F - float,I - int,J - long,S - short,Z - boolean,V - void,L - 对象类型,如Ljava/lang/String;

  2. 对于数组类型来说,每一个维度使用一个前置的[来表示,如int[]被记录为[I,String[][]被记录为[[Ljava/lang/String;

  3. 用描述符描述方法时,按照先参数列表,后返回值的顺序来描述。参数列表按照参数的严格顺序放在一组()之内,如方法String
    setNameAndAge(String name,int
    age)的描述符为:(Ljava/lang/String;,I)Ljava/lang/String;

      对比javap命令分析的结果,完美!OK,分析常量池就结束了,接下来文章将分析余下的结构。

访问权限

      紧接着常量池的2个字节是access_flags,它的值表示某个类或者接口的访问权限及属性。每个标志的取值及其含义如下表所示:

标志名 含义
ACC_PUBLIC 0x0001 声明为public,可以包外访问
ACC_FINAL 0x0010 声明为final,不允许有子类
ACC_SUPER 0x0020 当用到invokespecial指令时,需要对父类方法做特殊处理
ACC_INTERFACE 0x0200 该class文件定义的是接口而不是类
ACC_ABSTRACT 0x0400 声明为abstract,不能被实例化
ACC_SYNTHETIC 0x1000 声明为synthetic,表示该class文件并非由java源代码所生成
ACC_ANNOTATION 0x2000 标识注解类型
ACC_ENUM 0x4000 标识枚举类型

请输入图片描述

      示例代码的字节码显示access_flags为0x0021,可是表中没有这个值呀??这是因为JVM将不同的值进行加法运算得到不同的组合,这里就是ACC_SUPERACC_PUBLIC。ACC_SUPER可以不用管它,只需要知道ACC_PUBLIC,即这个类使用public修饰,确实如此。

类和父类

      继access_flags后的4个字节表当前类名索引和父类名索引,具体的值在常量池中。如图所示,this_class值为3,super_class值为4,分别对应常量池中的bytecode/Test1和java/lang/Object。如果这个class文件的super_class值为0,那么这个class文件只可能用来表示Object类,因为它是唯一没有父类的类。
请输入图片描述

接口

      接下来的4个字节与接口有关(如果有父接口的话是4个字节,无父接口则仅为2个字节)。前2个字节的interfaces_count(接口计数器)表示当前类或接口的直接超接口量。后2个字节的interfaces[](接口表)中的每个成员的值必须是对常量池表中某项的有效索引,它的长度为interfaces_count。每个interfaces[i]必须为CONSTANT_Class_info结构,其中0<=i<interfaces_count。在interfaces[]中,各成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左至右)一样,即interfaces[0]对应的是源代码中最左边的接口。如果interfaces_count为0,那么在字节码中是不存在interfaces[]的。
请输入图片描述

成员字段

      接下来的2个字节表示fields_count(字段计数器),它的值表示当前class文件fields表的成员个数。当前字节码中的值为0x0001(十进制为1),我只声明了一个实例字段a。fields表中每个成员都是一个field_info结构(如下所示),用于表示该类或接口所声明的类字段(static修饰)或者实例字段。

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

      field_info结构各项的说明如下:

  • access_flags:它的值用来表示字段的访问权限和基本属性。具体如下表所示:
标志名 含义
ACC_PUBLIC 0x0001 声明为public,可以包外访问
ACC_PRIVATE 0x0002 声明为private,只能在定义该字段的类中访问
ACC_PROTECTED 0x0004 声明为protected,子类可以访问
ACC_STATIC 0x0008 声明为static
ACC_FINAL 0x0010 声明为final,对象构造好之后就不能直接设置该字段了
ACC_VOLATILE 0x0040 声明为volatile,被标识的字段无法缓存
ACC_TRANSIENT 0x0080 声明为transient,被标识的字段不会为持久化对象管理器所写入或读取
ACC_SYNTHETIC 0x1000 被表示的字段由编译器产生,而没有写在源代码中
ACC_ENUM 0x4000 该字段声明为某个枚举类型(enum)的成员
  • name_index:顾名思义,这是表示字段名称的索引。它的值必须是常量池中的一个有效索引,且必须为CONSTANT_Utf8_info结构。

  • descriptor_index:它的值也必须是常量池中的一个有效索引,且必须是CONSTANT_Utf8_info结构。

  • attributes_count:它的值表示当前字段的附加属性(attribute_info)的数量。

  • attributes[]:属性表(attributes[])中的每个成员,其值必须是attribute_info结构(如下表示的是一个通用属性结构)。一个字段可以管理任意多个属性。

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

      示例class文件的分析如下:
请输入图片描述

方法

      接下来的2个字节表示methods_count(方法计数器),表示当前class文件methods表的成员个数。methods表中的每个成员都是一个method_info结构。methods[](方法表)中的每个成员都必须是一个method_info结构,用于表示当前类或接口中某个方法的完整描述。

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

      method_info结构各项的说明如下:

  • access_flags:它的值用来表示字段的访问权限和基本属性。具体如下表所示:
标志名 含义
ACC_PUBLIC 0x0001 声明为public,可以包外访问
ACC_PRIVATE 0x0002 声明为private,只能在定义该方法的类中访问
ACC_PROTECTED 0x0004 声明为protected,子类可以访问
ACC_STATIC 0x0008 声明为static
ACC_FINAL 0x0010 声明为final,不能被覆盖
ACC_SYNCHRONIZED 0x0020 声明为synchronized,对该方法的调用,将包装在同步锁(monitor)里
ACC_BRIDGE 0x0040 声明为bridge方法,由编译器产生
ACC_TRANSIENT 0x0080 声明为transient,被标识的字段不会为持久化对象管理器所写入或读取
ACC_VARARGS 0x0080 表示方法带有变长参数
ACC_NATIVE 0x0100 声明为native,该方法不是用Java语言实现
ACC_ABSTRACT 0x0400 声明为abstract,该方法没有实现代码
ACC_STRICT 0x0800 声明为strictfp,使用FP-strict浮点模式
ACC_SYNTHETIC 0x1000 该方法是由编译器合成的,而不是由源代码编译
  • name_index:顾名思义,这是表示字段名称的索引。它的值必须是常量池中的一个有效索引,且必须为CONSTANT_Utf8_info结构。

  • descriptor_index:它的值也必须是常量池中的一个有效索引,且必须是CONSTANT_Utf8_info结构。

  • attributes_count:它的值表示当前字段的附加属性(attribute_info)的数量。

  • attributes[]:属性表(attributes[])中的每个成员,其值必须是attribute_info结构。一个字段可以管理任意多个属性。方法中重点在于Code属性。有点复杂

      在实例代码的class文件中可以看见methods_count值为0x0003,说明代码中有3个方法,虽然我们只是声明了两个方法,可是我们不要忘记了无参构造方法,所以共有3个方法。
      接下来开始分析第一个方法。根据method_info结构,第一个access_flag属性为0x0001(public),第二个name_index属性为0x0007(#7),第三个descriptor_index属性为0x0008(#8),第四个attributes_count属性为0x0001(十进制为1),说明附加属性数量为1。不难发现第一个方法就是自动生成的无参构造方法了。
请输入图片描述

      根据attribute_info结构,第一个属性的name_index为0x0009,对应常量池中的Code。紧接着是4个字节的属性长度0x00000038(十进制为56),说明Code属性长度为56字节。接下来是分析这56个字节的内容了,前提是我们需要了解Code这个属性,这是JVM预定义的一个属性。其结构如下:

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[exceptionn_table_length];
    u2                attributes_count;
    attribute_info    attributes[attributes_count];
}

      Code_attribute结构各项的说明如下:

  • attribute_name_index:其值必须是对常量池的一个有效索引。常量池表在该索引处的成员必须是CONSTANT_Utf8_info结构,用以表示字符串"Code"。
  • attribute_length:表示当前属性的长度,不包括初始的6个字节(attribute_name_index和attribute_length)
  • max_stack:当前方法的操作数栈在方法执行的任何时间点的最大深度
  • max_locals:分配在当前方法引用的局部变量表中的局部变量个数,其中也包括调用此方法时用于传递参数的局部变量。
  • code_length:当前方法code[]数组的字节数,值必须大于0,即code[]数组不能为空
  • code[]:实现当前方法的Java虚拟机代码的实际字节内容
  • exception_table_length:exception_table表的成员个数
  • exception_table[]:数组每个成员表示code[]数组中的一个异常处理器。同时exception_table[]的每个成员包含如下4项:
    • start_pc和end_pc:表明了异常处理器在code[]中的有效范围。start_pc的值必须是对当前code[]中某一指令操作码的有效索引,end_pc的值要么是对当前code[]中某一指令操作码的有效索引,要么等于code_length的值。start_pc的值必须比end_pc小。当程序计数器在范围[start_pc,end_p)内时,异常处理器就将生效。
    • handler_pc:表示一个异常处理器的起点。handler_pc的值必须同时是对当前code[]和其中某一指令操作码的有效索引。
    • catch_type:如果catch_type的值不为0,那么它必须是对常量池表的一个有效索引。常量池表在该索引处的成员必须是CONSTANT_Class_info结构,用以表示当前异常处理器需要捕捉的异常类型。只有当抛出的异常是指定的类或者其子类的实例时,才会调用异常处理器。
  • attributes_count:表示Code属性中attributes[]数组中成员的个数。
  • attribute[]:属性表中的每个值都必须是attribute_info结构体,Code属性可以关联任意多个属性。

      接下来数56个字节,开始从max_stack开始分析(切勿从attribute_name_index开始分析,已经分析过了)。

maxstack        2
maxlocals       1 
code_length     10

也许大家会有疑惑了,无参构造方法是没有参数的呀!怎么maxlocals的值是1呢?那不是说明有一个局部变量吗?如果您学习过pyhton就知道python的方法中第一个参数必须是self,表示当前对象。Java在方法内部我们经常使用this.xxx获取对象中的属性,方法。所以这个隐藏的局部变量就是this。

      知道的code_length为10我们可以很简单的查看code具体的内容了,注意,这里说的内容不是Java代码,而是指令集对应的十六进制编号:
code

      我们通过这10个字节的16进制是很难得到下面的指令集的,这时候我们就可以开始使用插件来帮助我们了。

0: aload_0
1: invokespecial #1                  // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield      #2                  // Field a:I
9: return

      IDEA为我们提供了名为jclasslib的插件,通过它也提供非插件版供我们下载使用。https://github.com/ingokegel/jclasslib
请输入图片描述

      我们点击具体的助记符还会自动打开对应的官方文档,帮助我们更清楚的了解助记符的含义。以上助记符对应的十六进制编号如下所示:

aload_0        0x2a
invokespecial  0xb7
iconst_1       0x4
putfield       0xb5
return         0xb1

请输入图片描述

      注意:红色框部分表示的是额外的参数。
请输入图片描述

      接下来分析exception_table_length,值为0x0000,说明异常表成员数量为0。

LineNumberTable

      紧接着是attributes_count,值为0x0002,说明有两个附件属性。根据attribute_info结构,第一个属性的name_index为0x000A,对应常量池中的LineNumberTable
      LineNumberTable属性是可选的变长属性,位于Code结构的属性中。它被调试器用于确定源文件中由给定的行号所表示的内容,对应于Java虚拟机cod[]数组中的哪一部分。在Code属性的属性表中,LineNumberTable属性可以按照任意顺序出现。在Code属性attributes表中,可以有不止一个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];
}

      LineNumberTable结构各项的说明如下:

  • attribute_name_index:其值必须是对常量池的一个有效索引。常量池表在该索引处的成员必须是CONSTANT_Utf8_info结构,用以表示字符串"LineNumberTable"。
  • attribute_length:表示当前属性的长度,不包括初始的6个字节(attribute_name_index和attribute_length)
  • line_number_table_length:表示line_number_table[]数组的成员个数。
  • line_number_table[]:数组的每个成员都表明源文件中的行号会在code数组中的哪一条指令处发生变化。line_number_table的每个成员都具有如下两项:
    • start_pc:其值必须是code[]数组的一个索引,code[]在该索引处的指令码,表示源文件中新的行的起点。start_pc的值必须小于当前LineNumberTable属性所在的Code属性的code_length的值。
    • line_number:其值必须与源文件中对应的行号相匹配。

      接着分析attribute_length,值为0x0000000A(十进制为10),说明属性的长度为10。后续line_number_table_length的值为0x0002表示line_number_table的个数为2。对应插件生成的结果是一致的。至此,第一个属性分析完成。
请输入图片描述

请输入图片描述

LocalVariableTable

      开始分析第二个属性。根据attribute_info结构,第一个属性的name_index为0x000B,对应常量池中的LocalVariableTable
      LocalVariableTable属性是可选变长属性,位于Code属性的属性表中,调试器在执行方法的过程中可以用它来确定某个局部变量的值。在Code属性的属性表中,多个LineVariableTable属性可以按照任意顺序出现。Code属性attribute表中的每个局部变量,最多只能有一个LocalVariableTable属性。
      LocalVariableTable的结构如下所示:

LocalVariableTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_table_length;
    {
        u2    start_pc;
        u2    length;
        u2    name_index;
        u2    descriptor_index;
        u2    index;
    } local_variable_table[local_variable_table_length];
}

      LocalVariableTable结构各项的说明如下:

  • attribute_name_index:其值必须是对常量池的一个有效索引。常量池表在该索引处的成员必须是CONSTANT_Utf8_info结构,用以表示字符串"LocalVariableTable"。
  • attribute_length:表示当前属性的长度,不包括初始的6个字节(attribute_name_index和attribute_length)
  • local_variable_table_length:表示local_variable_table[]数组中成员的数量。
  • local_variable_table:数组中的每一项都以偏移量的形式给出了code数组中的某个范围。当局部变量处在这个范围内的时候,它是有值的。此项还会给出局部变量再当前帧的局部变量表(local variable array)中的索引。local_variable_table[]的每个成员都有如下5个项:
    • start_pc和length:当给定的局部变量处在code数组的[start_pc,start_pc + length)范围内,也就是处在由偏移量大于等于start_pc且小于start_pc + length的字节码所构成的范围内时,该局部变量必定具备某个值。start_pc的值必须是对当前Code属性的code[]的一个有效索弓|, code[]在这个索引处必须是一条指令的操作码。start_pc+length要么是当前Code属性的code[]数组的有效索引,且code[]在该索引处必须是一条指令的操作码,要么是刚超过code[]数组末尾的首个索引值。
    • name_index:值必须是对常量池表的一个有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,用来表示一个有效的非限定名,以指代这个局部变量。
    • descriptor_index:值必须是对常量池表的一个有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,此结构是个用来表示源程序中局部变量类型的字段描述符。
    • index:值为此局部变量在当前栈帧的局部变量表中的索引。如果在index索引处的局部变量是long或double类型,则占用index和index+1两个位置

      接下来的local_variable_table_length值为0x0001表示local_variable_table的个数为1。
      接着分析attribute_length,值为0x0000000C(十进制为10),说明属性的长度为10。后续local_variable_table_length的值为0x0001表示local_variable_table的个数为1。对应插件生成的结果是一致的。至此,第二个属性分析完成。
请输入图片描述

请输入图片描述

      查看local_variable_table可以看出无参构造方法中的一个局部变量为this。

      至此我们已经完整的分析完了第一个方法,后续方法也是一样的去分析。根据插件结构判断正确与否吧^_^

原创文章 234 获赞 1294 访问量 23万+

猜你喜欢

转载自blog.csdn.net/qq_25343557/article/details/105182611