编译与反编译技术实战
上QQ阅读APP看书,第一时间看更新

2.2 编译器的结构

目前常用的程序设计语言都已经有很多优秀的编译器,比如C语言有GCC和ICC、C++有G++和I++、Java有JAVAC和GCJ。然而,即使这些常用的程序设计语言,其本身也一直在改变,即不断地完善。因而,实现这些程序设计语言的编译器也需要做出相应的改动。对于程序设计语言自身的改变,有的是为了弥补自身的一些缺陷,如Java语言从设计至今,其体积已经增大了好几倍;有的是为了适应新的软件开发需求,比如为了更容易地开发大型软件等而进行的改善。

除了那些成熟语言的改动会带来编译器软件编程的需要外,新语言的诞生也需要程序员来完成新语言的编译器实现工作。比如,现在不断涌现的各种脚本语言都需要编译器程序员来编写这些语言的编译器或解释器。对于新语言的发明,有的是为了适应特殊领域的编程需要,比如SQL (Structured Query Language)是为关系数据库管理系统专门设计的专用语言;有的是为了更好地利用各种系统资源(尤其是硬件资源),比如OpenCL (Open Computing Language)是为了更好地开发异构平台的计算能力。作为高级语言到目标代码的翻译软件(或者不同语言间的翻译软件)的编译器,对它的编程需求一直都存在。也就是说,总有实现新的编译器或者改动现有编译器的需求存在。

不同的编译器虽然实现方式各异,但编译器的结构却非常相似,通常是按照编译过程的各个阶段来实现相关的程序模块。编译过程中每个阶段工作的逻辑关系如图2-1所示,图中的每个阶段的工作由相关程序模块承担,但其中的符号表管理程序和错误处理程序则贯穿编译过程的各个阶段。这些程序模块构成了编译器的基本结构。

图2-1 编译器结构图

通常,编译的阶段又被分成前端和后端两部分。前端是由只依赖于源语言的那些阶段或阶段的一部分组成,往往包含词法分析、语法分析、语义分析和中间代码生成等阶段,当然还包括与这些阶段同时完成的错误处理和独立于目标机器的优化。后端是指编译器中依赖于目标机器的部分,往往只与中间语言有关而独立于源语言。后端包括与目标机器相关的代码优化、代码生成和与这些阶段相伴的错误处理和符号表操作。这种前后端的划分使得编译器的设计更加清晰、合理与高效。

基于同一个前端,重写其后端就可以产生同一种源语言在另一种机器上的编译器,这是为不同类型机器编写编译器的常用做法。反过来,把几种不同的语言编译成同一种中间语言,使得不同的前端都使用同一个后端,进而得到一类机器上的几个编译器,却只取得了有限的成功,其原因在于不同源程序语言的区别较大,使得包容它们的中间语言庞大臃肿,难以得到高效率。但是,在反编译的过程中,设计一种中间语言,将不同体系结构的目标代码先翻译成这种中间语言代码,再由这种中间代码反编译为C代码,则是一种较为有效的途径。