LLVM、Clang 、dyld

LLVM

LLVM是iOS目前的构架编译器的框架系统,LLVM会对每个文件进行编译,生成 Mach-O(可执行文件);链接器会将项目中的多个 Mach-O 文件合并成一个。
编译的几个主要过程:
1.LLVM 会预处理你的代码,比如把宏嵌入到对应的位置
2.预处理完后,LLVM 会对代码进行词法分析和语法分析,以及代码优化,然后生成 AST 。AST 是抽象语法树,结构上比代码更精简,遍历起来更快,所以使用 AST 能够更快速地进行静态检查,同时还能更快地生成 IR(中间表示)
3.最后 AST 会生成 IR,IR 是一种更接近机器码的语言,区别在于和平台无关,通过 IR 可以生成多份适合不同平台的机器码。对于 iOS 系统,IR 生成的可执行文件就是 Mach-O

Clang

Clang基于LLVM、发布于LLVM BSD许可证下的C/C++/Objective-C/Objective-C++编译器

dyld

  • dyld(the dynamic link editor)是苹果的动态链接器,最主要作用就是将符号绑定到地址上。

Mach-O 文件里面的内容,主要就是代码和数据:代码是函数的定义;数据是全局变量的定义,包括全局变量的初始值。不管是代码还是数据,它们的实例都需要由符号将其关联起来。 因为 Mach-O 文件里的那些代码,比如 if、for、while 生成的机器指令序列,要操作的数据会存储在某个地方,变量符号就需要绑定到数据的存储地址。你写的代码还会引用其他的代码,引用的函数符号也需要绑定到该函数的地址上。 而链接器的作用,就是完成变量、函数符号和其地址绑定这样的任务。而这里我们所说的符号,就可以理解为变量名和函数名。

  • 链接器对代码做了什么

1.去项目文件里查找目标代码文件里没有定义的变量

  1. 扫描项目中的不同文件,将所有符号定义和引用地址收集起来,并放到全局符号表中

3.计算合并后长度及位置,生成同类型的段进行合并,建立绑定

  1. 对项目中不同文件里的变量进行地址重定位。

你在项目里为某项需求写了一些功能函数,但随着业务的发展,一些功能被下掉了或者被其他负责的同事在另一个文件里用其他函数更新了功能。那么这时,你以前写的那些函数就没有用武之地了。日长月久,无用的函数越来越多,生成的 Mach-O 文件也就越来越大。 这时,链接器在整理函数的符号调用关系时,就可以帮你理清有哪些函数是没被调用的,并自动去除掉。那这是怎么实现的呢? 链接器在整理函数的调用关系时,会以 main 函数为源头,跟随每个引用,并将其标记为 live。跟随完成后,那些未被标记 live 的函数,就是无用函数。然后,链接器可以通过打开 Dead code stripping 开关,来开启自动去除无用代码的功能。并且,这个开关是默认开启的。

  • 动态库链接

在真实的 iOS 开发中,你会发现很多功能都是现成可用的,不光你能够用,其他 App 也在用,比如 GUI 框架、I/O、网络等。链接这些共享库到你的 Mach-O 文件,也是通过链接器来完成的。 链接的共用库分为静态库和动态库:静态库是编译时链接的库,需要链接进你的 Mach-O 文件里,如果需要更新就要重新编译一次,无法动态加载和更新;而动态库是运行时链接的库,使用 dyld 就可以实现动态加载。 Mach-O 文件是编译后的产物,而动态库在运行时才会被链接,并没参与 Mach-O 文件的编译和链接,所以 Mach-O 文件中并没有包含动态库里的符号定义。也就是说,这些符号会显示为“未定义”,但它们的名字和对应的库的路径会被记录下来。运行时通过 dlopen 和 dlsym 导入动态库时,先根据记录的库路径找到对应的库,再通过记录的名字符号找到绑定的地址。 dlopen 会把共享库载入运行进程的地址空间,载入的共享库也会有未定义的符号,这样会触发更多的共享库被载入。dlopen 也可以选择是立刻解析所有引用还是滞后去做。dlopen 打开动态库后返回的是引用的指针,dlsym 的作用就是通过 dlopen 返回的动态库指针和函数符号,得到函数的地址然后使用。

简单来说, dyld 做了这么几件事儿:
1.先执行 Mach-O 文件,根据 Mach-O 文件里 undefined 的符号加载对应的动态库,系统会设置一个共享缓存来解决加载的递归依赖问题;
2.加载后,将 undefined 的符号绑定到动态库里对应的地址上;
3.最后再通过runtime处理 +load 方法,main 函数返回后运行 static terminator。

dyld开源地址:dyld开源地址
dyld更详细参考:dyld详解

推荐阅读更多精彩内容