iOS进阶专项分析(七)、App Launch之dyld流程分析

先来看一下iOS/Mac OS系统内核架构

iOS:Mac OS系统架构.png

需要注意:dyld是运行在用户态的进程(下面解释)。也就是说:App启动过程从系统内核XNU到内核把控制权交给dyld,这个过程完成了内核态到用户态的切换

一、dyld初识


什么是dyld

dyld是英文 the dynamic link editor 的简写,翻译过来就是动态链接器,是苹果操作系统的一个重要的组成部分。在iOS/Mac OSX系统中,仅有很少量的进程只需要内核就能完成加载,基本上所有的进程都是动态链接的,所以Mach-O镜像文件中会有很多对外部的库和符号的引用,但是这些引用并不能直接用,在启动时还必须要通过这些引用进行内容的填补,这个填补工作就是由动态链接器dyld来完成的,也就是符号绑定

dyld启动时机及位置

动态链接器dyld是内核执行内核命令LC_LOAD_DYLINKER加载命令时启动的,默认使用/usr/lib/dyld文件作为动态链接器。

补充:LC_MAIN指的就是程序main函数加载地址,LC_LOAD_DYLIB指向的都是程序依赖库加载信息,举个例子LC_LOAD_DYLIB(AFNetworking)指的就是AFNetworking依赖库的加载地址。

dyld与系统内核关系

dyld是一个用户态进程,不属于内核的一部分,单独由苹果维护,并且代码已经开源。也就是说dyld可以理解成一个可插入的组件,可以用第三方进行替换。

传送门:
dyld开源代码下载

二、从App启动的角度进行dyld源码流程分析


下载dyld最新版源码dyld-733.6

新建测试工程,断点设置在在main函数之前,打印调用堆栈信息,发现App在启动的时候会执行libdyld.dylibstart操作。

断点.png

然后就进不去了,根据这个线索,我们在源码的dyldStartup.s文件中找到入口_dyld_start,仔细分析_dyld_start源码,发现这个文件中按照不同架构分别做了逻辑处理,比如i386x86_64arm64arm

下面笔者摘出arm64架构下的部分的汇编源码:

#if __arm64__
    .text
    .align 2
    .globl __dyld_start
__dyld_start:
    mov     x28, sp
    and     sp, x28, #~15       // force 16-byte alignment of stack
    
    ...

    // call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
    bl  __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
    mov x16,x0                  // save entry point address in x16
    
    ...

}

找到关键部分bl跳转指令,根据注释信息,这里会跳转调用dyld的引导程序dyldbootstrap::start

1、dyld的引导程序dyldbootstrap::start

// call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
bl  __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm

搜索 dyldbootstrap 关键字,在dyldInitialization.cpp文件中找到定义,并找到方法start,代码如下:

