引言
本文主要探索dyld的加载流程,了解应用程序在main函数之前都做了什么准备工作,了解dyld是什么,我们所编写的代码、framework等是如何加载到内存里变活起来的。
###dyld

dyld(The dynamic link editor )是苹果的动态链接器,是苹果操作系统的重要组成部分,在我们的代码被编译打包成可执行文件Mach-O 文件之后 ,交由 dyld 负责链接 , 加载程序 。
dyld源码
本文用的是dyld-852版本的源码。
###探索1

main -> start符号断点,调用栈

我们新建一个iOS工程,在main.m中打个断点,运行项目,查看调用堆栈,如图所示:010-iOS底层原理-dyld加载流程 - 图30
查看左边,发现在main函数调用前,系统已经执行了start函数。根据以往经验,我们可以选择的操作方式是查看汇编添加符号断点
010-iOS底层原理-dyld加载流程 - 图31
图中我们看到了,start是在libdyld.dylib这个库中调用的,dyld是开源库,我们可以下载下来后,阅读源码。本文用的是dyld-852版本的源码。010-iOS底层原理-dyld加载流程 - 图32
在添加start符号断点,运行后,发现符号断点并未停住,而是直接来到了main.m的断点上。因此,start并不是我们所需要的符号断点。仍需努力。

load方法__attribute__

ViewController.m中添加load方法

  1. #import "ViewController.h"
  2. @interface ViewController ()
  3. @end
  4. @implementation ViewController
  5. +(void)load {
  6. NSLog(@"---%s---",__func__);
  7. }
  8. - (void)viewDidLoad {
  9. [super viewDidLoad];
  10. }
  11. @end

main.m中添加__attribute__

  1. int main(int argc, char * argv[]) {
  2. NSString * appDelegateClassName;
  3. @autoreleasepool {
  4. NSLog(@"main 函数");
  5. appDelegateClassName = NSStringFromClass([AppDelegate class]);
  6. }
  7. return UIApplicationMain(argc, argv, nil, appDelegateClassName);
  8. }
  9. //确保此函数在 在main函数被调用之前调用
  10. __attribute__ ((constructor))void before_main(){
  11. printf("main 前 :%s\n",__func__);
  12. }

这三者打印顺序如何呢?如图所示:010-iOS底层原理-dyld加载流程 - 图33
由图可知,三者执行的先后顺序为load方法__attribute__main函数。因此在main函数执行前的操作,可以从load方法中添加断点开始。
补充:__attribute__ ((constructor))void before_main(){}是确保此函数在 在main函数被调用之前调用。具体请查看这篇文章OC中的 _attribute _

load方法断点

ViewController.m中的+load方法添加断点,运行后如图所示:
010-iOS底层原理-dyld加载流程 - 图34
由图中我们可以看到左边的调用栈中,在load之前调用了load_images_dyld_start。我们在控制台中输入bt 打印详细调用栈,如图所示:010-iOS底层原理-dyld加载流程 - 图35
由此可见,我们的app最开始,是由_dyld_start开始的。
###dyld源码

下载好dyld-852后,打开dyld源码工程,由于工程底部所依赖的系统库太多(libdispatch,libsystem),运行不起来无法调试。
####dyldbootstrap::start
全局搜索_dyld_start010-iOS底层原理-dyld加载流程 - 图36
dyldStartup.s中(.s是汇编文件后缀),可以看到有多个重复的.global _dyld_start,分别点击后,可以看到是右边是由于架构判断,导致的重复,因此,我们挑arm64架构的源码来阅读。
阅读汇编代码,阅读注释很重要,我们在第240行看到了call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue),call的意思是呼叫,调用。这里我们全局搜索dyldbootstrap::start,发现搜不到我们想要的。我们全局搜索dyldbootstrap,发现在dyldInitialization.cppdyldbootstrap是一个命名空间C++语法中,有一个词叫namespace命名空间,可当做类来阅读。namespace内部成员和函数,相当于类的成员和方法。我们接着搜索start,如图所示:010-iOS底层原理-dyld加载流程 - 图37
rebaseDyld():dyld重定位。这是苹果用来保证应用安全的技术,其中包括:ASLRCode Sign
ASLRAddress Space Layout Randomization(地址空间布局随机化)的简称。App在被启动的时候,程序会被映射到逻辑地址空间,这个逻辑地址空间有一个起始地址ASLR技术让这个起始地址是随机的。这个地址如果是固定的,攻击者很容易就用起始地址+函数偏移地址找到对应的函数地址。
Code Sign:代码加密签名机制,但是在 Code Sign操作的时候,加密的哈希不是针对整个文件,而是针对每一个 Page的。这个就保证了 dyld在加载的时候,可以对每个 page进行独立的验证。
正是因为 ASLR使得地址随机化,导致起始地址不固定,以及 Code Sign,导致不能直接修改 Images。所以需要 rebase来处理符号引用问题,Rebase的时候只需要通过增加对应偏移量就行了。Rebase主要的作用就是修正内部(指向当前 Mach-O文件)的指针指向,也就是基地址复位功能。
####dyld::_main
dyld::_main函数是我们此次研究的重头戏,点进去之后,发现它是一个800多行代码的一个函数。010-iOS底层原理-dyld加载流程 - 图38
查看大量源码,分析流程的思路,需要整体把握,不需要一头扎进细节里,否则,将切身体会到“入门到放弃”的完整过程,迷失在未知的世界里。对于此类源码,我们可以根据返回值倒推逻辑。接下来,我们将逐步解析其内容。
1、通过return返回的是一个result010-iOS底层原理-dyld加载流程 - 图39
2、通过result的赋值,找到result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();以及其他有关sMainExecutable的代码位置,我们由此可知,resultsMainExecutable有很大的关联和关系;010-iOS底层原理-dyld加载流程 - 图40
3、通过sMainExecutable =,找到sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);,官方注释为// instantiate ImageLoader for main executable010-iOS底层原理-dyld加载流程 - 图41
4、点击进入instantiateFromLoadedImage,发现内部添加了image镜像,并返回此image镜像
5、进入instantiateMainExecutable查看如何获得image镜像的,内部的segCountlibCountdata_commandinfo_command等等内容,可进入sniffLoadCommands配合烂苹果MachOView进行对照查看,可以事半功倍。010-iOS底层原理-dyld加载流程 - 图42010-iOS底层原理-dyld加载流程 - 图43

