一、序言
作为iOSer,我们天天在xcode上写代码,然后打包发布或者测试,可曾想过代码是如何到内存种的?app是如何从点击图标到整个程序运行的呢?现在我们来探究一下。
二、库
什么是库?库就是能够被系统加载到内存中的可执行的二进制文件,库有这种形式: 静态库(.a .o等)和动态库(.framework .so .dylib等)
1、动态库和静态库的区别
我们梳理一下库的加载过程,首先我们的代码(.h .m .cpp .swift等)经过预编译、编译成汇编,然后链接装载生成可执行文件,大致流程图如下:

1.1、静态库
链接时,静态库会被挨个挨个完整地装载到可执行文件中,被多次使用就有多份冗余拷贝,如图:

图中会很明显的有重复的库,这样就会浪费很多性能。
1.2、动态库
动态库在加载时才进行链接,比如加载到某个节点发现需要某一个库时并不是直接加载,而是共享这个动态库。相比于静态库,动态库会被共用,这样会节省更多的空间和时间。加载的大致流程如下所示:

三、链接器
库是如何加载到内存种的呢?这就需要一个非常重要的东西了dyld。
1、App的启动入口
App是如何启动的呢?我们都知道所有程序的入口都是main函数,那么我们来验证一下。首先创建一个空的iOS项目,并在main函数上打个断点,执行程序,如图:

很容易发现在main之前有个start_sim,我们下个下个符号断点start,再次运行程序,如图:

很遗憾还是在这里,好像失去了方向。但是经验告诉我们有个方法会在main之前执行,对,就是load方法,我们随便添加个load并断点,如图:

运行起来,如图:

山穷水复疑无路,柳暗花明又一村。赶紧查看一下堆栈信息,如图:

好像这里才是我们程序的起点。
2、APP的启动流程
刚刚我们找到了APP的启动入口,那么从启动到main函数这个中间经历了什么呢?干了什么事情呢?我们需要到dyld的源码中寻找,打开dyld的源码,搜索_dyld_start(由于源码编译依赖太多就没有流程可以跟踪。还有因系统版本Xcode打开dyld源码闪退的问题,可以将dyld的源码拖入到新建的工程中,不要copy,可以避免闪退),如下所示:

全在深奥的汇编中,这不要紧,因为这里都写的注释。我们很容易发现所有的架构类型中都有call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)这个注释,直接搜索dyldbootstrap:: 没有任何结果,这是c++语法,要先找到命名空间dyldbootstrap,如图:

然后再搜索start(,如图所示:

这里返回的是个dyld::_main(我们再次去找实现,同样先找命名空间,在找函数,如图:

13180条结果,太多了,过滤一下,搜索namespace dyld {,如图:

锐减到14条结果,挨个搜索_main(,找到了实现如下所示:

好长,收起来一看近1000行代码,如图:

为了找准核心代码,我们可以直接渠道最后查看返回值,然后根据返回值找关键的东西,如图:

返回值是result,那么找出result赋值相关的代码,如图:


fake_main返回值是0,如图:

所以可以忽略不看,那么result的值很大可能与sMainExecutable相关。接下来找sMainExecutable相关的东西,如图:

这里的绑定可以说明sMainExecutable就是我们需要找的代码。所以我们要看sMainExecutable的初始化,如图:

然后进入到instantiateFromLoadedImage方法中,如图:

大概意思是为已经映射到可执行文件中的对象创建一个ImageLoader*,然后往下,如图:

加载动态库,然后如图:

link主程序,再然后:

link库。在所有镜像文件都链接完毕之后进行弱引用绑定符号表:

然后初始化执行主程序:

具体代码如图:

即拿到各个镜像文件,然后runInitializers,如图:

其中processInitializers代码如下:

第一个for大概意思就是对镜像文件列表中的镜像文件递归初始化,第二个for是继续初始化向上以来的文件。具体的初始在recursiveInitialization中,继续进去查看,如图:

里面是个try catch,那么我们从try中查找即可,如图:

再继续看notifySingle,如图:

notifySingle定义的是一个函数,接下来来就需要找到他赋值的地方,如图:

在然后实现的地方,如图:

接下来看1112行的sNotifyObjCInit,搜索到定义和赋值如下:


sNotifyObjCInit的赋值源于registerObjCNotifiers函数的第二个参数,所以继续搜索registerObjCNotifiers的调用,如图:

继续搜索,如图:

貌似没有找到调用,线索断了?放松一下。
3、从_objc_init开始的逆向推导
打开objc源码搜索registerObjCNotifiers,如图:

发现在_objc_init有调用registerObjCNotifiers,且objc源码是可编译的,断点伺候,如图:

点击_os_object_init,如图:

在libdispatch.dylib库中的_os_object_init有调用_objc_init,所以我们去libdispatch.dylib查找_os_object_init,如图:

果真在_os_object_init有调用_objc_init,继续往下找,如图:

在同一个库中的libdispatch_init中找到了_os_object_init的调用。那么libdispatch_init的调用是在哪里呢?当前库也搜索不到,所以我们回到objc源码中,在控制台输入bt查看堆栈信息,如图:

在libSystem.B.dylib中有调用,那么我们去libSystem.B.dylib查找libdispatch_init,如图:

在libSystem库的libSystem_initializer调用了libdispatch_init,继续搜索libSystem_initializer并没有找到的调用,再看堆栈信息,在dyld中的doModInitFunctions(ImageLoader::LinkContext const&)调用,所以回到dyld中搜索ImageLoaderMachO::doModInitFunctions,如图:

根据注释判断出该方法是加载libSystem的。其中func为libSystem_initializer,根据参数判断。然后查找doModInitFunctions的调用,如图:

再查找doInitialization的调用处,如图:

再次回到了recursiveInitialization中,大概理清楚了通知注册的方法,由于目前xcode直接打开dyld库会闪退,影响部分分析,请各位大神多多指导。























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)