LLVM初探

2020-11-15  本文已影响0人  Rachel_雷蕾

编译 想必都知道,那么LLVM是什么?
LLVM是一种编译器!LLVM编译流程是怎么样的?
本篇就LLVM进行初探

首先让我们来了解编译器是神马~

一、编译器是什么?

1、以python程序为例
新建一个python文件:helloDemo.py(内部一行代码打印hello)

print("hello\n")

进入python文件,执行python helloDemo.py

image.png
2、c语言程序为例
新建helloDemo.c文件:
#include <stdio.h>
int main(int a,char * argv[]){
        printf("hello \n");
        return 0;
}

执行clang helloDemo.c编译,生成a.out文件,file a.out查看文件

image.png

发现最终生成文件是:.out文件:64位的Mach-O可执行文件,当前clang出来的是x86_64架构, 说明mac电脑可读。 所以可以./a.out直接执行:


image.png

以上可以看到,python和C输出流程是完全不同的,pthyon直接用python命令就可以输出结果,而C需要先生成.out文件。这是由于两种编译过程不一样

注:
编译型语言:编译后输出的是指令(0、1组合),cpu可直接执行指令
解释性语言:生成的是数据,不是0、1组合,机器也能直接识别

补:
汇编语言是否需要编译?
直接解释
1、早期科学家,就是使用0、1编程。 为了摒弃手敲0和1的繁琐。开发者们创造了中间解释器,再创建出call、bl等这种容易记的指令来代表0、1组合(例如call对应00001111)。有了对应关系后,就好办了。程序员只用输入call、bl这样的标记指令,经过解释器,变成0和1的组合,就可以直接交给机器去执行了。 这就是汇编的由来。
2、基于汇编以上,再映射和封装相关对应关系。于是生成了跨时代性的c语言,再往上层封装,就出现了高级语言oc、swift、JAVA等语言。之所以汇编执行快,是因为它直接转换为机器语。
3、既然汇编速度这么快,为什么不都用汇编开发?
因为汇编的指令集,是针对同一操作系统而言,不支持跨平台。机器指令是cpu的在识别。早期的计算机厂家非常多,虽然都用0和1的组合,但相同组合背后却是相应不同的指令。所以汇编无法跨平台,不同操作系统下,汇编指令是不同的,所以需要可以跨平台的高级语言来开发~

二、LLVM概述

1、上面我们知道了编译器是什么。那么LLVM是什么呢?
llVM就是编译器的一种:

2、传统编译器


image.png

编译器的前端任务是解析源代码。 会进行词法分析语法分析语义分析检查源代码是否存在错误,然后构建抽象语法树(Abstract Syntax Tree AST),LLVM前端还会生成中间代码(intermediate representation, IR)

优化器负责各种优化改善代码的运行时间,如消除冗余计算

源代码中有很多函数调用,就会需要需要申请很多函数调用栈空间,调用函数栈时,需要压栈输入数据,调用完毕后,出栈。其中过程逻辑可能非常复杂,那么就会无形中就会占用很多内存,优化器就是用来优化一些逻辑,以节省时间和空间。

将代码映射到目标指令集生成机器语言,并进行机器相关的代码优化 (目标指不同操作系统)

3、iOS的编译器架构
Objective-C、C、C++编译器的前端用的都是Clang,Swift使用的是swift,后端使用的是llvm

image.png
三、LLVM的设计

1、llvm:支持多种语言多种硬件架构。使用通用代码表示形式:IR(用来在编译器中表示代码的形式)
⚠️GCC也是一个非常成功的编译器,但由于它作为整体应用程序设计的,用途受到了限制
因为llvm使用IR作为中间文件,所以

2、Clang

ClangLLVM项目的一个子项目。基于LLVM架构的轻量级编辑器,诞生之初就是为了替代GCC,提供更快编译速度。 他是负责编译C、C++、Objecte-C语言的编译器,它属于整个LLVM架构中的编译器前端
对于开发者而言,研究Clang可以给我们带来很出益处

下面就Clang分析一下OC编译流程

3、编译流程

此步骤为7步骤
0: 输入文件:找到源文件
1: 预处理:宏的展开,头文件的导入
2: 编译:词法、语法、语义分析,最终生成IR
3: 汇编: LLVM通过一个个的Pass去优化,每个Pass做一些事,最后生成汇编代码
4: 生成 目标文件
5: 链接: 链接需要的动态库和静态库,生成可执行文件
6:架构可执行文件:通过不同架构,生成对应的可执行文件
这里并没有出现optimizer优化器,因为它是独立触发,与流程阶段无关

使用命令:clang -E main.m >> main2.m,生成main2.m文件
查看main2.m:大部分是stdio库的代码,定位到main函数里,发现
C变成了50了

image.png

得知,
预处理:􏳀􏰟􏳅􏳆􏲿􏰋􏳇􏲉􏲪􏲚􏲛􏰆􏲫􏲙􏲽􏲧􏰆􏲨􏲩􏰱完成2个步骤,

  1. 导入头文件 2.替换宏

还有一个定义类型的叫做typedef,这里我们把 int替换成wl_INT64,试试是否还原

image.png

预处理结束:


image.png

发现tydef并没有被还原

由此我们可以根据预处理结果,做一些安全管理方面的混淆措施

使用define,将重要方法名称或者类进行替换。比如用#define BUY XXXDemo
代码中使用宏BUY,被hank时,实际代码是XXXDemo,不易被发现。
且#define的真实内容,不应该写成乱码,会让人有此地无银三百两的感觉,最好弄成系统类似名称或其他不经意的名称。才会被忽视,安全级别会更高 。

image.png

这些将代码拆分成一个个Token。标注了位置是第几行的第几个字符开始的。

【2】语法分析
是验证语法是否正确:

image.png

IR的基本语法
@ 全局标识
% 局部标识
alloca 开辟空间
align 内存对齐
i32 32个bit,4个字节
store 写入内存
load 读取数据
call 调用数据
ret 返回

bitCode
Xcode7之后,开启bitCode苹果会做进一步的优化,生成.bc的中间代码,我们通过优化后的IR代码生成.bc文件
clang -emit-llvm -c main.ll -o main.bc

补:日常开发中,我们引入第三方库,有时会提示不支持bitcode,可以通过一、将源码编译一下,二、工程设置不支持.bitcode即可

_printf 是一个是undefined external的
undefined表示在当前文件暂时找不到符号_printf
external表示这个符号是外部可以访问的。
以上打印结果表示是因为printf、PoolPop、PoolPush函数是来自于其他库,所以找不到,需要链接其他目标文件

四、重新编辑Clang插件
由于国内网络限制,我们需要借助镜像下载LLVM源码
https://mirror.tuna.tsinghua.edu.cn/help/llvm/

cd llvm/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang.git

通过Xcode编译llvm

一顿猛如虎的操作后,电脑没烧坏的情况下,就编译成功了,然后创建插件,请听下回分解~😄

上一篇 下一篇

猜你喜欢

热点阅读