//
//  This is code to bootstrap dyld.  This work in normally done for a program by dyld and crt.
//  In dyld we have to do this manually.
//
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
                const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{
   ...
   
   rebaseDyld(dyldsMachHeader);

    const char** envp = &argv[argc+1];
    const char** apple = envp;
    while(*apple != NULL) { ++apple; }
    ++apple;

    __guard_setup(apple);

    uintptr_t appsSlide = appsMachHeader->getSlide();
    return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

根据源码,总结dyldbootstrap::start主要的操作如下:

  1. 先调用rebaseDyld() dyld重定位
  2. 然后__guard_setup 栈溢出保护
  3. 最后调用dyld::_main 进入dyld_main函数

为什么要rebaseDyld()重定位呢?

这里要提到两种苹果用来保证应用安全的技术:ASLRCodeSign

ASLR:是Address Space Layout Randomization(地址空间布局随机化)的简称。App在被启动的时候,程序会被映射到逻辑地址空间,这个逻辑地址空间有一个起始地址,ASLR技术让这个起始地址是随机的。这个地址如果是固定的,黑客很容易就用起始地址+函数偏移地址找到对应的函数地址。

Code Sign:就是苹果代码加密签名机制,但是在Code Sign操作的时候,加密的哈希不是针对整个文件,而是针对每一个Page的。这个就保证了dyld在加载的时候,可以对每个page进行独立的验证。

正是因为ASLR使得地址随机化,导致起始地址不固定,以及Code Sign,导致不能直接修改Image。所以需要rebase来处理符号引用问题,Rebase的时候只需要通过增加对应偏移量就行了。Rebase主要的作用就是修正内部(指向当前Mach-O文件)的指针指向,也就是基地址复位功能。

下面就是rebaseDyld()的源码:

//
// On disk, all pointers in dyld's DATA segment are chained together.
// They need to be fixed up to be real pointers to run.

static void rebaseDyld(const dyld3::MachOLoaded* dyldMH)
{
    // walk all fixups chains and rebase dyld
    遍历所有固定的 chains 然后 rebase dyld
    const dyld3::MachOAnalyzer* ma = (dyld3::MachOAnalyzer*)dyldMH;
    assert(ma->hasChainedFixups());
    uintptr_t slide = (long)ma; // all fixup chain based images have a base address of zero, so slide == load address
    所有基于修正链的映像的基地址为零,因此slide == 加载地址
    __block Diagnostics diag;
    ma->withChainStarts(diag, 0, ^(const dyld_chained_starts_in_image* starts) {
        ma->fixupAllChainedFixups(diag, starts, slide, dyld3::Array<const void*>(), nullptr);
    });
    diag.assertNoError();

    // now that rebasing done, initialize mach/syscall layer
    mach_init();

    // <rdar://47805386> mark __DATA_CONST segment in dyld as read-only (once fixups are done)
    ma->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& info, bool& stop) {
        if ( info.readOnlyData ) {
            ::mprotect(((uint8_t*)(dyldMH))+info.vmAddr, (size_t)info.vmSize, VM_PROT_READ);
        }
    });
}

接下来进入dyld::_main()

2、dyld的主程序dyld::_main分析

dyldInitialization.cpp文件中找到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)
{
    //第1步:初始化程序运行环境++++++++++++++++++++++++++++++++
    //初始化运行环境配置以及拿到Mach-O头文件    (macho_header里面包含整个Mach-O文件信息其中包括所有链入的动态库信息)
    
    uint8_t mainExecutableCDHashBuffer[20];
    const uint8_t* mainExecutableCDHash = nullptr;
    if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) )
        mainExecutableCDHash = mainExecutableCDHashBuffer;

    notifyKernelAboutImage(mainExecutableMH, _simple_getenv(apple, "executable_file"));

    uintptr_t result = 0;
    
    //获取主程序的macho_header结构以及主程序的slide偏移值
    
    sMainExecutableMachHeader = mainExecutableMH;
    sMainExecutableSlide = mainExecutableSlide;
    ......
    CRSetCrashLogMessage("dyld: launch started");
    
    //设置上下文信息
    setContext(mainExecutableMH, argc, argv, envp, apple);

    //获取主程序路径
    // Pickup the pointer to the exec path.
    sExecPath = _simple_getenv(apple, "executable_path");

    if (!sExecPath) sExecPath = apple[0];

    if ( sExecPath[0] != '/' ) {
        // have relative path, use cwd to make absolute
        char cwdbuff[MAXPATHLEN];
        if ( getcwd(cwdbuff, MAXPATHLEN) != NULL ) {
            // maybe use static buffer to avoid calling malloc so early...
            char* s = new char[strlen(cwdbuff) + strlen(sExecPath) + 2];
            strcpy(s, cwdbuff);
            strcat(s, "/");
            strcat(s, sExecPath);
            sExecPath = s;
        }
    }

   //获取进程名称
    // Remember short name of process for later logging
    sExecShortName = ::strrchr(sExecPath, '/');
    if ( sExecShortName != NULL )
        ++sExecShortName;
    else
        sExecShortName = sExecPath;

    //配置进程受限模式
    configureProcessRestrictions(mainExecutableMH, envp);

    //检测环境变量
    checkEnvironmentVariables(envp);
    defaultUninitializedFallbackPaths(envp);

    //判断是否设置了sEnv.DYLD_PRINT_OPTS以及sEnv.DYLD_PRINT_ENV,分别打印argv参数和envp环境变量
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);

    //获取当前程序架构
    getHostInfo(mainExecutableMH, mainExecutableSlide);


    // load shared cache
    //第2步、加载共享缓存 shared cache
