App启动Dyld2流程:
- 加载dyld到App进程
- 加载动态库(包括所依赖的所有动态库)
- Rebase/Bind
- 初始化Objective C Runtime
- 其它的初始化代码
1.加载dyld到App进程
系统加载程序可执行文件Mach-O(Header,LoadCommands,Data)后,通过分析文件LoadCommands->LC_LOAD_DYLINKER来获得dyld所在路径/usr/lib/dyld 来加载dyld,然后就将后面的事情甩给dyld了。
2.加载动态库(包括所依赖的所有动态库)
dyld会首先读取mach-o文件的Header和load commands。接着就知道了这个可执行文件依赖的动态库。例如加载动态库A到内存,接着检查A所依赖的动态库,就这样的递归加载,直到所有的动态库加载完毕。通常一个App所依赖的动态库在100-400个左右,其中大多数都是系统的动态库,它们会被缓存到dyld shared cache,这样读取的效率会很高。
3.Rebase/Bind
ASLR的全称是Address space layout randomization,翻译过来就是“地址空间布局随机化”。App被启动的时候,程序会被影射到逻辑的地址空间,这个逻辑的地址空间有一个起始地址,而ASLR技术使得这个起始地址是随机的。如果是固定的,那么黑客很容易就可以由起始地址+偏移量找到函数的地址。
Code Sign相信大多数开发者都知晓,这里要提一点的是,在进行Code sign的时候,加密哈希不是针对于整个文件,而是针对于每一个Page的。这就保证了在dyld进行加载的时候,可以对每一个page进行独立的验证。
Rebase 修正内部(指向当前mach-o文件)的指针指向,Rebase解决了内部的符号引用问题,而外部的符号引用则是由Bind解决。在解决Bind的时候,是根据字符串匹配的方式查找符号表,所以这个过程相对于Rebase来说是略慢的。
Bind 修正外部指针指向,之所以需要Rebase,是因为刚刚提到的ASLR使得地址随机化,导致起始地址不固定,另外由于Code Sign,导致不能直接修改Image。Rebase的时候只需要增加对应的偏移量即可。待Rebase的数据都存放在__LINKEDIT中。
4.初始化Objective C Runtime
Objective C是动态语言,所以在执行main函数之前,需要把类的信息注册到一个全局的Table中。同时,Objective C支持Category,在初始化的时候,也会把Category中的方法注册到对应的类中,同时会唯一Selector,这也是为什么当你的Cagegory实现了类中同名的方法后,类中的方法会被覆盖。
另外,由于iOS开发时基于Cocoa Touch的,所以绝大多数的类起始都是系统类,所以大多数的Runtime初始化起始在Rebase和Bind中已经完成。
5.其它的初始化代码
接下来就是必要的初始化部分了,主要包括几部分:1. +load方法 2. C/C++静态初始化对象和标记为attribute(constructor)的方法。这里要提一点的就是,+load方法已经被弃用了,如果你用Swift开发,你会发现根本无法去写这样一个方法,官方的建议是实用initialize。区别就是,load是在类装载的时候执行,而initialize是在类第一次收到message前调用。
备注:
以上是dyld2加载流程
dyld3则是部分out-of-process,部分in-process。在App下载安装和版本更新的时候会去执行,out-of-process会做如下事情:
- 分析Mach-o Headers
- 分析依赖的动态库
- 查找需要Rebase & Bind之类的符号
- 把上述结果写入缓存
这样,在应用启动的时候,就可以直接从缓存中读取数据,加快加载速度。
在Xcode中,可以通过设置环境变量来查看App的启动时间,DYLD_PRINT_STATISTICS和DYLD_PRINT_STATISTICS_DETAILS。
Edit Scheme->Run->Arguments->Environment Variables->DYLD_PRINT_STATISTICS:1
Total pre-main time: 654.59 milliseconds (100.0%)
dylib loading time: 119.99 milliseconds (18.3%)
rebase/binding time: 380.23 milliseconds (58.0%)
ObjC setup time: 68.40 milliseconds (10.4%)
initializer time: 85.82 milliseconds (13.1%)
slowest intializers :
libSystem.B.dylib : 3.07 milliseconds (0.4%)
libMainThreadChecker.dylib : 14.79 milliseconds (2.2%)
LoveOfPomelo : 120.48 milliseconds (18.4%)
Edit Scheme->Run->Arguments->Environment Variables->DYLD_PRINT_STATISTICS_DETAILS:1
total time: 924.43 milliseconds (100.0%)
total images loaded: 305 (0 from dyld shared cache)
total segments mapped: 908, into 125240 pages with 9067 pages pre-fetched
total images loading time: 650.11 milliseconds (70.3%)
total load time in ObjC: 75.70 milliseconds (8.1%)
total debugger pause time: 462.64 milliseconds (50.0%)
total dtrace DOF registration time: 0.14 milliseconds (0.0%)
total rebase fixups: 3,212,220
total rebase fixups time: 43.73 milliseconds (4.7%)
total binding fixups: 366,847
total binding fixups time: 47.16 milliseconds (5.1%)
total weak binding fixups time: 1.59 milliseconds (0.1%)
total redo shared cached bindings time: 47.97 milliseconds (5.1%)
total bindings lazily fixed up: 0 of 0
total time in initializers and ObjC +load: 105.96 milliseconds (11.4%)
libSystem.B.dylib : 42.99 milliseconds (4.6%)
libBacktraceRecording.dylib : 3.14 milliseconds (0.3%)
CoreFoundation : 1.51 milliseconds (0.1%)
Foundation : 1.83 milliseconds (0.1%)
libMainThreadChecker.dylib : 15.29 milliseconds (1.6%)
LoveOfPomelo : 78.05 milliseconds (8.4%)
total symbol trie searches: 177022
total symbol table binary searches: 0
total images defining weak symbols: 31
total images using weak symbols: 82