**
一、基础知识
**
1.1 编程语言
在介绍编译和反编译之前,我们先来简单介绍下编程语言(Programming Language)。编程语言(Programming Language)分为低级语言(Low-level Language)和高级语言(High-level Language)。
机器语言(Machine Language)和汇编语言(Assembly Language)属于低级语言,直接用计算机指令编写程序。
而C、C++、Java、Python等属于高级语言,用语句(Statement)编写程序,语句是计算机指令的抽象表示。
举个例子,同样一个语句用C语言、汇编语言和机器语言分别表示如下:
计算机只能对数字做运算,符号、声音、图像在计算机内部都要用数字表示,指令也不例外,上表中的机器语言完全由十六进制数字组成。最早的程序员都是直接用机器语言编程,但是很麻烦,需要查大量的表格来确定每个数字表示什么意思,编写出来的程序很不直观,而且容易出错,于是有了汇编语言,把机器语言中一组一组的数字用助记符(Mnemonic)表示,直接用这些助记符写出汇编程序,然后让汇编器(Assembler)去查表把助记符替换成数字,也就把汇编语言翻译成了机器语言。
但是,汇编语言用起来同样比较复杂,后面,就衍生出了Java、C、C++等高级语言。
1.2 什么是编译
上面提到语言有两种,一种低级语言,一种高级语言。可以这样简单的理解:低级语言是计算机认识的语言、高级语言是程序员认识的语言。
那么如何从高级语言转换成低级语言呢?这个过程其实就是编译。
从上面的例子还可以看出,C语言的语句和低级语言的指令之间不是简单的一一对应关系,一条a=b+1;语句要翻译成三条汇编或机器指令,这个过程称为编译(Compile),由编译器(Compiler)来完成,显然编译器的功能比汇编器要复杂得多。用C语言编写的程序必须经过编译转成机器指令才能被计算机执行,编译需要花一些时间,这是用高级语言编程的一个缺点,然而更多的是优点。首先,用C语言编程更容易,写出来的代码更紧凑,可读性更强,出了错也更容易改正。
将便于人编写、阅读、维护的高级计算机语言所写作的源代码程序,翻译为计算机能解读、运行的低阶机器语言的程序的过程就是编译。负责这一过程的处理的工具叫做编译器
现在我们知道了什么是编译,也知道了什么是编译器。不同的语言都有自己的编译器,Java语言中负责编译的编译器是一个命令:javac
javac是收录于JDK中的Java语言编译器。该工具可以将后缀名为.java的源文件编译为后缀名为.class的可以运行于Java虚拟机的字节码。
当我们写完一个HelloWorld.java文件后,我们可以使用javac HelloWorld.java命令来生成HelloWorld.class文件,这个class类型的文件是JVM可以识别的文件。通常我们认为这个过程叫做Java语言的编译。其实,class文件仍然不是机器能够识别的语言,因为机器只能识别机器语言,还需要JVM再将这种class文件类型字节码转换成机器可以识别的机器语言。
1.3 什么是反编译
反编译的过程与编译刚好相反,就是将已编译好的编程语言还原到未编译的状态,也就是找出程序语言的源代码。就是将机器看得懂的语言转换成程序员可以看得懂的语言。Java语言中的反编译一般指将class文件转换成java文件。
有了反编译工具,我们可以做很多事情,最主要的功能就是有了反编译工具,我们就能读得懂Java编译器生成的字节码。如果你想问读懂字节码有啥用,那么我可以很负责任的告诉你,好处大大的。比如我的博文几篇典型的原理性文章,都是通过反编译工具得到反编译后的代码分析得到的。如深入理解多线程(一)——Synchronized的实现原理、深度分析Java的枚举类型—-枚举的线程安全性及序列化问题、Java中的Switch对整型、字符型、字符串型的具体实现细节、Java的类型擦除等。我最近在GitChat写了一篇关于Java语法糖的文章,其中大部分内容都用到反编译工具来洞悉语法糖背后的原理。
**
二、Java的编译与反编译
**
2.1 Java 编译
Java的编译有些不同,因为Java的特性是一次编写,到处运行。做到这种效果的主要依据就是JVM。Java的编译是分为两个阶段的,首先,利用JDK自带的编译器,将源代码经过词法分析,语法分析直至语义分析,然后就会产生一个class文件。这段过程称之为前端编译,此时产生的class文件还无法被计算机识别执行,只能算是整个编译过程中产生的一个中间产物。
然后JVM将读取到的二进制文件进行深度编译,将其编译成与具体平台相关的指令代码,这个过程是后端编译,它主要依赖于JVM。前端编译是与操作系统平台无关的,最终生成的class文件是可以在各个JVM平台进行深度编译;而后端编译就需要跟具体操作系统平台相关了,因为JVM有不同平台的版本,可以将这种统一格式的class文件进一步深度编译,将其转换成与具体平台相关的指令代码。
对于编译器,Java内置的有javac工具,此外很多IDE工具也内置了编译工具,但是这些都是前端编译器,主要功能就是把【.java】文件编程成【.class】文件。
2.1.1 词法分析器
这个阶段是将源程序文件从左到右一个字符一个字符地读入,将字符序列转换为标记(token)序列的过程。这里的标记是一个字符串,是构成源代码的最小单位,该过程中,词法分析器还会对标记进行分类。
词法分析器通常不会关心标记之间的关系(分析关系主要在语法分析阶段)。如:源程序中会有很多括号,但是词法分析器只是将其识别为标记,但是它并不关心这些括号是否能正确匹配(即:如果只有“{”,而没有“}”,词法分析中不会发现问题)。
2.1.2 语法分析器
它是在词法分析的基础上,将单词序列组合成各种短语,如“程序”、“语句”、“表达式”等等。语法分析能够判断源程序在结构上是否正确。
2.1.3 语义分析
该阶段是程序编译的一个逻辑阶段。它是对结构正确的源程序进行上下文有关性质的审查。它主要是审查源程序是否含有语义错误,同时也为代码的生成阶段收集类型信息。
语义分析中的一个很重要的部分就是类型审查,比如很多语言要求数组下标必须为整数,如果使用浮点数作为下标,编译器就会报错;再比如很多程序允许某些类型之间能够进行自动转换等等。
经历过上面的过程之后,就开始生成中间代码,中间代码具有两个很重要的性质:易于生成、能够轻松翻译成目标机器上的语言。著名的解语法糖操作就是在javac中完成的。
2.1.4 后端编译
Java在经历过前端编译之后,如果需要执行编译后的class文件,需要借助于JVM,JVM会将class文件中的内容逐条翻译成机器指令,这个解释的过程就是JVM的解释器(Interpreter)的功劳。很明显,这个解释是比较浪费时间的,为了提高这种解释的效率,Java引入了JIT技术。
虽然引入了JIT,Java仍然使用解释器进行代码解释,但是在解释的过程中,随着代码的不断执行,会识别出代码中执行比较频繁的代码段,这段代码就会被标记为“热点代码”。JIT就会将这段热点代码编译后的机器代码进行优化后,缓存起来,下次再执行到这段代码,直接跳过编译过程,使用缓存的机器码。
HotSpot虚拟机中内置了两种JIT编译器:Client Compiler和Server Compiler。目前主流的方式就是采用其中一种编译器与解释器配合工作的方式。那为什么不将其全部编译成热点代码呢?主要是出于资源最大化利用的考虑:在程序中,不可能所有代码都是热点代码,真正频繁执行的代码只是占据很少一部分,如果将那些只执行了一遍就再也不执行的代码也进行缓存,完全就是在浪费缓存资源。另外在将代码转换成热点代码过程中是需要经过一个编译过程的,如果这种只执行一次的代码也要编译,其实也是浪费时间。
热点检测
要想触发JIT编译,就必须满足热点代码的检测,目前主要有两种热点代码探测的方式:
- 基于采样的方式探测(Simple Based Hot Spot Detection):周期性的检测各个线程的栈顶,如果发现某个方法经常出现在栈顶,就可以认为是热点方法。它的缺点很明显:无法精确确认一个方法的热度,另外也容易受到线程阻塞或者别的原因干扰。
- 基于计数器的热点探测(Counter Based Hot Spot Detection):虚拟机会为每个方法,甚至是每个代码块建立计数器,统计方法和代码块的执行次数,一旦超过某个阈值就认为是热点方法,触发JIT编译。
HotSpot虚拟机采用的就是上面第二种探测方式,准备了两个计数器:方法计数器和回边计数器。它们分别对应方法调用次数统计和代码循环执行次数统计。
编译优化
其实JIT除了具有缓存的功能,还会对代码做各种优化,例如:逃逸分析、锁消除、锁膨胀、方法内联、空值检查消除、类型检测消除、公共子表达式消除等等。可以搜索相关概念介绍,了解其原理,这里暂时不再赘述。
2.2 Java 反编译
2.2.1 Java反编译工具
常见的Java的反编译工具:javap、jad、jd-gui和cfr
其中,jd-gui提供了UI界面,使用起来很方便
2.2.2 如何防止反编译
由于我们有工具可以对Class文件进行反编译,所以,对开发人员来说,如何保护Java程序就变成了一个非常重要的挑战。但是,魔高一尺、道高一丈。当然有对应的技术可以应对反编译咯。但是,这里还是要说明一点,和网络安全的防护一样,无论做出多少努力,其实都只是提高攻击者的成本而已。无法彻底防治。
典型的应对策略有以下几种:
- 隔离Java程序(让用户接触不到你的Class文件)
- 对Class文件进行加密(提高破解难度)
- 代码混淆(将代码转换成功能上等价,但是难于阅读和理解的形式)