编写的代码经过编译链接后生成的可执行文件就是 Mach-O 文件。不过 Mach-O 不仅仅只是可执行文件,它可以代表很多。
Mach-O 是 Mach object 的缩写,是 Mac\iOS 上用于存储程序、库的标准格式。
Mach-O 文件有哪些类型,查看 xun 的源码可以看到:
如何证明一个文件是 Mach-O 文件:
假设我们编写了一个 .c 文件 test.c,在终端中对它进行编译 $ clang -c test.c
,这时就可以看到目录中多出了一个 test.o
文件,然后执行$ file test.o
就可看见结果:test.o: Mach-O 64-bit object x86_64。
常见的 Mach-O 文件类型:
- MH_OBJECT
- 目标文件(.o):源文件与可执行文件的中间产物
.c -> .o -> 可执行文件
。假设项目里有三个 c 语言文件,每个文件分别编译成一个目标文件,三个目标文件经过连接之后生成一个可执行文件。 - 静态库文件(.a),静态库其实就是 N 个 .o 合并在一起
- 目标文件(.o):源文件与可执行文件的中间产物
- MH_EXECUTE: 可执行文件
$ clang -o test2 test.c
执行这个命令,让 test.c 生成可执行文件 test2 。执行命令$ file test2
,即可验证。
- MH_DYLIB: 动态库文件
- .dylib
- .framework/xx
- MH_DYLINKER: 动态链接编辑器
/usr/lib/dyld
。这是一个专门用来加载我们动态库文件的东西,它也属于 Mach-O 文件。
- MH_DSYM: 存储二进制文件符号信息的文件
- iOS 项目没次打包的时候就会生成一个 dsym 文件。调试崩溃的时候会用到。
Mach-O 的基本结构
Mach-O 文件都有共同的基本结构。
它主要由三部分组成:Header、Load commands、Data
- Header
- 文件类型、目标架构类型等
- Load commands
- 描述文件在虚拟内存中的逻辑结构、布局(平时我们说的内存都是虚拟内存,比如堆空间、栈空间等等)
- Raw segment data
- 在 Load commands 中定义的 Segment 的原始数据
Load commands 仅仅是描述了结构与布局,Raw segment data 说明了具体的数据。
简单认识dyld(/usr/lib/dyld)
- dyld 将可执行文件加载进内存。代码编写好后,编译链接后生成了一个可以行文件。要运行的话,就需要把它加载进内存,就是 dyld 将它加载进内存的。
- dyld 将动态库加载进内存。dyld 会先判断动态库共享缓存中是否有这个动态库,如果没有的话,就加载;如果有的话就直接链接就
dyld用于加载以下类型的 Mach-O 文件
- MH_EXECUTE
- MH_DYLIB
- MH_BUNDLE
动态库共享缓存(dyld shared cache)
从 iOS3.1 开始,为了提高性能,绝大部分的系统动态库都存放到了一个缓存文件中,路径是:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_armX
比如:MapKit.frameworks、UIKit.frameworks、CoreGraphics.frameworks 等等
动态库共享缓存非常明显的好处就是:节省内存。
- 假设现在有多个APP,APP1、APP2、APP3 它们都要用到 UIKit.frameworks。一种方式是把 UIKit.frameworks 分别加载到这三个 APP,另一种方式是把 UIKit.frameworks 放到 APP 内存之外的另一个地方,APP 们可以共享它,显然这样节省了内存。
- 还有一点如果有多个动态库 UIKit、MapKit 等等,苹果还会把他们合并成一个文件 dyld_shared_cache_armX,这样可以把多个动态库中一些通用的东西只用写一份就可以了。
点击运行按钮后,程序经历的过程
预处理(.i) -> 汇编(.s、.asm) -> 编译(.o、.obj) -> 链接(准确讲叫做静态链接) -> 可执行文件
接下来要运行程序,比如点击 APP 图标冷启动程序 或者 Xcode 自动运行:
- dyld 阶段:dyld 会把可执行文件加载到 APP 的内存中;还会加载动态库,加载动态库的时候会先判断动态共享缓存中有没有这个动态库,有的话就直接链接,没有的话就先进行加载。它还会加载 MH_BUNDLE 文件。简单地概括就是它可以加载部分 Mach-O 文件。
- runtime 阶段
- main 阶段