dyld::_main内部总结(借助注释来查看)
1.【第一步:条件准备】:环境配置、平台、版本、路径、主机信息等010-iOS底层原理-dyld加载流程 - 图44
2.【第二步:共享缓存配置】:checkSharedRegionDisable检查是否可用,通过mapSharedCache映射共享缓存到贡献缓存区域010-iOS底层原理-dyld加载流程 - 图45
3.【第三步:实例化主程序】:通过instantiateFromLoadedImage实例化主程序,得到一个sMainExecutable。将images加入到dyld_all_image_info表中。010-iOS底层原理-dyld加载流程 - 图46
4.【第四步:插入动态库】:for循环 DYLD_INSERT_LIBRARIES,调用loadInsertedDylib(*lib);插入动态库。010-iOS底层原理-dyld加载流程 - 图47
5.【第五步:link主程序和动态库】:直接调用link方法,将sMainExecutable链接,再for循环将第四步插入的动态库链接。010-iOS底层原理-dyld加载流程 - 图48
6.【第六步:weakBind弱引用绑定主程序】:for循环sImageRoots.size(),插入镜像符号;sMainExecutable->recursiveBindWithAccounting绑定主程序;image->recursiveBind绑定插入的动态库,并通知已经注册完毕。sMainExecutable->weakBind弱引用绑定主程序。010-iOS底层原理-dyld加载流程 - 图49
7.【第七步:初始化主程序】:调用initializeMainExecutable初始化主程序。010-iOS底层原理-dyld加载流程 - 图50
8.【第八步:寻找main函数入口】:寻找LC_MAIN入口地址并执行,如果找不到,则寻找LC_UNIXTHREADdyldstart设置成main()010-iOS底层原理-dyld加载流程 - 图51

【第七步:初始化主程序】initializeMainExecutable的分析

