实战详解java反编译字节码(操作指令助记符)

发布于:2023-01-20 ⋅ 阅读:(765) ⋅ 点赞:(0)

1 缘起

刚开始学习Java时,只知道Java程序需要编译成字节码,交给JVM执行(这里不讨论编译和解释执行),
以践行一次编译到处运行的伟大设计理念,
并不知道字节码长什么样,随着学习的深入,发现可以通过反编译的方式,
观察Java程序与字节码的映射关系,以更加深度了解Java程序的运作,
Java程序对于开发者是可读的,
字节码对于JVM是可读的,
二进制对于处理器是可读的,
不同的角色处理不同层面的计算机语言。
比如,java8中try…with…resource,通过字节码反编译可清晰地看到,确实有close操作,
所有,学习字节码相关知识,
现以实战方式分享Java字节码反编译,帮助读者进一步理解Java程序设计。

2 Code实践

2.1 源码

测试源码如下,
使用IDEA集成开发环境编译源码,生成的字节码保存在target文件夹,
以实例的方式讲解字节码反编译与Java程序对应关系,
为方便理解和记忆,我在源码中给出了每条语句对应的字节码反编译助记符,
由于源码会自动折叠,所以先给出截图,后面有完整的源码,

2.1.1 新建对象new

在这里插入图片描述

2.1.2 调用方法

在这里插入图片描述

2.1.3 局部变量赋值

在这里插入图片描述

2.1.4 新建引入的对象及调用方法

在这里插入图片描述

2.1.5 新建接口对象及调用方法

在这里插入图片描述

2.1.6 调用运行时解析方法

在这里插入图片描述

源码如下:

package com.monkey.java_study.clzz;

import com.monkey.java_study.common.entity.UserEntity;
import com.monkey.java_study.proxy.jdk_proxy.IUserService;
import com.monkey.java_study.proxy.jdk_proxy.impl.UserServiceImpl;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 字节码反编译测试.
 *
 * @author xindaqi
 * @since 2022-08-11 10:48
 */
public class ByteCodeTest {

    public static final String TEST_NAME = "hello world!";

    private void test() {
        System.out.println(">>>>>>>>I am test method in current class");
    }

    public void call() {
        // (1)new:新建对象,在堆中为对象分配存储空间,并压入操作数栈顶
        // (2)dup:复制栈顶部一个字长内容,入栈(此时栈有两个相同地址)
        // (3)invkespecial:构造函数调用初始化方法<init>:()V,操作数栈顶弹出ByteCodeTest对象引用(dup)
        // (4)astore_1:从操作数栈顶取出ByteCodeTest对象存入局部变量1
        ByteCodeTest byteCodeTest = new ByteCodeTest();

        // (1)aload_1:从局部变量1装在引用类型ByteCodeTest
        // (2)invokespecial:调用当前类方法test()
        byteCodeTest.test();

        // (1)ldc:将常量池数据入栈
        // (2)astore_2:引用类型值String(包装类)存入局部变量2
        String var1 = "m";
        // iconst_1:int类型常量1入栈,istore_3:int类型值存入局部变量3
        int var2 = 1;

        // 同:新建对象四步
        UserEntity userEntity = new UserEntity();

        // (1)aload:从局部变量加载引用类型UserEntity
        // (2)invokevirtual:调用UserEntity类的方法getNickName()
        // (3)pop:弹出栈顶一个字长内容
        userEntity.getNickname();

        // 同:新建对象四步
        IUserService userService = new UserServiceImpl();
        // (1)aload:从局部变量加载引用类型IUserService
        // (2)invokeinterface:调用接口方法add()
        userService.add();


        // iconst_3:int类型常量3压入栈
        // anewarray:新建成员为引用类型的数组
        // 开始赋值:a、b、c,循环操作
        // (1)dup:复制栈顶一个字长内容
        // (2)iconst_0:int类型常量0压入栈
        // (3)ldc:常量池数据入栈
        // (4)aastore:引用类型值存入数组
        // invokestatic:调用静态方法:Stream.of()
        // invokestatic:调用静态方法:Collectors.toList()
        // invokeinterface:调用接口方法Stream.collect
        // checkcast:检查数据类型为给定类型:List
        // astore:存储引用类型局部变量
        List<String> var4 = Stream.of("a", "b", "c").collect(Collectors.toList());

        // aload:加载引用类型数据var4
        // invokedynamic:运行时解析,调用方法accept
        // invokeinterface:forEach为接口方法
        var4.forEach(s -> {
            if ("a".equals(s)) {
                // invokestatic:System中out为static方法
                System.out.println(">>>>>>>>I am " + s);
            }
        });
    }

