Java的編譯和反編譯

Java的編譯和反編譯

什么是編譯

編譯就是把C脯燃、C++汤求、Java等高級語言轉(zhuǎn)換成匯編語言、機(jī)器語言等低級語言的過程仇轻,低級語言更利于計(jì)算機(jī)的識別,高級語言更利于程序員的編寫和閱讀奶甘,執(zhí)行這一過程的工具就是編譯器拯田。

Java語言也有自己的編譯器javac命令,當(dāng)我們編寫一個(gè)HelloWorld.java的文件后甩十,可以通過命令javac HelloWorld.java編譯出一個(gè)HelloWorld.class的文件船庇,這個(gè)class文件就是JVM可以識別的文件。這個(gè)過程就是編譯的過程侣监。

其實(shí)class文件也不是機(jī)器能夠識別的語言鸭轮,JVM還會進(jìn)一步將class字節(jié)碼文件轉(zhuǎn)換成機(jī)器可以識別的機(jī)器語言。

什么是反編譯

與編譯過程相反橄霉,反編譯就是把class文件轉(zhuǎn)換成java文件的過程窃爷,有時(shí)候通過java的反編譯,我們可以東西java語法背后的原理姓蜂。

Java常用的反編譯工具

javap

javap是jdk自帶的一個(gè)工具按厘,可以對代碼反編譯,也可以查看java編譯器生成的字節(jié)碼钱慢,我們可以通過一個(gè)示例來理解

如下是一個(gè)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;
        }
    }
}

這段代碼很簡單束莫,通過初始化一個(gè)字符串常亮懒棉,然后通過switch來匹配并輸出相應(yīng)的字符串草描,從源代碼中我們很難分析具體的比較方式,這個(gè)時(shí)候就可以用到編譯---反編譯的方法了策严。

我們可以先通過javac HelloWorld.java生成HelloWorld.class的字節(jié)碼文件穗慕,這個(gè)時(shí)候通過idea、eclipse在不借助插件的情況妻导,是看不懂HelloWorld.class文件內(nèi)容的逛绵。

然后通過javap -c HelloWorld.class對class文件進(jìn)行反編譯,輸出如下:

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 常用參數(shù):

-help 幫助

-l 輸出行和變量的表

-public 只輸出public方法和域

-protected 只輸出public和protected類和成員

-package 只輸出包倔韭,public和protected類和成員术浪,這是默認(rèn)的

-p -private 輸出所有類和成員

-s 輸出內(nèi)部類型簽名

-c 輸出分解后的代碼,例如狐肢,類中每一個(gè)方法內(nèi)添吗,包含java字節(jié)碼的指令沥曹,

-verbose 輸出棧大小份名,方法參數(shù)的個(gè)數(shù)

-constants 輸出靜態(tài)final常量

javap并沒有將字節(jié)碼反編譯成java文件,而是生成了一種我們可以看得懂字節(jié)碼妓美。其實(shí)javap生成的文件仍然是字節(jié)碼僵腺,只是程序員可以稍微看得懂一些。如果你對字節(jié)碼有所掌握壶栋,還是可以看得懂以上的代碼的辰如。其實(shí)就是把String轉(zhuǎn)成hashcode,然后進(jìn)行比較贵试。

一般只有在需要看字節(jié)碼的時(shí)候才會需要用到javap命令琉兜,字節(jié)碼中暴漏的信息是最全的,當(dāng)我們想通過反編譯生成易于閱讀的class文件時(shí)毙玻,就可以使用以下兩個(gè)神器了豌蟋。

jad

JAD是一個(gè)比較不錯(cuò)的反編譯工具,只要下載一個(gè)執(zhí)行工具桑滩,就可以實(shí)現(xiàn)對class文件的反編譯了梧疲。還是上面的源代碼,使用jad反編譯后內(nèi)容如下:

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

jad反編譯后會生成一個(gè)HelloWorld.jad文件运准,內(nèi)容如下

// 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;
        }
    }
}

其反編譯后的代碼已經(jīng)很接近源碼幌氮,而且更易于閱讀,不難看出胁澳,switch操作實(shí)際上是先cace的hashCode然后又通過equals方法來比較兩個(gè)字符串

jad常用參數(shù):

 -a - 用JVM字節(jié)格式來注解輸出 
-af - 同 -a,但是注解的時(shí)候用全名稱 
-clear - 清除所有的前綴 
-b - 輸出多于的括號 (e.g., if(a) { b(); }, default: no) 
-d <dir> - 指定輸出文件的文件目錄 
-dead -試圖反編譯代碼的dead 部分(default: no) 
-disass - 不用用字節(jié)碼的方式反編譯 (no JAVA source generated) 
-f - 輸出整個(gè)的名字,無論是類還是方法 
-ff -輸出類的成員在方法之前 (default: after methods) 
-i - 輸出所有的變量的缺省的最初值 
-l<num> - 將strings分割成指定數(shù)目的塊的字符 (default: no) 
-lnc - 將輸出文件用行號來注解 (default: no) 
-nl - 分割strings用新行字符 newline character (default: no) 
-nodos -不要去檢查class文件是否以dos方式寫 (CR before NL, default: check) 
-nocast - 不要生成輔助文件 
-nocode -不要生成方法的源代碼 
-noconv - 不要轉(zhuǎn)換java的定義符 (default: do) 
-noctor - 不允許空的構(gòu)造器存在 
-noinner -關(guān)掉對內(nèi)部類的支持 (default: turn on) 
-nolvt - 忽略局部變量的表信息 
-nonlb - 不要輸出一個(gè)新行在打開一個(gè)括號之前 (default: do) 
-o - 無需確認(rèn)直接覆蓋輸出 (default: no) 
-p - 發(fā)送反編譯代碼到標(biāo)準(zhǔn)輸出 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'

