字节码1

2020-12-12  本文已影响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字节码中有两种数据类型:

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

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

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

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

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>方法中

上一篇 下一篇

猜你喜欢

热点阅读