OC底层-dyld应用加载流程(下)

·  阅读 873
OC底层-dyld应用加载流程(下)

上篇文章OC底层-dyld应用加载流程(上)中我们探索到了_objc_init,然后我们看到了_dyld_objc_notify_register函数,这个函数有三个参数,有两个参数是直接调用分别是load_imagesunmap_image,还有一个参数map_images是通过地址访问的获取的也就是通过地址回调,那么就有一个问题,这个map_images是什么时候进行调用的呢?是在_dyld_objc_notify_register函数注册的时候吗?还是在它之前呢?那么今天我们就来探索下这个map_images的调用时机以及过程。

map_images

我们首先在dyld源码中进行搜索查看_dyld_objc_notify_register的实现,发现比较多,除去申明的,那么我们最后只得到如下两个地方实现:

  • APIs.cpp文件的714

image.png

  • 以及dyldAPIs.cpp文件的2204

image.png

然后再次分别进入setObjCNotifiers函数的实现和registerObjCNotifiers函数的实现,我们能够很明显的区别出来,我们应该去看setObjCNotifiers函数的实现,因为在registerObjCNotifiers函数的内部只是进行了在所有已经初始化的图像上调用init函数的操作,下面看下setObjCNotifiers函数的实现:

image.png

我们需要重点查看的是_objcNotifyMapped的调用时机,所以我们全局搜索看下这个函数是什么时候进行调用的,下面我们看下搜索结果:

image.png

我们找到是在runImageCallbacks函数中进行调用的,那么我们继续查看这个函数的调用,继续进行全局搜索,最后也是找到两处调用的地方,那么我们怎么区分应该看那个呢?首先大概去瞄了一眼,第一个调用的地方也就是const MachOLoaded* AllImages::loadImage函数,然后跟着流程继续查找,最后兜兜转转又回到了const MachOLoaded* AllImages::loadImage函数,我们顺带看这个函数:

image.png

函数中是读取image然后填充到allImages中等等操作,然后我们继续查看这个函数的调用机制,然后去看一下第二个调用的地方void AllImages::applyInitialImages(),然后全局搜索查看调用的地方,最后找到_dyld_initializer这个函数,我们看下:

image.png

找到这个函数后我们继续看下它是在哪个地方调用的?经过全局搜索,找到了一个关键性的注释如下:

image.png

然后全局搜索libSystem_initializer发现并没有,那么我们看下前缀libSystem,那就说明已经超出了dyld源码的范畴了,此时我们打开libSystem的源码,然后进行搜索,然后我们看下这个函数实现:

image.png

我们可以看到在这里就调用了_dyld_initializer这个函数。到此我们也找到了map_images的调用流程以及加载时机。也就是当程序初始化所有libSystem.dylib的时候就已经开始了。

load_images

上面我们看到了map_images的加载调用流程,那么我们继续看下load_images的调用流程,首先我们看下_dyld_objc_notify_register,然后我们搜索,刚才已经看过有两个地方,那么这次我们还是按照之前的方式还是另外一个呢?我想比较细心的人发现了一个问题,一个是dyld调用,一个是AllImages调用,我们这块是dyld调用,去读取所有的images,所以我们去看registerObjCNotifiers这个函数:

image.png

然后看下sNotifyObjCInit的调用,我们发现notifySingle函数中有相关的调用实现,而且传值了,然后我们看下notifySingle函数的调用是在哪,通过全局搜索,找到是在recursiveInitialization函数中进行了调用具体如下:

image.png

image.png

通过上面我们能够看到这块已经是直接开始传值调用了,那么具体这个函数里面是做了什么呢?我们回到objc的源码去看下:

image.png

在上面函数里面主要就是读取+load,那么重点就是3227行的函数,我们进去看下这个函数的实现:

image.png

在上面方法中递归读取非懒加载类的+load方法以及非懒加载类的分类的+load方法添加到一个+load方法列表中,然后回到load_images函数,然后调用call_load_methods函数,读取所有+load方法,我们也去看下这个函数的实现:

image.png

然后我们顺带看下类的+load方法调用实现:

image.png

以及分类的+laod方法调用实现:

image.png

到这dyld基本上一个加载流程完成了,但是我们在看源码的时候忽略了一个小小的点,这个点或许会导致程序执行顺序不是按照自己的设想哦,是啥呢?我们回头去探索下。

cxx函数的调用时机

前面我们探索了+load方法的加载时机,是在main函数之前,也看了dyld加载的一个流程,那么我们顺带看下cxx函数的加载时机,首先我们来个案例看下:

image.png

我们通过案例就可以看到,首先走的是+load方法,然后是cxx函数,最后才是main函数,那么问题就来了,cxx函数是什么时候进行调用加载的呢?跟上我的步伐,我们回到recursiveInitialization函数的实现:

image.png

在这个函数的第1651行开始也就是context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);调用+load函数后,调用了bool hasInitializers = this->doInitialization(context);函数,加载了cxx函数,或许会问,你咋知道,开天眼了啊?不要着急,我们在刚才的案例项目中,在KGFunc函数内打个断点,然后看下堆栈信息:

image.png

就是这么放肆,通过堆栈信息我们也是能够验证,就是通过bool hasInitializers = this->doInitialization(context);进行的调用。这也就是为啥在+load后,main之前了。

dyldmain

既然我们探索了dyld的加载流程,那么从dyldmain是怎么过度的呢?下面我们继续探索这个过程。我们开篇介绍OC底层-dyld应用加载流程(上)的时候,提到了一个关键字_dyld_start,而且是汇编实现的,那么我们再去详细看下这个汇编:

image.png

里面有两部分注释,如果汇编比较熟悉的话,我想看见LC_MAIN就应该能明白了,因为对于Mach-o文件来说LC_MAIN就是保存了mainIMP,既然这块是汇编实现的,那么我们就去运行程序看下,是否一致呢?

首先我们打开汇编模式:Debug->Debug Workflow->Always Show Disassembly。然后在KGFunc函数中添加一个断点,然后运行代码如下:

image.png

然后我们看下寄存器的值:

image.png

寄存器中保存的确实是main函数的IMP,这就解释了从dyldmain的加载过程。

总结

到此对于dyld的探索就结束了,如果后期还有其它的发现再补充吧,通过这两边文章,了解了+load方法和cxx方法以及main函数的执行顺序,同时也发现了一个点,非懒加载类的+load方法先按照isa走位递归,然后才去执行分类的+load方法。

分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改