iOS dyld代码精简释义

507 阅读5分钟

dyld源码

阅读指引:

阅读本文需要一定的耐心,要知道dyld本身就不是一个很容易理解的内容。如果有耐心阅读完文章,相信一定会对dyld有一些新的理解。

本文通过dyld的_main函数作为切入点,对函数调用流程及函数内代码进行精简处理,从而更好地分析dyld的工作流程。

在阅读文章之前顺便思考以下几个问题:

1、dyld的作用?它的工作流程是怎样的?
2、OC中的runtime跟dyld有关系吗,runtime是怎么被启动的?
3、OC中的load方法是何时被调用的?是libObjc中runtime初始化时就被调用了吗?同一个项目中动态库、代码、静态库中的load方法他们的调用顺序是什么?

dyld main函数精简代码

uintptr_t _main(const macho_header mainExecutableMH, uintptr_t mainExecutableSlide, int argc, const char argv[], const char envp[], const char apple[], uintptr_t* startGlue)
{
    // 获取主程序的信息
    getHostInfo(mainExecutableMH, mainExecutableSlide);
    // 设置主程序运行上下文
    setContext(mainExecutableMH, argc, argv, envp, apple);
    // 配置环境变量
    configureProcessRestrictions(mainExecutableMH, envp);
    // 加载共享缓存
    mapSharedCache(mainExecutableSlide);
    // 判断dyld3的启动闭包。dyld3系统会将启动数据进行缓存,下次启动时如果有缓存,直接从此步骤进行启动,返回主程序的main函数
    bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
    if ( launched ) {
        gLinkContext.startedInitializingMainExecutable = true;
        if (sSkipMain)
            result = (uintptr_t)&fake_main;
        return result;
    }
    // 实例化主程序的ImageLoader
    sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
    // 加载插入的动态库
    for (const char const lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) loadInsertedDylib(*lib);
    // 链接主程序
    link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
    // 遍历链接插入的动态库
    for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
        ImageLoader* image = sAllImages[i+1];
        link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
        image->setNeverUnloadRecursive();
     }
    // 实例化主程序
    initializeMainExecutable();
    // 返回主程序的main函数入口
    result = (uintptr_t)&fake_main;
    return result;
}

链接主程序

void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath)
{
    // 递归加载动态库
    this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
    
    // 对应libObjc中的map_images( -> _read_images),OC类的处理
    context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly);
}

void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool preflightOnly, const RPathChain& loaderRPaths, const char* loadPath)
{
    // 获取依赖的动态库
    this->doGetDependentLibraries(libraryInfos);
    // 加载动态库
    for(unsigned int i=0; i < fLibraryCount; ++i){
        setLibImage(i, dependentLib, depLibReExported, requiredLibInfo.upward);
    }
}

static void notifyBatch(dyld_image_states state, bool preflightOnly)
{
    notifyBatchPartial(state, false, NULL, preflightOnly, false);
}

static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification)
{
    // 直接函数调用 - 调用_dyld_objc_notify_mapped,_dyld_objc_notify_mapped是_dyld_objc_notify_register方法中第一个参数
    (*sNotifyObjCMapped)(objcImageCount, paths, mhs);
}

上文中的sNotifyObjCMapped定义:

_dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped  unmapped)
{
    dyld::registerObjCNotifiers(mapped, init, unmapped);
}

void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    // 三个参数的赋值
    
    sNotifyObjCMapped = mapped;
    sNotifyObjCInit = init;
    sNotifyObjCUnmapped = unmapped;
 }

_dyld_objc_notify_register函数是在libObjc中调用,_dyld_objc_notify_mapped参数对应libObjc中的map_images,map_iamges确定类的结构、继承体系、类及类别的信息合并等;_dyld_objc_notify_init参数对应libObjc中的load_images。

链接插入的动态库

遍历插入的动态库执行链接主程序过程中相同的link方法

初始化主程序

void initializeMainExecutable()
{
    // 先初始化插入的动态库
    const size_t rootCount = sImageRoots.size();
    for(size_t i=1; i < rootCount; ++i) {
        sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
    }
    // 再初始化主程序
    sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
}

void runInitializers(ImageLoader* image)
{
    image->runInitializers(gLinkContext, initializerTimes[0]);
}

void ImageLoader::runInitializers(**const** LinkContext& context, InitializerTimingList& timingInfo)
{
    processInitializers(context, thisThread, timingInfo, up);
    context.notifyBatch(dyld_image_state_initialized, **false**);
}