    public static void main(String[] args) {

    }
}

2.2 反编译获取汇编指令

进入对应的字节码路径:D:\java-basic-with-maven\target\classes\com\monkey\java_study\clzz
执行反编译字节码生成汇编指令:

javap -c ByteCodeTest.class

控制台生成的反编译汇编指令如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

PS D:\java-basic-with-maven\target\classes\com\monkey\java_study\clzz> javap -c .\ByteCodeTest.class
Compiled from "ByteCodeTest.java"
public class com.monkey.java_study.clzz.ByteCodeTest {
  public static final java.lang.String TEST_NAME;     
                                                      
  public com.monkey.java_study.clzz.ByteCodeTest();   
    Code:                                             
       0: aload_0                                                                           
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V         
       4: return                                                                            
                                                                                            
  public void call();                                                                       
    Code:                                                                                   
       0: new           #5                  // class com/monkey/java_study/clzz/ByteCodeTest
       3: dup
       4: invokespecial #6                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokespecial #7                  // Method test:()V
      12: ldc           #8                  // String m
      14: astore_2
      15: iconst_1
      16: istore_3
      17: new           #9                  // class com/monkey/java_study/common/entity/UserEntity
      20: dup
      21: invokespecial #10                 // Method com/monkey/java_study/common/entity/UserEntity."<init>":()V
      24: astore        4
      26: aload         4
      28: invokevirtual #11                 // Method com/monkey/java_study/common/entity/UserEntity.getNickname:()Ljava/lang/String;
      31: pop
      32: new           #12                 // class com/monkey/java_study/proxy/jdk_proxy/impl/UserServiceImpl
      35: dup
      36: invokespecial #13                 // Method com/monkey/java_study/proxy/jdk_proxy/impl/UserServiceImpl."<init>":()V
      39: astore        5
      41: aload         5
      43: invokeinterface #14,  1           // InterfaceMethod com/monkey/java_study/proxy/jdk_proxy/IUserService.add:()V
      48: iconst_3
      49: anewarray     #15                 // class java/lang/String
      52: dup
      53: iconst_0
      54: ldc           #16                 // String a
      56: aastore
      57: dup
      58: iconst_1
      59: ldc           #17                 // String b
      61: aastore
      62: dup
      63: iconst_2
      64: ldc           #18                 // String c
      66: aastore
      67: invokestatic  #19                 // InterfaceMethod java/util/stream/Stream.of:([Ljava/lang/Object;)Ljava/util/stream/Stream;       
      70: invokestatic  #20                 // Method java/util/stream/Collectors.toList:()Ljava/util/stream/Collector;
      73: invokeinterface #21,  2           // InterfaceMethod java/util/stream/Stream.collect:(Ljava/util/stream/Collector;)Ljava/lang/Object;
      78: checkcast     #22                 // class java/util/List
      81: astore        6
      83: aload         6
      85: invokedynamic #23,  0             // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
      90: invokeinterface #24,  2           // InterfaceMethod java/util/List.forEach:(Ljava/util/function/Consumer;)V
      95: return

  public static void main(java.lang.String[]);
    Code:
       0: return
}

2.3 常用字节码解析

2.3.1 常量入栈

序号 指令 描述
1 aconst_null null对象压入栈
2 iconst_m1 int类型常量-1压入栈
3 iconst_0 int类型常量0入栈,其中,多个常量,叠加,其余同
4 lconst_0 long类型常量0入栈
5 fconst_0 float类型常量0入栈
6 dconst_0 double类型常量入栈
7 bipush 8位有符号整数入栈
8 sipush 16位有符号整数入栈
9 ldc 常量池中的数据入栈
10 ldc_w 常量池中的数据入栈(使用宽索引)
11 ldc2_w 常量池中long或double类型数据入栈

2.3.2 栈中值存储到局部变量

