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