Java的编译和反编译

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/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容

  • 精心收集整理的教程笔记 第01天 Java基础入门 第1章计算机基础 1.1计算机 计算机(computer)俗称...
    Java帮帮阅读 5,943评论 1 109
  • 我喜欢绿植,无需花色亦好。 绿色,似生命的勇气和毅力,生生不息,在我极尽灰暗的谷底,蔓延出一条绿色...
    野朵阅读 378评论 2 2
  • 咋一看这题目,是不是有“我的地盘,我作主”的味儿?嗯,就是这个味儿。看过一则短小精悍的对话: 有人问毕加索:“你的...
    淡墨素笺Sally阅读 238评论 0 2