iOS dyld

一、應(yīng)用程序加載原理

在分析dyld加載應(yīng)用程序之前蚜厉,先清楚以下基本概念。
庫(kù):可執(zhí)行的二進(jìn)制文件畜眨,可以被系統(tǒng)加載到內(nèi)存昼牛。庫(kù)分為靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)术瓮,動(dòng)態(tài)和靜態(tài)庫(kù)的區(qū)別是鏈接的區(qū)別。

編譯過(guò)程

image.png

源文件->預(yù)編譯->編譯->匯編->鏈接->可執(zhí)行文件(MachO格式)贰健。

動(dòng)態(tài)庫(kù):動(dòng)態(tài)鏈接胞四。只會(huì)存在一份,在內(nèi)存中共享伶椿。減少了包的體積大小辜伟。這里有完全動(dòng)態(tài)特性的就是系統(tǒng)的動(dòng)態(tài)庫(kù)了。
靜態(tài)庫(kù):靜態(tài)鏈接脊另。靜態(tài)庫(kù)在裝載的時(shí)候會(huì)重復(fù)游昼,浪費(fèi)了空間。

那么這些庫(kù)是怎么加載到內(nèi)存中的呢尝蠕?
是通過(guò)dyld動(dòng)態(tài)鏈接器加載到內(nèi)存中的烘豌。整個(gè)過(guò)程大概如下:

image.png

dyldthe dynamic link editor)動(dòng)態(tài)鏈接器,是蘋(píng)果操作系統(tǒng)一個(gè)重要組成部分看彼,在系統(tǒng)內(nèi)核做好程序準(zhǔn)備工作之后交由dyld負(fù)責(zé)余下的工作廊佩。
這篇文章將詳細(xì)分析整個(gè)dyld加載過(guò)程。

二靖榕、dyld 初探

既然是dyld加載的庫(kù)标锄,那么在加載完成后肯定會(huì)進(jìn)入main函數(shù),那么在main函數(shù)上打個(gè)斷點(diǎn)看下調(diào)用:

image.png

可以看到是libdyld.dylib start:調(diào)用的main函數(shù)茁计。給start下個(gè)符號(hào)斷點(diǎn)并沒(méi)有進(jìn)入斷點(diǎn)料皇。那么證明在底層的符號(hào)不是start。實(shí)現(xiàn)一個(gè)+ load方法打個(gè)斷點(diǎn)發(fā)現(xiàn)如下調(diào)用棧:

image.png

可以看到是dyld _dyld_start發(fā)起的調(diào)用星压。opensoure上直接下載dyld-852源碼践剂。

搜索_dyld_start發(fā)現(xiàn)這個(gè)入口是在匯編中,其中主要是調(diào)用了dyldbootstrap::start

image.png

最終跳轉(zhuǎn)了返回的LC_MAIN娜膘。

也可以通過(guò)斷點(diǎn)查看匯編調(diào)用確定:


image.png

dyldbootstrapc++的命名空間逊脯,start是其中的函數(shù)。搜索后發(fā)現(xiàn)dyldbootstrap::startdyldInitialization.cpp中竣贪,這也就是函數(shù)開(kāi)始的地方军洼。接下來(lái)結(jié)合源碼分析怎么從start調(diào)用到loadmain方法,以及dyld是如何加載images的演怎。

三匕争、dyld源碼分析

3.1 dyldbootstrap::start(dyldInitialization.cpp

可以通過(guò)搜索dyldbootstrap命名空間找到start源碼。核心代碼如下:

uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
                const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{

    //告訴debug server dyld啟動(dòng)
    dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);

    //重定位dyld
    rebaseDyld(dyldsMachHeader);
    //棧溢出保護(hù)
    __guard_setup(apple);
    //初始化dyld
    _subsystem_init(apple);

    //偏移
    uintptr_t appsSlide = appsMachHeader->getSlide();
    //調(diào)用dyld main函數(shù)
    return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

start主要做了以下幾件事:

  • 告訴debug server dyld啟動(dòng)
  • 重定位dyld
  • 棧溢出保護(hù)
  • 初始化dyld
  • 調(diào)用dyld main函數(shù)

其中start只做了一些配置和初始化的工作爷耀,核心邏輯在main函數(shù)中甘桑,start返回了main函數(shù)的返回值。

3.2 dyld::_main(dyld2.cpp)

核心源碼如下:

uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
    //內(nèi)核檢測(cè)代碼
    if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
        launchTraceID = dyld3::kdebug_trace_dyld_duration_start(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, (uint64_t)mainExecutableMH, 0, 0);
    }
……
    //主程序可執(zhí)行文件 cdHash
    uint8_t mainExecutableCDHashBuffer[20];
    const uint8_t* mainExecutableCDHash = nullptr;
    if ( const char* mainExeCdHashStr = _simple_getenv(apple, "executable_cdhash") ) {
        unsigned bufferLenUsed;
        if ( hexStringToBytes(mainExeCdHashStr, mainExecutableCDHashBuffer, sizeof(mainExecutableCDHashBuffer), bufferLenUsed) )
            mainExecutableCDHash = mainExecutableCDHashBuffer;
    }
    //獲取主程序Header,Slide(ASLR的偏移值)
    getHostInfo(mainExecutableMH, mainExecutableSlide);

……

    CRSetCrashLogMessage("dyld: launch started");
    //配置環(huán)境 將信息放入 gLinkContext 中( notifySingle函數(shù) 賦值在其中)
    setContext(mainExecutableMH, argc, argv, envp, apple);

……

    //根據(jù)環(huán)境變量 envp 配置進(jìn)程是否受限制扇住,AMFI相關(guān)(Apple Mobile File Integrity蘋(píng)果移動(dòng)文件保護(hù))
    configureProcessRestrictions(mainExecutableMH, envp);

……

#if TARGET_OS_OSX
    if ( !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache ) {
        pruneEnvironmentVariables(envp, &apple);
        // set again because envp and apple may have changed or moved
        //又設(shè)置一次上下文,在文件受限的時(shí)候可能更改了envp盗胀。
        setContext(mainExecutableMH, argc, argv, envp, apple);
    }
    else
#endif
    {
        //檢測(cè)環(huán)境變量并設(shè)置默認(rèn)值艘蹋,這個(gè)時(shí)候還沒(méi)有加載數(shù)據(jù)。
        checkEnvironmentVariables(envp);
        defaultUninitializedFallbackPaths(envp);
    }
……
    //打印環(huán)境變量票灰,可以在"Scheme -> Arguments -> Environment Variables"中配置
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);

……

    //檢查共享緩存是否可用,到了這里只讀了主程序還沒(méi)有加載主程序女阀。iOS必須有共享緩存。
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
        if ( sSharedCacheOverrideDir)
            mapSharedCache(mainExecutableSlide);
#else
        //加載共享緩存方法
        mapSharedCache(mainExecutableSlide);
#endif
……
    }
……
#if !TARGET_OS_SIMULATOR
    //dyld3 ClosureMode模式屑迂,iOS11引入ClosureMode浸策,iOS13后動(dòng)態(tài)庫(kù)和三方庫(kù)都使用ClosureMode加載。
    if ( sClosureMode == ClosureMode::Off ) {
        //ClosureMode off打印log惹盼,往 if-else 后面走了
        if ( gLinkContext.verboseWarnings )
            dyld::log("dyld: not using closures\n");
    } else {
        //啟動(dòng)模式 閉包模式 DYLD_LAUNCH_MODE_USING_CLOSURE
        sLaunchModeUsed = DYLD_LAUNCH_MODE_USING_CLOSURE;
        const dyld3::closure::LaunchClosure* mainClosure = nullptr;
        //主程序 info 和 Header
        dyld3::closure::LoadedFileInfo mainFileInfo;
        mainFileInfo.fileContent = mainExecutableMH;
        mainFileInfo.path = sExecPath;
……
        //第一次從共享緩存找閉包
        if ( sSharedCacheLoadInfo.loadAddress != nullptr ) {
            //先從共享緩存找實(shí)例閉包
            mainClosure = sSharedCacheLoadInfo.loadAddress->findClosure(sExecPath);
            if ( gLinkContext.verboseWarnings && (mainClosure != nullptr) )
                dyld::log("dyld: found closure %p (size=%lu) in dyld shared cache\n", mainClosure, mainClosure->size());
            if ( mainClosure != nullptr )
                //如果拿到設(shè)置狀態(tài)
                sLaunchModeUsed |= DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
        }
……
        //拿到閉包 && 驗(yàn)證閉包庸汗,如果閉包失效
        if ( (mainClosure != nullptr) && !closureValid(mainClosure, mainFileInfo, mainExecutableCDHash, true, envp) ) {
            mainClosure = nullptr;
            //閉包失效設(shè)置狀態(tài)
            sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
        }

……
        //判斷mainClosure是否為空
        if ( (mainClosure == nullptr) && allowClosureRebuilds ) {
            // if forcing closures, and no closure in cache, or it is invalid, check for cached closure
            if ( !sForceInvalidSharedCacheClosureFormat )
                //緩存中找
                mainClosure = findCachedLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
            if ( mainClosure == nullptr ) {
                // if  no cached closure found, build new one
                //緩存中找不到則創(chuàng)建一個(gè),一直拿 mainClosure 是為了拿他創(chuàng)建主程序手报。
                mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
                if ( mainClosure != nullptr )
                    //創(chuàng)建失敗則設(shè)置狀態(tài)
                    sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH;
            }
        }
……
        // try using launch closure
        if ( mainClosure != nullptr ) {
            CRSetCrashLogMessage("dyld3: launch started");
            if ( mainClosure->topImage()->fixupsNotEncoded() )
                sLaunchModeUsed |= DYLD_LAUNCH_MODE_MINIMAL_CLOSURE;
            Diagnostics diag;
            bool closureOutOfDate;
            bool recoverable;
            //啟動(dòng)主程序蚯舱,mainClosure 相當(dāng)于加載器
            bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
                                              mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
            //啟動(dòng)失敗或者過(guò)期 允許重建
            if ( !launched && closureOutOfDate && allowClosureRebuilds ) {
                // closure is out of date, build new one
                //再創(chuàng)建一個(gè)
                mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
                if ( mainClosure != nullptr ) {
                    diag.clearError();
                    sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH;
                    if ( mainClosure->topImage()->fixupsNotEncoded() )
                        sLaunchModeUsed |= DYLD_LAUNCH_MODE_MINIMAL_CLOSURE;
                    else
                        sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_MINIMAL_CLOSURE;
                    //啟動(dòng)
                    launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
                                                 mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
                }
            }
            if ( launched ) {
                //啟動(dòng)成功保存狀態(tài),主程序加載成功
                gLinkContext.startedInitializingMainExecutable = true;
                if (sSkipMain)
                    //主程序main函數(shù)掩蛤,dyld的main執(zhí)行完畢返回主程序的main
                    result = (uintptr_t)&fake_main;
                return result;
            }
            else {
                //失敗報(bào)錯(cuò)
                if ( gLinkContext.verboseWarnings ) {
                    dyld::log("dyld: unable to use closure %p\n", mainClosure);
                }
                if ( !recoverable )
                    halt(diag.errorMessage());
            }
        }
    }
