dyld & objc的关联

2020-10-14  本文已影响0人  深圳_你要的昵称

抛出问题

上回说到iOS应用程序加载大致流程分析,我们大致了解到:dyld 链接所有的库,加载到内存,生成mach-O格式的文件,但是类里面的方法、属性、协议等信息是什么时候加载到内存里的呢,说白了,就是如何加载到底层objc_class结构体class_rw_t *data()里的?

objc_class结构体

找关联

dyld::main函数流程中,有一步initializeMainExecutable(主程序表初始化)时,跟着调用链走到recursiveInitialization,如下图:

其中第3点,通过回调的方式告知初始化完成:
context.notifySingle(dyld_image_state_initialized, this, NULL);
再看notifySingle实现:

最终是调用(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());这个函数,那么接下来的目标就是sNotifyObjCInit,它是在哪里赋值的?
全局搜索发现:

registerObjCNotifiers中,继续跟着调用链向上查找,最终找到_dyld_objc_notify_register

接着全局搜_dyld_objc_notify_register,在dyld库源码搜不到,说明不是在dyld库调用的,再回到类何时加载到内存这个问题,既然是类的加载,类是什么-->是对象,是object,那么我们去到objc源码里搜,果然搜到了,如图⬇️

综上我们发现,在dyld链接库的执行过程中,即在dyld::main()的函数流程中的初始化主程序表这一步时,会调用sNotifyObjCInit这个回调方法,而回调方法sNotifyObjCInit的注册是在objc源码的_objc_init()函数中实现的-->_dyld_objc_notify_register(&map_images, load_images, unmap_image),根据入参对应的位置,sNotifyObjCInit是第二个入参,对应到_dyld_objc_notify_register的第二个入参,最终 sNotifyObjCInit == load_images

再看看其它两个入参map_imagesunmap_image,看看是在哪里触发回调的。
registerObjCNotifiers源码如下:

void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    // record functions to call
    sNotifyObjCMapped   = mapped;
    sNotifyObjCInit     = init;
    sNotifyObjCUnmapped = unmapped;

    // call 'mapped' function with all images mapped so far
    try {
        notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
    }
    catch (const char* msg) {
        // ignore request to abort during registration
    }

    // <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
    for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
        ImageLoader* image = *it;
        if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
            (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        }
    }
}

map_images的回调触发

先看看map_images,即sNotifyObjCMapped。一样全局搜索,


只有唯一一处地方调用,在函数notifyBatchPartial里,同理全局搜索:
第一处

第二处

第三处

第三处就是将map_images赋给sNotifyObjCMapped后就直接调用了。第二处是在registerImageStateBatchChangeHandler函数中,全局搜索,没有地方调用,暂不考虑。剩下就是第一处,在函数notifyBatch中,全局搜索,如图⬇️

一共5个地方调用:

  1. 在函数runAllStaticTerminators中,继续向上搜索,发现是在函数initializeMainExecutable中,这不就是在dyld::_main()里的初始化主程序表这步,一处关联。
  2. 也是在dyld::_main()里,在绑定主程序表的注册结果后,将结果回调给objc那边,一处关联。

    3.在ImageLoader::link链接这一步,明显也是dyld里面,一处关联。

    4.在函数runInitializers

    继续向上搜索,依旧是在initializeMainExecutable()中,又一处关联。

    5.在函数ImageLoaderMegaDylib::dlopenFromCache

    继续向上搜索,发现调用方是函数dyld::dlopenFromCache

    继续查看dyld::dlopenFromCache,在函数dyld::dlopen_internal里调用,但都是在 if 判断语句里,不是重点分析对象,但也是dyld与objc的一处关联地方。

综上,我们查找map_images的调用,总共搜寻到7处关联。

unmap_image的回调触发

最后查看unmap_image,即sNotifyObjCUnmapped。一样全局搜索,


在函数dyld::removeImage里触发,即移除镜像文件时。这也是一处关联。

总结

我们在dyld::_main()函数的流程里,在初始化主程序表initializeMainExecutable的过程中,沿着调用链顺序向下查找到recursiveInitialization,查看其源码知道了在主程序表完成初始化后,通过回调notifySingle对外通知完成状态,接着我们再沿着调用链向上搜索notifySingle,最终发现_dyld_objc_notify_register,在这里完成了回调函数的赋值操作,接着再查询到_dyld_objc_notify_register是在objc源码的_objc_init里调用的,进而知道了dyld与objc是相互关联的,在objc里注册回调函数,在dyld里触发回调函数。

接着我们重点查看_dyld_objc_notify_register函数里三个函数map_images load_images unmap_image的调用链,找到了9处关联的地方。

上一篇下一篇

猜你喜欢

热点阅读