美文网首页
二十五、LLVM

二十五、LLVM

作者: KB_MORE | 来源:发表于2020-11-13 17:25 被阅读0次
  1. 什么是编译器
  2. LLVM概述
  3. LLVM案例体验
  4. LLVM源码 & 编译流程

1 什么是编译器?

1.1 Python案例

  • 创建python文件夹,新建helloDemo.py文件,内容如下:
print("hello")

  • 调用python helloDemo.py执行文件,打印出python

    image

1.2 C 案例

  • vim创建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

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

image

Q:解释型语言与编译型语言

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

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

编译器的作用,就是将高级语言转化为机器能够识别语言(可执行文件)。

Q:汇编有指令吗?

  • 早期科学家,使用0、1编码。 比如 00001111 对应 call00000111 对应bl。有了对应关系后。 再手敲0和1就有点难受了。于是写个中间解释器,我们只用输入callbl这样的标记指令,经过解释器,变成0和1的组合,再交给机器去执行。 这就是汇编的由来
  • 而基于汇编往上,再映射封装相关对应关系。就跨时代性c语言,再往上层封装,就出现了高级语言ocswift等语言。所以汇编执行快,因为它是直接转换机器语言的。
  • 汇编指令集,是针对同一操作系统而言,它支持跨平台机器指令cpu的在识别。早期的计算机厂家非常,虽然都用01组合,但相同组合背后却是相应不同指令。所以汇编无法跨平台不同操作系统下,汇编指令不同的。

2. LLVM概述

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

2.1 传统编译器的设计

image
- 编译器前端(Frontend):

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

- 优化器(Optimizer)

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

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

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

iOS的编译器架构:

Objective C / C / C++ 使用的编译器前端ClangSwiftswift,后端都是LLVM

image

2.2 LLVM的设计

  • GCC是一个非常成功编译器,但由于它作为整体应用程序设计的,用途受到了限制

  • LLVM最重要的地方:支持多种语言多种硬件架构。使用通用代码表示形式:IR(用来在编译器中表示代码的形式)

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

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

image

2.3 Clang

ClangLLVM项目的一个子项目。基于LLVM架构轻量级编辑器诞生之初就是为了替代GCC,提供更快编译速度。 他是负责编译CC++Objecte-C语言的编译器,它属于整个LLVM架构中的编译器前端

  • 对于开发者而言,研究Clang可以给我们带来很多好处

3. LLVM案例体验

  • 新建一个Mac OS命令行工程:

    image
  • 没有改动代码

    image

3.1 编译流程

  • cd到main.m的文件夹。使用下面命令查看main.m的编译步骤:
clang -ccc-print-phases main.m

image

编译流程分为以下7步

  • 0: input, "main.m", objective-c
    输入文件:找到源文件
  • 1: preprocessor, {0}, objective-c-cpp-output
    预处理:宏的展开,头文件的导入
  • 2: compiler, {1}, ir
    编译:词法、语法、语义分析,最终生成IR
  • 3: backend, {2}, assembler ()
    汇编: LLVM通过一个个的Pass去优化,每个Pass做一些事,最后生成汇编代码
  • 4: assembler, {3}, object
    目标文件
  • 5: linker, {4}, image
    链接: 链接需要的动态库和静态库,生成可执行文件
  • 6: bind-arch, "x86_64", {5}, image
    架构可执行文件:通过不同架构,生成对应的可执行文件

optimizer优化没有作为一个独立阶段,在编译阶段内部完成

3.2 预处理阶段

  • main.m中准备测试代码:
#import <stdio.h>
#define C 30

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        int b = 20;
        printf("%d", a + b + C);
    }
    return 0;
}

  • clang预编译输出main2.m文件:
clang -E main.m >> main2.m

  • 打开main2.m,有575行。其中大部分是stdio库的代码:

    image
  • 我们发现测试代码中的宏C,在预编译阶段完成了替换,变成了30

预编译阶段: 1. 导入头文件 2.替换宏

  • 修改测试代码,给int类型取个别名HT_INT_64,再次预编译处理
#define C 30

typedef int HT_INT_64;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        HT_INT_64 a = 10;
        HT_INT_64 b = 20;
        printf("%d", a + b + C);
    }
    return 0;
}

  • 发现typedef不会被替换

    image

安全拓展:

  1. 使用define重要方法名称进行替换。比如#define Pay XXXTest这样开发者使用宏Pay开发舒服,但是被hank时,实际代码是XXXTest,不容易被察觉。
    #define真实内容不应该写成乱码,会让人有此地无银三百两的感觉,最好弄成系统类似名称或其他不经意名称。这样才容易忽视安全级别更高 😃)
    typedef没有这个偷梁换柱的效果。define只影响预处理期。