#endif // TARGET_OS_SIMULATOR
    // could not use closure info, launch old way

    //dyld2模式
    sLaunchModeUsed = 0;


    // install gdb notifier
    //兩個(gè)回調(diào)地址放入stateToHandlers數(shù)組中
    stateToHandlers(dyld_image_state_dependents_mapped, sBatchHandlers)->push_back(notifyGDB);
    stateToHandlers(dyld_image_state_mapped, sSingleHandlers)->push_back(updateAllImages);
    // make initial allocations large enough that it is unlikely to need to be re-alloced
    //分配初始化空間枉昏,盡可能大一些保證后面夠用。
    sImageRoots.reserve(16);
    sAddImageCallbacks.reserve(4);
    sRemoveImageCallbacks.reserve(4);
    sAddLoadImageCallbacks.reserve(4);
    sImageFilesNeedingTermination.reserve(16);
    sImageFilesNeedingDOFUnregistration.reserve(8);

……

    try {
        // add dyld itself to UUID list
        //dyld加入uuid列表
        addDyldImageToUUIDList();

#if SUPPORT_ACCELERATE_TABLES
……

        //主程序還沒(méi)有rebase
        bool mainExcutableAlreadyRebased = false;
        if ( (sSharedCacheLoadInfo.loadAddress != nullptr) && !dylibsCanOverrideCache() && !sDisableAcceleratorTables && (sSharedCacheLoadInfo.loadAddress->header.accelerateInfoAddr != 0) ) {
            struct stat statBuf;
            if ( dyld3::stat(IPHONE_DYLD_SHARED_CACHE_DIR "no-dyld2-accelerator-tables", &statBuf) != 0 )
                sAllCacheImagesProxy = ImageLoaderMegaDylib::makeImageLoaderMegaDylib(&sSharedCacheLoadInfo.loadAddress->header, sSharedCacheLoadInfo.slide, mainExecutableMH, gLinkContext);
        }
//加載所有的可執(zhí)行文件 image list揍鸟,這里相當(dāng)于是個(gè)標(biāo)簽兄裂。會(huì)循環(huán)。
reloadAllImages:
#endif

……
        //實(shí)例化主程序阳藻,加入到allImages(第一個(gè)靠dyld加載的image就是主程序)
        sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
        gLinkContext.mainExecutable = sMainExecutable;
        //代碼簽名
        gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

#if TARGET_OS_SIMULATOR
        // check main executable is not too new for this OS
        //檢查主程序是否屬于當(dāng)前系統(tǒng)
        {……}
#endif
……

#if defined(__x86_64__) && !TARGET_OS_SIMULATOR
        //設(shè)置加載動(dòng)態(tài)庫(kù)版本
        if (dyld::isTranslated()) {……}
#endif

        // Now that shared cache is loaded, setup an versioned dylib overrides
    #if SUPPORT_VERSIONED_PATHS
        //檢查版本路徑
        checkVersionedPaths();
    #endif
……
        //DYLD_INSERT_LIBRARIES 插入動(dòng)態(tài)庫(kù)
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            //遍歷加載插入動(dòng)態(tài)庫(kù)
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
                loadInsertedDylib(*lib);
        }
                //-1為了排除主程序
        sInsertedDylibCount = sAllImages.size()-1;

        // link main executable
        //記錄鏈接主程序
        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 ( sInsertedDylibCount > 0 ) {
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                //i+1因?yàn)橹鞒绦蛭保迦氲膇mage在主程序后面
                ImageLoader* image = sAllImages[i+1];
                //鏈接插入動(dòng)態(tài)庫(kù)
                link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
                image->setNeverUnloadRecursive();
            }
……
        }
……
    #if SUPPORT_ACCELERATE_TABLES
        //判斷條件不滿(mǎn)足,持續(xù) goto reloadAllImages
        if ( (sAllCacheImagesProxy != NULL) && ImageLoader::haveInterposingTuples() ) {
            // Accelerator tables cannot be used with implicit interposing, so relaunch with accelerator tables disabled
            ImageLoader::clearInterposingTuples();
            // unmap all loaded dylibs (but not main executable)
            for (long i=1; i < sAllImages.size(); ++i) {
                ImageLoader* image = sAllImages[i];
                if ( image == sMainExecutable )
                    continue;
                if ( image == sAllCacheImagesProxy )
                    continue;
                image->setCanUnload();
                ImageLoader::deleteImage(image);
            }
            // note: we don't need to worry about inserted images because if DYLD_INSERT_LIBRARIES was set we would not be using the accelerator table
            sAllImages.clear();
            sImageRoots.clear();
            sImageFilesNeedingTermination.clear();
            sImageFilesNeedingDOFUnregistration.clear();
            sAddImageCallbacks.clear();
            sRemoveImageCallbacks.clear();
            sAddLoadImageCallbacks.clear();
            sAddBulkLoadImageCallbacks.clear();
            sDisableAcceleratorTables = true;
            sAllCacheImagesProxy = NULL;
            sMappedRangesStart = NULL;
            mainExcutableAlreadyRebased = true;
            gLinkContext.linkingMainExecutable = false;
            resetAllImages();
            goto reloadAllImages;
        }
    #endif
……
        //綁定主程序
        sMainExecutable->recursiveBindWithAccounting(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
……

        // 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];
                //綁定插入動(dòng)態(tài)庫(kù)
                image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true, nullptr);
            }
        }
        
        // <rdar://problem/12186933> do weak binding only after all inserted images linked
        //弱引用綁定主程序腥泥,所有鏡像文件綁定完成后進(jìn)行畅涂。
        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
        // run all initializers
        //初始化主程序,到目前為止還沒(méi)有執(zhí)行到主程序中的代碼道川。
        initializeMainExecutable(); 
……

        {
            // find entry point for main executable
            //找到主程序入口 LC_MAIN
            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 (sSkipMain) {
        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);
        result = (uintptr_t)&fake_main;
        *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
    }
    //返回主程序
    return result;
}