指定輸出文件的名字的話该互,用以下的轉(zhuǎn)移命令

jad -p example1.class > myexm1.java

但是,由于JAD已經(jīng)很久不更新了韭畸,在對Java7生成的字節(jié)碼進(jìn)行反編譯時(shí)慢洋,偶爾會出現(xiàn)不支持的問題塘雳,在對Java 8的lambda表達(dá)式反編譯時(shí)就徹底失敗

cfr

JAD很好用,但是無奈的是很久沒更新了普筹,所以只能用一款新的工具替代他败明,CFR是一個(gè)不錯(cuò)的選擇,相比JAD來說太防,他的語法可能會稍微復(fù)雜一些妻顶,但是好在他可以用.

CFR將反編譯現(xiàn)代Java特性–Java 8 lambdas(Java和更早版本中的Java beta 103),已經(jīng)反編譯Java 7 String蜒车,但CFR是完全用Java 6編寫的.

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

執(zhí)行反編譯過程

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執(zhí)行的結(jié)果類似,參數(shù)

  • --decodestringswitch表示對于switch支持string的細(xì)節(jié)進(jìn)行解碼酿愧。
  • 類似的還有--decodeenumswitch沥潭、--decodefinally--decodelambdas等嬉挡。
  • --decodelambdas可以對lambda表達(dá)式進(jìn)行反編譯钝鸽。

可以通過java -jar cfr_0_125.jar --help了解有哪些cfr參數(shù),這里不一一說明了庞钢。

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 是一個(gè)用 C++ 開發(fā)的 Java反編譯工具拔恰,由 Pavel Kouznetsov開發(fā),支持Windows基括、Linux和蘋果Mac Os三個(gè)平臺颜懊。而且提供了Eclipse平臺下的插件JD-Eclipse。JD-GUI 基于GPLv3開源協(xié)議风皿,對個(gè)人使用是完全免費(fèi)的河爹。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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末炊苫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子冰沙,更是在濱河造成了極大的恐慌侨艾,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拓挥,死亡現(xiàn)場離奇詭異唠梨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)侥啤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門当叭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茬故,“玉大人,你說我怎么就攤上這事蚁鳖』前牛” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵醉箕,是天一觀的道長钾腺。 經(jīng)常有香客問我,道長讥裤,這世上最難降的妖魔是什么放棒? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮己英,結(jié)果婚禮上间螟,老公的妹妹穿的比我還像新娘。我一直安慰自己损肛,他們只是感情好厢破,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著荧关,像睡著了一般溉奕。 火紅的嫁衣襯著肌膚如雪褂傀。 梳的紋絲不亂的頭發(fā)上忍啤,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天,我揣著相機(jī)與錄音仙辟,去河邊找鬼同波。 笑死,一個(gè)胖子當(dāng)著我的面吹牛叠国,可吹牛的內(nèi)容都是我干的未檩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼粟焊,長吁一口氣:“原來是場噩夢啊……” “哼冤狡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起项棠,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤悲雳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后香追,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體合瓢,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年透典,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晴楔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顿苇。...
    茶點(diǎn)故事閱讀 39,769評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖税弃,靈堂內(nèi)的尸體忽然破棺而出纪岁,到底是詐尸還是另有隱情,我是刑警寧澤则果,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布蜂科,位于F島的核電站,受9級特大地震影響短条,放射性物質(zhì)發(fā)生泄漏导匣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一茸时、第九天 我趴在偏房一處隱蔽的房頂上張望贡定。 院中可真熱鬧,春花似錦可都、人聲如沸缓待。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽旋炒。三九已至,卻和暖如春签杈,著一層夾襖步出監(jiān)牢的瞬間瘫镇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工答姥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铣除,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓鹦付,卻偏偏與公主長得像尚粘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子敲长,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 精心收集整理的教程筆記 第01天 Java基礎(chǔ)入門 第1章計(jì)算機(jī)基礎(chǔ) 1.1計(jì)算機(jī) 計(jì)算機(jī)(computer)俗稱...
    Java幫幫閱讀 5,958評論 1 109
  • 我喜歡綠植郎嫁,無需花色亦好。 綠色祈噪,似生命的勇氣和毅力泽铛,生生不息,在我極盡灰暗的谷底钳降,蔓延出一條綠色...
    野朵閱讀 383評論 2 2
  • 咋一看這題目,是不是有“我的地盤铲觉,我作主”的味兒澈蝙?嗯,就是這個(gè)味兒撵幽〉朴看過一則短小精悍的對話: 有人問畢加索:“你的...
    淡墨素箋Sally閱讀 242評論 0 2