美文网首页
字节码1

字节码1

作者: 得力小泡泡 | 来源:发表于2020-12-12 15:52 被阅读0次

    什么是反编译?

    ※ 编译 Compile

    将一个 *.java文件编译成 *.class 文件的过程,称为编译。

    比如,HelloWorld.java 被编译后得到 HelloWorld.class

    ※ 反编译 Decompile

    在.class文件里包含了完全的信息,包含类名、方法、属性、注解,除了注释文字之外的所有信息。所以从.class文件可以恢复得到原来的*.java文件,而且一丝不差!

    从 *.class 逆向得到 *.java 的过程,称为反编译。

    源代码

    package com.bytecode;
    
    public class MyTest1 {
        private int a = 1;
    
        public int getA() {
            return a;
        }
    
        public void setA(int a) {
            this.a = a;
        }
    }
    
    

    反编译的代码

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by FernFlower decompiler)
    //
    
    package com.bytecode;
    
    public class MyTest1 {
        private int a = 1;
    
        public MyTest1() {
        }
    
        public int getA() {
            return this.a;
        }
    
        public void setA(int a) {
            this.a = a;
        }
    }
    

    F:\titled2\target\classes>javap com.bytecode.MyTest1

    Compiled from "MyTest1.java"
    public class com.bytecode.MyTest1 {
      public com.bytecode.MyTest1();
      public int getA();
      public void setA(int);
    }
    

    F:\titled2\target\classes>javap -c com.bytecode.MyTest1

    Compiled from "MyTest1.java"
    public class com.bytecode.MyTest1 {
      public com.bytecode.MyTest1();
        Code:
           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
    
      public int getA();
        Code:
           0: aload_0
           1: getfield      #2                  // Field a:I
           4: ireturn
    
      public void setA(int);
        Code:
           0: aload_0
           1: iload_1
           2: putfield      #2                  // Field a:I
           5: return
    }
    

    F:\titled2\target\classes>javap -verbose -p com.bytecode.MyTest1

    Classfile /F:/titled2/target/classes/com/bytecode/MyTest1.class
      Last modified 2020-12-12; size 471 bytes
      MD5 checksum a136dc27b0f590f1bd86dde81d103bcb
      Compiled from "MyTest1.java"
    public class com.bytecode.MyTest1
      minor version: 0
      major version: 51
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #4.#20         // java/lang/Object."<init>":()V
       #2 = Fieldref           #3.#21         // com/bytecode/MyTest1.a:I
       #3 = Class              #22            // com/bytecode/MyTest1
       #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/bytecode/MyTest1;
      #14 = Utf8               getA
      #15 = Utf8               ()I
      #16 = Utf8               setA
      #17 = Utf8               (I)V
      #18 = Utf8               SourceFile
      #19 = Utf8               MyTest1.java
      #20 = NameAndType        #7:#8          // "<init>":()V
      #21 = NameAndType        #5:#6          // a:I
      #22 = Utf8               com/bytecode/MyTest1
      #23 = Utf8               java/lang/Object
    {
      public com.bytecode.MyTest1();
        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 3: 0
            line 4: 4
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      10     0  this   Lcom/bytecode/MyTest1;
    
      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 7: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lcom/bytecode/MyTest1;
    
      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 11: 0
            line 12: 5
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       6     0  this   Lcom/bytecode/MyTest1;
                0       6     1     a   I
    }
    SourceFile: "MyTest1.java"
    

    规则
    1、使用javap -verbose命令分析一个字节码文件时,将会分析该字节码文件的魔数、版本号、常量池、类信息、类的构造方法、类中的方法信息、类变量与成员变量等信息
    2、魔数:所有的.class字节码文件的前4个字节都是魔数,魔数值为固定值:0xCAFEBABE。(咖啡宝贝)
    3、魔数之后的4个字节为版本信息,前两个字节表示minor version(次版本号),后两上字节表示major version(主版本号),所以这里的版本号为“00 00 00 33”,换算成十进制,表示次版本号为0,主版本号为51,所以,该文件的版本号为:1.7.0


    image.png

    javap -verbose后有

    minor version: 0
    major version: 51
    

    4、常量池(constant pool):紧接着主版本号之后的就是常量池的入口。一个Java类中定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是Class文件的资源仓库,比如说Java类中定义的方法与变量信息,都是存储在常量池中。常量池主要存储两类常量:字面量与符号引用。字面量如文本字符串,Java声明为final的常量等,而符号引用如类和接口的全局限定名(包名+类名),字段的名称和描述符,方法的名称和描述符。
    【注意】:常量池千万不要理解成它里面只能存不变的常量值,里面也可以有变量相关的信息。

    5、常量池的总体结构:Java类所对应的常量池主要由常量池数量常量池数组这两部分共同构成。常量池数量紧跟在主版本号后面,占据2个字节;常量池数组则紧跟常量池数量之后,常量池数组与一般的数组不同的是,常量池数组中不同的元素的类型、结构都是不同的,长度当然也就不同,但是每一种元素的第一个数据都是一个u1类型,该字节是一个标志位,占据1个字节,JVM在解析常量池时,会根据这个u1类型来获取元素的具体类型。
    值得注意的是,常量池数组中元素的个数 = 常量池 - 1(其中0暂时不使用),目的是满足某些常量池索引值的数据在特定情况下表达【不引用任何一个常量池】的含义;根本原因在于,索引为0也是一个常量(保留常量),只不过它不位于常量表中,这个常量就对应null值;所以,常量池的索引从1而非0开始

    例如:
    18的十六进制是24


    image.png image.png
    常量池数据类型结构表

    Class字节码中有两种数据类型:

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

    6、在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

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

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

    image.png image.png

    9、Access_flag访问标记
    访问标志信息包括该Class文件是类还是接口,是否被定义成public,是否是abstract,如果是类,是否被声明为final。而我们所定义的源代码就知道文件是类而且是public的

    JVM预定义的attribute为如下表


    image.png

    0x0021:是0x0020和0x0001的并集,表示ACC_PUBLIC与ACC_SUPER

    image.png
    flags: ACC_PUBLIC, ACC_SUPER
    

    10、字段表:fields
    字段表用于描述类和接口中声明的变量。这里的字段包含了类级别变量以及实例变量,但是不包括方法内部声明的局部变量。

    结构:

    field_info{
        u2 access_flags; 0002
        u2 name_index; 0005
        u2 descriptor_index; 0006
        u2 attributes_count; 0000
        attribute_info attributes[attributes_count];
    }
    

    access_flags代表访问修饰符,占2个字节;
    name_index代表字段的名称索引,占2个字节;
    descriptor_index代表描述符的索引,占2个字节;前三个信息就可以完整的描述一个字段的信息;
    attributes_count:属性个数,可有可无;

    11、方法表:
    结构

    method_info{
        u2 access_flags; 
        u2 name_index; 
        u2 descriptor_index; 
        u2 attributes_count; 
        attribute_info attributes[attributes_count];
    }
    
    • access_flags:占用两个字节,表示访问标记。
    • name_index:占用两个字节,名字索引,指向的是常量池。
    • descriptor_index:占用两个字节,描述索引,指向的是常量池。
    • attributes_count:占用两个字节,属性个数,如果为0,则下面的属性表就不显示了。
    • attributes_info:属性表。

    12、属性表
    属性表是attribute_info类型,很显然也有它自己的结构,那长啥样呢?

    attribute_info{
        u2 attribute_name_index;
        u4 attribute_length;
        u1 info[attribute_length];
    }
    
    • attribute_name_index:占2个字节,表示属性名字的索引,指向常量池。(每个方法表都有一个Utf8 的 Code的属性,如下)
    • attribute_length:占4个字节,表示属性的长度。
    • info[attribute_length]:占1个字节,表示具体的信息
      依照上面的顺序,先数2个字节:


      image.png

      对应常量池:


      image.png

    13、Code属性
    每个方法表都有一个Utf8 的 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];
    }
    
    • attribute_length表示attribute所包含的字节数,不包含attribute_name_index和attribute_length字段。
    • max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度。
    • max_locals表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量。(当该方法是非静态方法时,会把this的变量传进方法内作为局部变量)
    • code_length表法该方法所包含的字节码的字节数以及具体的指令码。
      具体的字节码既是该方法被调用时,虚拟机所执行的字节码。
    • exception_table:这里存放的是处理异常的信息。
      第一个exception_table表项由start_pc、end_pc、handler_pc、catch_type组成。
    • start_pc和end_pc表示在code数组中的从start_pc到end_pc处(包含start_pc,不包含end_pc)的指令抛出的异常会由这个表项来处理。
    • handler_pc表示处理异常的代码的开始处。catch_type表示会被处理的异常类型,它指向常量池里的一个异常类。当catch_type为0时,表示处理所有的异常。

    code属性中也有两个附加属性
    ①LineNumberTable:这个属性用来表示code数组中的字节码和Java代码行数之间的关系。这个属性可以用来再调试的时候定位代码执行的行数
    结构

    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];
    }
    

    ②LocalVariableTable:记录code数组中的局部变量表(如果是实例方法,则一定会有个this的局部遍历)
    结构

    LineNumberTable_attribute{
        u2 attribute_name_index;
        u4 attribute_length;
        u2 local_variable_table_length;
        {
            u2 start_pc;
            u2 length;
            u2 index;
            u2 name;
            u2 descriptor;
        } local_variable_table[local_variable_table_length];
    }
    

    14、构造方法
    1、当类没有构造方法时,编译成class文件的时候,会自动创建构造方法,并且将类中的成员变量(非静态)放到构造方法中优先执行
    2、当类中存在1个构造方法是,则不会自动创建构造方法,同时将将类中的成员变量(非静态)放到构造方法中优先执行
    3、当类中存在多个构造方法时,则不会自动创建构造方法,同时每个构造方法中都会有成员变量(非静态)放到构造方法中优先执行

    package com.bytecode;
    
    public class MyTest2 {
    
        String str = "Welcome";
    
        private int x = 5;
    
        public static Integer in = 10;
    
        public MyTest2(){
    
        }
    
        public MyTest2(int i){
            System.out.println("aaa");
        }
    
        public static void main(String[] args){
            MyTest2 myTest2 = new MyTest2();
    
            myTest2.setX(8);
        }
    
        private synchronized void setX(int x){
            this.x = x;
        }
    
        private void test(String str){
            synchronized (str){
                System.out.println("hello world");
            }
        }
    
        private synchronized static void test2(){
    
        }
    }
    
    public com.bytecode.MyTest2();
        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: ldc           #2                  // String Welcome
             7: putfield      #3                  // Field str:Ljava/lang/String;
            10: aload_0
            11: iconst_5
            12: putfield      #4                  // Field x:I
            15: return
          LineNumberTable:
            line 11: 0
            line 5: 4
            line 7: 10
            line 13: 15
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      16     0  this   Lcom/bytecode/MyTest2;
    
      public com.bytecode.MyTest2(int);
        descriptor: (I)V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=2, args_size=2
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: aload_0
             5: ldc           #2                  // String Welcome
             7: putfield      #3                  // Field str:Ljava/lang/String;
            10: aload_0
            11: iconst_5
            12: putfield      #4                  // Field x:I
            15: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
            18: ldc           #6                  // String aaa
            20: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            23: return
          LineNumberTable:
            line 15: 0
            line 5: 4
            line 7: 10
            line 16: 15
            line 17: 23
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      24     0  this   Lcom/bytecode/MyTest2;
                0      24     1     i   I
    
    

    反编译后可以看到两个构造方法的Code属性中前14行都是一样的,都是对成员变量的初始化

    15、<clinit>方法
    静态代码块的的赋值操作和执行都会放在<clinit>方法中,并且按照从上到下的顺序放在<clinit>方法中

    相关文章

      网友评论

          本文标题:字节码1

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