Java 的编译与反编译

454 阅读2分钟

Java 反编译

对 Java 的反编译工具做出介绍,包含:javap、cfr。

1. javap

javap 是 JDK 自带的反编译工具。

PS F:\> 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>    覆盖引导类文件的位置

其中常用的选项有:-p-c

下面使用 -p 参数查看枚举类的所有类和成员:

PS F:\> javap -p .\TestEnum.class
Compiled from "TestEnum.java"
public final class test.TestEnum extends java.lang.Enum<test.TestEnum> {
  public static final test.TestEnum A;
  public static final test.TestEnum B;
  private static final test.TestEnum[] $VALUES;
  public static test.TestEnum[] values();
  public static test.TestEnum valueOf(java.lang.String);
  private test.TestEnum();
  static {};
}

可以出 TestEnum 类中的所有变量与方法,但是想要查看其实现细节,-c 参数是一个选择。

PS F:\> javap -c .\TestEnum.class
Compiled from "TestEnum.java"
public final class test.TestEnum extends java.lang.Enum<test.TestEnum> {
  public static final test.TestEnum A;

  public static final test.TestEnum B;

  public static test.TestEnum[] values();
    Code:
       0: getstatic     #1                  // Field $VALUES:[Ltest/TestEnum;
       3: invokevirtual #2                  // Method "[Ltest/TestEnum;".clone:()Ljava/lang/Object;
       6: checkcast     #3                  // class "[Ltest/TestEnum;"
       9: areturn

  public static test.TestEnum valueOf(java.lang.String);
    Code:
       0: ldc           #4                  // class test/TestEnum
       2: aload_0
       3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       6: checkcast     #4                  // class test/TestEnum
       9: areturn

  static {};
    Code:
       0: new           #4                  // class test/TestEnum
       3: dup
       4: ldc           #7                  // String A
       6: iconst_0
       7: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic     #9                  // Field A:Ltest/TestEnum;
      13: new           #4                  // class test/TestEnum
      16: dup
      17: ldc           #10                 // String B
      19: iconst_1
      20: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
      23: putstatic     #11                 // Field B:Ltest/TestEnum;
      26: iconst_2
      27: anewarray     #4                  // class test/TestEnum
      30: dup
      31: iconst_0
      32: getstatic     #9                  // Field A:Ltest/TestEnum;
      35: aastore
      36: dup
      37: iconst_1
      38: getstatic     #11                 // Field B:Ltest/TestEnum;
      41: aastore
      42: putstatic     #1                  // Field $VALUES:[Ltest/TestEnum;
      45: return
}

-c 参数正如描述中所说,对 TestEnum.class 代码进行反汇编,生成了我们能看懂的字节码。

javap 使用的机会不多,一般只有在真的需要看字节码的时候才会用到。但是字节码中间暴露的东西是最全的。比如在分析synchronized的原理的时候就有是用到javap。通过javap生成的字节码,我发现synchronized底层依赖了ACC_SYNCHRONIZED标记和monitorentermonitorexit两个指令来实现同步。

2. crf

CFR 将反编译现代 Java 特性,支持 Java 9、12 和 14 的大部分。它完全是用 Java 6 编写的,所以在任何地方都可以工作!

下载链接:www.benf.org/other/cfr/

使用 --help 参数查看可使用的参数,及其相关注释

