前言:
之前一直想研究我们写的一段字符串(代码)到底是怎么编译成可执行文件的,到底怎么去解析?中间到底发生了哪些 ?最终的可执行文件
到底是怎么去执行的?之前看SandHook源码的时候也提到了编译优化的知识点,但是一直也没理解,或者说当我们想去魔改一份编译器的时候,把一段 int a= 1+2;如果可以编译成只有机器能看懂并且无法反编译的事情也是一件很酷的事情
之前也是一直没有时间去总结,最近心血来潮,也是抽时间准备这系列文章,希望对此有想法的开发者在学习的过程中能减少一些弯路。
什么是编译器:
这个东西做过软件开发的过程中可能都会使用,但是他具体如何去工作的呢?
当我们拿到一份代码源文件的时候首先要经过几个阶段
源文件->前端编译器->中端编译器(优化器)->后端编译器->机器码
机器码也就是计算器最终可执行的二进制文件,现在主要比较肯定的编译器架构是三段式编译器,如下图
![](https://img.haomeiwen.com/i12722767/ff6ff02704a90654.png)
前端编译器(Frontend):
其输入为源代码,输出为中间表示(Intermediate Representation),简写为IR,IR也被称作中间代码、中间语言)。IR没有标准语法。各编译器都可以自定义IR。比如LLVM就有LLVM IR,而Java字节码也是一种IR。前端的工作主要是解析输入的源码,并对其进行 词法分析、语法分析、语义分析、生成对应的IR等。
词法分析:
词法分析的目标是识别输入字符流中的特定单词,把输入的字符串翻译成编译器能够明白的语言
语法分析:
判断我们写的字符串是否含有语法错误,比如有未定义的变量,写错了的关键字。
语义分析
用来确认我们这行代码到底想告诉计算机具体做什么?具体怎么去分配内容,怎么去执行和解析
前端编译器主要会经过以下五部分的处理:
Lexical Analyisis
Parsing
Semantic Analyisis
Optimization
Code Generation
中端编译器(优化器(Backend)):
优化器的输入是未优化的IR,输出是优化后的IR。常用的优化手段有循环优化、常量传播和折叠、无用代码消除、方法内联优化等。另外,优化器在优化阶段的最后还要执行一项非常重要的工作,即考虑如何分配物理寄存器。比如,IR中往往使用不限个数的虚拟寄存器,而目标机器的物理寄存器的个数却是有限的。所以优化器需要有一种方法来合理分配物理寄存器。而对那些不能保存在物理寄存器中的值,优化器还需要生成将这些值存储到内存、从内存中读取它们的指令。
可以理解为主要是性能上的优化
IR可以理解为一种中间语言,编译器使用的一种语言,一种字节流。
后端编译器(Backend):
其输入为优化后的IR,输出为目标机器的机器码。后端的主要功能是将IR翻译成机器码
为什么大众会认可三段式编译器架构呢?
不同语言的开发者可以开发针对特定语言的前端模块,比如C、C++、Fortran。这些前端模块只要将对应语言的源代码转换成IR即可。
·优化器模块的输入是IR,输出也是IR。这种设计的好处是擅长优化的开发者可以将精力集中在如何优化IR上,而不会被输入的编程语言以及目标机器的特性所束缚。
·同理,开发者可为不同目标机器开发对应的后端模块。如图6-2中针对X86、ARM和MIPS机器的后端模块。
总体而言,三段式编译器架构的设计充分体现了“术业有专攻”的重要性和必要性。因为对难度如此之大的编译器开发而言,很少有开发者能同时精通这三个模块。通过将编译器拆分成三个较为独立的模块,开发者就能集中精力于他们所擅长的部分,不断去发展和完善它们。
![](https://img.haomeiwen.com/i12722767/799636e499712cb3.png)
LLVM
什么是LLVM?
LLVM是构架编译器(compiler)的框架系统,以C++编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本。
LLVM计划启动于2000年,最初由美国UIUC大学的Chris Lattner博士主持开展。2006年Chris Lattner加盟Apple Inc.并致力于LLVM在Apple开发体系中的应用。Apple也是LLVM计划的主要资助者。
目前LLVM已经被Apple、Microsoft、Google、Facebook等各大公司采用。
-----------------《来自百度百科》
趣闻: Chris Latter
本来只是想写一个底层的虚拟机,这也是 LLVM
名字的由来, low level virtual machine
,跟 Java
的 JVM
虚拟机一样,可是后来,llvm从来没有被用作过虚拟机,哪怕 LLVM
的名气已经传开了。所以人们决定仍然叫他 LLVM
,更多的时候只是当作“商标”一样的感觉在使用,其实它跟虚拟机没有半毛钱关系。
LLVM只是一个编译器基础架构和编译器开发库
但是他不是一个全套的编译器,因为不包含前段编译器
在LLVM架构上,搭配针对不同编程语言的前端就可以构造相应语言的编译器。比如Clang搭配LLVM就构成了C/C++编译器。
比如:
clang+LLVM: clang是LLVM的前端,把各种源码编译处理;
clang-tools-extra :clang默认以外的认为不是很重要的工具;
![](https://img.haomeiwen.com/i12722767/ee8fcda5afda4acb.png)
GCC(GNU编译器套件)和Clang
Gcc是一款全套的编译器,有一套自己的IR体系
LLVM也可以搭配GCC使用,只要将GCC的前端模块和LLVM组合,就能得到一个性能比GCC还强大的编译器
现在用Clang比较多,具体原因细节可参考,
LLVM编译工具链编译流程
使用LLVM的对一门语言编译的简图如下所示:
LLVM编译一个源文件的过程:预处理 -> 词法分析 -> Token -> 语法分析 -> AST -> 代码生成 -> LLVM IR -> 优化 -> 生成汇编代码 -> Link -> 目标文件
完全需要我们手工,或者依靠其他工具如lex, yacc来做的事情,是从源代码到token的词法分析和从token到AST的语法分析;词法分析的输出是将源代码解析成一个个的token。这些token就是有类型和值的一些小单元,比如是关键字,还是数字,还是标识符,从AST转LLVM开始,LLVM就开始提供一系列的工具帮助我们快速开发。从IR(中间指令代码)到DAG(有向无环图)再到机器指令,针对常用的平台,LLVM有完善的后端。也就是说,我们只要完成了到IR这一步,后面的工作我们就享有和Clang一样的先进生产力了。
CodeGen负责将语法树从顶至下遍历,翻译成LLVM IR,LLVM IR是Frontend的输出,也是LLVM Backerend的输入,桥接前后端。
Art虚拟机编译简介:
Dex在ART机内部默认的执行方式是解释执行,在不同的Android版本对Dex进行二次编译
比如OAT或者7.0以上JIT和OAT的联合编译,在Art虚拟机内部的编译器只包括优化器和后端,优化器的输入是dex字节码,其输出是ART定义的HInstruction。
ART虚拟机的后端支持X86、ARM、MIPS架构的32位和64位平台
[图片上传失败...(image-efc70a-1610786999213)]
编译优化有很多方法,分别针对不同的优化目标,比如图6-3中的方法内联优化,以及无用代码消除优化、常量折叠优化等。
参考:
《深入理解Android:Java虚拟机ART》
https://www.jianshu.com/p/1367dad95445
https://blog.csdn.net/feibabeibei_beibei/article/details/88073678
网友评论