main函數(shù)主要是返回了主程序的函數(shù)入口(main)午衰,主要流程如下:

  • 配置環(huán)境,獲取主程序Header冒萄,SlideASLR
  • 加載共享緩存:mapSharedCache臊岸。這個(gè)時(shí)候只讀了主程序還沒(méi)有加載主程序。iOS必須有共享緩存尊流。
    checkSharedRegionDisable方法中說(shuō)明了iOS必須有共享緩存:
    image.png
  • dyld2/dyld3ClosureMode閉包模式)加載程序:iOS11引入dyld3閉包模式帅戒,以回調(diào)的方式加載。閉包模式加載速度更快,效率更高逻住。iOS13后動(dòng)態(tài)庫(kù)和三方庫(kù)都使ClosureMode加載钟哥。
    • dyld3:
      • 使用mainClosure來(lái)加載。
      • 找到/創(chuàng)建mainClosure后瞎访,通過(guò)launchWithClosure啟動(dòng)主程序腻贰,啟動(dòng)失敗后會(huì)有重新創(chuàng)建mainClosure重新啟動(dòng)的邏輯。成功后返回result(主程序入口main)扒秸。launchWithClosure中的邏輯和dyld2啟動(dòng)主程序邏輯基本相同播演。
    • dyld2:?jiǎn)?dòng)主程序
      • 實(shí)例化主程序instantiateFromLoadedImagesMainExecutable 是通過(guò)instantiateFromLoadedImage賦值的伴奥,也就是把主程序加入allImages中写烤。
      • 插入&加載動(dòng)態(tài)庫(kù) loadInsertedDylib。加載在loadInsertedDylib中調(diào)用load(主程序和動(dòng)態(tài)庫(kù)都會(huì)添加到allImagesloadAllImages
      • 鏈接主程序和鏈接插入動(dòng)態(tài)庫(kù)(link拾徙,主程序鏈接在前)洲炊。在這個(gè)過(guò)程中記錄了dyld加載的時(shí)長(zhǎng)∧岱龋可以通過(guò)配置環(huán)境變量打印出來(lái)选浑。
      • 綁定符號(hào)(非懶加載、弱符號(hào))玄叠,懶加載在調(diào)用時(shí)綁定古徒。
      • 初始化主程序initializeMainExecutable,這個(gè)時(shí)候還沒(méi)有執(zhí)行到主程序中的代碼读恃。
      • 找到主程序入口 LC_MAIN隧膘,然后返回主程序。

DYLD_PRINT_OPTS寺惫,DYLD_PRINT_ENV環(huán)境變量配置疹吃,可以打印環(huán)境變量配置(在"Scheme -> Arguments -> Environment Variables"中配置):

環(huán)境變量配置

ASLRimage list0個(gè)主程序第一個(gè)地址。
ASLR

關(guān)于dyld2/dyld3更多信息將在后面做進(jìn)一步總結(jié)西雀。

3.3 mapSharedCache加載共享緩存

共享緩存專(zhuān)門(mén)緩存系統(tǒng)動(dòng)態(tài)庫(kù)萨驶,如:UIKitFoundation等艇肴。(自己的庫(kù)腔呜、三方庫(kù)不行)
mapSharedCache真正調(diào)用的是loadDyldCache

static void mapSharedCache(uintptr_t mainExecutableSlide)
{
    ……
    //真正調(diào)用的是loadDyldCache
    loadDyldCache(opts, &sSharedCacheLoadInfo);
    ……
}

3.3.1 loadDyldCache

bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results)
{
    results->loadAddress        = 0;
    results->slide              = 0;
    results->errorMessage       = nullptr;

#if TARGET_OS_SIMULATOR
    // simulator only supports mmap()ing cache privately into process
    return mapCachePrivate(options, results);
#else
    if ( options.forcePrivate ) {
        // mmap cache into this process only
        //僅加載到當(dāng)前進(jìn)程
        return mapCachePrivate(options, results);
    }
    else {
        // fast path: when cache is already mapped into shared region
        bool hasError = false;
        //已經(jīng)加載不進(jìn)行任何處理
        if ( reuseExistingCache(options, results) ) {
            hasError = (results->errorMessage != nullptr);
        } else {
            // slow path: this is first process to load cache
            //當(dāng)前進(jìn)程第一次加載
            hasError = mapCacheSystemWide(options, results);
        }
        return hasError;
    }
#endif
}

loadDyldCache3個(gè)邏輯:
1.僅加載到當(dāng)前進(jìn)程調(diào)用mapCachePrivate。不放入共享緩存再悼,僅自己使用核畴。
2.已經(jīng)加載過(guò)不進(jìn)行任何處理。
3.當(dāng)前進(jìn)程第一次加載調(diào)用mapCacheSystemWide

動(dòng)態(tài)庫(kù)的共享緩存在整個(gè)應(yīng)用的啟動(dòng)過(guò)程中是最先被加載的冲九。

3.4 instantiateFromLoadedImage 實(shí)例化主程序(創(chuàng)建image

static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
    //實(shí)例化image
    ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
    //將image添加到all Images中
    addImage(image);
    return (ImageLoaderMachO*)image;
//  throw "main executable not a known format";
}
  • 傳入主程序的Header谤草、ASLRpath實(shí)例化主程序生成image
  • image加入all images中丑孩。

實(shí)際上實(shí)例化真正調(diào)用的是ImageLoaderMachO::instantiateMainExecutable

// create image for main executable
ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
{
    bool compressed;
    unsigned int segCount;
    unsigned int libCount;
    const linkedit_data_command* codeSigCmd;
    const encryption_info_command* encryptCmd;
    //獲取Load Commands
    sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
    // instantiate concrete class based on content of load commands
    //根據(jù) compressed 確定用哪個(gè)子類(lèi)進(jìn)行加載image冀宴,ImageLoader是個(gè)抽象類(lèi),根據(jù)值選擇對(duì)應(yīng)的子類(lèi)實(shí)例化image温学。
    if ( compressed ) 
        return ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
    else
#if SUPPORT_CLASSIC_MACHO
        return ImageLoaderMachOClassic::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
#else
        throw "missing LC_DYLD_INFO load command";
#endif
}
  • 調(diào)用sniffLoadCommands生成相關(guān)信息略贮,比如compressed
  • 根據(jù) compressed 確定用哪個(gè)子類(lèi)進(jìn)行加載image枫浙,ImageLoader是個(gè)抽象類(lèi),根據(jù)值選擇對(duì)應(yīng)的子類(lèi)實(shí)例化主程序古拴。

sniffLoadCommands

void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* path, bool inCache, bool* compressed,
                                            unsigned int* segCount, unsigned int* libCount, const LinkContext& context,
                                            const linkedit_data_command** codeSigCmd,
                                            const encryption_info_command** encryptCmd)
{
    //根據(jù)LC_DYLIB_INFO 和  LC_DYLD_INFO_ONLY 來(lái)獲取的
    *compressed = false;
    //segment數(shù)量
    *segCount = 0;
    //lib數(shù)量
    *libCount = 0;
    //代碼簽名和加密
    *codeSigCmd = NULL;
    *encryptCmd = NULL;
        ……
    // fSegmentsArrayCount is only 8-bits
    //segCount 最多 256 個(gè)
    if ( *segCount > 255 )
        dyld::throwf("malformed mach-o image: more than 255 segments in %s", path);

    // fSegmentsArrayCount is only 8-bits
    //libCount最多 4096 個(gè)
    if ( *libCount > 4095 )
        dyld::throwf("malformed mach-o image: more than 4095 dependent libraries in %s", path);

    if ( needsAddedLibSystemDepency(*libCount, mh) )
        *libCount = 1;

    // dylibs that use LC_DYLD_CHAINED_FIXUPS have that load command removed when put in the dyld cache
    if ( !*compressed && (mh->flags & MH_DYLIB_IN_CACHE) )
        *compressed = true;
}
  • compressed是根據(jù)LC_DYLIB_INFOLC_DYLD_INFO_ONLY來(lái)獲取的箩帚。
  • segCount最多256個(gè)。
  • libCount最多4096個(gè)黄痪。

3.5 loadInsertedDylib 插入&加載動(dòng)態(tài)庫(kù)

static void loadInsertedDylib(const char* path)
{
    unsigned cacheIndex;
    try {
    ……
        //調(diào)用load紧帕,加載動(dòng)態(tài)庫(kù)的真正函數(shù)
        load(path, context, cacheIndex);
    }
    ……
}
  • 根據(jù)上下文初始化配置調(diào)用load加載動(dòng)態(tài)庫(kù)。

3.6 ImageLoader::link鏈接主程序/動(dòng)態(tài)庫(kù)

void link(ImageLoader* image, bool forceLazysBound, bool neverUnload, const ImageLoader::RPathChain& loaderRPaths, unsigned cacheIndex)
{
    // add to list of known images.  This did not happen at creation time for bundles
    if ( image->isBundle() && !image->isLinked() )
        addImage(image);

    // we detect root images as those not linked in yet 
    if ( !image->isLinked() )
        addRootImage(image);
    
    // process images
    try {
        const char* path = image->getPath();
#if SUPPORT_ACCELERATE_TABLES
        if ( image == sAllCacheImagesProxy )
            path = sAllCacheImagesProxy->getIndexedPath(cacheIndex);
#endif
        //最終會(huì)調(diào)用到image的link
        image->link(gLinkContext, forceLazysBound, false, neverUnload, loaderRPaths, path);
    }
}
  • link最終調(diào)用的是ImageLoader::link桅打。

ImageLoader::link

void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath)
{   
    // clear error strings
    (*context.setErrorStrings)(0, NULL, NULL, NULL);
    //起始時(shí)間是嗜。用于記錄時(shí)間間隔
    uint64_t t0 = mach_absolute_time();
    //遞歸加載主程序依賴(lài)的庫(kù),完成之后發(fā)通知挺尾。
    this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
    context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly);
