上一篇文章介绍了加载我们程序之前所做的准备工作。也就是环境配置,加载共享缓存,MachO初始化,插入动态库加载等部分。接下来就需要对我们主程序的加载做出分析了。
正常函数栈执行
初始化主程序的函数:
#if SUPPORT_OLD_CRT_INITIALIZATION// Old way is to run initializers via a callback from crt1.o if ( ! gRunInitializersOldWay ) initializeMainExecutable(); #else // run all initializers initializeMainExecutable(); #endif
从这个函数开始,我们可以从DYLD中很容易找到接下来的函数调用顺序,也就是函数调用栈中的执行顺序。
寻找 load_images 的调用处
当执行到 notifySingle 函数的时候,我们怎么样也找不到一个叫做 load_images 的函数了……
但是分析整个函数,我们找到一个可疑的代码:
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo){//dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());std::vector
来,我们分析一下。
一、需要执行上面那一句,从 if 可以判定,需要 sNotifyObjCInit 不为空。
二、sNotifyObjCInit 是怎么来的,谁给他赋值形成的非空。
很容易找到,来自这个函数,registerObjCNotifiers。函数中的参数 init 被赋值给了 sNotifyObjCInit。
三、谁调用了 registerObjCNotifiers 函数,传进来 init 参数。
很明显是函数 _dyld_objc_notify_register。那么谁又调用了 _dyld_objc_notify_register 函数?不管通过这样的寻找,发现都没有办法找到这个函数的调用。
最后只能断定,是别的地方调用了,因为这里是一个回调函数。
四、探索 _dyld_objc_notify_register 的调用者。
1、打开Xcode,给函数 _dyld_objc_notify_register 下一个符号断点,因为这样可以看到函数调用栈。很清晰可以看到,这个函数的调用来自objc_init,很明显是OC对象中调用的。
2、此时为了找到具体调用流程,我们打开第二份开源代码,objc的源码。
直接找到 _objc_init 函数,找到了,就是这里调用了 _dyld_objc_notify_register 函数。
函数中有一个函数指针,load_images,这就找到了。
void _objc_init(void){static bool initialized = false; if (initialized) return; initialized = true; // fixme defer initialization until an objc-using image is found? environ_init(); tls_init(); static_init(); lock_init(); exception_init(); _dyld_objc_notify_register(&map_images, load_images, unmap_image);}
寻找load方法的调用
load_images 方法中会调用 call_load_methods();
static void call_class_loads(void){int i; // Detach current loadable list. struct loadable_class *classes = loadable_classes; int used = loadable_classes_used; loadable_classes = nil; loadable_classes_allocated = 0; loadable_classes_used = 0; // Call all +loads for the detached list. for (i = 0; i < used; i++) { Class cls = classes[i].cls; load_method_t load_method = (load_method_t)classes[i].method; if (!cls) continue;if (PrintLoading) { _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging()); } (*load_method)(cls, SEL_load); } // Destroy the detached list. if (classes) free(classes);}
从 call_load_methods 中很明显看到,这里在循环调用类中的load方法。
寻找load完毕!
回到我们的程序 main
执行完回调函数,下面的步骤 bool hasInitializers = this->doInitialization(context); 就是加载这些特殊的函数。
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);bool hasInitializers = this->doInitialization(context);
DYLD 的main执行完毕,result 就返回到我们的main函数了。
到此,我们DYLD整个加载流程完成了。
总结,DYLD的加载过程:
配置环境——加载共享缓存——初始化MechO——加载插入动态库——初始化主程序——objc_init给DYLD一个回调——load_images——load。