PS F:\PrivateData\Notes\on_java_8\out\production\on_java_8\test> java -jar .\cfr-0.151.jar --help
CFR 0.151

   --aexagg                         (boolean)
   --aexagg2                        (boolean)
   --aggressivedocopy               (int >= 0)  default: 0
   --aggressivedoextension          (boolean)
   --aggressiveduff                 (boolean)
   --aggressivesizethreshold        (int >= 0)  default: 15000
   --allowcorrecting                (boolean)  default: true
   --allowmalformedswitch           (boolean)
   --analyseas                      (One of [DETECT, JAR, WAR, CLASS])
   --antiobf                        (boolean)  default: false
   --arrayiter                      (boolean)  default: true if class file from version 49.0 (Java 5) or greater
   --caseinsensitivefs              (boolean)  default: true
   --clobber                        (boolean)
   --collectioniter                 (boolean)  default: true if class file from version 49.0 (Java 5) or greater
   --commentmonitors                (boolean)  default: false
   --comments                       (boolean)  default: true
   --constobf                       (boolean)  default: Value of option 'antiobf'
   --decodeenumswitch               (boolean)  default: true if class file from version 49.0 (Java 5) or greater
   --decodefinally                  (boolean)  default: true
   --decodelambdas                  (boolean)  default: true if class file from version 52.0 (Java 8) or greater
   --decodestringswitch             (boolean)  default: true if class file from version 51.0 (Java 7) or greater
   --dumpclasspath                  (boolean)  default: false
   --eclipse                        (boolean)  default: true
   --elidescala                     (boolean)  default: false
   --extraclasspath                 (string)
   --forbidanonymousclasses         (boolean)  default: false
   --forbidmethodscopedclasses      (boolean)  default: false
   --forceclassfilever              (string, specifying either java version as 'j6', 'j1.0', or classfile as '56', '56.65535')
   --forcecondpropagate             (boolean)
   --forceexceptionprune            (boolean)
   --forcereturningifs              (boolean)
   --forcetopsort                   (boolean)
   --forcetopsortaggress            (boolean)
   --forcetopsortnopull             (boolean)
   --forloopaggcapture              (boolean)
   --hidebridgemethods              (boolean)  default: Value of option 'obfattr'
   --hidelangimports                (boolean)  default: true
   --hidelongstrings                (boolean)  default: false
   --hideutf                        (boolean)  default: true
   --ignoreexceptions               (boolean)  default: false
   --ignoreexceptionsalways         (boolean)  default: false
   --importfilter                   (string)
   --innerclasses                   (boolean)  default: true
   --instanceofpattern              (boolean)  default: true if class file from version 58.0 (Java 14) or greater, or experimental in 58.0 (Java 14)
   --j14classobj                    (boolean)  default: false if class file from version 49.0 (Java 5) or greater
   --jarfilter                      (string)
   --labelledblocks                 (boolean)  default: true
   --lenient                        (boolean)  default: false
   --liftconstructorinit            (boolean)  default: true
   --lomem                          (boolean)  default: false
   --methodname                     (string)
   --obfattr                        (boolean)  default: Value of option 'antiobf'
   --obfcontrol                     (boolean)  default: Value of option 'antiobf'
   --obfuscationpath                (string)
   --outputdir                      (string)
   --outputpath                     (string)
   --override                       (boolean)  default: true if class file from version 50.0 (Java 6) or greater
   --previewfeatures                (boolean)  default: true
   --pullcodecase                   (boolean)  default: false
   --recordtypes                    (boolean)  default: true if class file from version 58.0 (Java 14) or greater, or experimental in 58.0 (Java 14)
   --recover                        (boolean)  default: true
   --recovertypeclash               (boolean)
   --recovertypehints               (boolean)
   --reducecondscope                (boolean)
   --relinkconststring              (boolean)  default: true
   --removebadgenerics              (boolean)  default: true
   --removeboilerplate              (boolean)  default: true
   --removedeadconditionals         (boolean)
   --removedeadmethods              (boolean)  default: true
   --removeinnerclasssynthetics     (boolean)  default: true
   --rename                         (boolean)  default: false
   --renamedupmembers               (boolean)  default: Value of option 'rename'
   --renameenumidents               (boolean)  default: Value of option 'rename'
   --renameillegalidents            (boolean)  default: Value of option 'rename'
   --renamesmallmembers             (int >= 0)  default: 0
   --showinferrable                 (boolean)  default: false if class file from version 51.0 (Java 7) or greater
   --showversion                    (boolean)  default: true
   --silent                         (boolean)  default: false
   --skipbatchinnerclasses          (boolean)  default: true
   --staticinitreturn               (boolean)  default: true
   --stringbuffer                   (boolean)  default: false if class file from version 49.0 (Java 5) or greater
   --stringbuilder                  (boolean)  default: true if class file from version 49.0 (Java 5) or greater
   --stringconcat                   (boolean)  default: true if class file from version 53.0 (Java 9) or greater
   --sugarasserts                   (boolean)  default: true
   --sugarboxing                    (boolean)  default: true
   --sugarenums                     (boolean)  default: true if class file from version 49.0 (Java 5) or greater
   --switchexpression               (boolean)  default: true if class file from version 57.0 (Java 13) or greater, or experimental in 56.0 (Java 12)
   --tidymonitors                   (boolean)  default: true
   --trackbytecodeloc               (boolean)  default: false
   --tryresources                   (boolean)  default: true if class file from version 51.0 (Java 7) or greater
   --usenametable                   (boolean)  default: true
   --usesignatures                  (boolean)  default: true
   --help                           (string)

Please specify '--help optionname' for specifics, eg
   --help pullcodecase

常用的参数有:

1)--decodeenumswitch:对于 switch 支持 String 的细节进行解码。

2)--decodestringswitch:对于 switch 支持 enum 的细节进行解码。

3)--decodelambdas:对 Lambda 表达式细节进行反编译。

4)--sugarenums:对 enum 语法糖细节进行节码。

比如:可以使用 --sugarenums 参数查看 Enum 的语法糖,对 TestEnum.class 的细节进行查看。

PS F:\PrivateData\Notes\on_java_8\out\production\on_java_8\test> java -jar .\cfr-0.151.jar .\TestEnum.class --sugarenums false
/*
 * Decompiled with CFR 0.151.
 */
package test;

public final class TestEnum
extends Enum<TestEnum> {
    public static final /* enum */ TestEnum A = new TestEnum("A", 0);
    public static final /* enum */ TestEnum B = new TestEnum("B", 1);
    private static final /* synthetic */ TestEnum[] $VALUES;

    public static TestEnum[] values() {
        return (TestEnum[])$VALUES.clone();
    }

    public static TestEnum valueOf(String name) {
        return Enum.valueOf(TestEnum.class, name);
    }

    private TestEnum(String string, int n) {
        super(string, n);
    }

    static {
        $VALUES = new TestEnum[]{A, B};
    }
}

参考资料