……
    uint64_t t1 = mach_absolute_time();
    context.clearAllDepths();
    this->updateDepth(context.imageCount());

    __block uint64_t t2, t3, t4, t5;
    {
        dyld3::ScopedTimer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0, 0, 0);
        t2 = mach_absolute_time();
        //Rebase修正ASLR
        this->recursiveRebaseWithAccounting(context);
        context.notifyBatch(dyld_image_state_rebased, false);

        t3 = mach_absolute_time();
        if ( !context.linkingMainExecutable )
            //綁定NoLazy符號(hào)
            this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);

        t4 = mach_absolute_time();
        if ( !context.linkingMainExecutable )
            //綁定弱符號(hào)
            this->weakBind(context);
        t5 = mach_absolute_time();
    }

    // interpose any dynamically loaded images
    if ( !context.linkingMainExecutable && (fgInterposingTuples.size() != 0) ) {
        dyld3::ScopedTimer timer(DBG_DYLD_TIMING_APPLY_INTERPOSING, 0, 0, 0);
        //遞歸應(yīng)用插入的動(dòng)態(tài)庫(kù)
        this->recursiveApplyInterposing(context);
    }

    // now that all fixups are done, make __DATA_CONST segments read-only
    if ( !context.linkingMainExecutable )
        this->recursiveMakeDataReadOnly(context);

    if ( !context.linkingMainExecutable )
        context.notifyBatch(dyld_image_state_bound, false);
    uint64_t t6 = mach_absolute_time();

    if ( context.registerDOFs != NULL ) {
        std::vector<DOFInfo> dofs;
        this->recursiveGetDOFSections(context, dofs);
        //注冊(cè)
        context.registerDOFs(dofs);
    }
    //計(jì)算結(jié)束時(shí)間.
    uint64_t t7 = mach_absolute_time();

    // clear error strings
    //配置環(huán)境變量鹅搪,就可以看到dyld應(yīng)用加載的時(shí)長(zhǎng)。
    (*context.setErrorStrings)(0, NULL, NULL, NULL);
    fgTotalLoadLibrariesTime += t1 - t0;
    fgTotalRebaseTime += t3 - t2;
    fgTotalBindTime += t4 - t3;
    fgTotalWeakBindTime += t5 - t4;
    fgTotalDOF += t7 - t6;
    
    // done with initial dylib loads
    fgNextPIEDylibAddress = 0;
}
  • 修正ASLR遭铺。
  • 綁定NoLazy符號(hào)丽柿。
  • 綁定弱符號(hào)。
  • 注冊(cè)魂挂。
  • 記錄時(shí)間甫题,可以通過(guò)配置看到dyld應(yīng)用加載時(shí)長(zhǎng)。

3.7 initializeMainExecutable 初始化主程序

void initializeMainExecutable()
{
    // record that we've reached this step
    gLinkContext.startedInitializingMainExecutable = true;

    // run initialzers for any inserted dylibs
    //拿到所有的鏡像文件
    ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
    initializerTimes[0].count = 0;
    const size_t rootCount = sImageRoots.size();
    if ( rootCount > 1 ) {
        //從1開(kāi)始到最后涂召。(第0個(gè)為主程序)
        for(size_t i=1; i < rootCount; ++i) {
            //image初始化坠非,調(diào)用 +load 和 構(gòu)造函數(shù)
            sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
        }
    }
    
    // run initializers for main executable and everything it brings up
    //調(diào)用主程序初始化
    sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
    
    // register cxa_atexit() handler to run static terminators in all loaded images when this process exits
    if ( gLibSystemHelpers != NULL ) 
        (*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);

    // dump info if requested
    if ( sEnv.DYLD_PRINT_STATISTICS )
        ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
    if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
        ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}
  • 初始化images,下標(biāo)從1開(kāi)始果正,然后再初始化主程序(下標(biāo)0runInitializers炎码。
  • 可以配置環(huán)境變量DYLD_PRINT_STATISTICSDYLD_PRINT_STATISTICS_DETAILS打印相關(guān)信息。

dyld ImageLoader::runInitializers(ImageLoader.cpp)

void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
    uint64_t t1 = mach_absolute_time();
    mach_port_t thisThread = mach_thread_self();
    ImageLoader::UninitedUpwards up;
    up.count = 1;
    up.imagesAndPaths[0] = { this, this->getPath() };
    //加工初始化
    processInitializers(context, thisThread, timingInfo, up);
    context.notifyBatch(dyld_image_state_initialized, false);
    mach_port_deallocate(mach_task_self(), thisThread);
    uint64_t t2 = mach_absolute_time();
    fgTotalInitTime += (t2 - t1);
}
  • up.count值設(shè)置為1然后調(diào)用processInitializers秋泳。

ImageLoader::processInitializers

void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
                                     InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
    uint32_t maxImageCount = context.imageCount()+2;
    ImageLoader::UninitedUpwards upsBuffer[maxImageCount];
    ImageLoader::UninitedUpwards& ups = upsBuffer[0];
    ups.count = 0;
    for (uintptr_t i=0; i < images.count; ++i) {
        //遞歸初始化
        images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
    }
    // If any upward dependencies remain, init them.
    if ( ups.count > 0 )
        processInitializers(context, thisThread, timingInfo, ups);
}
  • 最終調(diào)用了recursiveInitialization辅肾。

ImageLoader::recursiveInitialization(ImageLoader.cpp)

void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
                                          InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
……
    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
            //先初始化下級(jí)lib
            for(unsigned int i=0; i < libraryCount(); ++i) {
                ImageLoader* dependentImage = libImage(i);
                if ( dependentImage != NULL ) {
……
                    else if ( dependentImage->fDepth >= fDepth ) {
                        //依賴(lài)文件遞歸初始化
                        dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
                    }
                }
            }       
……
            fState = dyld_image_state_dependents_initialized;
            oldState = fState;
            //這里調(diào)用傳遞的狀態(tài)是dyld_image_state_dependents_initialized,image傳遞的是自己轮锥。也就是最后調(diào)用了自己的+load矫钓。從libobjc.A.dylib開(kāi)始調(diào)用。
            context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
            
            // initialize this image
            //初始化鏡像文件,調(diào)用c++構(gòu)造函數(shù)新娜。libSystem的libSystem_initializer就是在這里調(diào)用的赵辕。會(huì)調(diào)用到objc_init中。_dyld_objc_notify_register 中會(huì)調(diào)用自身的+load方法概龄,然后c++構(gòu)造函數(shù)汇四。
            //1.調(diào)用libSystem_initializer->objc_init 注冊(cè)回調(diào)。
            //2._dyld_objc_notify_register中調(diào)用 map_images站故,load_images泌豆,這里是首先初始化一些系統(tǒng)庫(kù),調(diào)用系統(tǒng)庫(kù)的load_images衰粹。比如libdispatch.dylib锣光,libsystem_featureflags.dylib,libsystem_trace.dylib铝耻,libxpc.dylib誊爹。
            //3.自身的c++構(gòu)造函數(shù)
            bool hasInitializers = this->doInitialization(context);

            // let anyone know we finished initializing this image
            fState = dyld_image_state_initialized;
            oldState = fState;
            //這里調(diào)用不到+load方法。 notifySingle內(nèi)部fState==dyld_image_state_dependents_initialized 才調(diào)用+load瓢捉。
            context.notifySingle(dyld_image_state_initialized, this, NULL);
……
        }
……
    }
    recursiveSpinUnLock();
}
  • 整個(gè)過(guò)程是一個(gè)遞歸的過(guò)程频丘,先調(diào)用依賴(lài)庫(kù)的,再調(diào)用自己的泡态。
  • 調(diào)用notifySingle最終調(diào)用到了objc中所有的+ load方法搂漠。這里第一個(gè)notifySingle調(diào)用的是+load方法,第二個(gè)notifySingle由于參數(shù)是dyld_image_state_initialized不會(huì)調(diào)用到+load方法某弦。這里的dyld_image_state_dependents_initialized意思是依賴(lài)文件初始化完畢了状答,可以初始化自己了。
  • 調(diào)用doInitialization最終調(diào)用了c++的系統(tǒng)構(gòu)造函數(shù)刀崖。先調(diào)用的是libSystem_initializer -> objc_init進(jìn)行注冊(cè)回調(diào)惊科。在回調(diào)中調(diào)用了map_imagesload_images(+load)亮钦。這里的load_images是調(diào)用一些加載一些系統(tǒng)庫(kù)馆截,比如:libdispatch.dylib,libsystem_featureflags.dylib蜂莉,libsystem_trace.dylib蜡娶,libxpc.dylib

c++系統(tǒng)構(gòu)造函數(shù)

__attribute__((constructor)) void func() {
   printf("\n ---func--- \n");
}

??這里也就說(shuō)明了對(duì)于同一個(gè)image而言映穗,+ load方法是比c++構(gòu)造函數(shù)更早調(diào)用的窖张。

dyld::notifySingledyld2.cpp
notifySingle對(duì)應(yīng)一個(gè)函數(shù),在setContext的時(shí)候賦值:

image.png

//調(diào)用到objc里面去
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);
        //回調(diào)指針 sNotifyObjCInit 是在 registerObjCNotifiers 中賦值的蚁滋。這里執(zhí)行會(huì)跑到objc的load_images中
        (*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);
        }
    }
……
}
  • notifySingle中找不到load image的調(diào)用(從堆棧信息中可以看到notifySingle之后是load image)宿接。
  • 這個(gè)函數(shù)執(zhí)行一個(gè)回調(diào)sNotifyObjCInit赘淮,條件是statedyld_image_state_dependents_initialized

搜索下回調(diào)sNotifyObjCInit的賦值操作睦霎,發(fā)現(xiàn)是在registerObjCNotifiers中賦值的

registerObjCNotifiers

