美文网首页
Java的编译和反编译

Java的编译和反编译

作者: 人在码途 | 来源:发表于2018-06-06 00:07 被阅读171次

    Java的编译和反编译

    什么是编译

    编译就是把C、C++、Java等高级语言转换成汇编语言、机器语言等低级语言的过程,低级语言更利于计算机的识别,高级语言更利于程序员的编写和阅读,执行这一过程的工具就是编译器。

    Java语言也有自己的编译器javac命令,当我们编写一个HelloWorld.java的文件后,可以通过命令javac HelloWorld.java编译出一个HelloWorld.class的文件,这个class文件就是JVM可以识别的文件。这个过程就是编译的过程。

    其实class文件也不是机器能够识别的语言,JVM还会进一步将class字节码文件转换成机器可以识别的机器语言。

    什么是反编译

    与编译过程相反,反编译就是把class文件转换成java文件的过程,有时候通过java的反编译,我们可以东西java语法背后的原理。

    Java常用的反编译工具

    javap

    javap是jdk自带的一个工具,可以对代码反编译,也可以查看java编译器生成的字节码,我们可以通过一个示例来理解

    如下是一个HelloWorld.java的源文件,是通过switch来匹配字符串,源码如下:

    public class HelloWorld {
    
        public static void main(String[] args) {
            String str = "world";
            switch (str) {
                case "hello":
                    System.out.println("hello");
                    break;
                case "world":
                    System.out.println("world");
                    break;
                default:
                    break;
            }
        }
    }
    
    

    这段代码很简单,通过初始化一个字符串常亮,然后通过switch来匹配并输出相应的字符串,从源代码中我们很难分析具体的比较方式,这个时候就可以用到编译---反编译的方法了。

    我们可以先通过javac HelloWorld.java生成HelloWorld.class的字节码文件,这个时候通过idea、eclipse在不借助插件的情况,是看不懂HelloWorld.class文件内容的。

    然后通过javap -c HelloWorld.class对class文件进行反编译,输出如下:

    Compiled from "HelloWorld.java"
    public class HelloWorld {
      public HelloWorld();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: ldc           #2                  // String world
           2: astore_1
           3: aload_1
           4: astore_2
           5: iconst_m1
           6: istore_3
           7: aload_2
           8: invokevirtual #3                  // Method java/lang/String.hashCode:()I
          11: lookupswitch  { // 2
                  99162322: 36
                 113318802: 50
                   default: 61
              }
          36: aload_2
          37: ldc           #4                  // String hello
          39: invokevirtual #5                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
          42: ifeq          61
          45: iconst_0
          46: istore_3
          47: goto          61
          50: aload_2
          51: ldc           #2                  // String world
          53: invokevirtual #5                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
          56: ifeq          61
          59: iconst_1
          60: istore_3
          61: iload_3
          62: lookupswitch  { // 2
                         0: 88
                         1: 99
                   default: 110
              }
          88: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
          91: ldc           #4                  // String hello
          93: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          96: goto          110
          99: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
         102: ldc           #2                  // String world
         104: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         107: goto          110
         110: return
    }
    
    

    javap 常用参数:

    -help 帮助
    
    -l 输出行和变量的表
    
    -public 只输出public方法和域
    
    -protected 只输出public和protected类和成员
    
    -package 只输出包,public和protected类和成员,这是默认的
    
    -p -private 输出所有类和成员
    
    -s 输出内部类型签名
    
    -c 输出分解后的代码,例如,类中每一个方法内,包含java字节码的指令,
    
    -verbose 输出栈大小,方法参数的个数
    
    -constants 输出静态final常量
    
    

    javap并没有将字节码反编译成java文件,而是生成了一种我们可以看得懂字节码。其实javap生成的文件仍然是字节码,只是程序员可以稍微看得懂一些。如果你对字节码有所掌握,还是可以看得懂以上的代码的。其实就是把String转成hashcode,然后进行比较。

    一般只有在需要看字节码的时候才会需要用到javap命令,字节码中暴漏的信息是最全的,当我们想通过反编译生成易于阅读的class文件时,就可以使用以下两个神器了。

    jad

    JAD是一个比较不错的反编译工具,只要下载一个执行工具,就可以实现对class文件的反编译了。还是上面的源代码,使用jad反编译后内容如下:

    jad HelloWorld.class 
    Parsing HelloWorld.class... Generating HelloWorld.jad
    

    jad反编译后会生成一个HelloWorld.jad文件,内容如下

    // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
    // Jad home page: http://www.kpdus.com/jad.html
    // Decompiler options: packimports(3) 
    // Source File Name:   HelloWorld.java
    
    import java.io.PrintStream;
    
    public class HelloWorld
    {
    
        public HelloWorld()
        {
        }
    
        public static void main(String args[])
        {
            String s = "world";
            String s1 = s;
            byte byte0 = -1;
            switch(s1.hashCode())
            {
            case 99162322: 
                if(s1.equals("hello"))
                    byte0 = 0;
                break;
    
            case 113318802: 
                if(s1.equals("world"))
                    byte0 = 1;
                break;
            }
            switch(byte0)
            {
            case 0: // '\0'
                System.out.println("hello");
                break;
    
            case 1: // '\001'
                System.out.println("world");
                break;
            }
        }
    }
    
    

    其反编译后的代码已经很接近源码,而且更易于阅读,不难看出,switch操作实际上是先cace的hashCode然后又通过equals方法来比较两个字符串

    jad常用参数:

     -a - 用JVM字节格式来注解输出 
    -af - 同 -a,但是注解的时候用全名称 
    -clear - 清除所有的前缀 
    -b - 输出多于的括号 (e.g., if(a) { b(); }, default: no) 
    -d <dir> - 指定输出文件的文件目录 
    -dead -试图反编译代码的dead 部分(default: no) 
    -disass - 不用用字节码的方式反编译 (no JAVA source generated) 
    -f - 输出整个的名字,无论是类还是方法 
    -ff -输出类的成员在方法之前 (default: after methods) 
    -i - 输出所有的变量的缺省的最初值 
    -l<num> - 将strings分割成指定数目的块的字符 (default: no) 
    -lnc - 将输出文件用行号来注解 (default: no) 
    -nl - 分割strings用新行字符 newline character (default: no) 
    -nodos -不要去检查class文件是否以dos方式写 (CR before NL, default: check) 
    -nocast - 不要生成辅助文件 
    -nocode -不要生成方法的源代码 
    -noconv - 不要转换java的定义符 (default: do) 
    -noctor - 不允许空的构造器存在 
    -noinner -关掉对内部类的支持 (default: turn on) 
    -nolvt - 忽略局部变量的表信息 
    -nonlb - 不要输出一个新行在打开一个括号之前 (default: do) 
    -o - 无需确认直接覆盖输出 (default: no) 
    -p - 发送反编译代码到标准输出 STDOUT (e.g., for piping) 
    
    其次.常用命令
    
    jad -o -r -sjava -dsrc test.class
    
    tree目录下的所有*.class文件
        jad -o -r -sjava -dsrc tree/**/*.class
    
        unix可以表示为:jad -o -r -sjava -dsrc 'tree/**/*.class'
    
    指定输出文件的名字的话,用以下的转移命令
    
    jad -p example1.class > myexm1.java
    

    但是,由于JAD已经很久不更新了,在对Java7生成的字节码进行反编译时,偶尔会出现不支持的问题,在对Java 8的lambda表达式反编译时就彻底失败

    cfr

    JAD很好用,但是无奈的是很久没更新了,所以只能用一款新的工具替代他,CFR是一个不错的选择,相比JAD来说,他的语法可能会稍微复杂一些,但是好在他可以用.

    CFR将反编译现代Java特性–Java 8 lambdas(Java和更早版本中的Java beta 103),已经反编译Java 7 String,但CFR是完全用Java 6编写的.

    cfr的jar包可以通过http://www.benf.org/other/cfr/cfr_0_129.jar下载

    执行反编译过程

    java -jar /Users/home/Desktop/cfr_0_129.jar  HelloWorld.class --decodestringswitch false
    

    输出如下:

    src $ java -jar /Users/tongkun/Desktop/cfr_0_129.jar  HelloWorld.class --decodestringswitch false
    /*
     * Decompiled with CFR 0_129.
     */
    import java.io.PrintStream;
    
    public class HelloWorld {
        public static void main(String[] arrstring) {
            String string;
            String string2 = string = "world";
            int n = -1;
            switch (string2.hashCode()) {
                case 99162322: {
                    if (!string2.equals("hello")) break;
                    n = 0;
                    break;
                }
                case 113318802: {
                    if (!string2.equals("world")) break;
                    n = 1;
                }
            }
            switch (n) {
                case 0: {
                    System.out.println("hello");
                    break;
                }
                case 1: {
                    System.out.println("world");
                    break;
                }
            }
        }
    }
    
    

    可以看出,与jad执行的结果类似,参数

    • --decodestringswitch表示对于switch支持string的细节进行解码。
    • 类似的还有--decodeenumswitch--decodefinally--decodelambdas等。
    • --decodelambdas可以对lambda表达式进行反编译。

    可以通过java -jar cfr_0_125.jar --help了解有哪些cfr参数,这里不一一说明了。

    CFR 0_129
    
       --aexagg                         (boolean) 
       --aggressivesizethreshold        (int >= 0)  default: 15000
       --allowcorrecting                (boolean)  default: true
       --analyseas                      (One of [JAR, WAR, CLASS]) 
       --arrayiter                      (boolean)  default: true if class file from version 49.0 (Java 5) or greater
       --caseinsensitivefs              (boolean)  default: false
       --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
       --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) 
       --forcecondpropagate             (boolean) 
       --forceexceptionprune            (boolean) 
       --forcereturningifs              (boolean) 
       --forcetopsort                   (boolean) 
       --forcetopsortaggress            (boolean) 
       --forloopaggcapture              (boolean) 
       --hidebridgemethods              (boolean)  default: true
       --hidelangimports                (boolean)  default: true
       --hidelongstrings                (boolean)  default: false
       --hideutf                        (boolean)  default: true
       --ignoreexceptions               (boolean)  default: false
       --innerclasses                   (boolean)  default: true
       --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
       --outputdir                      (string) 
       --outputpath                     (string) 
       --override                       (boolean)  default: true if class file from version 50.0 (Java 6) or greater
       --pullcodecase                   (boolean)  default: false
       --recover                        (boolean)  default: true
       --recovertypeclash               (boolean) 
       --recovertypehints               (boolean) 
       --relinkconststring              (boolean)  default: true
       --removebadgenerics              (boolean)  default: true
       --removeboilerplate              (boolean)  default: true
       --removedeadmethods              (boolean)  default: true
       --removeinnerclasssynthetics     (boolean)  default: true
       --rename                         (boolean)  default: false
       --renamedupmembers              
       --renameenumidents              
       --renameillegalidents           
       --renamesmallmembers             (int >= 0)  default: 0
       --showinferrable                 (boolean)  default: false if class file from version 51.0 (Java 7) or greater
       --showops                        (int >= 0)  default: 0
       --showversion                    (boolean)  default: true
       --silent                         (boolean)  default: false
       --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
       --sugarasserts                   (boolean)  default: true
       --sugarboxing                    (boolean)  default: true
       --sugarenums                     (boolean)  default: true if class file from version 49.0 (Java 5) or greater
       --tidymonitors                   (boolean)  default: true
       --tryresources                   (boolean)  default: true if class file from version 51.0 (Java 7) or greater
       --usenametable                   (boolean)  default: true
       --help                           (string) 
    
    
    

    JD-GUI

    JD-GUI 是一个用 C++ 开发的 Java反编译工具,由 Pavel Kouznetsov开发,支持Windows、Linux和苹果Mac Os三个平台。而且提供了Eclipse平台下的插件JD-Eclipse。JD-GUI 基于GPLv3开源协议,对个人使用是完全免费的。JD-GUI主要的是提供了可视化操作,直接拖拽文件到窗口既可,效果图如下

    感兴趣可以到http://jd.benow.ca/下载GUI以及idea、eclipse的反编译插件。

    JD-GUI使用也非常方便,只要把jar包或者class文件拖入JD-GUI界面或者打开即可完成反编译,这里不想起说明。

    参考感谢:

    https://blog.csdn.net/u011479200/article/details/80019827

    https://blog.csdn.net/dongnan591172113/article/details/51832628

    http://jd.benow.ca/

    相关文章

      网友评论

          本文标题:Java的编译和反编译

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