++++++++++++++++++++++++++++++++
    //检查共享缓存是否开启,iOS必须开启!!!!!!
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
        if ( sSharedCacheOverrideDir)
            mapSharedCache();
#else
        mapSharedCache();
#endif
    }

   ......
    
    try {
        // add dyld itself to UUID list
        addDyldImageToUUIDList();
        
        // 第3步:实例化主程序,并赋值给ImageLoader::LinkContext
+++++++++++++++++++++
        
        sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
        gLinkContext.mainExecutable = sMainExecutable;
        gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

    ......

    #if SUPPORT_VERSIONED_PATHS
        checkVersionedPaths();
    #endif
        // dyld_all_image_infos image list does not contain dyld
        // add it as dyldPath field in dyld_all_image_infos
        // for simulator, dyld_sim is in image list, need host dyld added
        
#if TARGET_OS_SIMULATOR
        // get path of host dyld from table of syscall vectors in host dyld
        void* addressInDyld = gSyscallHelpers;
#else
        // get path of dyld itself
        void*  addressInDyld = (void*)&__dso_handle;
#endif
        char dyldPathBuffer[MAXPATHLEN+1];
        int len = proc_regionfilename(getpid(), (uint64_t)(long)addressInDyld, dyldPathBuffer, MAXPATHLEN);
        if ( len > 0 ) {
            dyldPathBuffer[len] = '\0'; // proc_regionfilename() does not zero terminate returned string
            if ( strcmp(dyldPathBuffer, gProcessInfo->dyldPath) != 0 )
                gProcessInfo->dyldPath = strdup(dyldPathBuffer);
        }

       //第4步 加载插入的动态库++++++++++++++++++++
       
        // load any inserted libraries
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                loadInsertedDylib(*lib);
        }
        
        // record count of inserted libraries so that a flat search will look at 
        // inserted libraries, then main, then others.
        sInsertedDylibCount = sAllImages.size()-1;

        // link main executable
        //第5步:链接主程序++++++++++++++
        
        gLinkContext.linkingMainExecutable = true;
#if SUPPORT_ACCELERATE_TABLES
        if ( mainExcutableAlreadyRebased ) {
            // previous link() on main executable has already adjusted its internal pointers for ASLR
            // work around that by rebasing by inverse amount
            sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
        }
#endif
        link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
        sMainExecutable->setNeverUnloadRecursive();
        if ( sMainExecutable->forceFlat() ) {
            gLinkContext.bindFlat = true;
            gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
        }

    //第6步、链接插入的动态库++++++++
    
        // link any inserted libraries
        // do this after linking main executable so that any dylibs pulled in by inserted 
        // dylibs (e.g. libSystem) will not be in front of dylibs the program uses
        if ( sInsertedDylibCount > 0 ) {
            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();
            }
            // only INSERTED libraries can interpose
            // register interposing info after all inserted libraries are bound so chaining works
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                ImageLoader* image = sAllImages[i+1];
                image->registerInterposing(gLinkContext);
            }
        }

        // <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES
        for (long i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {
            ImageLoader* image = sAllImages[i];
            if ( image->inSharedCache() )
                continue;
            image->registerInterposing(gLinkContext);
        }
    
        ......
    
        // apply interposing to initial set of images
        for(int i=0; i < sImageRoots.size(); ++i) {
            sImageRoots[i]->applyInterposing(gLinkContext);
        }
        ImageLoader::applyInterposingToDyldCache(gLinkContext);

        // Bind and notify for the main executable now that interposing has been registered
        uint64_t bindMainExecutableStartTime = mach_absolute_time();
        sMainExecutable->recursiveBindWithAccounting(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
        uint64_t bindMainExecutableEndTime = mach_absolute_time();
        ImageLoaderMachO::fgTotalBindTime += bindMainExecutableEndTime - bindMainExecutableStartTime;
        gLinkContext.notifyBatch(dyld_image_state_bound, false);

        // Bind and notify for the inserted images now interposing has been registered
        if ( sInsertedDylibCount > 0 ) {
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                ImageLoader* image = sAllImages[i+1];
                image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
            }
        }
        
        //第7步、在链接所有插入的image后,执行弱绑定++++++++++++++++++++++++++++++
        
        // <rdar://problem/12186933> do weak binding only after all inserted images linked
        sMainExecutable->weakBind(gLinkContext);
        gLinkContext.linkingMainExecutable = false;

        sMainExecutable->recursiveMakeDataReadOnly(gLinkContext);

        CRSetCrashLogMessage("dyld: launch, running initializers");
    #if SUPPORT_OLD_CRT_INITIALIZATION
        // Old way is to run initializers via a callback from crt1.o
        if ( ! gRunInitializersOldWay ) 
            initializeMainExecutable(); 
    #else
    
    //第8步:执行所有的初始化方法+++++++++++++++++++++
    
        // run all initializers
        initializeMainExecutable(); 
    #endif

        // notify any montoring proccesses that this process is about to enter main()
        notifyMonitoringDyldMain();
        if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
            dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 2);
        }
        ARIADNEDBG_CODE(220, 1);