//誰(shuí)調(diào)用的 registerObjCNotifiers 梢卸? _dyld_objc_notify_register。這里賦值了三個(gè)參數(shù) _dyld_objc_notify_mapped副女,_dyld_objc_notify_init蛤高,_dyld_objc_notify_unmapped
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    // record functions to call
    //第一個(gè)參數(shù) map_images
    sNotifyObjCMapped   = mapped;
    //第二個(gè)參數(shù) load_images
    sNotifyObjCInit     = init;
    //第三個(gè)參數(shù) unmap_image
    sNotifyObjCUnmapped = unmapped;

    // call 'mapped' function with all images mapped so far
    try {
        //賦值后馬上回調(diào) map_images
        notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
    }
    catch (const char* msg) {
        // ignore request to abort during registration
    }

    // <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
    for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
        ImageLoader* image = *it;
        if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
            //調(diào)用一些系統(tǒng)庫(kù)的 load_images。
            (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        }
    }
}
  • registerObjCNotifiers賦值來(lái)自于第二個(gè)參數(shù)_dyld_objc_notify_init碑幅。
  • 賦值后里面調(diào)用了notifyBatchPartial(內(nèi)部調(diào)用了sNotifyObjCMapped)戴陡。
  • 循環(huán)調(diào)用load_images,這里調(diào)用的是依賴(lài)的系統(tǒng)庫(kù)的libdispatch.dylib沟涨,libsystem_featureflags.dylib恤批,libsystem_trace.dylib,libxpc.dylib拷窜。

搜索發(fā)現(xiàn)是_dyld_objc_notify_register調(diào)用的registerObjCNotifiers开皿。

_dyld_objc_notify_registerdyldAPIs.cpp

//_objc_init中調(diào)用的涧黄。
//單個(gè)鏡像文件的加載來(lái)到了這里->_dyld_objc_notify_register,打符號(hào)斷點(diǎn)查看被objc-os.mm中 _objc_init 調(diào)用篮昧。
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的調(diào)用者在dyld中找不到。

打符號(hào)斷點(diǎn)_dyld_objc_notify_register排查調(diào)用情況:

image.png

可以看到是被_objc_init調(diào)用的笋妥。

_objc_init的調(diào)用在objc-os.mm中懊昨,查看源碼:

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();
    runtime_init();
    exception_init();
#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();

    //_objc_init 調(diào)用dyldAPIs.cpp 中_dyld_objc_notify_register,第二個(gè)參數(shù)是load_images
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
  • 證實(shí)是_objc_init調(diào)用了_dyld_objc_notify_register春宣。
  • 第一個(gè)參數(shù)是map_images酵颁,賦值給sNotifyObjCMapped
  • 第二個(gè)參數(shù)是load_images月帝,賦值給sNotifyObjCInit躏惋。
  • 第三個(gè)參數(shù)是unmap_image,賦值給sNotifyObjCUnmapped嚷辅。

這三個(gè)參數(shù)將在后面詳細(xì)介紹是如何與dyld進(jìn)行交互的簿姨。

ImageLoaderMachO::doInitialization(ImageLoaderMachO.cpp)

bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
    CRSetCrashLogMessage2(this->getPath());

    // mach-o has -init and static initializers
    doImageInit(context);
    //加載c++構(gòu)造函數(shù)
    doModInitFunctions(context);
    
    CRSetCrashLogMessage2(NULL);
    
    return (fHasDashInit || fHasInitializers);
}

加上以下代碼查看MachO文件:

__attribute__((constructor)) void func1() {
    printf("\n ---func1--- \n");
}

__attribute__((constructor)) void func2() {
    printf("\n ---func2--- \n");
}

會(huì)發(fā)現(xiàn)MachO中多了__mod_init_func

image.png

  • 調(diào)用doModInitFunctions函數(shù)加載c++構(gòu)造函數(shù)(__attribute__((constructor))修飾的c函數(shù))

ImageLoaderMachO::doModInitFunctions

image.png

  • 內(nèi)部是對(duì)macho文件的一些讀取操作。
  • 會(huì)進(jìn)行__mod_init_func section的確認(rèn)簸搞,與上面的驗(yàn)證符合扁位。
  • 加載前必須加載完libSystem庫(kù)。

四趁俊、反推objc與dyld的關(guān)聯(lián)

在上面的符號(hào)斷點(diǎn)過(guò)程中可以看到在_dyld_objc_notify_registerdoModInitFunctions之間還有非dyld的庫(kù)域仇。
_objc_init中打個(gè)斷點(diǎn)有如下調(diào)用棧:

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
  * frame #0: 0x00000001002d2d44 libobjc.A.dylib`_objc_init at objc-os.mm:925:9
    frame #1: 0x000000010044b0bc libdispatch.dylib`_os_object_init + 13
    frame #2: 0x000000010045bafc libdispatch.dylib`libdispatch_init + 282
    frame #3: 0x00007fff69543791 libSystem.B.dylib`libSystem_initializer + 220
    frame #4: 0x000000010002f1d3 dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 535
    frame #5: 0x000000010002f5de dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
    frame #6: 0x0000000100029ffb dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 493
    frame #7: 0x0000000100029f66 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 344
    frame #8: 0x00000001000280b4 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
    frame #9: 0x0000000100028154 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
    frame #10: 0x0000000100016662 dyld`dyld::initializeMainExecutable() + 129
    frame #11: 0x000000010001bbba dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 6667
    frame #12: 0x0000000100015227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
    frame #13: 0x0000000100015025 dyld`_dyld_start + 37
image.png

對(duì)于doModInitFunctions后面的流程是未知的。從doModInitFunctions->_objc_init流程是未知的寺擂。那么最好的方式就是從_objc_init反推調(diào)用到它的整個(gè)流程暇务。

4.1 _os_object_init

_objc_init是被_os_object_init調(diào)用的泼掠,這個(gè)函數(shù)在libdispatch.dylib中。下載libdispatch最新源碼1271.120.2直接搜索_os_object_init

void
_os_object_init(void)
{
        //_objc_init調(diào)用
    _objc_init();
    Block_callbacks_RR callbacks = {
        sizeof(Block_callbacks_RR),
        (void (*)(const void *))&objc_retain,
        (void (*)(const void *))&objc_release,
        (void (*)(const void *))&_os_objc_destructInstance
    };
    _Block_use_RR2(&callbacks);
#if DISPATCH_COCOA_COMPAT
    const char *v = getenv("OBJC_DEBUG_MISSING_POOLS");
    if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
    v = getenv("DISPATCH_DEBUG_MISSING_POOLS");
    if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
    v = getenv("LIBDISPATCH_DEBUG_MISSING_POOLS");
    if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
#endif
}

發(fā)現(xiàn)確實(shí)是在_os_object_init中直接調(diào)用了_objc_init()般卑。
接著在libdispatch_init中發(fā)現(xiàn)了_os_object_init的調(diào)用:

image.png

  • 其中進(jìn)行了TLS鍵值處理以及線(xiàn)程處理武鲁。

4.2 libSystem_initializer

libSystem_initializer是在libSystem庫(kù)中,下載libSystem最新源碼1292.120.1蝠检。
同樣直接搜索libSystem_initializer

image.png

  • 其中直接調(diào)用了libdispatch_init沐鼠,同樣還調(diào)用了__malloc_init_dyld_initializer以及_libtrace_init叹谁。
    libSystem_initializerImageLoaderMachO::doModInitFunctions調(diào)用的饲梭,這樣整個(gè)流程就回到了dyld中。整個(gè)流程就串起來(lái)了焰檩。

doModInitFunctions中發(fā)現(xiàn)了如下代碼:

image.png

  • libSystem庫(kù)必須第一個(gè)被初始化憔涉。這也能被理解,因?yàn)橐跏蓟?code>dispatch以及objc析苫。其它image都依賴(lài)它兜叨。
  • func是對(duì)c++構(gòu)造函數(shù)的調(diào)用。

那么libSystem_initializer是在哪里調(diào)用的呢衩侥?在doModInitFunctions中并沒(méi)有看到libSystem_initializer的調(diào)用国旷。但是斷點(diǎn)讀取確實(shí)讀取到了:

image.png

前面已經(jīng)分析過(guò)了doModInitFunctions中是對(duì)c++構(gòu)造函數(shù)的調(diào)用。libSystem_initializer正好是c++構(gòu)造函數(shù):
image.png

這樣整個(gè)流程就通了茫死。只不過(guò)libSystem_initializer這個(gè)c++構(gòu)造函數(shù)被先調(diào)用跪但。

image.png

libSystemc++構(gòu)造函數(shù)在dyldlibobjc峦萎、Foundationc++構(gòu)造函數(shù)之后屡久,主程序之前執(zhí)行。

五爱榔、 dyld注冊(cè)objc回調(diào)簡(jiǎn)單分析

通過(guò)上面的分析在_objc_init中調(diào)用了_dyld_objc_notify_register進(jìn)行回調(diào)注冊(cè)被环,有如下賦值:

//第一個(gè)參數(shù) map_images
sNotifyObjCMapped   = mapped;
//第二個(gè)參數(shù) load_images
sNotifyObjCInit     = init;
//第三個(gè)參數(shù) unmap_image
sNotifyObjCUnmapped = unmapped;

接下來(lái)將詳細(xì)分析這3個(gè)回調(diào)的邏輯。

5.1 sNotifyObjCMapped(map_images)

sNotifyObjCMappeddyld中的調(diào)用只在notifyBatchPartial中:

image.png

notifyBatchPartial的調(diào)用是在registerObjCNotifiers详幽、registerImageStateBatchChangeHandler筛欢、以及notifyBatch中。那么根據(jù)之前的分析這里的調(diào)用就是registerObjCNotifiers注冊(cè)回調(diào)后就在里面調(diào)用了妒潭。