void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread, InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
    for (uintptr_t i=0; i < images.count; ++i) {
        images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
    }
    if ( ups.count > 0 )
        processInitializers(context, thisThread, timingInfo, ups);
}

// 递归初始化Mach-O(动态库、主程序可执行文件都是Mach-O文件)
void ImageLoader::recursiveInitializationconst LinkContext& context, mach_port_t this_thread, const char pathToInitialize, InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
     // 通知对应的objc,将要初始化对应的image
    context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
    // 初始化image
    bool hasInitializers = this->doInitialization(context);
    // 向外发送完成image完成初始化的通知
    context.notifySingle(dyld_image_state_initialized, this, NULL);
}

bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
    // mach-o has -init and static initializers
    doImageInit(context);
    doModInitFunctions(context);
    return (fHasDashInit || fHasInitializers);
}

void ImageLoaderMachO::doImageInit(**const** LinkContext& context)
{
    if ( ! dyld::gProcessInfo->libSystemInitialized ) {
        // libStem库必须第一个进行初始化
        // <* rdar://problem/17973316 *> libSystem initializer must run first*
        dyld::throwf("-init function in image (%s) that does not link with libSystem.dylib\n", **this**->getPath());
        cmd = (**const** **struct** load_command*)(((**char***)cmd)+cmd->cmdsize);
    }
}

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
    if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
    // 调用libObjc中注册的回调函数。函数真正的执行是在libObjc中执行的,但是是由dyld发起的
        (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
    }
}

至此我们已经分析完毕dyld的主要工作流程。

疑点分析

研究过libObjc源码的同学应该还有一个疑问:_dyld_objc_notify_register是在libObjc中的_objc_init方法中添加的,那么_objc_init是什么时候进行的调用的呢? 细心的同学在查看主程序初始化过程最后的doImageInit方法时,会发现libSystem库必须要第一个进行初始化,可以继续去研究一下libSysem的初始化方法。

libSystem初始化

// 初始化了相当多的系统库,保留向个常见的,需要研究的
libSystem_initializer(int argc, const char* argv[], const char* envp[], const char* apple[], const struct ProgramVars* vars)
{
    __pthread_init(&libpthread_funcs, envp, apple, vars);
    __malloc_init(apple);
    _dyld_initializer();
    libdispatch_init();
}

libDispatch初始化

libdispatch_init(void)
{
    _dispatch_hw_config_init();
    _dispatch_time_init();
    _dispatch_vtable_init();
    // OC的初始化!!!!
    _os_object_init();
    _voucher_init();
    _dispatch_introspection_init();
}

_os_object_init(void)
{
    // 调用libObjc中的_objc_init
    _objc_init();
}

通过分析,可以得知_objc_init的调用流程:(libSystem)libSystem_initializer -> (libdispatch)libdispatch_init -> (libdispatch)_os_object_init -> (libObjc)_objc_init。 又在_objc_init中进行了runtime_init()_dyld_objc_notify_register等操作。

问题解答及总结

OC中的runtime跟dyld有关系吗,runtime是怎么被启动的?

libSystem_initializer -> libdispatch_init -> _os_object_init -> _objc_init -> runtime_init

算有关系吧,因为毕竟dyld负责库的初始化工作

OC中的load方法是libObjc中runtime初始化时就被调用了吗?具体是什么时候被调用的?同一个项目中动态库、代码、静态库中的load方法他们的调用顺序是什么?

load方法是在dyld初始化主程序过程中进行的函数直接调用。

在runtime初始化过程中并没有被调用,load方法是在load_images中被调用,load_images在_objc_init中只是注册成了dyld中_dyld_objc_notify_register回调函数,并没有进行调用。

因为dyld初始化主程序会先遍历插入的动态库进行初始化,所以动态库的load_images会比主程序先调用,又因为静态库是拷贝作用,所以load方法的调用顺序是:动态库load、OC代码load、静态库load。

dyld的作用?它具体都干了些什么

dyld主要为应用程序启动做准备,主要工作流程如下:

  1. 设置上下文运行环境
  2. 加载共享缓存库
  3. 为主程序实例化ImageLoader
  4. 加载插入的动态库
  5. 链接主程序
  6. 链接插入的动态库
  7. 初始化主程序
  8. 返回主程序入口main函数