iOS進(jìn)階專項(xiàng)分析(七)煤裙、App Launch之dyld流程分析

先來看一下iOS/Mac OS系統(tǒng)內(nèi)核架構(gòu)

iOS:Mac OS系統(tǒng)架構(gòu).png

需要注意:dyld是運(yùn)行在用戶態(tài)的進(jìn)程(下面解釋)算灸。也就是說:App啟動(dòng)過程從系統(tǒng)內(nèi)核XNU到內(nèi)核把控制權(quán)交給dyld癞志,這個(gè)過程完成了內(nèi)核態(tài)到用戶態(tài)的切換

一登舞、dyld初識(shí)


什么是dyld贰逾?

dyld是英文 the dynamic link editor 的簡(jiǎn)寫,翻譯過來就是動(dòng)態(tài)鏈接器菠秒,是蘋果操作系統(tǒng)的一個(gè)重要的組成部分疙剑。在iOS/Mac OSX系統(tǒng)中,僅有很少量的進(jìn)程只需要內(nèi)核就能完成加載践叠,基本上所有的進(jìn)程都是動(dòng)態(tài)鏈接的言缤,所以Mach-O鏡像文件中會(huì)有很多對(duì)外部的庫和符號(hào)的引用,但是這些引用并不能直接用酵熙,在啟動(dòng)時(shí)還必須要通過這些引用進(jìn)行內(nèi)容的填補(bǔ)轧简,這個(gè)填補(bǔ)工作就是由動(dòng)態(tài)鏈接器dyld來完成的,也就是符號(hào)綁定匾二。

dyld啟動(dòng)時(shí)機(jī)及位置

動(dòng)態(tài)鏈接器dyld是內(nèi)核執(zhí)行內(nèi)核命令LC_LOAD_DYLINKER加載命令時(shí)啟動(dòng)的哮独,默認(rèn)使用/usr/lib/dyld文件作為動(dòng)態(tài)鏈接器。

補(bǔ)充:LC_MAIN指的就是程序main函數(shù)加載地址察藐,LC_LOAD_DYLIB指向的都是程序依賴庫加載信息皮璧,舉個(gè)例子LC_LOAD_DYLIB(AFNetworking)指的就是AFNetworking依賴庫的加載地址。

dyld與系統(tǒng)內(nèi)核關(guān)系

dyld是一個(gè)用戶態(tài)進(jìn)程分飞,不屬于內(nèi)核的一部分悴务,單獨(dú)由蘋果維護(hù),并且代碼已經(jīng)開源。也就是說dyld可以理解成一個(gè)可插入的組件讯檐,可以用第三方進(jìn)行替換羡疗。

傳送門:
dyld開源代碼下載

二、從App啟動(dòng)的角度進(jìn)行dyld源碼流程分析


下載dyld最新版源碼dyld-733.6别洪。

新建測(cè)試工程叨恨,斷點(diǎn)設(shè)置在在main函數(shù)之前,打印調(diào)用堆棧信息挖垛,發(fā)現(xiàn)App在啟動(dòng)的時(shí)候會(huì)執(zhí)行libdyld.dylibstart操作痒钝。

斷點(diǎn).png

然后就進(jìn)不去了,根據(jù)這個(gè)線索痢毒,我們?cè)谠创a的dyldStartup.s文件中找到入口_dyld_start送矩,仔細(xì)分析_dyld_start源碼,發(fā)現(xiàn)這個(gè)文件中按照不同架構(gòu)分別做了邏輯處理哪替,比如i386栋荸、x86_64arm64夷家、arm蒸其。

下面筆者摘出arm64架構(gòu)下的部分的匯編源碼:

#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
    
    ...

}

找到關(guān)鍵部分bl跳轉(zhuǎn)指令敏释,根據(jù)注釋信息库快,這里會(huì)跳轉(zhuǎn)調(diào)用dyld的引導(dǎo)程序dyldbootstrap::start

1、dyld的引導(dǎo)程序dyldbootstrap::start

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

搜索 dyldbootstrap 關(guān)鍵字钥顽,在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);
}

根據(jù)源碼蜂大,總結(jié)dyldbootstrap::start主要的操作如下:

  1. 先調(diào)用rebaseDyld() dyld重定位
  2. 然后__guard_setup 棧溢出保護(hù)
  3. 最后調(diào)用dyld::_main 進(jìn)入dyld_main函數(shù)