objc源碼map_images中打個(gè)斷點(diǎn):

image.png

可以驗(yàn)證在注冊(cè)回調(diào)后立馬調(diào)用了map_images悴能。

map_images中直接加鎖調(diào)用了map_images_nolock,其中進(jìn)行了類(lèi)的加載相關(guān)的操作雳灾。這塊邏輯將單獨(dú)寫(xiě)篇文章進(jìn)行分析漠酿。

5.2 sNotifyObjCInit(load_images)

sNotifyObjCInitdyld中的調(diào)用分為以下情況:
1.notifySingleFromCache中。
2.notifySingle中谎亩。
3.registerObjCNotifiers炒嘲。
notifySingleFromCachenotifySingle邏輯基本相同宇姚,無(wú)非就是有沒(méi)有緩存的區(qū)別。
registerObjCNotifiers是在注冊(cè)回調(diào)函數(shù)的時(shí)候直接進(jìn)行的回調(diào)夫凸。直接在load_images中打個(gè)斷點(diǎn)可以跟蹤到如下信息:

image.png

可以看到系統(tǒng)的基礎(chǔ)庫(kù)在注冊(cè)回調(diào)后就馬上進(jìn)行了load_images的調(diào)用浑劳。

而對(duì)于其他庫(kù)是通過(guò)notifySingle走的回調(diào)邏輯:

image.png

5.2.1 load_images(objc-runtime-new.mm

sNotifyObjCInit其實(shí)就是load_images,它的實(shí)現(xiàn)如下:

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        //加載所有分類(lèi)
        loadAllCategories();
    }

    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        //準(zhǔn)備所有l(wèi)oad方法
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    //調(diào)用 + load方法
    call_load_methods();
}
  • 加載所有分類(lèi)夭拌。
  • 準(zhǔn)備所有load方法魔熏。
  • 最終調(diào)用了call_load_methods

prepare_load_methods

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();

    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        //添加主類(lèi)的load方法
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    //分類(lèi)準(zhǔn)備好
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        //實(shí)現(xiàn)類(lèi)
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        //添加分類(lèi)的load方法鸽扁。
        add_category_to_loadable_list(cat);
    }
}
  • 添加主類(lèi)的load方法蒜绽。
  • 添加分類(lèi)的load方法。

schedule_class_load

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    ASSERT(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    //調(diào)度類(lèi)的load方法桶现,遞歸到nil
    schedule_class_load(cls->getSuperclass());

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}
  • 遞歸調(diào)度類(lèi)的load方法躲雅,直到父類(lèi)為nil

add_class_to_loadable_list & add_category_to_loadable_list

void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();
    //load方法
    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    //空間不足開(kāi)辟空間
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    //將load方法添加到loadable_classes中骡和。相當(dāng)于一個(gè)下標(biāo)中存儲(chǔ)的是cls-method
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

void add_category_to_loadable_list(Category cat)
{
    IMP method;

    loadMethodLock.assertLocked();
    //獲取load方法
    method = _category_getLoadMethod(cat);

    // Don't bother if cat has no +load method
    if (!method) return;

    if (PrintLoading) {
        _objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
                     _category_getClassName(cat), _category_getName(cat));
    }
    
    if (loadable_categories_used == loadable_categories_allocated) {
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)
            realloc(loadable_categories,
                              loadable_categories_allocated *
                              sizeof(struct loadable_category));
    }
    //分類(lèi)添加到loadable_categories中
    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}
  • 通過(guò)字符出那比較獲取load方法相赁。
  • 空間不足的情況下開(kāi)辟空間嗎,每次開(kāi)辟的空間大小為(2倍+16)* 16 字節(jié)慰于。
struct loadable_class {
  Class cls;  // may be nil
  IMP method;
};
  • 將對(duì)應(yīng)的數(shù)據(jù)添加進(jìn)loadable_classesloadable_categories中钮科。

??加載過(guò)程中類(lèi)和分類(lèi)是有區(qū)分的。為什么區(qū)分將在后續(xù)的文章中詳細(xì)分析东囚。

getLoadMethod

IMP 
objc_class::getLoadMethod()
{
    runtimeLock.assertLocked();

    const method_list_t *mlist;

    //遞歸所有的baseMethods跺嗽,查找load方法战授。
    mlist = ISA()->data()->ro()->baseMethods();
    if (mlist) {
        for (const auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            //匹配load
            if (0 == strcmp(name, "load")) {
                return meth.imp(false);
            }
        }
    }

    return nil;
}

IMP 
_category_getLoadMethod(Category cat)
{
    runtimeLock.assertLocked();

    const method_list_t *mlist;

    mlist = cat->classMethods;
    if (mlist) {
        for (const auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            if (0 == strcmp(name, "load")) {
                return meth.imp(false);
            }
        }
    }

    return nil;
}
  • load方法獲取是通過(guò)字符出那比較獲取的页藻。

5.2.2 call_load_methods (objc-loadmethod.mm)

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();
    
    //循環(huán)調(diào)用 call_class_loads,類(lèi)的load方法在這一刻被調(diào)用
    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            //調(diào)用每個(gè)類(lèi)的load
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        //調(diào)用分類(lèi)load,這里也就說(shuō)明分類(lèi)的 load 在所有類(lèi)的load方法調(diào)用后才調(diào)用植兰。(針對(duì)image而言)
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}
  • 調(diào)用call_class_loads加載類(lèi)的+ load份帐。
  • 接著調(diào)用call_category_loads加載分類(lèi)的+ load。這里也就說(shuō)明分類(lèi)的 load在所有類(lèi)的load方法調(diào)用后才調(diào)用楣导。(針對(duì)image而言)废境。

在這里也就調(diào)用到了+ load方法,這也就是+ loadmain之前被調(diào)用的原因筒繁。

call_class_loads

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;
        //從classes中獲取method
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        //調(diào)用load
        (*load_method)(cls, @selector(load));
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}
  • 內(nèi)部也是從loadable_classes中循環(huán)取到load方法進(jìn)行調(diào)用噩凹。

call_category_loads

static bool call_category_loads(void)
{
    int i, shift;
    bool new_categories_added = NO;
    
    // Detach current loadable list.
    struct loadable_category *cats = loadable_categories;
    int used = loadable_categories_used;
    int allocated = loadable_categories_allocated;
    loadable_categories = nil;
    loadable_categories_allocated = 0;
    loadable_categories_used = 0;

    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        //從cats中取出load
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) continue;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            (*load_method)(cls, @selector(load));
            cats[i].cat = nil;
        }
    }
……
}
  • 分類(lèi)load的調(diào)用也是從loadable_categories循環(huán)取load方法進(jìn)行調(diào)用。分類(lèi)中內(nèi)部處理邏輯更多一些毡咏。

所以在調(diào)用完+ load以及c++構(gòu)造函數(shù)才返回LC_MAIN進(jìn)行main函數(shù)的調(diào)用驮宴。可以通過(guò)匯編斷點(diǎn)驗(yàn)證:

image.png

這樣就和開(kāi)頭的時(shí)候?qū)?yīng)上了呕缭。那么如果修改main函數(shù)的名稱(chēng)堵泽,編譯的時(shí)候就報(bào)錯(cuò)了修己。主程序的入口main是寫(xiě)死的,可以通過(guò)Hook去操作main隱藏自己的邏輯迎罗。

根據(jù)以上分析可以看到dyld是按image list順序從第1個(gè)image調(diào)用runInitializers(可以看做是以image分組)睬愤。再調(diào)用下一個(gè)imagerunInitializers最后再調(diào)用主程序(下標(biāo)為0)的runInitializers。在runInitializers內(nèi)部先調(diào)用所有類(lèi)的+load纹安,再調(diào)用所有分類(lèi)的+ load尤辱,最后調(diào)用c++的構(gòu)造函數(shù)。
objc中調(diào)用load厢岂,dyld中調(diào)用doModInitFunctions啥刻。
??如果在+ load中做了防護(hù),那么可以通過(guò)在+ load執(zhí)行前斷住外部符號(hào)做處理咪笑。這樣就可以繞過(guò)防護(hù)了可帽。
防護(hù)最重要的就是不讓別人找到防護(hù)的邏輯,只要能找到那么破解就很容易了窗怒。
案例分析:你真的了解dyld么映跟?

5.3 sNotifyObjCUnmapped(unmap_image)

sNotifyObjCUnmappeddyld中只有removeImage進(jìn)行了調(diào)用:

image.png

removeImagecheckandAddImagegarbageCollectImages扬虚、_dyld_link_module調(diào)用努隙。

  • garbageCollectImages:在link等其它異常以及回收的時(shí)候調(diào)用。
  • checkandAddImage:檢測(cè)加載的image不在鏡像列表中的時(shí)候直接刪除辜昵。
  • _dyld_link_module:暫時(shí)不確定是哪里調(diào)用的荸镊。

5.3.1 unmap_image

unmap_image中調(diào)用了unmap_image_nolock,核心代碼如下:

void 
unmap_image_nolock(const struct mach_header *mh)
{
 ……
    header_info *hi; 
 ……
    //釋放類(lèi)堪置,分類(lèi)相關(guān)資源躬存。
    _unload_image(hi);

    // Remove header_info from header list
    //移除remove Header
    removeHeader(hi);
    free(hi);
}
  • 移除釋放類(lèi),分類(lèi)相關(guān)資源舀锨。
  • 移除Header信息岭洲。

六 、dyld3閉包模式分析

關(guān)于閉包模式在開(kāi)啟閉包模式的情況下就直接return了坎匿,所以核心邏輯就在launchWithClosure中了:

static bool launchWithClosure(const dyld3::closure::LaunchClosure* mainClosure,
                              const DyldSharedCache* dyldCache,
                              const dyld3::MachOLoaded* mainExecutableMH, uintptr_t mainExecutableSlide,
                              int argc, const char* argv[], const char* envp[], const char* apple[], Diagnostics& diag,
                              uintptr_t* entry, uintptr_t* startGlue, bool* closureOutOfDate, bool* recoverable)
{
    ……
    libDyldEntry->runInitialzersBottomUp((mach_header*)mainExecutableMH);
    ……
}

launchWithClosure中發(fā)現(xiàn)了runInitialzersBottomUp的調(diào)用:

void AllImages::runInitialzersBottomUp(const closure::Image* topImage)
{
    // walk closure specified initializer list, already ordered bottom up
    topImage->forEachImageToInitBefore(^(closure::ImageNum imageToInit, bool& stop) {
        // get copy of LoadedImage about imageToInit, but don't keep reference into _loadedImages, because it may move if initialzers call dlopen()
        uint32_t    indexHint = 0;
        LoadedImage loadedImageCopy = findImageNum(imageToInit, indexHint);
        // skip if the image is already inited, or in process of being inited (dependency cycle)
        if ( (loadedImageCopy.state() == LoadedImage::State::fixedUp) && swapImageState(imageToInit, indexHint, LoadedImage::State::fixedUp, LoadedImage::State::beingInited) ) {
            // tell objc to run any +load methods in image
            if ( (_objcNotifyInit != nullptr) && loadedImageCopy.image()->mayHavePlusLoads() ) {
                dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)loadedImageCopy.loadedAddress(), 0, 0);
                const char* path = imagePath(loadedImageCopy.image());
                log_notifications("dyld: objc-init-notifier called with mh=%p, path=%s\n", loadedImageCopy.loadedAddress(), path);
                //+load
                (*_objcNotifyInit)(path, loadedImageCopy.loadedAddress());
            }

            // run all initializers in image
            //c++構(gòu)造函數(shù)
            runAllInitializersInImage(loadedImageCopy.image(), loadedImageCopy.loadedAddress());

            // advance state to inited
            swapImageState(imageToInit, indexHint, LoadedImage::State::beingInited, LoadedImage::State::inited);
        }
    });
}
  • _objcNotifyInit最終調(diào)用到了+ load方法盾剩。
  • runAllInitializersInImage調(diào)用c++構(gòu)造函數(shù),其中包括注冊(cè)回調(diào)替蔬。
void AllImages::runAllInitializersInImage(const closure::Image* image, const MachOLoaded* ml)
{
    image->forEachInitializer(ml, ^(const void* func) {
        Initializer initFunc = (Initializer)func;
#if __has_feature(ptrauth_calls)
        initFunc = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)initFunc, 0, 0);
#endif
        {
            ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)ml, (uint64_t)func, 0);
            //c++構(gòu)造函數(shù)
            initFunc(NXArgc, NXArgv, environ, appleParams, _programVars);

        }
        log_initializers("dyld: called initialzer %p in %s\n", initFunc, image->path());
    });
}

在真機(jī)/模擬器調(diào)試中對(duì)_dyld_objc_notify_register下符號(hào)斷點(diǎn)發(fā)現(xiàn)_dyld_objc_notify_register()的注冊(cè)回調(diào)是dyld3::_dyld_objc_notify_register調(diào)用的:

image.png

但是最終的回調(diào)以及調(diào)用方確是dyld2的邏輯告私。看下源碼:

void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
    if ( gUseDyld3 )
        return dyld3::_dyld_objc_notify_register(mapped, init, unmapped);

    DYLD_LOCK_THIS_BLOCK;
    typedef bool (*funcType)(_dyld_objc_notify_mapped, _dyld_objc_notify_init, _dyld_objc_notify_unmapped);
    static funcType __ptrauth_dyld_function_ptr p = NULL;

    if(p == NULL)
        dyld_func_lookup_and_resign("__dyld_objc_notify_register", &p);
    p(mapped, init, unmapped);
}

那么就說(shuō)明gUseDyld3NULL承桥,走了dyld2的邏輯驻粟。但是如果走dyld3可以得到以下信息,注冊(cè)的三個(gè)回調(diào)函數(shù)指針與dyld2名稱(chēng)不同:

_objcNotifyMapped   = map;
_objcNotifyInit     = init;
_objcNotifyUnmapped = unmap;
  • _objcNotifyInit已經(jīng)清楚了是在runInitialzersBottomUp中調(diào)用的快毛。
  • _objcNotifyUnmapped是在garbageCollectImages ->removeImages中調(diào)用的格嗅。
  • _objcNotifyMapped是在runImageCallbacks中調(diào)用的番挺,它有兩個(gè)調(diào)用方applyInitialImages以及loadImage
    • applyInitialImages是被_dyld_initializer調(diào)用的屯掖。_dyld_initializer在第四部分已經(jīng)明確了是在libSystem_initializer中調(diào)用的玄柏。而由于_dyld_initializer是在libdispatch_init之前調(diào)用的,所以這個(gè)時(shí)候應(yīng)該還沒(méi)有注冊(cè)回調(diào)贴铜。
    • loadImage是在dlopen中調(diào)用的粪摘。

由于真機(jī)和模擬器以及mac都沒(méi)有辦法進(jìn)入閉包模式調(diào)試驗(yàn)證。并且閉包模式代碼邏輯可讀性比較差绍坝,所以這里只是根據(jù)源碼得出的結(jié)論徘意,不一定成立。

dyld3閉包模式調(diào)用流程

七轩褐、dyld簡(jiǎn)介

啟動(dòng)時(shí)間(Startup Time):main函數(shù)執(zhí)行之前所用的時(shí)間椎咧。
啟動(dòng)收尾(Lacunch Closure):?jiǎn)?dòng)應(yīng)用程序必須的所有信息。

dyld發(fā)展到如今已經(jīng)有3個(gè)大版本了把介,接下來(lái)將對(duì)dyld的演進(jìn)過(guò)程做簡(jiǎn)單總結(jié)勤讽。

7.1 dyld 1.0 (1996–2004)

  • 包含在NeXTStep 3.3中一起發(fā)布,在這之前NeXT使用靜態(tài)二進(jìn)制數(shù)據(jù)拗踢。
  • dyld1的歷史早于標(biāo)準(zhǔn)化POSIX dlopen()調(diào)用脚牍。
  • dyld1是在大多數(shù)使用c++動(dòng)態(tài)庫(kù)的系統(tǒng)之前編寫(xiě)的。
    c++有許多的特性比如其初始化器排序方式等在靜態(tài)環(huán)境中工作良好巢墅,但是在動(dòng)態(tài)環(huán)境中可能降低性能诸狭。因此大型c++代碼庫(kù)導(dǎo)致dyld需要完成大量的工作,速度變慢君纫。
  • macOS Cheetah(10)中增加了預(yù)綁定技術(shù)驯遇。
    預(yù)綁定為系統(tǒng)中所有的dylib和你的程序找到固定地址。dyld將會(huì)加載這些地址的所有內(nèi)容庵芭。加載成功會(huì)編輯所有這些二進(jìn)制數(shù)據(jù)以獲得所有預(yù)計(jì)算地址妹懒。然后下次當(dāng)它將所有數(shù)據(jù)放入相同地址時(shí)不必進(jìn)行任何其它額外的工作雀监。這樣會(huì)大幅提高速度双吆,但是這也意味著每次啟動(dòng)時(shí)會(huì)編輯你的二進(jìn)制數(shù)據(jù)。從安全性來(lái)說(shuō)這樣并不是很好的做法会前。

7.2 dyld2.0 (2004-2007)

  • 隨著macOS Tiger發(fā)布好乐。
  • dyld2dyld的完全重寫(xiě)版本。
  • 正確支持c++初始化器語(yǔ)義瓦宜,擴(kuò)展了mach-o格式并且更新了dyld蔚万。
  • 具有完整的本機(jī)dlopendlsym實(shí)現(xiàn),具有正確的語(yǔ)義临庇,棄用了舊版API(舊版API仍然僅位于macOS中)反璃。
  • dyld2的設(shè)計(jì)目標(biāo)是提高速度昵慌,因此僅進(jìn)行有限的健全性檢查(以前惡意程序并不多)。
  • dyld有一些安全性問(wèn)題淮蜈,對(duì)一些功能性改進(jìn)提高它在現(xiàn)在平臺(tái)上的安全性斋攀。
  • 由于速度大幅提升可以減少預(yù)綁定工作量。不同于dyld1編輯你的程序數(shù)據(jù)梧田,dyld2僅編輯系統(tǒng)庫(kù)淳蔼。可以?xún)H在軟件更新時(shí)做這些事情裁眯。因此在軟件更新時(shí)可能會(huì)看到“優(yōu)化系統(tǒng)性能”之類(lèi)的文字鹉梨,這時(shí)就是在更新預(yù)綁定。

