美文网首页Java 杂谈程序员javatool
JDK自带的反编译工具 javap

JDK自带的反编译工具 javap

作者: pjmike | 来源:发表于2018-09-06 17:52 被阅读0次

    前言

    以前经常看一些文章使用 javac 反编译 class文件,然后生成一堆字节码,再一顿骚操作分析字节码,可谓是非常炫酷。这里有时间刚好也来玩玩JDK的 javap

    javap 介绍

    javap是 JDK自带的一个工具,可以将 class文件反编译成字节码,它并没有将class文件反编译成 java文件,但是依然反编译成程序员能读的格式。

    下面举一个小例子,java源代码如下:

    public class JavapTest2 {
        private String username;
    
        public void say(String username) {
            System.out.println("hi,"+username);
        }
    }
    

    将其编译后,使用 javap来查询 JavapTest2的字节码

    javac JavapTest2.java
    javap -p -v JavapTest2
    

    生成的字节码如下:

    Classfile ../JavapTest2.class
      Last modified 2018-8-31; size 608 bytes
      MD5 checksum 25f04ad8674616cb2f0e7fe9d35e6ab1
      Compiled from "JavapTest2.java"
    public class com.pjmike.JVM.JavapTest2
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #10.#21        // java/lang/Object."<init>":()V
       #2 = Fieldref           #22.#23        // java/lang/System.out:Ljava/io/PrintStream;
       #3 = Class              #24            // java/lang/StringBuilder
       #4 = Methodref          #3.#21         // java/lang/StringBuilder."<init>":()V
       #5 = String             #25            // hi,
       #6 = Methodref          #3.#26         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/String
    Builder;
       #7 = Methodref          #3.#27         // java/lang/StringBuilder.toString:()Ljava/lang/String;
       #8 = Methodref          #28.#29        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #9 = Class              #30            // com/pjmike/JVM/JavapTest2
      #10 = Class              #31            // java/lang/Object
      #11 = Utf8               username
      #12 = Utf8               Ljava/lang/String;
      #13 = Utf8               <init>
      #14 = Utf8               ()V
      #15 = Utf8               Code
      #16 = Utf8               LineNumberTable
      #17 = Utf8               say
      #18 = Utf8               (Ljava/lang/String;)V
      #19 = Utf8               SourceFile
      #20 = Utf8               JavapTest2.java
      #21 = NameAndType        #13:#14        // "<init>":()V
      #22 = Class              #32            // java/lang/System
      #23 = NameAndType        #33:#34        // out:Ljava/io/PrintStream;
      #24 = Utf8               java/lang/StringBuilder
      #25 = Utf8               hi,
      #26 = NameAndType        #35:#36        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      #27 = NameAndType        #37:#38        // toString:()Ljava/lang/String;
      #28 = Class              #39            // java/io/PrintStream
      #29 = NameAndType        #40:#18        // println:(Ljava/lang/String;)V
      #30 = Utf8               com/pjmike/JVM/JavapTest2
      #31 = Utf8               java/lang/Object
      #32 = Utf8               java/lang/System
      #33 = Utf8               out
      #34 = Utf8               Ljava/io/PrintStream;
      #35 = Utf8               append
      #36 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
      #37 = Utf8               toString
      #38 = Utf8               ()Ljava/lang/String;
      #39 = Utf8               java/io/PrintStream
      #40 = Utf8               println
    {
      private java.lang.String username;
        descriptor: Ljava/lang/String;
        flags: ACC_PRIVATE
    
      public com.pjmike.JVM.JavapTest2();
        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 7: 0
    
      public void say(java.lang.String);
        descriptor: (Ljava/lang/String;)V
        flags: ACC_PUBLIC
        Code:
          stack=3, locals=2, args_size=2
             0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: new           #3                  // class java/lang/StringBuilder
             6: dup
             7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
            10: ldc           #5                  // String hi,
            12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/
    lang/StringBuilder;
            15: aload_1
            16: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/
    lang/StringBuilder;
            19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
            22: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            25: return
          LineNumberTable:
            line 11: 0
            line 12: 25
    }
    SourceFile: "JavapTest2.java"
    
    

    默认情况下 javap 会打印所有非私有的字段和方法,如下:

    javap JavapTest2
    
    Compiled from "JavapTest2.java"
    public class com.pjmike.JVM.JavapTest2 {
      public com.pjmike.JVM.JavapTest2();
      public void say(java.lang.String);
    }
    
    

    javap -help查看其选项:

    用法: javap <options> <classes>
    其中, 可能的选项包括:
      -help  --help  -?        输出此用法消息
      -version                 版本信息
      -v  -verbose             输出附加信息
      -l                       输出行号和本地变量表
      -public                  仅显示公共类和成员
      -protected               显示受保护的/公共类和成员
      -package                 显示程序包/受保护的/公共类
                               和成员 (默认)
      -p  -private             显示所有类和成员
      -c                       对代码进行反汇编
      -s                       输出内部类型签名
      -sysinfo                 显示正在处理的类的
                               系统信息 (路径, 大小, 日期, MD5 散列)
      -constants               显示最终常量
      -classpath <path>        指定查找用户类文件的位置
      -cp <path>               指定查找用户类文件的位置
      -bootclasspath <path>    覆盖引导类文件的位置
    
    

    从上面就可以看到 javap 选项的一些作用,在最开始的地方,我们使用了 javap -v -p JavapTest2 。加了 -p 选项后,还会打印私有的字段和方法,加上 -v 选项后,它会尽可能地打印出所有信息,如果只需要查询相关方法对应的字节码,可以使用 -c 代替 -v,代码如下:

    Compiled from "JavapTest2.java"
    public class com.pjmike.JVM.JavapTest2 {
      private java.lang.String username;
    
      public com.pjmike.JVM.JavapTest2();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public void say(java.lang.String);
        Code:
           0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: new           #3                  // class java/lang/StringBuilder
           6: dup
           7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
          10: ldc           #5                  // String hi,
          12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/la
    ng/StringBuilder;
          15: aload_1
          16: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/la
    ng/StringBuilder;
          19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
          22: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          25: return
    }
    
    

    可以看出少了很多附加信息,让我们更加专心的去关注方法对应的字节码。

    下面简要分析下 say方法中的打印语句,里面涉及了字符串的拼接操作:

    1. 首先是new 指令,创建类实例的指令,在Java源代码的字符串拼接,到了编译器在编译阶段使用 StringBuilder类进行优化
        3: new           #3                  // class java/lang/StringBuilder
    
    1. 然后 invokespecial指令,用于调用实例初始化方法,将 StringBuilder对象初始化
           7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
    
    
    1. ldc将"hi"字符串常量加载到操作数栈,然后invokevirtual指令用于调用对象的实例方法,这里调用 StringBuilderappend()拼接字符串的方法
          10: ldc           #5                  // String hi,
          12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/la
    ng/StringBuilder;
    
    1. 最后调用 StringBuildertoString(),将拼接后的字符串输出
          19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    
    

    以上非常简要的分析了 字符串拼接的字节码操作,更多关于字节码的指令介绍,请参阅相关文档

    小结

    关于 javap 以及相关字节码知识目前还是接触不多,这里只是简单玩一玩javap,更多字节码相关的知识以及其他反编译工具,如`jad,cfr等还需要后续进一步深入探究。

    相关文章

      网友评论

        本文标题:JDK自带的反编译工具 javap

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