為什么要rebaseDyld()重定位呢闽铐?

這里要提到兩種蘋果用來保證應(yīng)用安全的技術(shù):ASLRCodeSign

ASLR:是Address Space Layout Randomization(地址空間布局隨機(jī)化)的簡(jiǎn)稱。App在被啟動(dòng)的時(shí)候奶浦,程序會(huì)被映射到邏輯地址空間兄墅,這個(gè)邏輯地址空間有一個(gè)起始地址,ASLR技術(shù)讓這個(gè)起始地址是隨機(jī)的澳叉。這個(gè)地址如果是固定的隙咸,黑客很容易就用起始地址+函數(shù)偏移地址找到對(duì)應(yīng)的函數(shù)地址。

Code Sign:就是蘋果代碼加密簽名機(jī)制成洗,但是在Code Sign操作的時(shí)候五督,加密的哈希不是針對(duì)整個(gè)文件,而是針對(duì)每一個(gè)Page的瓶殃。這個(gè)就保證了dyld在加載的時(shí)候充包,可以對(duì)每個(gè)page進(jìn)行獨(dú)立的驗(yàn)證。

正是因?yàn)锳SLR使得地址隨機(jī)化遥椿,導(dǎo)致起始地址不固定基矮,以及Code Sign淆储,導(dǎo)致不能直接修改Image。所以需要rebase來處理符號(hào)引用問題家浇,Rebase的時(shí)候只需要通過增加對(duì)應(yīng)偏移量就行了遏考。Rebase主要的作用就是修正內(nèi)部(指向當(dāng)前Mach-O文件)的指針指向,也就是基地址復(fù)位功能蓝谨。

下面就是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);
        }
    });
}

接下來進(jìn)入dyld::_main()

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

dyldInitialization.cpp文件中找到dyld::_main()的實(shí)現(xiàn)部分譬巫,大概六七百行咖楣,筆者把非關(guān)鍵的代碼省略掉,關(guān)鍵部分及翻譯部分貼出來如下:

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步:初始化程序運(yùn)行環(huán)境++++++++++++++++++++++++++++++++
    //初始化運(yùn)行環(huán)境配置以及拿到Mach-O頭文件    (macho_header里面包含整個(gè)Mach-O文件信息其中包括所有鏈入的動(dòng)態(tài)庫信息)
    
    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結(jié)構(gòu)以及主程序的slide偏移值
    
    sMainExecutableMachHeader = mainExecutableMH;
    sMainExecutableSlide = mainExecutableSlide;
    ......
    CRSetCrashLogMessage("dyld: launch started");
    
    //設(shè)置上下文信息
    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;
        }
    }

   //獲取進(jìn)程名稱
    // Remember short name of process for later logging
    sExecShortName = ::strrchr(sExecPath, '/');
    if ( sExecShortName != NULL )
        ++sExecShortName;
    else
        sExecShortName = sExecPath;

    //配置進(jìn)程受限模式
    configureProcessRestrictions(mainExecutableMH, envp);

    //檢測(cè)環(huán)境變量
    checkEnvironmentVariables(envp);
    defaultUninitializedFallbackPaths(envp);

    //判斷是否設(shè)置了sEnv.DYLD_PRINT_OPTS以及sEnv.DYLD_PRINT_ENV芦昔,分別打印argv參數(shù)和envp環(huán)境變量
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);

    //獲取當(dāng)前程序架構(gòu)
    getHostInfo(mainExecutableMH, mainExecutableSlide);


    // load shared cache
    //第2步诱贿、加載共享緩存 shared cache
++++++++++++++++++++++++++++++++
    //檢查共享緩存是否開啟,iOS必須開啟9径小V槭!F竞馈1翰洹!
    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步:實(shí)例化主程序嫂伞,并賦值給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步 加載插入的動(dòng)態(tài)庫++++++++++++++++++++
       
        // 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步孔厉、鏈接插入的動(dòng)態(tài)庫++++++++
    
        // 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后帖努,執(zhí)行弱綁定++++++++++++++++++++++++++++++
        
        // <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步:執(zhí)行所有的初始化方法+++++++++++++++++++++
    
        // 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步:查找主程序的入口點(diǎn)并返回
        
            // 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;
}


