编译器的原理浅析

时间:2022-02-11 04:34:04

一.编译器介绍

1.对于iOS的编译器来说,就是将Objective-C转化为更为低级的语言(即机器语言,转为可执行文件),以及对代码的分析,确保没有明显的错误
2.Xcode的默认编译器是Clang/LLVM,其中clang主要作为编译前端,而LLVM主要作为编译器后端

二.编译器是将源代码编译成可执行文件的步骤

编译器的原理浅析

可以使用clang命令看到编译器的处理过程:
clang -ccc-print-phases main.m 
0: input, “main.m”, objective-c 
1: preprocessor, {0}, objective-c-cpp-output 
2: compiler, {1}, ir 
3: backend, {2}, assembler 
4: assembler, {3}, object 
5: linker, {4}, image 
6: bind-arch, “x86_64”, {5}, image 

1.预处理指令

1).主要是处理#开头的预处理指令,包括import头文件、宏展开、条件预处理指令、
删除注释、添加行号和文件名标识。
可以通过 $clang -E main.m 查看替换的情况

编译器的原理浅析

2).pch文件可以加快库的编译时间
3).引入了模块 - modules功能,这使预处理变得更加的高级。当引入苹果自己的库时,可以使用@import关键字引入,
告诉编译器使用modules的引用形式,苹果已经将这些库进行了封装,生成了一个已经编译的modules文件列表,编译时
首先会去已编译的列表中查找,如果存在就直接使用,没有再进行编译

2.Lexical Analysis - 词法分析

预处理之后,每一个.m文件都有一堆的声明和定义,将这些代码文本从string转化为特殊的标记流,
也就说拆分成一个个的tokens。

比如下面的一段代码:
int main() {
 NSLog(@"hello, %@", @"world");
   return 0;
}
使用 $clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m 将上面的代码的标记流导出
 -dump-tokens:作用是透传给编译器前端,将tokens打印出来

编译器的原理浅析

每一个标记都包含了对应的源码内容和其在源码中的位置,如果编译中遇到什么错误,clang 能够在源码中指出出错的具体位置

3.Semantic Analysis - 语法分析

语法分析由Clang 中 Parser 和 Sema 配合完成
1).验证语法是否正确
2).提示各种错误警告提示
3).根据设置语言的语法,形成语义结点,并将所有节点组合形成抽象语法树AST
   $clang -fmodules -fsyntax-only -Xclang -ast-dump main.m 
   生成抽象的语法树

编译器的原理浅析

在抽象语法树中的每一个节点都标注了其对应源码中的位置,如果产生了什么问题,clang可以定位到问题所在处的源码位置。
4).AST语法树相关的关键字
a.TranslationUnitDecl 是根节点,表示一个源文件。Decl表示一个声明,Expr表示表达式,Literal表示字面是特殊的Expr,Stmt表示语句

编译器的原理浅析

ObjCUnusedIVarsChecker

Static Analysis - 静态分析

 一旦编译器把源码生成了抽象语法树,编译器可以对这棵树做分析处理,找出代码中的错误。
 1).类型检查
  最为常见的是是否发送正确的消息给正确的对象,是否在正确的值上调用了正确的函数。
 2).其他分析
   clang源码中进入目录lib/StaticAnalyzer/Checkers,包含了所有静态检查内容
   a.ObjCUnusedIVarsChecker.cpp是用来检查是否有定义了但是没有使用过的变量
   b.ObjCSelfInitChecker.cpp  检查在你的初始化方法中调用self之前,
     是否已经调用了[self initWith...]或[super init]
 3).clang静态分析是通过分析引擎和checkers所组成的架构。
   a.clang static analyzer分为analyzer core 分析引擎和checkers两部分,
     所有的checker都是基于底层分析引擎之上,通过分析引擎提供的功能能够编写新的checker.
   b.$clang -cc1 -analyzer-checker-help可以列出能调用的checker。
   c.整个静态分析clang static analyzer的入口在lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp

4.CodeGen - IR 代码生成