1.进入initializeMainExecutable后,for循环将插入的动态库初始化。然后执行runInitializers初始化主程序。010-iOS底层原理-dyld加载流程 - 图52
2.进入sMainExecutable->runInitializers,找到processInitializers函数,进入下一步。010-iOS底层原理-dyld加载流程 - 图53
3.进入processInitializers,主线为递归初始化images list镜像列表里的所有镜像,并创建一个list列表,用于存储为初始化的向上的依赖关系。ups > 0的判断,是如果仍有依赖关系,将他们初始化。010-iOS底层原理-dyld加载流程 - 图54
4.全局搜索recursiveInitialization,找到ImageLoader里的recursiveInitialization 函数。查看try{}内部的实现:
1)for循环初始化更底层的库
2)terminationRecorder记录终止命令
3)notifySingle通知objc记得要初始化这个image镜像。
4)doInitialization初始化这个image镜像
#####doInitialization
进入到doInitialization函数,有两个函数调用
010-iOS底层原理-dyld加载流程 - 图55
1.进入doImageInit,发现是 获取mach-o的init方法的地址并调用:010-iOS底层原理-dyld加载流程 - 图56
2.进入doModInitFunctions,解析并执行DATA,mod_init_func这个section中保存的函数(这里保存的是全局C++对象的构造函数以及所有带__attribute((constructor)的C函数),也就是我们之前在main.m中写的before_main()函数。并将libSystem库中的libSystem_initializer执行。010-iOS底层原理-dyld加载流程 - 图57

*陷入迷茫,无法继续下一步,返回到ImageLoader::recursiveInitialization函数,探索notifySingle
#####notifySingle
1.点击进入notifySingle,发现来到的是函数声明的地方,`void (
notifySingle)(dyld_image_states, const ImageLoader image, InitializerTimingList);<br />2.全局搜索notifySingle(,找到它的实现:![](https://upload-images.jianshu.io/upload_images/4096235-f50b628f95687297.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#id=sdhvz&originHeight=692&originWidth=1240&originalType=binary&ratio=1&status=done&style=none)<br />3.全局搜索sNotifyObjCInit,无实现函数,在registerObjCNotifiers函数中找到= init赋值。![](https://upload-images.jianshu.io/upload_images/4096235-ca270054df008228.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#id=fzCFS&originHeight=447&originWidth=1240&originalType=binary&ratio=1&status=done&style=none)<br />4.全局搜索registerObjCNotifiers,在dyldAPIs.cpp中的_dyld_objc_notify_register函数中,找到调用位置。![](https://upload-images.jianshu.io/upload_images/4096235-c30229544da2531d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#id=riUqM&originHeight=260&originWidth=1240&originalType=binary&ratio=1&status=done&style=none)<br />5.全局搜索_dyld_objc_notify_register,发现没有调用该函数的代码。细看类名dyldAPIs.cpp,该类为dyld对外的接口类。由于上面提到的时候,对通知objc记得初始化该镜像,因此,我们将打开libobjc`源码。

6.打开objc4_818_2工程,全局搜索_dyld_objc_notify_register。在_objc_init函数中,找到其调用位置,其中load_imagesdyldregisterObjCNotifierssNotifyObjCInit赋值的参数。而load_images,是一个函数,由此可知,dyld通知objc的方式,是通过load_images函数进行回调的。也就是说,**dyld**中的**notifySingle**是一个回调函数。 010-iOS底层原理-dyld加载流程 - 图58

load_images
进入load_images,首先查找发现所有+load方法,然后执行所有+load方法010-iOS底层原理-dyld加载流程 - 图59
进入call_load_methods中,内部为创建一个autoreleasepool,并在autoreleasepooldo-while循环执行所有的+load方法。010-iOS底层原理-dyld加载流程 - 图60内部继续调用的call_class_loads()call_category_loads()都是最终调用(*load_method)(cls, @selector(load));函数。 010-iOS底层原理-dyld加载流程 - 图61
到此为止,+load的加载流程已经梳理通顺。对应上了我们前面的ViewController.m+load方法。
#####+load流程
调用流程如图所示:
010-iOS底层原理-dyld加载流程 - 图62
_dyld_start (dyld)
dyldbootstrap::start (dyld)
dyld::_main (dyld)
initializeMainExecutable(dyld)
ImageLoader::runInitializers (dyld)
ImageLoader::processInitializers (dyld)
ImageLoader::recursiveInitialization (dyld)
dyld::notifySingle (dyld,回调函数,sNotifyObjCInit = load_images函数)
libobjc.A.dylib load_images (objc,接收回调,调所有+load方法)
VC +load。(开发者代码)
doInitialization未完成的流程
前面进入doInitialization内部后,发现无法继续深入,此时,我们回到objc调试工程,添加符号断点_objc_init。如图所示010-iOS底层原理-dyld加载流程 - 图63
从调用栈中可以看到,doModInitFunctions_objc_init中缺失了两个流程。
######_os_object_init
_os_object_init位于libdispatch.dylib库中,我们将下载libdispatch-1271.40.12源码
,解压后打开工程,全局搜索_os_object_init
010-iOS底层原理-dyld加载流程 - 图64
发现_os_object_init是在libdispatch_init调起的。
010-iOS底层原理-dyld加载流程 - 图65

在之前的调用栈中,libdispatch_init是由libSystem_initializer调起的,此函数位于libSystem.B.dylib库中。我们将下载Libsystem-1292.60.1源码解压后打开工程,全局搜索libdispatch_init,确实位于libSystem_initializer中调用了。010-iOS底层原理-dyld加载流程 - 图66

_objc_init流程
_dyld_start(dyld)
dyldbootstrap::start(dyld)
dyld::_main(dyld)
dyld::initializeMainExecutable(dyld)
ImageLoader::runInitializers(dyld)
ImageLoader::processInitializers(dyld)
ImageLoader::recursiveInitialization(dyld)
ImageLoaderMachO::doInitialization(dyld)
ImageLoaderMachO::doModInitFunctions(dyld)
libSystem_initializer(libSystem)
libdispatch_init(libdispatch)
_os_object_init(libdispatch)
_objc_init(libobjc)

总结
本文Demo)
dyld主线加载流程流程图如下:010-iOS底层原理-dyld加载流程 - 图67