總結(jié)dyld::_main主要做了以下操作(就不一一分析了):

  1. 主程序運(yùn)行環(huán)境初始化及配置撰豺,拿到Mach-O頭文件 (macho_header里面包含整個(gè)Mach-O文件信息其中包括所有鏈入的動(dòng)態(tài)庫信息)
  2. 加載共享緩存 shared cache
  3. 實(shí)例化主程序,并賦值給ImageLoader::LinkContext
  4. 加載所有插入的動(dòng)態(tài)庫拼余,將可執(zhí)行文件以及相應(yīng)的依賴庫與插入庫加載進(jìn)內(nèi)存生成對(duì)應(yīng)的ImageLoader類的image(鏡像文件)對(duì)象
  5. 鏈接主程序(必須先鏈接主程序后才能插入)
  6. 鏈接所有的動(dòng)態(tài)庫ImageLoader的image(鏡像文件)對(duì)象污桦,并注冊(cè)插入的信息,方便后續(xù)進(jìn)行綁定
  7. 在鏈接完所有插入的動(dòng)態(tài)庫鏡像文件之后執(zhí)行弱綁定
  8. 執(zhí)行所有動(dòng)態(tài)庫image的初始化方法initializeMainExecutable
  9. 查找主程序的入口點(diǎn)LC_MAIN并返回result結(jié)果匙监,結(jié)束整個(gè)_dyld_start流程凡橱,進(jìn)入我們App的main()函數(shù)!

這里解釋一下共享緩存機(jī)制:

dyld加載時(shí)舅柜,為了優(yōu)化程序啟動(dòng)梭纹,在dyld::_main中啟用了共享緩存(shared cache)技術(shù)。共享緩存會(huì)在進(jìn)程啟動(dòng)時(shí)被dyld映射到內(nèi)存中致份,之后变抽,當(dāng)任何Mach-O映像加載時(shí),dyld首先會(huì)檢查該Mach-O映像與所需的動(dòng)態(tài)庫是否在共享緩存中,如果存在绍载,則直接將它在共享內(nèi)存中的內(nèi)存地址映射到進(jìn)程的內(nèi)存地址空間诡宗。在程序依賴的系統(tǒng)動(dòng)態(tài)庫很多的情況下,這種做法對(duì)程序啟動(dòng)性能會(huì)有明顯提升击儡。

接下來分析一下_main的第8步塔沃,initializeMainExecutable()

在源碼中搜索initializeMainExecutable,然后在dyld2.cpp文件中找到實(shí)現(xiàn)部分:

void initializeMainExecutable()
{
    ......

    對(duì)每一個(gè)插入進(jìn)來的dylib調(diào)用runInitializers方法進(jìn)行初始化
    
    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]);
        }
    }
    
    
    對(duì)主程序調(diào)用runInitializers方法初始化
    
    sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
    
    // register cxa_atexit() handler to run static terminators in all loaded images when this process exits
    注冊(cè)cxa_atexit()回調(diào)以在此進(jìn)程退出時(shí)在所有加載的圖像中運(yùn)行靜態(tài)終止符
    
    ......
    
}


總結(jié)initializeMainExecutable函數(shù)中間做了什么:

  1. 對(duì)每一個(gè)插入進(jìn)來的dylib調(diào)用runInitializers方法進(jìn)行初始化
  2. 對(duì)主程序調(diào)用runInitializers方法初始化

注意阳谍!這兩步都涉及到了關(guān)鍵的函數(shù) runInitializers()蛀柴,我們進(jìn)入它的源碼,發(fā)現(xiàn)內(nèi)部調(diào)用了processInitializers矫夯,繼續(xù)進(jìn)入鸽疾,發(fā)現(xiàn)processInitializers內(nèi)部又調(diào)用了recursiveInitialization下面是recursiveInitialization的實(shí)現(xiàn):

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的實(shí)現(xiàn)中發(fā)現(xiàn)關(guān)鍵代碼notifySingle

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

繼續(xù)深入训貌,在dyld2.cpp文件中找到實(shí)現(xiàn)

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


