java反编译篇--miaow.Y.Hu

好了,好久没更新了,今天来一篇关于java反编译的篇举例子。

Java编译文件是指通过Java编译器将Java源代码文件(.java文件)编译成字节码文件(.class文件)的过程。在Java中,源代码文件包含人类可读的文本,而字节码文件是计算机可以理解的二进制指令集。

Java编译器的主要任务是将源代码文件中的语法、类型检查和语义分析转换为字节码文件。这个过程包括两个主要阶段:

编译和链接。

  • 编译阶段:编译器首先对源代码进行语法分析和类型检查,然后将代码转换为抽象语法树(AST)。接下来,编译器会优化AST,并将其转换为中间表示(IR)。最后,编译器将IR转换为字节码文件。
  • 链接阶段:链接器负责将生成的类文件和相关的库文件合并成一个可执行的Java程序。链接器需要确保所有引用的类和方法都存在,并按照正确的顺序进行加载。

Java编译器的输出是一个或多个字节码文件,通常以.class扩展名结尾。这些字节码文件可以在Java虚拟机(JVM)上运行,或者使用Java开发工具(如Eclipse、IntelliJ IDEA等)进行调试和分析。

需要注意的是,Java源代码文件本身不能直接被Java编译器编译。用户需要先使用文本编辑器或其他开发工具编写源代码,然后将其保存为.java文件。接下来,用户可以通过命令行或集成开发环境(IDE)调用Java编译器(如javac命令)来编译源代码文件。

创建一个最简单的例子:

java 复制代码
public class Demo01{
	public static void main(String[] args){
		String name = "LXH";
		System.out.println(name);
	}
}

在cmd控制台,我们进行反编译这个.java文件。

shell 复制代码
# 进入这个类所在的文件夹
javac -version # 检查一下javac -version 是否配置好了环境变量
javac Demo01.java # 将我们编写的类文件转换为二进制文件
java Demo01 # Java虚拟机执行二进制文件
# 接下来,重头戏,反编译
javap -verbose Demo01

得到反编译后的代码

java 复制代码
Classfile /D:/工作中的学习资料/Git Down/java_play/CMD_PLAY/DEMO1/Demo01.class 
  Last modified 2023-8-28; size 415 bytes
  MD5 checksum 1e5cfb3099439767a8a3f40ebccce0b7
  Compiled from "Demo01.java"
public class Demo01
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = String             #16            // LXH
   #3 = Fieldref           #17.#18        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #21            // Demo01
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               Demo01.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Utf8               LXH
  #17 = Class              #23            // java/lang/System
  #18 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #19 = Class              #26            // java/io/PrintStream
  #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #21 = Utf8               Demo01
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
{
  public Demo01();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  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: ldc           #2                  // String LXH
         2: astore_1
         3: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         6: aload_1
         7: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        10: return
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 7: 10
}
SourceFile: "Demo01.java"

我们根据上述的代码进行分析:
Classfile /D:/工作中的学习资料/Git Down/java_play/CMD_PLAY/DEMO1/Demo01.class,代表这个类当前所在的文件夹具体位置。

Last modified 2023-8-28; size 415 bytes 表示上次修改日期,另外这个生成的文件的大小,415个字节。
重点,我们看常量池:Constant pool 存储了编译后的字节码指令和相关信息。

  • #1: 表示对java/lang/Object类的""方法的引用。
  • #2: 表示字符串常量"LXH"。
  • #3: 表示对System.out字段的引用,它是java.io.PrintStream类的静态成员变量。
  • #4: 表示对java.io.PrintStream.println方法的引用,它是java.io.PrintStream类的实例方法。
  • #5: 表示Demo01类本身。
  • #6: 表示java/lang/Object类本身。
  • #7: 表示构造函数的签名,即无参数的构造函数。
  • #8: 表示无参数构造函数的方法类型。
  • #9: 表示行号表条目,用于在调试时定位源代码行。
  • #10: 表示主方法的签名,即接受一个String数组参数的方法。
  • #11: 表示主方法所在的源文件名。
  • #12: 表示主方法的参数类型,即String数组。
  • #13: 表示行号表中的主方法行号。
  • #14: 表示主方法所在的源文件名。
  • #15: 表示主方法的参数类型,即String数组。
  • #16: 表示字符串常量"LXH"。
  • #17: 表示java/lang/System类。
  • #18: 表示System.out字段的名称和类型。
  • #19: 表示java/io/PrintStream类。
  • #20: 表示java.io.PrintStream.println方法的名称和类型。
  • #21: 表示Demo01类本身。
  • #22: 表示java/lang/Object类本身。
  • #23: 表示java/lang/System类。
  • #24: 表示System.out字段的名称和类型。
  • #25: 表示java.io.PrintStream类中的out字段名称。
  • #26: 表示java.io.PrintStream类。
  • #27: 表示java.io.PrintStream.println方法的名称和类型。

