LLVM介绍及OLLVM原理分析

为什么需要编译?

计算机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

 

Clang编译过程原理

https://www.jianshu.com/p/c9fccc93ed15

pass

https://blog.csdn.net/baimafujinji/article/details/78823319

 

 

 

 

 

 

  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
污点分析是一种静态程序分析技术,用于确定程序中哪些变量可以被恶意输入或其他安全漏洞所利用。LLVM是一个广泛使用的编译器基础设施,可以用于实现污点分析。下面是一个简单的LLVM Pass,它实现了简单的污点分析。 首先,我们需要定义一个Pass类,该类继承自llvm::FunctionPass。然后,我们需要在runOnFunction函数中实现我们的污点分析逻辑。在这个例子中,我们将通过检查函数的参数和指令来确定哪些变量是受污染的。 ```c++ #include "llvm/IR/Function.h" #include "llvm/Pass.h" #include "llvm/Support/raw_ostream.h" #include "llvm/IR/Instructions.h" using namespace llvm; namespace { struct TaintAnalysis : public FunctionPass { static char ID; TaintAnalysis() : FunctionPass(ID) {} bool runOnFunction(Function &F) override { // 遍历函数的所有基本块 for (auto &BB : F) { // 遍历基本块的所有指令 for (auto &I : BB) { // 如果指令是一个存储指令 if (auto *SI = dyn_cast<StoreInst>(&I)) { // 如果存储指令的源操作数是一个指针类型 if (auto *Ptr = dyn_cast<PointerType>(SI->getOperand(1)->getType())) { // 如果指针指向的类型是整数类型 if (auto *IntTy = dyn_cast<IntegerType>(Ptr->getElementType())) { // 如果整数类型的位宽为8 if (IntTy->getBitWidth() == 8) { // 输出受污染的指针值和存储的值 errs() << "Tainted pointer value: " << *SI->getOperand(1) << "\n"; errs() << "Tainted value: " << *SI->getOperand(0) << "\n"; } } } } } } return false; } }; } char TaintAnalysis::ID = 0; static RegisterPass<TaintAnalysis> X("taint-analysis", "Taint Analysis Pass"); ``` 我们在runOnFunction函数中遍历函数的所有基本块和指令。我们检查每个存储指令,以确定它是否存储了一个指向整数类型的指针,并且该整数类型具有8位的位宽。如果是的话,我们输出受污染的指针值和存储的值。 最后,我们将该Pass注册到LLVM中,以便在编译时运行。我们使用static RegisterPass来注册我们的Pass,并将其命名为“taint-analysis”。 现在,我们可以使用LLVM编译器运行我们的Pass,以便对C或C++程序进行污点分析。例如,假设我们有以下C程序: ```c++ #include <stdio.h> void foo(int *ptr) { int x = *ptr; printf("The value of x is: %d\n", x); } int main() { int y = 42; foo(&y); return 0; } ``` 我们可以使用以下命令编译程序并运行我们的Pass: ``` clang -Xclang -load -Xclang MyPass.so -c test.c ``` 这将生成一个名为“test.o”的目标文件,并使用我们的Pass进行污点分析。如果程序中存在受污染的指针,我们的Pass将输出它们的值。在这个例子中,我们应该得到以下输出: ``` Tainted pointer value: i32* %ptr Tainted value: i32 42 ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值