Java的编译和反编译

2018-06-06  本文已影响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执行的结果类似,参数

可以通过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/

上一篇下一篇

猜你喜欢

热点阅读