iOS开发|初识DYLD,揭秘iPhone上的App是怎么运行起来的(二)

上一篇文章介绍了加载我们程序之前所做的准备工作。也就是环境配置,加载共享缓存,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* handlers = stateToHandlers(state, sSingleHandlers);if ( handlers != NULL ) {dyld_image_info info;info.imageLoadAddress= image->machHeader();info.imageFilePath = image->getRealPath();info.imageFileModDate= image->lastModified();for (std::vector::iterator it = handlers->begin(); it != handlers->end(); ++it) {const char* result = (*it)(state, 1, &info);if ( (result != NULL) && (state == dyld_image_state_mapped) ) {//fprintf(stderr, " image rejected by handler=%p\n", *it);// make copy of thrown string so that later catch clauses can free itconst char* str = strdup(result);throw str;}}}if ( state == dyld_image_state_mapped ) {// Save load addr + UUID for images from outside the shared cacheif ( !image->inSharedCache() ) {dyld_uuid_info info;if ( image->getUUID(info.imageUUID) ) {info.imageLoadAddress = image->machHeader();addNonSharedCacheImageUUID(info);}}}if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {uint64_t t0 = mach_absolute_time();dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());uint64_t t1 = mach_absolute_time();uint64_t t2 = mach_absolute_time();uint64_t timeInObjC = t1-t0;uint64_t emptyTime = (t2-t1)*100;if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {timingInfo->addTime(image->getShortName(), timeInObjC);}}// mach message csdlc about dynamically unloaded imagesif ( image->addFuncNotified() && (state == dyld_image_state_terminated) ) {notifyKernel(*image, false);const struct mach_header* loadAddress[] = { image->machHeader() };const char* loadPath[] = { image->getPath() };notifyMonitoringDyld(true, 1, loadAddress, loadPath);}}(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());

来,我们分析一下。

一、需要执行上面那一句,从 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。

你可能感兴趣的:(iOS开发|初识DYLD,揭秘iPhone上的App是怎么运行起来的(二))