美文网首页
Java字节码解析

Java字节码解析

作者: 洋葱520 | 来源:发表于2020-05-24 21:35 被阅读0次

    官网:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html

    1. 字节码整体结构

    02.jpg 03.jpg
    04.jpg

    1.0 class字节码数据类型

    • 字节数据直接量:这是基本的数据类型,细分为u1,u2,u4,u8四种,分别代表1个字节,2个字节,4个字节,8个字节组成的整体数据
    • 表(数组):表是由多个基本数据或其他表,按照既定顺序组成的大的数据集合。表示有结构的,它的结构体:组成表的成分所在的位置和顺序都是已经严格定义好的。

    1.1 魔数(Magic)

    • magic:u4,魔数,代表本文件是.class文件

    1.2 版本号(version)

    • minor_version:u2,次版本号
    • major_version:u2,主版本号,1.6(50) 1.7(51) 1.8(52)

    1.3 常量池(constant pool)

    • constant_pool_count,u2,常量池个数

    • 常量池数组(常量表),n,它与一般的数组不同的是,常量池数组中不同的元素的类型、结构都是不同的,长度当然也就不同,值得注意的是,常量池数组元素的个数=( constant_pool_count-1 ),0位暂时不使用。根本原因是,索引0也是一个常量(保留常量),只不过它不位于常量表中,这个常量就对应null值;所以常量池的索引从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

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

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

    • 常量池通常存两种常量:
      -- 字面量:如字符串、final修饰的常量等;
      -- 符号引用:如类/接口的全限定名、方法的名称和描述、字段的名称和描述等。
      -- 常量池每一种元素的第一个数据都是一个u1类型,该字节是个标志位,占据1个字节,jvm解析常量池时,会根据这个u1类型来获取元素的具体类型(如下表),

      常量池.jpg

    1.4 访问控制符

    • access_flags,u2,主要目的是标记该类是类还是接口,访问权限是否public,abstract,final


      访问控制符.jpg

    1.5 this class

    • u2,常量索引,用于确定类的全限定名。

    1.6 super class

    • u2,的父类索引,用于确定直接父类的全限定名

    1.7 interfaces

    • interfaces_count:u2,表示当前类实现的接口数量,注意是直接实现的接口数量。
    • Interfaces:表示接口的全限定名索引。每个接口u2,共interfaces_count个

    1.8 fields

    • fields_count:u2,表示类变量和实例变量总的个数。
    • fields:fileds的长度为filed_info,filed_info是一个复合结构,
    filed_info: {
      u2        access_flags;   
      u2        name_index;
      u2        descriptor_index;      
      u2        attributes_count;
      attribute_info   attributes[attributes_count];
    }
    
    字段访问控制符.jpg

    1.9 methods

    • methods_count:u2,表示方法个数。
    • methods:methods的长度为一个method_info结构
    • Java类中的每一个实例方法(非static方法),在编译生成的字节码中,方法的参数数量总比源代码的参数数量多一个(this),它位于方法的第一个参数位置处:这样我们就可以在Java的实例方法中使用this来访问当前对象的属性及方法
    • 这个操作是在编译期间完成的,由javac编译器在编译的时候将对this的访问转换为对一个普通实例方法的访问,接下在运行期间由jvm在调用实例方法是,自动向实例方法中传入该this参数,所以在实例方法的局部变量表中,至少有一个指向当前对象的局部变量
    • Java字节码对于异常的处理,
      -- 统一采用异常表的方式来对异常进行处理
      -- 在jdk1.4.2之前的版本中,是采用特定的指令方式
      -- 当异常处理存在finally语句时,现代化的jvm采取的处理方式是将finally语句块的字节码拼接到每一个catch块的后面,也就是程序中有多少个catch块,就会在每一个catch块后面重复多少个finally语句块的字节码
    方法访问控制符.jpg
    method_info {
      u2        access_flags;         
      u2        name_index;         
      u2        descriptor_index;       
      u2        attributes_count;       
      attribute_info   attributes[attributes_count];  
    }
    通用的
    attribute_info {
      u2  attribute_name_index;  
      u4  attribute_length;
      u1  info[attribute_length];
    }
    
    
    Code_attribute {  //Code_attribute包含某个方法、实例初始化方法、类或接口初始化方法的Java虚拟机指令及相关辅助信息
      u2  attribute_name_index;
      u4  attribute_length;
      u2  max_stack;//当前方法的操作数栈在方法执行的任何时间点的最大深度
      u2  max_locals;//分配在当前方法引用的局部变量表中的局部变量个数
      u4  code_length;//给出当前方法code[]数组的字节数
      u1  code[code_length];//给出了实现当前方法的Java虚拟机代码的实际字节内容 (这些数字代码实际对应一些Java虚拟机的指令)
      u2  exception_table_lentgh; //异常的信息
      {
        u2  start_pc;   //这两项的值表明了异常处理器在code[]中的有效范围,即异常处理器x应满足:start_pc≤x≤end_pc
        u2  end_pc;   //start_pc必须在code[]中取值,end_pc要么在code[]中取值,要么等于code_length的值
        u2  handler_pc; //表示一个异常处理器的起点
        u2  catch_type; //表示当前异常处理器需要捕捉的异常类型。为0,则都调用该异常处理器,可用来实现finally。
      } exception_table[exception_table_lentgh];
      u2  attribute_count;  //表示该方法的其它附加属性,本类有1个
      attribute_info  attributes[attributes_count]; //LineNumberTable、LocalVariableTable
    }
    
    LineNumberTable和LocalVariableTable又是两个预定义的attribute,其结构如下:
    
    LineNumberTable_attribute { //被调试器用来确定源文件中由给定的行号所表示的内容,对应于Java虚拟机code[]数组的哪部分
      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];
    }
    
    以及:
    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];
    }
    

    1.10 attributes

    • attributes_count:u2,这里的attribute表示整个class文件的附加属性,和前面方法的attribute结构相同
    • attributes:class文件附加属性,本类中为0017,指向常量池#17,为SourceFile,SourceFile的结构如下:
    SourceFile_attribute {
      u2  attribute_name_index;
      u4  attribute_length;
      u2  sourcefile_index;//表示本class文件是由ByteCodeTest.java编译来的
    }
    

    2. 案例分析

    public class Test1 {
        private int a = 1;
        public int getA() {
            return a;
        }
        public void setA(int a) {
            this.a = a;
        }
    }
    
    
    CA FE BA BE 00 00 00 34 00 18 0A 00 04 00 14 09 00 03 00 15 07 00 16 07 00 17 01 00 01 61 01 00 
    01 49 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E 
    75 6D 62 65 72 54 61 62 6C 65 01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 01 
    00 04 74 68 69 73 01 00 15 4C 63 6F 6D 2F 68 75 69 2F 63 6C 61 7A 7A 2F 54 65 73 74 31 3B 01 00 
    04 67 65 74 41 01 00 03 28 29 49 01 00 04 73 65 74 41 01 00 04 28 49 29 56 01 00 0A 53 6F 75 72 
    63 65 46 69 6C 65 01 00 0A 54 65 73 74 31 2E 6A 61 76 61 0C 00 07 00 08 0C 00 05 00 06 01 00 13 
    63 6F 6D 2F 68 75 69 2F 63 6C 61 7A 7A 2F 54 65 73 74 31 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 
    4F 62 6A 65 63 74 00 21 00 03 00 04 00 00 00 01 00 02 00 05 00 06 00 00 00 03 00 01 00 07 00 08 
    00 01 00 09 00 00 00 38 00 02 00 01 00 00 00 0A 2A B7 00 01 2A 04 B5 00 02 B1 00 00 00 02 00 0A 
    00 00 00 0A 00 02 00 00 00 07 00 04 00 09 00 0B 00 00 00 0C 00 01 00 00 00 0A 00 0C 00 0D 00 00 
    00 01 00 0E 00 0F 00 01 00 09 00 00 00 2F 00 01 00 01 00 00 00 05 2A B4 00 02 AC 00 00 00 02 00 
    0A 00 00 00 06 00 01 00 00 00 0C 00 0B 00 00 00 0C 00 01 00 00 00 05 00 0C 00 0D 00 00 00 01 00 
    10 00 11 00 01 00 09 00 00 00 3E 00 02 00 02 00 00 00 06 2A 1B B5 00 02 B1 00 00 00 02 00 0A 00 
    00 00 0A 00 02 00 00 00 10 00 05 00 11 00 0B 00 00 00 16 00 02 00 00 00 06 00 0C 00 0D 00 00 00 
    00 00 06 00 05 00 06 00 01 00 01 00 12 00 00 00 02 00 13
    
    
    命令: javap -verbose -p Test1.class 
    
    Classfile /Users/zmhui/code/web/target/classes/com/hui/clazz/Test1.class
      Last modified 2020-5-17; size 467 bytes
      MD5 checksum 3bef9b467a2cb9f695390fa753edc5a1
      Compiled from "Test1.java"
    public class com.hui.clazz.Test1
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #4.#20         // java/lang/Object."<init>":()V
       #2 = Fieldref           #3.#21         // com/hui/clazz/Test1.a:I
       #3 = Class              #22            // com/hui/clazz/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               Lcom/hui/clazz/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               com/hui/clazz/Test1
      #23 = Utf8               java/lang/Object
    {
      private int a;
        descriptor: I
        flags: ACC_PRIVATE
    
      public com.hui.clazz.Test1();
        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 a:I
             9: return
          LineNumberTable:
            line 7: 0
            line 9: 4
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      10     0  this   Lcom/hui/clazz/Test1;
    
      public int getA();
        descriptor: ()I
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: getfield      #2                  // Field a:I
             4: ireturn
          LineNumberTable:
            line 12: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lcom/hui/clazz/Test1;
    
      public void setA(int);
        descriptor: (I)V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=2, args_size=2
             0: aload_0
             1: iload_1
             2: putfield      #2                  // Field a:I
             5: return
          LineNumberTable:
            line 16: 0
            line 17: 5
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       6     0  this   Lcom/hui/clazz/Test1;
                0       6     1     a   I
    }
    SourceFile: "Test1.java"
    

    2.0 对上方class文件进行分析

    2.1 魔数-magic,u4

    • CA FE BA BE

    2.2 版本号-version,u2+u2

    • 00 00 00 34
      -- 34(16进制)=52,Java1.8版本

    2.3 常量池-constant pool,u2+n

    • 00 18
      -- 代表:24-1 个常量
    • 0A 00 04 00 14
      -- 第一个常量:0A=值10,查常量表可知常量类型为:constant_methodref_info,可知00 04和00 14都是分别指向常量4和常量20的索引值
      -- 即为:#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
    • 以此方式进行分析
    -第一个
    0A 00 04 00 14 
    09 00 03 00 15 
    07 00 16
    07 00 17 
    01 00 01 61 
    01 00 01 49 
    01 00 06 3C 69 6E 69 74 3E 
    01 00 03 28 29 56 
    01 00 04 43 6F 64 65 
    01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 
    01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 
    01 00 04 74 68 69 73 
    01 00 15 4C 63 6F 6D 2F 68 75 69 2F 63 6C 61 7A 7A 2F 54 65 73 74 31 3B 
    01 00 04 67 65 74 41 
    01 00 03 28 29 49 
    01 00 04 73 65 74 41 
    01 00 04 28 49 29 56 
    01 00 0A 53 6F 75 72 63 65 46 69 6C 65 
    01 00 0A 54 65 73 74 31 2E 6A 61 76 61 
    0C 00 07 00 08 
    0C 00 05 00 06 
    01 00 13 63 6F 6D 2F 68 75 69 2F 63 6C 61 7A 7A 2F 54 65 73 74 31 
    01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 
    -最后一个
    

    2.4 访问控制符-access flags,u2

    • 00 21,根据上图<访问控制符.jpg>进行查找并没有00 21值,
    • 访问标志可以是由多个标志名称组成的,也就是字节码标志值00 21可以是多个值进行《或运算》的结果,由此可以得出 00 21 是ACC_PUBLIC,ACC_SUPER的或运算结果,

    2.5 类索引-this class,u2

    • 00 03, 用来确定这个类的全限定名,00 03 指向了常量池的第三个常量,
    • 即: #3 = Class #22 // com/hui/clazz/Test1

    2.6 父类索引-super class,u2

    • 00 04,用于确定这个类的父类的全限定名,00 04 指向常量池第四个常量
    • 即:#4 = Class #23 // java/lang/Object

    2.7 接口-interfaces,u2+n

    • 00 00,接口索引集合就用来描述类实现了哪些接口,00 00 (interfaces_count)接口数量,表示没有接口

    2.8 字段表-fields,u2+n

    • 00 01,fields_count表示有一个字段
    • 00 02 00 05 00 06 00 00,
    • 00 02,查询字段控制符可知为private
    • 00 05,名称,指向常量5,即:#5 = Utf8 a
    • 00 06,描述,指向常量6,即:#6 = Utf8 I
    • 00 00,属性数量,这里为0

    2.9 方法表-methods,u2+n

    • 00 03,methods_count,表示有3个方法
    第一个方法
    • 00 01 00 07 00 08 00 01
    • 00 01,访问控制符查看<方法访问控制符.jpg>可知为ACC_PUBLIC
    • 00 07,方法名索引,指向常量7,即:#7 = Utf8 <init>
    • 00 08,方法描述索引,指向常量8,即:#8 = Utf8 ()V
    • 00 01,表示有一个属性
    第一个属性
    • 00 09,attribute_name_index指向常量9,即:#9 = Utf8 Code
    • 说明此属性是方法的字节码描述Code_attribute,
    • 00 00 00 38,attribute_length说明属性长度为56,(不包括前6),即以下56个字节
    • 00 02 00 01 00 00 00 0A 2A B7 00 01 2A 04 B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 07 00 04 00 09 00 0B 00 00 00 0C 00 01 00 00 00 0A 00 0C 00 0D 00 00
    • 00 02,max_stack操作数栈深度的最大值
    • 00 01,max_locals局部变量表所需的存储空间为 1 个 Slot,max_locals的单位是Slot,Slot是虚拟机为局部变量分配内存所使用的最小单位。
    • 00 00 00 0A,code_length生成字节码长度,即为10,那么紧接着10个字节就是对应的数据,2A B7 00 01 2A 04 B5 00 02 B1,
      -- 可以利用idea jclasslib Bytecode viewer插件
      -- 读入2A,官网查表可知:aload_0 = 42 (0x2a),即aload_0
      -- 读入B7,官网查表可知:invokespecial = 183 (0xb7),作用是以栈顶的reference类型的数据所指向的对象作为方法接收者,调用此对象的实例构造器方法、private方法或者它的父类的方法。后面两个字节u2类型的参数说明具体调用哪一个方法
      -- 读入00 01,这是invokespecial的参数,查常量池得0x0001对应的常量为实例构造器“”方法的符号引用。即:#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
      -- 读入2A,查表可知:aload_0 = 42 (0x2a),即aload_0
      -- 读入04,官网查表可知:iconst_1 = 4 (0x4)
      -- 读入B5,官网查表可知:putfield = 181 (0xb5)
      -- 读入00 02,这个是putfield的参数,查常量池对应的常量为: #2 = Fieldref #3.#21 // com/hui/clazz/Test1.a:I
      -- 读入B1,官网
      查表可知:return = 177 (0xb1)
    • 00 00,exception_table_lentgh,异常信息这里为0
    • 00 02,attribute_count表示两个属性
    第一个attribute_info:00 0A 00 00 00 0A 00 02 00 00 00 07 00 04 00 09
    • 00 0A,attribute_info的attribute_name_index索引,指向常量池10=LineNumberTable,即此属性表为LineNumberTable,即: #10 = Utf8 LineNumberTable
    • 00 00 00 0A,attribute_length即属性长度为10,
    • 00 02 00 00 00 07 00 04 00 09,
    • 00 02,line_number_table_length,表示有个表信息
    • 00 00 00 07,start_pc为0,line_number为7代码的实际位置
    • 00 04 00 09,start_pc为4,line_number为9
    第二个attribute_info:00 0B 00 00 00 0C 00 01 00 00 00 0A 00 0C 00 0D 00 00
    • 00 0B:attribute_info的attribute_name_index索引,指向常量池11=LocalVariableTable
      ,即 #11 = Utf8 LocalVariableTable
    • 00 00 00 0C,attribute_length即12
    • 00 01,local_variable_table_length即1个
    • 00 00,start_pc
    • 00 0A,length,10
    • 00 0C,name_index,即: #12 = Utf8 this
    • 00 0D,descriptor_index, #13 = Utf8 Lcom/hui/clazz/Test1;
    • 00 00,index
    第二/三个方法
    • 00 01 00 0E 00 0F 00 01 00 09 00 00 00 2F 00 01 00 01 00 00 00 05 2A B4 00 02 AC 00 00 00 02 00 0A 00 00 00 06 00 01 00 00 00 0C 00 0B 00 00 00 0C 00 01 00 00 00 05 00 0C 00 0D 00 00
    • 00 01 00 10 00 11 00 01 00 09 00 00 00 3E 00 02 00 02 00 00 00 06 2A 1B B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 10 00 05 00 11 00 0B 00 00 00 16 00 02 00 00 00 06 00 0C 00 0D 00 00 00 00 00 06 00 05 00 06 00 01

    2.10 类属性-attributes,u2+n

    • 00 01 00 12 00 00 00 02 00 13
    • 00 01,attributes_count附加属性个数
    • 00 12 00 00 00 02 00 13,
    • 00 12,attribute_name_index指向 #18 = Utf8 SourceFile
    • 00 00 00 02,attribute_length长度2
    • 00 13,sourcefile_index指向 #19 = Utf8 Test1.java
    来自网络

    相关文章

      网友评论

          本文标题:Java字节码解析

          本文链接:https://www.haomeiwen.com/subject/qihlahtx.html