Class的二进制文件分析---方法解析

Class的二进制文件分析—方法解析

1.代码

public class Man {
  private String name = "edwardy";
  private Integer age = 28;

  public void outPrint(){
    System.out.println("name : "+name+" age: "+age);
    }

  public static void main(String[] args) {
    Man man = new Man();
    man.outPrint();
  }
}

2.二进制文件(只截取了当前小节需要的部分)

在这里插入图片描述

3.javap verbose的常量池图(只截取了当前小节需要的部分)

public class Man
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // Man
   #2 = Utf8               Man
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               name
   #6 = Utf8               Ljava/lang/String;
   #7 = Utf8               age
   #8 = Utf8               Ljava/lang/Integer;
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Methodref          #3.#13         // java/lang/Object."<init>":()V
  #13 = NameAndType        #9:#10         // "<init>":()V
  #14 = String             #15            // edwardy
  #15 = Utf8               edwardy
  #16 = Fieldref           #1.#17         // Man.name:Ljava/lang/String;
  #17 = NameAndType        #5:#6          // name:Ljava/lang/String;
  #18 = Methodref          #19.#21        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  #19 = Class              #20            // java/lang/Integer
  #20 = Utf8               java/lang/Integer
  #21 = NameAndType        #22:#23        // valueOf:(I)Ljava/lang/Integer;
  #22 = Utf8               valueOf
  #23 = Utf8               (I)Ljava/lang/Integer;
  #24 = Fieldref           #1.#25         // Man.age:Ljava/lang/Integer;
  #25 = NameAndType        #7:#8          // age:Ljava/lang/Integer;
  #26 = Utf8               LineNumberTable
  #27 = Utf8               LocalVariableTable
  #28 = Utf8               this
  #29 = Utf8               LMan;
  #30 = Utf8               outPrint
  #31 = Fieldref           #32.#34        // java/lang/System.out:Ljava/io/PrintStream;
  #32 = Class              #33            // java/lang/System
  #33 = Utf8               java/lang/System
  #34 = NameAndType        #35:#36        // out:Ljava/io/PrintStream;
  #35 = Utf8               out
  #36 = Utf8               Ljava/io/PrintStream;
  #37 = Class              #38            // java/lang/StringBuilder
  #38 = Utf8               java/lang/StringBuilder
  #39 = String             #40            // name :
  #40 = Utf8               name :
  #41 = Methodref          #37.#42        // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
  #42 = NameAndType        #9:#43         // "<init>":(Ljava/lang/String;)V
  #43 = Utf8               (Ljava/lang/String;)V
  #44 = Methodref          #37.#45        // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #45 = NameAndType        #46:#47        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #46 = Utf8               append
  #47 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #48 = String             #49            //  age:
  #49 = Utf8                age:
  #50 = Methodref          #37.#51        // java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
  #51 = NameAndType        #46:#52        // append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
  #52 = Utf8               (Ljava/lang/Object;)Ljava/lang/StringBuilder;
  #53 = Methodref          #37.#54        // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #54 = NameAndType        #55:#56        // toString:()Ljava/lang/String;
  #55 = Utf8               toString
  #56 = Utf8               ()Ljava/lang/String;
  #57 = Methodref          #58.#60        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #58 = Class              #59            // java/io/PrintStream
  #59 = Utf8               java/io/PrintStream
  #60 = NameAndType        #61:#43        // println:(Ljava/lang/String;)V
  #61 = Utf8               println
  #62 = Utf8               main
  #63 = Utf8               ([Ljava/lang/String;)V
  #64 = Methodref          #1.#13         // Man."<init>":()V
  #65 = Methodref          #1.#66         // Man.outPrint:()V
  #66 = NameAndType        #30:#10        // outPrint:()V
  #67 = Utf8               args
  #68 = Utf8               [Ljava/lang/String;
  #69 = Utf8               man
  #70 = Utf8               SourceFile
  #71 = Utf8               Man.java
{
  public Man();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #12                 // Method java/lang/Object."<init>":()V
         4: aload_0
         5: ldc           #14                 // String edwardy
         7: putfield      #16                 // Field name:Ljava/lang/String;
        10: aload_0
        11: bipush        28
        13: invokestatic  #18                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        16: putfield      #24                 // Field age:Ljava/lang/Integer;
        19: return
      LineNumberTable:
        line 2: 0
        line 3: 4
        line 4: 10
        line 2: 19
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      20     0  this   LMan;

  public void outPrint();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=1, args_size=1
         0: getstatic     #31                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #37                 // class java/lang/StringBuilder
         6: dup
         7: ldc           #39                 // String name :
         9: invokespecial #41                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        12: aload_0
        13: getfield      #16                 // Field name:Ljava/lang/String;
        16: invokevirtual #44                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: ldc           #48                 // String  age:
        21: invokevirtual #44                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: aload_0
        25: getfield      #24                 // Field age:Ljava/lang/Integer;
        28: invokevirtual #50                 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
        31: invokevirtual #53                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        34: invokevirtual #57                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        37: return
      LineNumberTable:
        line 7: 0
        line 8: 37
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      38     0  this   LMan;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #1                  // class Man
         3: dup
         4: invokespecial #64                 // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #65                 // Method outPrint:()V
        12: return
      LineNumberTable:
        line 11: 0
        line 12: 8
        line 13: 12
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      13     0  args   [Ljava/lang/String;
            8       5     1   man   LMan;
}

4.方法解析

   在字段解析后,剩余的便是方法字节码。

4.1. methods_count

   methods_count是U2的两个字节的长度,顾名思义它的值代表的含义是有几个方法。具体到本案例中,可以看到字节码是0x0003,所以表示有三个方法。

4.2. methods_info集合

   method_info的长度是不同的,下面首先会给出method info 的结构图

类型名称数量含义
U2access_flag1方法的访问标识
U2name_index1名称索引
U2descriptor_index1方法信息描述索引
U2attributes_count1方法属性数量
U2attributesattributes_count方法属性(九大属性,由于篇幅精力有限,后面只介绍第一个方法用的属性,其它属性只列出)

   需要注意的是,access_flag又对应了一个值表,方法属性又包含9种,下面将分别列出。

4.2.1 access_flag图表
标志名称二进制值含义
ACC_PUBLIC0x0001方法是否是public
ACC_PRIVATE0x0002方法是否是private
ACC_PROTECTED0x0004方法是否是protected
ACC_STATIC0x0008方法是否是static
ACC_FINAL0x0010方法是否是final
ACC_SYNCHRONIZED0x0020方法是否是synchrionzed
ACC_BRIDGE0x0040方法是否有编译器产生的桥接方法
ACC_VARARGS0x0080是否接受不定参数
ACC_NATIVE0x0100字段是否是native
ACC_ABSTRACT0x0400字段是否是abstract
ACC_STRICTFP0x0800字段是否是strictfp
ACC_SYNTHETIC0x1000字段是否为编译器自动产生
4.2.2 九大属性之一:code属性

   code属性即java程序中方法体重的代码经过编译之后处理的字节码,注意接口和抽象方法是不存在code属性。code属性的结构图如下

类型名称数量含义
U2attribute_name_index1属性名索引地址
U4attribute_length1整个code属性的长度
U2max_stack1操作数栈深度最大值
U2max_locals1局部变量表的空间
U4code_length1code的长度
U1codecode_length
U2exception_table_length1异常长度
U2exception_tableexception_table_length异常信息
U2attributes_count1属性数
U2attributesattributes_count

   (1)attribute_length 指的整个是code属性的长度

   (2)max_locals 这个大小不是所有局部变量占用的Slot总和,而是局部变量的声明周期结束会分配给其它局部变量

   (3)code和code_length可以确定出编译后的字节码指令,code中存储的是一些列字节流指令。
需要注意的是,每个指令都是一个U1的单字节,一个U1的单字节长度是8位,共可以组合成256种指令,足够支撑JVM虚拟机的200多个指令。所以在解析字节码的时候,只要分割正确
,即每个字节码的第一个字节肯定是指令,至于该指令后面是否需要操作数,便需要根据第一个字节的指令查询指令集合判断后面是否需要操作数。

    java的指令集: https://blog.csdn.net/shi1122/article/details/8053605
4.2.3 九大属性之二:LineNumberTable属性

   用于描述java源码和字节码对应的关系,可以再编译的时候通过参数进行设置或关闭该属性,该属性的作用是在堆栈抛异常的时候可以记录代码的行号或者调试程序时候记录代码行号,其结构如下

类型名称数量含义
U2attribute_name_index1属性名索引地址
U4attribute_length1整个LineNumberTable属性的长度
U2line_number_table_length1长度
line_number_infoline_number—_table1line_number_info类型的数组

line_number_info类型的结构如下

类型名称数量含义
U2start_pc1字节码行号
U2line_number1java源码行号
4.2.4 九大属性之三:LocalVariableTable属性

   用于描述栈帧中局部变量表中的变量和源码中的变量之间的关系(非运行是必须,可以编译的时候指定不生成),该结构在IDE使用参数占位符的时候或调试获取上下文参数有用,其结构如下

类型名称数量含义
U2attribute_name_index1属性名索引地址
U4attribute_length1整个LocalVariableTable属性的长度
U2local_variable_table_length1长度
local_variable_infolocal_variable_table1local_variable_info类型的数组

而local_variable_info的结构如下

类型名称数量含义
U2start_pc1局部变量的声明周期开始的字节码便宜量
U2length1局部变量的作用范围覆盖的长度
U2name_index1指向常量池的索引,即局部变量名称
U2descriptor_index1指向常量池的索引,即局部变量描述名称索引
U2index1局部变量在栈帧局部变量表中的slot位置
4.2.5 九大属性之四:ConstantValue属性
https://blog.csdn.net/honjane/article/details/51835636
4.2.6 九大属性之五:Exception属性
https://blog.csdn.net/u014629433/article/details/51626686
4.2.7 九大属性之六:InnerClass属性
https://blog.csdn.net/silentbalanceyh/article/details/42640739
4.2.7 九大属性之七:SourceFile属性
https://blog.csdn.net/qq_36051652/article/details/80716165
4.2.9 九大属性之八:Deprecated和Synthetic属性
https://blog.csdn.net/csdn1017355712/article/details/79788456

4.3. 第一个方法

4.3.1 access_flag

   根据前面描述的规则可以知道,在methods_count后面的的U2字节码是00 01,代表的是第一个方法的访问标识,通过查询表可以知道是public。

4.3.2 name_index

   标识的方法名在常量池中的索引号,具体到当前的案例的字节码是00 09,通过查看对应的常量池可以推断出字符串是<init>,
即第一个方法名称是<init>

#7 = Utf8               age
#8 = Utf8               Ljava/lang/Integer;
#9 = Utf8               <init>
#10 = Utf8               ()V
#11 = Utf8               Code
4.3.3 descriptor_index

   也是指向常量池的描述信息索引,代表的含义是函数的入参和返回类型,结合当前的案例得字节码是00 0a,通过查询常量池可以知道,其值是()V,
()表示该函数的入参是空,V表示该函数的返回参数是void类型。

#7 = Utf8               age
#8 = Utf8               Ljava/lang/Integer;
#9 = Utf8               <init>
#10 = Utf8               ()V
#11 = Utf8               Code
4.3.4 attributes_count

   字节码是00 01,表示该属性只有1个

4.3.5 attributes info

   (1)attribute_name_index:U2长度,所以第一个方法的前两个字节是00 0b,即指向的是常量池为11的字符串,通过分析可知道代表的含义是Code

   (2)attribute_length:U4长度,00 00 00 4a,即整个code属性的长度是74个字节

   (3)max_stack:U2长度,00 02,即操作数栈的最大值2。

   (4)max_locals:U2长度,00 01,即局部变量表所需要的存储空间为1

   (5)code_length:U4长度,00 00 00 14,即代码的长度20

   (6)code:长度不定,长度是code_length,所以这里的长度是20,即2a b7 00 0c 2a 12 0e b5 00 10 2a 10 1c b8 00 12 b5 00 18 b1。

   2a是code字节指令中的第一个字节码,所以2a表示的是一个指令,即查询2a代表的指令aload_0,将第一个引用类型本地变量推送至栈顶。

0x2a  aload_0        将第一个引用类型本地变量推送至栈顶
0xB7  invokespecial   调用超类构造方法、实例初始化方法、私有方法(跟一个U2的字节数据,指向常量池中的方法编号)
0x12  ldc             将int、float或String型常量值从常量池中推送至栈顶,后面跟一个U1字节数据
0xB5  putfield        为指定的类的实例域赋值,后面跟一个U2字节
0x10  bipush          将一个byte型常量值推送至栈顶,后面跟一个U1字节数据
0xB8  invokestatic    调用静态方法,后面跟U2字节
0xB1  return          当前方法返回void

   b7 00 0c,invokespecial #12 调用 java/lang/Object."<init>"?)V进行初始化

   2a,同上,将第一个引用类型本地变量推送至栈顶

   12 0e,将字符串edwardy推送到栈顶

   b5 00 12,对Ljava/lang/string类型的字段name进行赋值,其值是上面推送到栈顶得edwardy

   2a,同上

   10 1c,将byte类型的28推送到栈顶(注意这里是28值,前面没有#,所以不是常量池的索引)

   b8 00 12,调用静态方法java.lang.Integer.valueOf(int i),将其转换为Integer类型

   b5 00 18,对Ljava/lang/Integer类型的字段age进行赋值,将上一步转换成Integer的28进行赋值

   b1。当前函数执行完,返回void

   总结第一个函数<init>,实际上就是对类和变量进行初始化


   (7)exception_table_length:exception_table_length长度为00 00,所以长度0

   (8)exception_table:exception_table_length长度为0,所以没有该属性

   (9)attributes_count:00 02,属性个数为2,表示该方法引用了其它两个属性表,所以要按照其它属性表进行分析

   (10)attributes:

    10.1 属性名是00 1a,即执行26的常量池,翻译过来即LineNumberTable属性,所以需要参考LineNumberTable属性结构进行分析。整个LineNumberTable的属性的字节码长度是00 00 00 12,即18个字节,而Line_number_info的个数有00 04即4个这种类型的数组。
分别是00 00 00 02,即第一组info的字节码行号是0,java源码行号是2,以此类推可以知道其他几个info分别是(4,3)(10,4)(19,2)

    10.2 属性名是00 1b,即LocalVariableTable属性,其整个属性的长度是00 00 00 0c,即长度是12。而局部变量属性的个数只有1个,即字节码是5个U2类型数据,00 00 00 14 00 1c 00 1d 00 00,所以start_pc是0、覆盖范围length是20、局部变量名称是28对应的是this、类型描述符是LMan,index为0。换言之即该变量的名称是this 描述符是LMan,其作用域是范围0+20,且在Slot为0的地方存储。

4.3.5 javap verbose的方法图
public Man();
  descriptor: ()V
  flags: ACC_PUBLIC
  Code:
    stack=2, locals=1, args_size=1
       0: aload_0
       1: invokespecial #12                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: ldc           #14                 // String edwardy
       7: putfield      #16                 // Field name:Ljava/lang/String;
      10: aload_0
      11: bipush        28
      13: invokestatic  #18                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      16: putfield      #24                 // Field age:Ljava/lang/Integer;
      19: return
    LineNumberTable:
      line 2: 0
      line 3: 4
      line 4: 10
      line 2: 19
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      20     0  this   LMan;

   可以看到,上述按照字节码一个个解析后的各个属性,最后都体现在上图。

4.4. 第二个方法是outPut的解析,和上述类似,略。

4.5. 第三个方法是main函数的解析,和上述类似,略。

5.总结

   可以看出方法的解析是最为麻烦的部分,涉及到9种属性。但只要严格按照字节码标准分割,一定可以准确解析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值