1).语法树从上至下遍历,翻译成LLVM中间代码,作为前后端的桥接语言,是Clang 编译器前端的输出,LLVM 编译器后端的输入。
2).与runtime桥接
   2.1).内存结构的生成
        a.Class/Meta Class/Protocol/Category 生成并存放在指定section中,_DATA 或 _objc_classrefs
        b.Method/Ivar/Property 生成
        c.组成method_list/ivar_list/property_list 并填入Class
    2.2).为每个 Ivar 合成偏移值常量,其地址为对象的基地址 + 偏移量
    2.3).将语法树中的ObjCMessageExpr翻译成相应objc_msgSend,对super关键字的调用翻译成objc_msgSendSuper
    2.4).根据修饰符strong/weak/copy/atomic 合成@property,自动实现setter/getter,处理@synthesize
    2.5).生成block_layout数据结构
         a.变量的capture _block _weak
         b.生成_block_invoke 函数
    2.6).分析对象引用关系,插入ARC代码,将objc-storeStrong/objc-storeWeak等ARC代码插入
         a.自动调用[super dealloc]
         b.为每个拥有ivar 的 Class 合成 .cxx_destructor 方法来自动释放类的成员变量自动释放池的管理,
          将ObjcAutoreleasePoolStmt 转译成 objc_autoreleasePoolPush/Pop
$clang -S -fobjc-arc -emit-llvm main.m -o main.ll
生成中间代码

5.Optimize - 优化

1).$clang -S -fobjc-arc -emit-llvm main.m -o main.ll

编译器的原理浅析

2).$clang -O3 -S -fobjc-arc -emit-llvm main.m -o main.ll
   可采用不同优化级别优化中间代码

编译器的原理浅析

3).可以再Xcode中设置优化级别

编译器的原理浅析

LLVM Bitcode - 生成字节码

字节码是一种包含执行程序、由一序列 op 代码/数据对组成的二进制文件,但与特定机器码无关,需要直译器转译后才能生成机器码,
可以看作是包含一个执行程序的二进制文件
$clang -emit-llvm -c main.m -o main.bc
形成二进制流

6.Assemble - 生成 Target 相关汇编

1).$clang -S -fobjc-arc main.m -o main.s
   生成汇编代码
   $clang -fmodules -c main.m -o main.o 
   Mach-o 是苹果系统的目标文件

编译器的原理浅析

2).可以在MachOView中查看.o目标文件,其中包含
   a.Mach64 Header头部:标记一些元信息,比如架构、CPU、大小端等信息。
   b.Load Commands加载命令:表示如何加载每个段的信息。
   c.Section区域:多个Segment及Section包含每个段自身的信息,包括数据、代码等.
    __TEXT表示只读数据,只读常量,C strings
    __DATA表示全局/静态变量
   d.Relocations重要定位信息:
   e.Symbols符号表
   f.string字符串表

3).也可以通过Xcode的link Map File查看二进制文件的布局,在编译之后可以在
 /Library/Developer/Xcode/DerivedData/Build_demo-bzowijmyxpepxzfhfxchuovmtumr/Build/Intermediates.noindex/Build_demo.build/Debug-iphonesimulator/Build_demo.build
 目录下看到对应的Build_demo-LinkMap-normal-x86_64文件

编译器的原理浅析

a.Setions这个区域提供了各个段(segment)和(Section)在可执行文件中的位置和大小,这个区域完整地描述了可执行文件中的全部内容
b.setions分为两种__TEXT代码段和__DATA数据段
c.Symbols对Section中方的各个段进行了二级划分,从中我们可以看出OC方法的存储方式
d.对于
 0x100004F80    0x00000010  __DATA   __objc_ivar
 这个ivar在对应的symbols的区域是
 # Symbols:
 # Address  Size        File  Name
 0x100004F80    0x00000008  [  6] _OBJC_IVAR_$_AppDelegate._persistentContainer
e.对于string,会显示存储到数据段中
 0x100003CB4    0x0000000D  [  5] literal string: hello world 
$clang main.m -o main