3.3 编译阶段

3.3.1 词法分析
  • 编译main.m文件:
clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m

-词法分析,就是根据空格括号这些将代码拆分成一个个Token。标注了位置第几行第几个字符开始的。

image
3.3.2 语法分析
  • 语法分析是验证语法是否正确
    在词法分析的基础上,将单词序列组合成各类语法短语,如“程序”,“语句”,“表达式”等,然后将所有节点组成抽象语法树(Abstract Syntax Tree,AST)。 语法分析程序判断源程序结构上是否正确
clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

  • 作用域类型运算方式十分清晰。( 语法树一次只能处理一次计算。两次运算,就得多分一层级。)

    image
  • 语法分析,就是在生成语法树完成检测的。

  • 头文件找不到时,可以指定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

3.4 生成中间代码IR(Intermediate representation)

3.4.1 生成中间代码
  • 完成以上步骤后,就开始生成中间代码IR,代码生成器(Code Generation)会将语法树自顶向下遍历逐步翻译成LLVMIR

  • 便于理解,我们简化代码:

#import <stdio.h>

int test(int a, int b) {
    return a + b + 3;
}

int main(int argc, const char * argv[]) {
    int a = test(1,2);
    printf("%d",a);
    return 0;
}

通过下面命令生成.ll文本文件,查看IR代码:

clang -S -fobjc-arc -emit-llvm main.m

  • IR基本语法
    @ 全局标识
    % 局部标识
    alloca 开辟空间
    align 内存对齐
    i32 32个bit,4个字节
    store 写入内存
    load 读取数据
    call 调用数据
    ret 返回
  • 使用VSCodeSublime Text可以打开代码:(可以指定文件语言,让代码高亮色
image
  • Q:图中为何多创建那么多局部变量?(如test函数内的a5、a6)
  • 因为在上一阶段(编译阶段),我们将代码编译成了语法树结构。而此时,我们只是沿语法树进行读取。 语法树每一个层级,都需要一个临时变量承接。再返回上一层级处理
  • 所以会产生那么多局部变量
3.4.2 IR优化
  • 我们可以在XcodeBuild Settings中搜索Optimization,可以看到优化级别。
    (Debug模式默认None [O0]无优化,Release模式默认Fastest,Smallest [Os]最快最小)
image
  • LLVM的优化级别分为 -O0-O1-O2-O3-Os(第一个字母是Optimization的O)。

  • 分别选择O0Os两个优化等级进行中间代码的生成比较:

clang -S -fobjc-arc -emit-llvm main.m -o mainO0.ll      //  O0  无优化
clang -Os -S -fobjc-arc -emit-llvm main.m -o mainOs.ll  //  Os 最快最小

image image
  • 优化后的代码,舒服多了。之前那些冗余临时局部变量,也都被优化代码量减少很多。
3.4.3 bitCode再优化
  • Xcode7之后开启bitCode苹果会再进一步优化,生成.bc中间代码

优化体现:上传APPstore的包,针对不同型号手机做了区分,不同型号手机下载时,大小不同

clang -emit-llvm -c main.ll -o main.bc

3.5 生成汇编代码

  • 完成中间代码的生成后,可以将代码转变汇编代码了。

  • 此刻我们有4种不同程度的代码(源代码->无优化IR代码->Os优化IR代码 -> bitcode优化代码):

    image
  • 分别对4种程度的代码输出汇编文件:

clang -S -fobjc-arc main.m -o main.s
clang -S -fobjc-arc main.ll -o mainO0.s
clang -S -fobjc-arc mainOs.ll -o mainOs.s
clang -S -fobjc-arc main.bc -o mainbc.s

image

可以看到在生成汇编代码时,只有选择优化等级,才能减少汇编代码量

【拓展】在生成中间代码前后都可以进行优化

  • [尝试一] 将main.m直接选择Os级别优化生成.s汇编文件
clang -Os -S -fobjc-arc main.m -o mainOs.s

  • [尝试二] 将main.m生成无优化main.s,再main.s选择Os级别优化生成.s汇编文件
clang -S -fobjc-arc -emit-llvm main.m -o mainO0.ll
clang -Os -S -fobjc-arc mainO0.ll -o mainOoOs.s

  • [尝试三] 将main.m选择Os级别优化生成main.s,再main.s选择无优化级别生成.s汇编文件
clang -Os -S -fobjc-arc -emit-llvm main.m -o mainOs.ll
clang -S -fobjc-arc mainOs.ll -o mainOsOo.s

  • [尝试四] 将main.m选择Os级别优化生成main.s,再main.s选择Os级别优化生成.s汇编文件
clang -Os -S -fobjc-arc -emit-llvm main.m -o mainOs.ll
clang -Os -S -fobjc-arc mainOs.ll -o mainOsOs.s

  • 内容比较:
image

3.6 生成目标文件(机器代码)

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

  • file对比一下main.s汇编代码和main.o机器代码:
file main3.m
file main.o

image
  • xcrun执行nm命令查看main.o文件中的符号:
xcrun nm -nm main.o

image
  • 此时只是把当前文件编译为了机器码外部符号(如printf)无法识别。

undefined: 表示当前文件暂时找不到符号
external:表示这个符号外部可以访问的。(实现不在我这,在外部某个地方

所以当前虽转换成了机器代码。但是只是目标文件,并不能直接执行,需要所有资源链接起来,才可以执行

3.7 生成可执行文件(链接)

  • 通过链接器把编译产生的.o文件和.dylib.a文件链接关联起来,生成真正的mach-o可执行文件
clang main.o -o main // 将目标文件转成可执行文件
file main            // 查看文件
xcrun nm -nm main    // 查看main的符号

image
  • 对比main.o目标文件,此时生成的main文件:
  1. object文件变成了executable可执行文件
  2. 虽然都有undefined,但是可执行文件中指定了该符号来源库。机器在运行时,会从相应的库中取读取符号(printf)

至此,我们已完整分析:源代码可执行文件整个流程

image

4. LLVM源码 & 编译流程

⚠️ ⚠️ ⚠️ 【注意】

  1. LLVM源码2.29G编译后文件30G,请确保电脑硬盘空间足够
  2. 编译时,电脑温度会飙升90多度,请用空调伺候着,可能黑屏
  3. 编译时间长达1个多小时,请合理安排时间。

如果以上3点,你确定能接受,那我们就开始吧。

4.1 LLVM下载

拓展:

  • github上的官方源码:https://github.com/llvm/llvm-project(国内网络限制)
  • 需要注意的是,官方源码不能直接编译,需要下载clangcompiler-rtlibcxxlibcxxabi这4个库。
  • 建议使用上面Gitee源。

4.2 LLVM编译

  • 30G90°C1hour之旅,begin~
  • 最新的LLVM只支持cmake编译,需要使用Homebrew安装cmake:
4.2.1 安装cmake
  • 查看brew列表,检查是否安装过cmake,如果有,就跳过此步骤
brew list

  • 如果没有,就使用brew安装:
brew install cmake

如果报权限错误,可sudo chown -Rwhoami:admin /usr/local/share放开权限

image
4.2.2 编译llvm
  • cmake编译成Xcode项目
cd llvm-project
mkdir build
cd build
cmake -G Xcode ../llvm   
// 或者: cmake -G Xcode CMAKE_BUILD_TYPE="Release" ../llvm
// 或者: cmake -G Xcode CMAKE_BUILD_TYPE="debug" ../llvm 

  • 成功之后,可以看到生成的Xcode文件:

    image
  • 打开LLVM.xcodeproj,选择自动创建Schemes

    image
  • 自动创建完成后,选择ALL_BUILD进行编译(耗时0.5-1小时,CPU满负荷运转)

    image
  • 编译完成。

相关文章

  • 二十五、LLVM

    什么是编译器 LLVM概述 LLVM案例体验 LLVM源码 & 编译流程 1 什么是编译器? 1.1 Python...

  • LLVM

    LLVM 什么是LLVM? 官网:https://llvm.org/ The LLVM Project is a ...

  • iOS_LLVM

    LLVM 官网:https://llvm.org/[https://llvm.org/] The LLVM Pro...

  • iOS 逆向 day 18 GCC LLVM Clang

    一、LLVM 1. 什么是 LLVM 官网:https://llvm.org/ The LLVM Project ...

  • 浅谈LLVM

    何为LLVM 在LLVM的官网(https://llvm.org/[https://llvm.org/])中写到T...

  • iOS逆向-day10:LLVM 编译器

    一、LLVM的简单介绍 1.1、什么是LLVM官网:https://llvm.org/LLVM官网解释:The L...

  • 初识LLVM&Clang-开发Xcode插件

    初识LLVM&Clang-开发Xcode插件 LLVM Xcode现在使用的编译器就是LLVM。LLVM比以前使用...

  • LLVM 初始 ①

    什么是LLVM 官网:https://llvm.org/ The LLVM Project is a collec...

  • LLVM 工具

    llvm-dis llvm-dis 是一个反汇编工具,通过 LLVM 字节码文件(.bc)得到 LLVM 汇编文件...

  • LLVM(1)LLVM了解

    一、什么是LLVM 1、官网:https://llvm.org/The LLVM Project is a col...

网友评论

      本文标题:二十五、LLVM

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