找到一個(gè)關(guān)鍵的函數(shù)指針* sNotifyObjCInit, 我們來看看這個(gè)指針是用來干嘛的, 在當(dāng)前文件下制肮,搜索,找到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這個(gè)方法會(huì)被誰調(diào)用豺鼻,找到調(diào)用的地方_dyld_objc_notify_register函數(shù)

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

繼續(xù)搜索,發(fā)現(xiàn)找不到_dyld_objc_notify_register方法的調(diào)用者款慨,那么問題來了:

_dyld_objc_notify_register在啥時(shí)候調(diào)用了呢儒飒?

接下來我們回到測(cè)試工程,打符號(hào)斷點(diǎn)如下:

斷點(diǎn)調(diào)試.png

運(yùn)行發(fā)現(xiàn):

步驟分析.png

首先根據(jù)調(diào)用堆棧信息樱调,我們能看出來_dyld_objc_notify_register_objc_init進(jìn)行調(diào)用的约素。而_objc_init函數(shù)則是Runtime的入口函數(shù)届良!

Objc源碼下載地址

打開Objc源碼,搜索_objc_init笆凌,列出_objc_init()實(shí)現(xiàn)的源碼部分:

/***********************************************************************
* _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();
    
    //注冊(cè)回調(diào)函數(shù)
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

在最底部發(fā)現(xiàn)了我們要找的_dyld_objc_notify_register()!接下來重點(diǎn)看一下這個(gè)函數(shù)的注釋部分

* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld. 
* //引導(dǎo)程序初始化士葫。 用dyld注冊(cè)我們的image通知程序乞而。
* Called by libSystem BEFORE library initialization time
* //在庫初始化之前由libSystem調(diào)用!!!!!
* 

注釋的意思就是說這個(gè)函數(shù)_objc_init的調(diào)用時(shí)機(jī)是在其他動(dòng)態(tài)庫加載之前由libSystem系統(tǒng)庫先調(diào)用的。

那么到現(xiàn)在就很明確了慢显,其實(shí)在dyld::_main主程序的第8步爪模,初始化所有動(dòng)態(tài)庫及主程序的時(shí)候之前,就先注冊(cè)了load_images的回調(diào)荚藻,之后在Runtime調(diào)用load_images加載完所有load方法之后屋灌,就會(huì)回調(diào)到dyld::_maininitializeMainExecutable()內(nèi)部執(zhí)行回調(diào)。

我們來通過斷點(diǎn)驗(yàn)證一下:

給我們的測(cè)試工程加個(gè)自定義load方法应狱,斷點(diǎn)截圖如下:

斷點(diǎn)調(diào)試.png

三共郭、App啟動(dòng)之dyld流程圖總結(jié)如下:

dyld流程.png

喜歡的可以給個(gè)贊哦~

溪浣雙鯉的技術(shù)摸爬滾打之路

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子除嘹,更是在濱河造成了極大的恐慌写半,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尉咕,死亡現(xiàn)場(chǎng)離奇詭異叠蝇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)年缎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門悔捶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人单芜,你說我怎么就攤上這事炎功。” “怎么了缓溅?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵蛇损,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我坛怪,道長(zhǎng)淤齐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任袜匿,我火速辦了婚禮更啄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘居灯。我一直安慰自己祭务,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布怪嫌。 她就那樣靜靜地躺著义锥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪岩灭。 梳的紋絲不亂的頭發(fā)上拌倍,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音噪径,去河邊找鬼柱恤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛找爱,可吹牛的內(nèi)容都是我干的梗顺。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼车摄,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼寺谤!你這毒婦竟也來了珍德?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤矗漾,失蹤者是張志新(化名)和其女友劉穎锈候,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體敞贡,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡泵琳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了誊役。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片获列。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蛔垢,靈堂內(nèi)的尸體忽然破棺而出击孩,到底是詐尸還是另有隱情,我是刑警寧澤鹏漆,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布巩梢,位于F島的核電站,受9級(jí)特大地震影響艺玲,放射性物質(zhì)發(fā)生泄漏括蝠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一饭聚、第九天 我趴在偏房一處隱蔽的房頂上張望忌警。 院中可真熱鬧,春花似錦秒梳、人聲如沸法绵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽朋譬。三九已至,卻和暖如春婆跑,著一層夾襖步出監(jiān)牢的瞬間此熬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工滑进, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人募谎。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓扶关,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親数冬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子节槐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345