7.2.1dyld2.x(2007-2017)

  • 增加更多的架構(gòu)和平臺(tái)穿稳。
    • x86存皂、x86_64arm64
    • iOS逢艘、 tvOS艰垂、watchOS
  • 提升安全性
    • 增加代碼簽名和ASLR
    • mach-o header 邊界檢查,避免惡意二進(jìn)制數(shù)據(jù)的加入埋虹。
  • 提升性能
    • 使用共享緩存替換預(yù)綁定猜憎。

7.2.2 共享緩存(shared cache)

共享緩存(dyld預(yù)連接器)最早被引入iOS3.1 & macOS Snow Leopard,完全取代預(yù)綁定搔课。

  • 它是一個(gè)單文件胰柑,含有大多數(shù)系統(tǒng)dylib
    由于合并成一個(gè)文件爬泥,因此可以進(jìn)行優(yōu)化

    • 重新調(diào)整二進(jìn)制數(shù)據(jù)以提高加載速度(重新調(diào)整所有文本段和所有數(shù)據(jù)段重寫(xiě)整個(gè)符號(hào)表以減小大屑硖帧)。
    • 允許打包二進(jìn)制數(shù)據(jù)段節(jié)省大量ram
    • 預(yù)生成數(shù)據(jù)結(jié)構(gòu)供dyldobjc使用袍啡,在運(yùn)行時(shí)使用讓我們不必在應(yīng)用啟動(dòng)時(shí)做這些事情踩官。這樣也會(huì)節(jié)約更多ram和時(shí)間。
  • 共享緩存在macOS上本地生成運(yùn)行dyld共享代碼大幅優(yōu)化系統(tǒng)性能境输。其它平臺(tái)由Apple提供蔗牡。

7.3 dyld3.0(2017-)

dyld3是全新的動(dòng)態(tài)連接器,2017(iOS11)年所有系統(tǒng)程序都默認(rèn)使用dyld3嗅剖,第三方在2019(iOS13)年完全取代dyld2辩越。
dyld3主要做了以下三方面的改進(jìn):

  1. 性能,提高啟動(dòng)速度信粮。dyld3可以幫助我們獲得更快的程序啟動(dòng)和運(yùn)行速度黔攒。
  2. 安全性。dyld2增加的安全性很難跟隨現(xiàn)實(shí)情形增強(qiáng)安全性。
  3. 可測(cè)試性和可靠性督惰。
    XCTest依賴(lài)于dyld的底層功能不傅,將它們的庫(kù)插入進(jìn)程。因此不能用于測(cè)試現(xiàn)有的dyld代碼赏胚。這讓難以測(cè)試安全性和性能水平蛤签。
    dyld3將大多數(shù)dyld移出進(jìn)程,現(xiàn)在大多數(shù)dyld只是普通的后臺(tái)程序栅哀≌鸢梗可以使用標(biāo)準(zhǔn)測(cè)試工具進(jìn)行測(cè)試。另外也允許部分dyld駐留在進(jìn)程中留拾,駐留部分盡可能小戳晌,從而減少程序的受攻擊面積。

7.4 dyld2與dyld3加載對(duì)比

dyld2&dyld3官網(wǎng)對(duì)比圖

7.4.1 dyld2流程

  • Parse mach-o headers & Find dependencies:分析macho headers痴柔,確認(rèn)需要哪些庫(kù)沦偎。遞歸分析依賴(lài)的庫(kù)直到獲得所有的dylib庫(kù)。普通iOS程序需要3-600個(gè)dylib咳蔚,數(shù)據(jù)龐大需要進(jìn)行大量處理豪嚎。
  • Map mach-o files:映射所有macho文件將他們放入地址空間(映射進(jìn)內(nèi)存)。
  • Perform symbol lookups:執(zhí)行符號(hào)查找谈火。 比如使用printf函數(shù)侈询,將會(huì)查找printf是否在庫(kù)系統(tǒng)中,然后找到它的地址糯耍,將它復(fù)制給應(yīng)用程序中的函數(shù)指針扔字。
  • Bind and rebase:綁定和基址重置。復(fù)制這些指針温技,所有指針必須使用基地址(ASLR的存在)革为。
  • Run initializers:運(yùn)行所有初始化器。這之后就開(kāi)始準(zhǔn)備執(zhí)行main函數(shù)舵鳞。

7.4.2 dyld3流程

dyld3整個(gè)被分成了3個(gè)流程:

  • dyld3是一個(gè)進(jìn)程外macho分析器和編譯器(對(duì)應(yīng)上圖中紅色部分)震檩。
    • 解析所有搜索路徑、rpaths蜓堕、環(huán)境變量抛虏。
    • 分析macho二進(jìn)制數(shù)據(jù)。
    • 執(zhí)行所有符號(hào)查找俩滥。
    • 利用上面的這些結(jié)果創(chuàng)建閉包處理嘉蕾。
    • 它是一個(gè)普通的后臺(tái)程序,可以進(jìn)行正常測(cè)試霜旧。
    • 大多數(shù)程序啟動(dòng)會(huì)使用緩存,始終不需要調(diào)用進(jìn)程外macho分析器或編譯器。
    • 啟動(dòng)閉包比macho更簡(jiǎn)單挂据,它們是內(nèi)存映射文件以清,不需要用復(fù)雜的方式進(jìn)行分析,可以簡(jiǎn)單的驗(yàn)證它們崎逃,作用是為了提高速度。
  • dyld3也是一個(gè)進(jìn)程內(nèi)引擎。
    • 檢查閉包是否正確收叶。
    • 使用閉包映射所有dylibs写穴。
    • 綁定和基址重置。
    • 運(yùn)行所有初始化器巴柿,然后跳轉(zhuǎn)主程序main()

    ??dyld3不需要分析macho Headers或者執(zhí)行符號(hào)查找凛虽。在App啟動(dòng)時(shí)沒(méi)有這個(gè)過(guò)程,因此極大的提升了程序的啟動(dòng)速度广恢。

  • 啟動(dòng)閉包緩存服務(wù)
    • 系統(tǒng)app閉包模式構(gòu)建在共享緩存中凯旋。
    • 第三方應(yīng)用在安裝時(shí)構(gòu)建,在軟件更新時(shí)重新構(gòu)建钉迷。
    • macOS上后臺(tái)進(jìn)程引擎可以在后臺(tái)進(jìn)程被調(diào)用至非,在其它平臺(tái)上不需要這么做。

詳細(xì)情況參考官方:wwdc2017-413(App Startup Time: Past, Present, and Future)

八糠聪、總結(jié)

8.1dyld調(diào)用流程

dyld調(diào)用流程

8.2 核心點(diǎn)文字總結(jié)

DYLD加載流程文字版總結(jié)
最后編輯于
?著作權(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)離奇詭異信卡,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)题造,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)傍菇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人界赔,你說(shuō)我怎么就攤上這事丢习。” “怎么了淮悼?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵咐低,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我袜腥,道長(zhǎng)见擦,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮鲤屡,結(jié)果婚禮上损痰,老公的妹妹穿的比我還像新娘。我一直安慰自己酒来,他們只是感情好卢未,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著堰汉,像睡著了一般辽社。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上翘鸭,一...
    開(kāi)封第一講書(shū)人閱讀 49,071評(píng)論 1 285
  • 那天滴铅,我揣著相機(jī)與錄音,去河邊找鬼矮固。 笑死失息,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的档址。 我是一名探鬼主播盹兢,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼守伸!你這毒婦竟也來(lái)了绎秒?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤尼摹,失蹤者是張志新(化名)和其女友劉穎见芹,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(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
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望搔体。 院中可真熱鬧恨樟,春花似錦半醉、人聲如沸疚俱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)呆奕。三九已至,卻和暖如春衬吆,著一層夾襖步出監(jiān)牢的瞬間梁钾,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工逊抡, 沒(méi)想到剛下飛機(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

推薦閱讀更多精彩內(nèi)容

  • 本文的目的主要是分析dyld的加載流程方咆,了解在main函數(shù)之前,底層還做了什么 引子 創(chuàng)建一個(gè)project蟀架,在V...
    輝輝歲月閱讀 991評(píng)論 0 2
  • 本文的目的主要是分析 dyld的加載流程瓣赂,了解一下在main 函數(shù)之前,底層還做了哪些事情片拍。 0. 引子 創(chuàng)建一個(gè)...
    沉江小魚(yú)閱讀 1,153評(píng)論 1 3
  • 關(guān)于程序是否是從 main 函數(shù)開(kāi)始運(yùn)行的疑問(wèn)煌集,可以參照 運(yùn)行庫(kù)[http://www.reibang.com/...
    奉灬孝閱讀 2,562評(píng)論 1 16
  • dyld加載的詳細(xì)流程可以參考文章 iOS dyld加載流程[http://www.reibang.com/p...
    哦呵呵y閱讀 554評(píng)論 0 1
  • 前言: 動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù) ? 我們都知道,一段程序的運(yùn)行捌省,都會(huì)依賴(lài)各種各樣的庫(kù)苫纤,那么什么是程序依賴(lài)的庫(kù)呢?簡(jiǎn)而言之所禀,...
    xxxxxxxx_123閱讀 450評(píng)論 0 0