美文网首页
LLVM初探

LLVM初探

作者: Rachel_雷蕾 | 来源:发表于2020-11-15 23:42 被阅读0次

    编译 想必都知道,那么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文件。这是由于两种编译过程不一样

    • 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-rt􏰊libcxx、􏰊libcxxabi
    • 在clang的tools下安装extra工具
    • llvm编译,最新的只支持cmake来编译,所以需要安装cmake
    • 通过brew安装cmake
      brew install cmake

    通过Xcode编译llvm

    • cmake编译成Xcode项目

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

    相关文章

      网友评论

          本文标题:LLVM初探

          本文链接:https://www.haomeiwen.com/subject/qipnbktx.html