分析:

shell 复制代码
#1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
这是一个方法引用(Methodref),它引用了常量池中的第6个项(#6),该项是一个类引用,指向 java/lang/Object。
#15 表示常量池中的第15个项,是一个名称和类型(NameAndType)引用,它指向构造函数的名字和描述符(<init>:()V)。
这个引用表示调用 java/lang/Object 类的无参数构造函数。

#2 = String             #16            // LXH
这是一个字符串常量(String),它引用了常量池中的第16个项(#16),表示字符串 "LXH"。

#3 = Fieldref           #17.#18        // java/lang/System.out:Ljava/io/PrintStream;
这是一个字段引用(Fieldref),它引用了常量池中的第17个项(#17),该项是一个类引用,指向 java/lang/System。#18 表示常量池中的第18个项,是一个名称和类型引用,它指向 out 字段的名字和描述符(Ljava/io/PrintStream;)。
这个引用表示访问 java/lang/System 类中的 out 字段,该字段的类型是 java/io/PrintStream。

#4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
这是一个方法引用,它引用了常量池中的第19个项(#19),该项是一个类引用,指向 java/io/PrintStream。#20 表示常量池中的第20个项,是一个名称和类型引用,它指向 println 方法的名字和描述符((Ljava/lang/String;)V)。这个引用表示调用 java/io/PrintStream 类的 println 方法,该方法接受一个 java/lang/String 类型的参数,返回类型是 void。

#5 = Class              #21            // Demo01
这是一个类引用,它引用了常量池中的第21个项(#21),表示类 Demo01。

#6 = Class              #22            // java/lang/Object
这是一个类引用,它引用了常量池中的第22个项(#22),表示类 java/lang/Object。

#7 = Utf8               <init>
这是一个UTF-8编码的字符串,表示字符串 "<init>",通常用于表示构造函数。

#8 = Utf8               ()V
这是一个UTF-8编码的字符串,表示方法的描述符,( )V 表示该方法没有参数且返回类型是 void。

#9 = Utf8               Code
这是一个UTF-8编码的字符串,表示"Code",通常用于标识字节码中的代码部分。

#10 = Utf8              LineNumberTable
这是一个UTF-8编码的字符串,表示"LineNumberTable",通常用于存储字节码指令与源代码行号之间的映射关系。

#11 = Utf8              main
这是一个UTF-8编码的字符串,表示方法名"main"。

#12 = Utf8              ([Ljava/lang/String;)V
这是一个UTF-8编码的字符串,表示方法的描述符,([Ljava/lang/String;)V 表示方法接受一个 java/lang/String 类型的数组作为参数,返回类型是 void。

#13 = Utf8              SourceFile
这是一个UTF-8编码的字符串,表示"SourceFile",通常用于指定源代码文件的名称。

#14 = Utf8              Demo01.java
这是一个UTF-8编码的字符串,表示源代码文件的名称为"Demo01.java"。

#15 = NameAndType       #7:#8          // "<init>":()V
这是一个名称和类型引用,它引用了常量池中的第7个项(#7),表示构造函数的名字。#8 表示常量池中的第8个项,是一个方法描述符,()V 表示该方法没有参数且返回类型是 void。这个引用表示构造函数的名字和描述符。

#16 = Utf8              LXH
这是一个UTF-8编码的字符串,表示字符串"LXH"。

#17 = Class             #23            // java/lang/System
这是一个类引用,引用了常量池中的第23个项,表示类java/lang/System。

#18 = NameAndType       #24:#25        // out:Ljava/io/PrintStream;
这是一个名称和类型引用,它引用了常量池中的第24个项(#24),表示字段名"out"。#25 表示常量池中的第25个项,是一个字段的类型描述符,Ljava/io/PrintStream; 表示字段的类型是java/io/PrintStream。这个引用表示字段名和类型描述符。

#19 = Class             #26            // java/io/PrintStream
这是一个类引用,引用了常量池中的第26个项,表示类java/io/PrintStream。

#20 = NameAndType       #27:#28        // println:(Ljava/lang/String;)V
这是一个名称和类型引用,它引用了常量池中的第27个项(#27),表示方法名"println"。#28 表示常量池中的第28个项,是一个方法描述符,(Ljava/lang/String;)V 表示方法接受一个java/lang/String类型的参数,返回类型是 void。这个引用表示方法名和方法描述符。

#21 = Utf8              Demo01
这是一个UTF-8编码的字符串,表示类名"Demo01"。

#22 = Utf8              java/lang/Object
这是一个UTF-8编码的字符串,表示类名"java/lang/Object"。

#23 = Utf8              java/lang/System
这是一个UTF-8编码的字符串,表示类名"java/lang/System"。

#24 = Utf8              out
这是一个UTF-8编码的字符串,表示字段名"out"。

#25 = Utf8              Ljava/io/PrintStream;
这是一个UTF-8编码的字符串,表示字段类型描述符Ljava/io/PrintStream;,表示类型是java/io/PrintStream。

#26 = Utf8              java/io/PrintStream
这是一个UTF-8编码的字符串,表示类名"java/io/PrintStream"。

#27 = Utf8              println
这是一个UTF-8编码的字符串,表示方法名"println"。

#28 = Utf8              (Ljava/lang/String;)V
这是一个UTF-8编码的字符串,表示方法的描述符,(Ljava/lang/String;)V 表示方法接受一个java/lang/String类型的参数,返回类型是 void。

类定义部分:

  • 类名:Demo01
  • 访问标志:ACC_PUBLIC,表示类是公共的,可以从其他类访问。
  • 超类:java/lang/Object,该类继承自 java.lang.Object。
  • 常量池(Constant Pool): 常量池包含了一系列的常量项,例如方法引用、字段引用、字符串字面值等。

构造函数(Constructor):

  • 公共构造函数:public Demo01(),无参数,无返回值。
  • 构造函数访问标志:ACC_PUBLIC,表示构造函数是公共的。

构造函数的字节码指令(Code)部分:

  • 栈大小(stack):1,表示最大堆栈深度为1。
  • 本地变量表大小(locals):1,表示本地变量表中有1个元素。
  • 参数大小(args_size):1,表示方法的参数个数为1。

构造函数内容:

  • 指令1:aload_0,将当前对象引用(this)加载到操作数栈。
  • 指令2:invokespecial #1,调用父类 Object 的构造函数(""),完成对象的初始化。
  • 指令4:return,从构造函数返回。
  • 构造函数的行号表(LineNumberTable):展示了字节码指令和源代码行号的映射。在这里只有一行,行号为3。

主方法(main):

  • 公共静态主方法:public static void main(String[] args)。
  • 方法访问标志:ACC_PUBLIC、ACC_STATIC,表示方法是公共的和静态的。

主方法的字节码指令部分:

  • 指令0:将字符串 "LXH" 加载到操作数栈。
  • 指令2:将操作数栈的值存储到局部变量表索引为1的位置(astore_1)。
  • 指令3:获取静态字段 System.out。
  • 指令6:加载局部变量1(存储的是 "LXH" 字符串)到操作数栈。
  • 指令7:调用 System.out 的 println 方法,将字符串打印到控制台。
  • 指令10:返回主方法。

主方法的行号表部分:

  • 源代码行号5对应指令0。
  • 源代码行号6对应指令2。
  • 源代码行号7对应指令10。
  • 源文件名(SourceFile):表示源文件名为 "Demo01.java"。

实际上,我在拿到这段编译后的代码也是吓了一跳,我没想到写的就4行代码,结果反编译出来就这么长,但是认真去看了一下,还是很长,但是认真去分析,发现还是有规律可循的,认真看,还是有收货的。

相关推荐
idealzouhu12 分钟前
Java 并发编程 —— AQS 抽象队列同步器
java·开发语言
听封16 分钟前
Thymeleaf 的创建
java·spring boot·spring·maven
写bug写bug22 分钟前
6 种服务限流的实现方式
java·后端·微服务
楠枬33 分钟前
双指针算法
java·算法·leetcode
奔驰的小野码39 分钟前
java通过org.eclipse.milo实现OPCUA客户端进行连接和订阅
java·开发语言
huapiaoy41 分钟前
Spring mvc
java·spring·mvc
风控牛1 小时前
【chromedriver编译-绕过selenium机器人检测】
java·python·selenium·测试工具·安全·机器人·行为验证
小川_wenxun1 小时前
优先级队列(堆)
java·开发语言·算法
前端专业写bug1 小时前
jspdf踩坑 htmltocanvas
java·前端·javascript