#if __MAC_OS_X_VERSION_MIN_REQUIRED
        if ( gLinkContext.driverKit ) {
            result = (uintptr_t)sEntryOveride;
            if ( result == 0 )
                halt("no entry point registered");
            *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
        }
        else
#endif
        {
        
        //第9步:查找主程序的入口点并返回
        
            // find entry point for main executable
            result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
            
            if ( result != 0 ) {
                // main executable uses LC_MAIN, we need to use helper in libdyld to call into main()
                if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) )
                    *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
                else
                    halt("libdyld.dylib support not present for LC_MAIN");
            }
            else {
                // main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
                result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD();
                *startGlue = 0;
            }
        }
#if __has_feature(ptrauth_calls)
        // start() calls the result pointer as a function pointer so we need to sign it.
        result = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)result, 0, 0);
#endif
    }
    catch(const char* message) {
        syncAllImages();
        halt(message);
    }
    catch(...) {
        dyld::log("dyld: launch failed\n");
    }

    ......
    
    return result;
}


总结dyld::_main主要做了以下操作(就不一一分析了):

  1. 主程序运行环境初始化及配置,拿到Mach-O头文件 (macho_header里面包含整个Mach-O文件信息其中包括所有链入的动态库信息)
  2. 加载共享缓存 shared cache
  3. 实例化主程序,并赋值给ImageLoader::LinkContext
  4. 加载所有插入的动态库,将可执行文件以及相应的依赖库与插入库加载进内存生成对应的ImageLoader类的image(镜像文件)对象
  5. 链接主程序(必须先链接主程序后才能插入)
  6. 链接所有的动态库ImageLoader的image(镜像文件)对象,并注册插入的信息,方便后续进行绑定
  7. 在链接完所有插入的动态库镜像文件之后执行弱绑定
  8. 执行所有动态库image的初始化方法initializeMainExecutable
  9. 查找主程序的入口点LC_MAIN并返回result结果,结束整个_dyld_start流程,进入我们App的main()函数!

这里解释一下共享缓存机制:

dyld加载时,为了优化程序启动,在dyld::_main中启用了共享缓存(shared cache)技术。共享缓存会在进程启动时被dyld映射到内存中,之后,当任何Mach-O映像加载时,dyld首先会检查该Mach-O映像与所需的动态库是否在共享缓存中,如果存在,则直接将它在共享内存中的内存地址映射到进程的内存地址空间。在程序依赖的系统动态库很多的情况下,这种做法对程序启动性能会有明显提升。

接下来分析一下_main的第8步,initializeMainExecutable()

在源码中搜索initializeMainExecutable,然后在dyld2.cpp文件中找到实现部分:

void initializeMainExecutable()
{
    ......

    对每一个插入进来的dylib调用runInitializers方法进行初始化
    
    ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
    initializerTimes[0].count = 0;
    const size_t rootCount = sImageRoots.size();
    if ( rootCount > 1 ) {
        for(size_t i=1; i < rootCount; ++i) {
            sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
        }
    }
    
    
    对主程序调用runInitializers方法初始化
    
    sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
    
    // register cxa_atexit() handler to run static terminators in all loaded images when this process exits
    注册cxa_atexit()回调以在此进程退出时在所有加载的图像中运行静态终止符
    
    ......
    
}