序号 指令 描述
1 istore int类型数据存入局部变量
2 lstore long类型数据存入局部变量
3 fstore float类型数据存入局部变量
4 dstore double类型数据存入局部变量
5 astore 引用类型或返回地址类型数据存入局部变量
6 iastore int类型数据存入数组
7 lastore long类型数据存入数组
8 fastore float类型数据存入数组
9 dastore double类型数据存入数组
10 aastore 引用类型或返回地址类型数据存入数组
11 bastore byte或boolean类型存入数组
12 castore char类型数据存入数组
13 sastore short类型数据存入数组

2.3.3 从栈中加载数据

序号 指令 描述
1 iload 从局部变量中加载int类型数据
2 lload 从局部变量中加载long类型数据
3 fload 从局部变量中加载float类型数据
4 dload 从局部变量中加载double类型数据
5 aload 从局部变量中加载引用类型数据
6 baload 从数组加载byte或boolean类型数据
7 caload 从数组加载char类型数据
8 saload 从数组加载short类型数据
9 aaload 从数组加载引用类型数据
10 faload 从数组加载float数据
11 iaload 从数组加载int类型数据
12 daload 从数组加载double类型数据

2.3.4 通用栈操作

序号 指令 描述
1 nop 不操作
2 pop 弹出栈顶两个字长内容
3 pop2 复制栈顶部2个字长
4 dup 复制栈顶部一个字长
5 dup_x1 复制栈顶一个字长内容,然后将复制内容和原来弹出的2个字长内容入栈
6 dup_x2 复制栈顶一个字长内容,然后将复制内容和原来弹出的3个字长内容入栈
7 dup2 复制栈顶两个字长内容
8 dup2_x1 复制栈顶两个字长内容,然后将复制内容和原来弹出的三个字长内容入栈
9 dup2_x2 复制栈顶两个字长内容,然后将复制内容和原来弹出的四个字长内容入栈
10 swap 交换栈顶两个字长内容

2.3.5 对象操作

序号 指令 描述
1 new 新建对象
2 checkcast 检查对象是否为给定类型
3 getfield 获取对象中字段值
4 putfield 设置对象中字段值
5 getstatic 获取类中静态字段值
6 putstatic 设置类中静态字段值
7 instanceof 判断对象是否为给定类型

2.3.6 数组操作

序号 指令 描述
1 newarray 新建成员为基础类型的数组
2 anewarray 新建成员为引用类型的数组
3 arraylength 获取数组长度
4 multianewarray 分配新的多维数组

2.3.7 异常

序号 指令 描述
1 athrow 抛出异常或错误
2 goto 跳转,比如使用finally时,会有goto
3 jsr 跳转到子例程
4 jsr_w 跳转到子例程(宽索引)
5 rct 从子例程返回

2.3.8 方法调用

序号 指令 描述
1 invokespecial:调用当前类方法
2 invokevirtual 调用引入类的方法
3 invokeinterface 调用接口方法
4 invokestatic 调用静态方法
5 invokedynamic 调用运行时解析的方法

2.3.9 方法返回

序号 指令 描述
1 ireturn 方法返回值为int或者boolean
2 lreturn 方法返回值为long
3 freturn 方法返回值为float
4 dreturn 方法返回值为double
5 areturn 方法返回值为引用类型
6 return 从方法中直接返回,void

2.3.10 线程同步

序号 指令 描述
1 monitorenter 进入并获取监视器
2 monitorexit 释放并退出对象监视器

3 小结

新建对象分为4步:
(1)new:新建对象,在堆中为对象分配存储空间,并压入操作数栈顶;
(2)dup:复制栈顶部一个字长内容,入栈(此时栈有两个相同地址);
(3)invkespecial:构造函数调用初始化方法😦)V,操作数栈顶弹出ByteCodeTest对象引用(dup);
(4)astore_1:从操作数栈顶取出ByteCodeTest对象存入局部变量1。
方法调用有5种方式:
(1)invokespecial:调用当前类方法;
(2)invokevirtual:调用引入类的方法;
(3)invokeinterface:调用接口方法;
(4)invokestatic:调用静态方法;
(5)invokedynamic:调用运行时解析的方法。


网站公告

今日签到

点亮在社区的每一天
去签到