Xcode编译过程

Xcode编译过程

Posted by Ted on June 3, 2019

1、编译简介

编译器的作用便是把我们的高级编程语言(Objective-C)通过一系列的操作转化成可被计算机执行的机器语言(MachineCode)。

经典的三段式设计(three phase design):前端(Frontend)–优化器(Optimizer)–后端(Backend)

img

  • 前端:负责分析源代码,可以检查语法级错误,并构建针对该语言的抽象语法树(AST),生成中间代码(Intermediate Representation ),在这个过程中,会进行类型检查,如果发现错误或者警告会标注出来在哪一行。
  • 优化:此时进行与机器类型无关的优化
  • 后端:根据不同的机器和架构,进行优化并且生成不同的机器码

这种三段式架构的优势在于:假如你需要增加一种语言,只需要增加一种前端;假如你需要增加一种处理器架构,也只需要增加一种后端;通过共享优化器的中转,其他的地方都不需要改动。

img

2、Xcode编译器发展过程

  • Xcode3 以前: GCC;
  • Xcode3:增加LLVM,GCC(前端) + LLVM(后端);
  • Xcode4.2:出现Clang - LLVM 3.0成为默认编译器;
  • Xcode4.6:LLVM 升级到4.2版本;
  • Xcode5:GCC被废弃,新的编译器是LLVM 5.0,从GCC过渡到Clang-LLVM的时代正式完成,Objective-Cswift都采用Clang作为编译器前端

3、Clang-LLVM架构

Clang-LLVM架构中,Clang作为前端生成中间代码IR,LLVM优化器进行优化,LLVM机器码生成器生成不同的机器码

img

再具体一些的话:

img

具体来说,在Xcode按下CMD+B之后,一个源文件的编译过程如下

img

如上图所示,

  • 预处理(Pre-process):他的主要工作就是将宏替换,删除注释展开头文件,生成.i文件。
  • 词法分析(Lexical Analysis):将代码切成一个个 token,比如大小括号,等于号还有字符串等。是计算机科学中将字符序列转换为标记序列的过程。
  • 语义分析(Semantic Analysis):验证语法是否正确,然后将所有节点组成抽象语法树 AST 。由 Clang 中 Parser 和 Sema 配合完成。
  • 静态分析(Static Analysis):使用它来表示用于分析源代码以便自动发现错误。
  • 中间代码生成(Code Generation):生成中间代码 IR,CodeGen 会负责将语法树自顶向下遍历逐步翻译成 LLVM IR,IR 是编译过程的前端的输出,后端的输入。
  • 优化(Optimize):LLVM 会去做些优化工作,在 Xcode 的编译设置里也可以设置优化级别-O1-O3-Os…还可以写些自己的 Pass,官方有比较完整的 Pass 教程: Writing an LLVM Pass 。如果开启了Bitcode苹果会做进一步的优化,有新的后端架构还是可以用这份优化过的Bitcode去生成。可以在这一层自定义Pass对IR代码做代码混淆
  • 生成目标文件(Assemble):生成Target相关Object(Mach-o)。
  • 链接(Link):生成Executable可执行文件。

4、编写自己的PASS

新建PASS目录

cd /llvm/lib/Transforms/
mkdir MyPass
cd MyPass

创建文件

mkfile -nv 0 MyPass.cpp
mkfile -nv 0 CMakeLists.txt

在 CMakeLists.txt中输入如下内容,这些内容用于标记模块名称和源文件信息,以及声明依赖关系。

add_llvm_loadable_module( MyPass
  MyPass.cpp

  DEPENDS
  intrinsics_gen
  PLUGIN_TOOL
  opt
  )

在MyPass.cpp内编写自己的Pass

#include "llvm/ADT/Statistic.h"
#include "llvm/IR/Function.h"
#include "llvm/Pass.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
namespace {
    // 使用空的名空间避免冲突
    struct mypass : public FunctionPass {// 继承FunctionPass用于处理IR中的方法
        static char ID; // Pass identification, replacement for typeid
        mypass() : FunctionPass(ID) {}
        
        // 遍历IR,每当遇到方法定义则调用该函数
        bool runOnFunction(Function &F) override {
            // 输出方法名
            errs() << "funcName: ";
            errs().write_escaped(F.getName()) << '\n';
            return false;
        }
    };
}

// 注册pass
char mypass::ID = 0;
static RegisterPass<mypass>
Y("MyPass", "MyPass");

返回上级/llvm/lib/Transforms/,修改这一层的CMakeLists.txt文件,注意不是MyPass文件夹里的CMakeListst.txt文件

在末尾加上

add_subdirectory(MyPass)

到xcode目录,运行cmake重新编译配置文件

cd /llvm_xcode/
cmake -G Xcode ../llvm

打开LLVM.xcodeproj

找到MyPass Target运行,会得到MyPass.dylib

首先

clang -c -emit-llvm code.c

运行LLVM Pass的两种方式

opt -load MyPass.dylib -MyPass code.bc
clang -Xclang -load -Xclang MyPass.dylib code.c