总结initializeMainExecutable函数中间做了什么:

  1. 对每一个插入进来的dylib调用runInitializers方法进行初始化
  2. 对主程序调用runInitializers方法初始化

注意!这两步都涉及到了关键的函数 runInitializers(),我们进入它的源码,发现内部调用了processInitializers,继续进入,发现processInitializers内部又调用了recursiveInitialization下面是recursiveInitialization的实现:

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
                                          InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
    recursive_lock lock_info(this_thread);
    recursiveSpinLock(lock_info);

    if ( fState < dyld_image_state_dependents_initialized-1 ) {
        uint8_t oldState = fState;
        // break cycles
        fState = dyld_image_state_dependents_initialized-1;
        try {
            // initialize lower level libraries first
            for(unsigned int i=0; i < libraryCount(); ++i) {
                ImageLoader* dependentImage = libImage(i);
                if ( dependentImage != NULL ) {
                    // don't try to initialize stuff "above" me yet
                    if ( libIsUpward(i) ) {
                        uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };
                        uninitUps.count++;
                    }
                    else if ( dependentImage->fDepth >= fDepth ) {
                        dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
                    }
                }
            }
            
            // record termination order
            if ( this->needsTermination() )
                context.terminationRecorder(this);

            // let objc know we are about to initialize this image
            uint64_t t1 = mach_absolute_time();
            fState = dyld_image_state_dependents_initialized;
            oldState = fState;
            context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
            
            // initialize this image
            bool hasInitializers = this->doInitialization(context);

            // let anyone know we finished initializing this image
            fState = dyld_image_state_initialized;
            oldState = fState;
            context.notifySingle(dyld_image_state_initialized, this, NULL);
            
            if ( hasInitializers ) {
                uint64_t t2 = mach_absolute_time();
                timingInfo.addTime(this->getShortName(), t2-t1);
            }
        }
        catch (const char* msg) {
            // this image is not initialized
            fState = oldState;
            recursiveSpinUnLock();
            throw;
        }
    }
    
    recursiveSpinUnLock();
}


recursiveInitialization的实现中发现关键代码notifySingle

context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);

继续深入,在dyld2.cpp文件中找到实现

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
    
    ...
    
    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);
        }
    }
    
    ...


找到一个关键的函数指针* sNotifyObjCInit, 我们来看看这个指针是用来干嘛的, 在当前文件下,搜索,找到sNotifyObjCInit赋值的地方

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;

    ...
}

全局搜索,看看registerObjCNotifiers这个方法会被谁调用,找到调用的地方_dyld_objc_notify_register函数

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

继续搜索,发现找不到_dyld_objc_notify_register方法的调用者,那么问题来了:

_dyld_objc_notify_register在啥时候调用了呢?

接下来我们回到测试工程,打符号断点如下:

断点调试.png

运行发现:

步骤分析.png

首先根据调用堆栈信息,我们能看出来_dyld_objc_notify_register_objc_init进行调用的。而_objc_init函数则是Runtime的入口函数!

Objc源码下载地址

打开Objc源码,搜索_objc_init,列出_objc_init()实现的源码部分:

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

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);
}

在最底部发现了我们要找的_dyld_objc_notify_register()!接下来重点看一下这个函数的注释部分

* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld. 
* //引导程序初始化。 用dyld注册我们的image通知程序。
* Called by libSystem BEFORE library initialization time
* //在库初始化之前由libSystem调用!!!!!
* 

注释的意思就是说这个函数_objc_init的调用时机是在其他动态库加载之前由libSystem系统库先调用的。

那么到现在就很明确了,其实在dyld::_main主程序的第8步,初始化所有动态库及主程序的时候之前,就先注册了load_images的回调,之后在Runtime调用load_images加载完所有load方法之后,就会回调到dyld::_maininitializeMainExecutable()内部执行回调。

我们来通过断点验证一下:

给我们的测试工程加个自定义load方法,断点截图如下:

断点调试.png

三、App启动之dyld流程图总结如下:

dyld流程.png

喜欢的可以给个赞哦~

溪浣双鲤的技术摸爬滚打之路

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343