为什么需要编译?
计算机CPU只能读懂机器码(一堆0和1组成的编码),但是我们编写的代码并不是机器码,而是高级编程语言(OC、swift、Java、...),最终也可以被计算机所执行,这就需要编译了,在编译的过程中,编译器的作用便是把我们的高级汇编语言通过一系列的操作转化成可被计算机执行的机器语言。
什么是LLVM
当前,LLVM已经发展成为被用于开发从编译器前端到后端的“一套模块及可重用的编译器及工具链技术的集合”
它的命名源自底层虚拟机(Low Level Virtual Machine)的缩写,但它已经不是底层虚拟机了,LLVM 就是这个项目的全称,包含LLVM中介码(LLVM IR)、LLVM除错工具、LLVM C++标准库等一套工具,和传统底层虚拟机并没什么关系。
它是一个编译器的基础建设,是为了任意一种编程语言写成的程序,利用虚拟技术,创造出编译时期,链接时期,运行时期以及“闲置时期”的优化。最早是以C/C++为实现对象,后来支持了Objective-C。
简单来说,LLVM可以作为多种语言编译器的后台来使用。把很多编译器需要的功能以可调用的模块形式实现出来并包装成库,供其他编译器实现者可以根据自己的需求选择使用或者扩展。主要聚焦于编译器的后端功能,如代码生成、代码优化、JIT等。
LLVM优势
编译器前端和后端就是编译器经典的三段式设计的组成。如下图
前端(Frontend)-- 优化器(Optimizer)-- 后端(Backend)
前端负责分析源代码,可以检查语法级错误,并构建针对语言的抽象语法树(AST)
抽象语法树可以进一步转换为优化,最终转为新的表示方式,然后再交给让优化器和后端处理
最终由后端生成可执行的机器码。
LLVM也采用经典的三段式设计。前端可以使用不同的编译工具对代码文件做词法分析以形成抽象语法树AST,然后将分析好的代码转换成LLVM的中间表示IR(intermediate representation);中间部分的优化器只对中间表示IR操作,通过一系列的pass对IR做优化;后端负责将优化好的IR解释成对应平台的机器码。LLVM的优点在于,中间表示IR代码编写良好,而且不同的前端语言最终都转换成同一种的IR。如下图所示
为什么使用三段式设计?优势在哪里?
首先解决一个很大的问题:假如有N种语言(C、OC、C++、Swift...)的前端,同时也有M个架构(模拟器、arm64、x86...)的target,是否就需要N*M个编译器?
三段式架构的价值就提现出来了,通过共享优化器的中转,很好的解决了这个问题。
如果你需要增加一种语言,只需要增加一种前端;假如你需要增加一种处理器架构,也只需要增加一种后端,而其他的地方都不需要改动。这复用思想很牛逼吧。
编译源文件有哪些主要步骤?
源代码(source code)
预处理器(preprocessor)
编译器 (compiler)
汇编程序(assembler)
目标代码(object code)
链接器(Linker)
可执行文件(executables)
从源码角度分析,如果你下载LLVM的代码,那么它就是一个IR到ARM/机器码的编译器。比如bin/opt就是对IR的优化器,bin/llc就是IR->ASM的翻译,bin/llvm-mc就是汇编器。如果你再从http://llvm.org下载clang,那么就有了C->IR的翻译以及完整的编译器Driver。GDB是GNU的调试器。只要编译器支持DWARF格式,就可以用GDB调试。
LLVM IR是LLVM的中间表示,优化器就是对IR进行操作的,具体的优化操作有一系列的pass来完成,当前生成初级IR后,pass会依次对IR进行处理,最终生成后端可用的IR。
Clang与LLVM关系
LLVM与Clang是C/C++编译器套件。对于整个LLVM的框架来说,包含了Clang,因为Clang是LLVM的框架的一部分,是它的一个C/C++的前端。Clang使用了LLVM中的一些功能,目前知道的就是针对中间格式代码的优化,或许还有一部分生成代码的功能。从源代码角度来讲,clang是基于LLVM的一个工具。而功能的角度来说,LLVM可以认为是一个编译器的后端,而clang是一个编译器的前端,他们的关系更加的明了,一个编译器前端想要程序最终变成可执行文件,是缺少不了对编译器后端的介绍的。
clang的编译过程
https://www.jianshu.com/p/c9fccc93ed15
1、预处理
预先处理,具体做了如下事情:
(1)import头文件替换
(2)macro宏展开:替换
(3)处理其他的预编译指令(其实预编译工程也是处理预编译指令的过程):条件编译语句也是在预处理阶段完成并且条件编译只允许编译源程序中满足条件的程序段,使其生成的目标程序较短,从而减少了内存的开销并提高了程序的效率。
(4)总结:简单来说“#”这个符号是预编译器处理的标志,以下是一些常用的预处理指令参考:
#undef 取消已定义的宏
#if 如果给定条件为真,则编译以下代码
#ifdef 如果宏已经定义,则编译以下代码
#ifndef 如果宏没有定义,则编译以下代码
#elif 如果前面的#if给定条件不为真,当前条件为真,则编译以下代码
#endif 结束一个#if......#else条件编译块
2、词法分析
3、语法分析
根据当前语言的语法,生成语意节点,并将所有节点组合成抽象语法树(AST)
pass
LLVM的pass框架是LLVM系统的一个很重要的部分。LLVM的优化和转换工作就是由多个pass来一起完成得。类似流水线操作一样,每个pass完成特定的优化工作。要想真正发挥LLVM的威力,掌握pass是不可或缺的一环。LLVM中pass架构的可重用性和可控制性都非常好,这允许用户自己开发pass或者关闭一些默认提供的pass。总的来说,所有的pass大致可以分为两类:分析和转换分析类的pass以提供信息为主,转换类的会修改中间代码。
OLLVM
https://www.jianshu.com/p/82e52dabfc9d
https://blog.csdn.net/box_kun/article/details/80681205
OLLVM(obfuscator-LLVM)是提供一个LLVM编译套件的开源分支,能够通过代码混淆和防篡改,增加对逆向工程的难度,提供更高的软件安全性。
它是一个基于LLVM框架的一个开源代码混淆器,整个项目包含了三个相对独立的LLVM pass,每个pass实现了一种混淆方式,通过这些混淆手段,可以模糊原程序或者某一部分的算法,给逆向分析带来一些困难。
每种pass的详细文档,可以查看下面的这三个链接:
Instructions Substitution(指令变换)
Bogus Control Flow(流程伪造)
Control Flow Flattening(流程平坦化)
OLLVM的混淆操作就是在中间表示IR层,通过编写pass来混淆IR,然后后端依据IR来生成的目标代码也就被混淆了。得益于LLVM的设计,OLLVM适用LLVM支持的所有语言(C,C++,Objective-C,Ada和Fortran)和目标平台(x86,x86-64.PowerPC-64,ARM,Thumb,SPARC,Alpha,CellSPU,MIPS,MSP430,SystemZ,和XCore)。
流程平坦化
混淆后:
^ 异或:用于位运算,每个位相同为0,不同为1
控制流平坦化的实现:
https://bbs.pediy.com/thread-209203.htm
如何使用
Windows
在Windows上可以使用MinGW编译LLVM,也可以用VisualStudio编译
Linux
GCC编译或者是以LLVM/Clang编译
macOS x
xcode自带
https://blog.csdn.net/water1307/article/details/81299898
https://blog.csdn.net/box_kun/article/details/80681205
https://bbs.pediy.com/thread-209203.htm
https://www.jianshu.com/p/c9fccc93ed15
pass
https://blog.csdn.net/baimafujinji/article/details/78823319