LLVM初探

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

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

一、编译器是什么?

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

print("hello\n")

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

image.png

2、c语言程序为例
新建helloDemo.c文件:

#include 
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文件。这是由于两种编译过程不一样

  • python是解释型语言,一边翻译一边执行,机器可直接执行。
  • C语言是编译型语言,不可以直接执行,需要编译器将其转换成机器识别语言。

注:
编译型语言:编译后输出的是指令(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就是编译器的一种:

  • 架构编译器(compiler)的框架系统,以c++编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)链接时间(link-time)运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼任已有脚本
  • 2006年Chris Lattner加盟Apple Inc.并致力于LLVM在Apple开发体系中的应用。Apple也是LLVM计划的主要资助者
    目前LLVM已经被苹果iOS开发工具、Xilinx Vivado、Facebook、Google等各大公司采用

2、传统编译器


image.png
  • 编译器前端(Frontend)

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

  • 优化器(Optimizer)

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

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

  • 后端(Backkend)/ 代码生成器(CodeGenerator)

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

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

image.png
三、LLVM的设计

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

  • LLVM可以为任何编程语言独立编写前端,也可以为任何硬件架构独立编写后端.

  • LLVM是一个简单编译器,而是架构编译器,可以兼容所有前端后端

    image.png

2、Clang

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

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

3、编译流程

  • 新建一个.m文件


    image.png
  • 进入文件Test2
    执行命令clang -ccc-print-phases main.m

    image.png

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

  • 预处理
    在.文件里定义一个宏:C


    image.png

使用命令: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的真实内容,不应该写成乱码,会让人有此地无银三百两的感觉,最好弄成系统类似名称或其他不经意的名称。才会被忽视,安全级别会更高 。

  • 编译
    【1】词法分析
    使用clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
image.png

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

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

  • 在词法分析的基础上,将单词序列组合成各类语法短语,如“程序”,“语句”,“表达式”等,然后将所有节点组成抽象语法树(Abstract Syntax Tree,AST)。 语法分析程序判断源程序在结构上是否正确。
    使用clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
image.png
  • 可以看到语法树。每一个操作都生成一个变量,作用域、数据类型、运算方式都在语法树体现出来。
  • 语法树一次只能处理一次计算两次运算,就得多分一层。
  • 语法分析,就是在生成语法树时完成检测的
    如果头文件找不到,可以指定SDK
    clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.2.sdk(自己SDK路径) -fmodules -fsyntax-only -Xclang -ast-dump main.m
  • 生成中间代码IR(Intermediate representation)
    完成以上步骤后,就开始生成中间代码IR,代码生成器(Code Generation)会将语法树自顶向下遍历逐步翻译成LLVM的IR。通过下面命令生成.ll文本文件,查看IR代码:
    clang -S -fobjc-arc -emit-llvm main.m

    image.png

  • 代码在这一阶段进行runtime的桥接:property的合成,ARC处理等

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

  • IR的优化
    llvm的优化级别分别是 -O0 -O1 -O2 -O3 -Os(O)
    使用命令clang -Os -S -fobjc-arc -emit-llvm main.m -o main.ll

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

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

  • 生成汇编代码
    我们通过最终生成的.bc或者.ll代生成汇编代码
    clang -S -fobjc-arc main.bc -o main.s
    clang -S -fobjc-arc main.ll -o main.s

    image.png

    汇编代码也可以进行优化clang -Os -S -fobjc-arc main.m -o main.s

  • 生成目标文件(汇编器)
    目标文件的生成,是汇编器以汇编代码作为输入,将汇编代码转换为机器代码,最后输出目标文件(object file)
    clang -fmodules -c main.s -o main.o

    image.png

    通过nm命令,查看下main.o中的符号:nm -nm main.o
    image.png

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

  • 生成可执行文件(链接)
    连接器把编译产生的.o文件和(.dylib.a)文件,生成一个mach-o文件
    clang main.o -o main
    查看链接后的符号
    nm -nm main

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

  • 在llvm的tools目录下下载Clang

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

  • 在LLVM的projects目录下下载compiler-rtlibcxx、libcxxabi
  • 在clang的tools下安装extra工具
  • llvm编译,最新的只支持cmake来编译,所以需要安装cmake
  • 通过brew安装cmake
    brew install cmake

通过Xcode编译llvm

  • cmake编译成Xcode项目

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

你可能感兴趣的:(LLVM初探)