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
网友评论