dyld 調(diào)用流程分析

dyld 簡介

dyld(the dynamic link editor)是蘋果的動態(tài)鏈接器,用來加載所有的庫和可執(zhí)行文件玉罐,是蘋果操作系統(tǒng)一個重要組成部分,在系統(tǒng)內(nèi)核做好程序準(zhǔn)備工作之后,交由 dyld 負(fù)責(zé)余下的工作选浑。而且它是開源的杖玲,任何人可以通過蘋果官網(wǎng)下載它的源碼來閱讀理解它的運(yùn)作方式顿仇,那下面我們就通過源碼嘗試看一下 dyld 的加載流程。

下面我們通過對控制器的 load 方法添加斷點摆马,看看 dyld 是從哪里開始執(zhí)行的:

load之前的方法調(diào)用.png

從函數(shù)的調(diào)用棧我們可以看到臼闻,dyld 是從 start 方法開始執(zhí)行的,那我我們就打開源碼找到 start 方法囤采,來一步一步的分析 dyld 的調(diào)用過程述呐。這里用到是832.7.3版本。

dyld 入口函數(shù) start

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

    // 首先發(fā)出kdebug消息蕉毯,告訴dyld已經(jīng)開始啟動了
    dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);

    // 第一步乓搬,重定位dyld地址,把dyld加載進(jìn)來(虛擬內(nèi)存下代虾,一個進(jìn)程啟動都需要做地址重定位)
    rebaseDyld(dyldsMachHeader);

    // kernel sets up env pointer to be just past end of agv array
    const char** envp = &argv[argc+1];
    
    // kernel sets up apple pointer to be just past end of envp array
    const char** apple = envp;
    while(*apple != NULL) { ++apple; }
    ++apple;

    // 棧溢出的保護(hù)
    __guard_setup(apple);

#if DYLD_INITIALIZER_SUPPORT
    // run all C++ initializers inside dyld
    runDyldInitializers(argc, argv, envp, apple);
#endif
    // 初始化dyld
    _subsystem_init(apple);

    // now that we are done bootstrapping dyld, call dyld's main
    uintptr_t appsSlide = appsMachHeader->getSlide();
    // 調(diào)用main(最核心的代碼在main函數(shù)里面进肯,因為dyld幾乎所有的邏輯都在main里面處理)
    return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

start 方法里面主要是做了一些準(zhǔn)備工作,是開始啟動階段棉磨,最核心的代碼在 main 函數(shù)里面江掩,下面我們接著進(jìn)入 main 函數(shù)。

dyld 的 main 函數(shù)

現(xiàn)在我們來到 main函數(shù)

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. 內(nèi)核檢測代碼,這里省略......

    // 2.主程序配置相關(guān) 
    uint8_t mainExecutableCDHashBuffer[20];
    // 主程序的hash
    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;
    }
    // mainExecutableMH :主程序的header频敛,mainExecutableSlide :主程序的slide
    // 設(shè)置cpu项郊,cpu的類型等信息
    getHostInfo(mainExecutableMH, mainExecutableSlide);
    // 對header跟slide賦值
    uintptr_t result = 0;
    sMainExecutableMachHeader = mainExecutableMH;
    sMainExecutableSlide = mainExecutableSlide;

    // 3.設(shè)置上下文,把配置信息保存到gLinkContext這個變量里面斟赚,到這里都是做一些配置相關(guān)的操作并保存下來
    setContext(mainExecutableMH, argc, argv, envp, apple);

    // 配置進(jìn)程是否受限着降,envp:環(huán)境變量
    // AMFI相關(guān)(Apple Mobile File Integrity蘋果移動文件保護(hù))
    configureProcessRestrictions(mainExecutableMH, envp);
    // 判斷是否要強(qiáng)制使用dyld3 (dyld3是iOS11之后新增的一種閉包模式,處理效率更高拗军,加載速度更快)
    if ( dyld3::internalInstall() ) {...}

    // 4. 檢測環(huán)境變量任洞,并給環(huán)境變量設(shè)置默認(rèn)值
    checkEnvironmentVariables(envp);
    defaultUninitializedFallbackPaths(envp);

    // 打印相關(guān)的環(huán)境變量(在load之前打印)
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);

    // 5. 加載共享緩存发侵,UIKit交掏,F(xiàn)oundtion等動態(tài)庫 (在這之前還沒有加載主程序,但是已經(jīng)拿到主程序的header刃鳄,開始讀主程序了盅弛,根據(jù)主程序的header可以知道主程序的cup,架構(gòu)叔锐,系統(tǒng)等信息)
    // 共享緩存: 用來存儲UIkit等系統(tǒng)庫的挪鹏,保證這些庫只加載一份,因為iOS進(jìn)程之間訪問受限愉烙,所以要放到共享緩存里面
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);

// 6.加載各項我們所依賴的framework讨盒,還有三方庫

// 7.開始判斷是加載`dyld2`還是`dyld3`。

dyld3 的加載流程

    // ClosureMode: 閉包模式,iOS11后引入的步责,iOS13開始動態(tài)庫返顺,三方庫都用ClosureMode模式加載
    // 判斷是否是閉包模式,是的話就先開始創(chuàng)建閉包模式加載器
    if ( sClosureMode == ClosureMode::Off ) {...} else {
    // 給sLaunchModeUsed變量設(shè)置一個表示蔓肯,表示用閉包模式去啟動dyld
        sLaunchModeUsed = DYLD_LAUNCH_MODE_USING_CLOSURE;
    // 1. 配置主程序的信息:mainFileInfo遂鹊,主程序的header:mainExecutableMH
        const dyld3::closure::LaunchClosure* mainClosure = nullptr;
        dyld3::closure::LoadedFileInfo mainFileInfo;
        mainFileInfo.fileContent = mainExecutableMH;
        
        // check for closure in cache first
        if ( sSharedCacheLoadInfo.loadAddress != nullptr ) {

        // 先去共享緩存中取查看 mainClosure 實例
        mainClosure = sSharedCacheLoadInfo.loadAddress->findClosure(sExecPath);
        // 如果實例對象為空就主動創(chuàng)建
        if ( mainClosure == nullptr ) {
            mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
        }

        // 2. 這里開始進(jìn)入到啟動
        if ( mainClosure != nullptr ) {
            // 開啟啟動并返回啟動結(jié)果
            bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
                                              mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
            // 如果啟動失敗或者closureOutOfDate過期
            if ( !launched && closureOutOfDate && allowClosureRebuilds ) {
                // 再次創(chuàng)建一個mainClosure實例
                mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
                // 并再次啟動
                launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
                                                 mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
                }
            }
            // 如果啟動成功,就保存變量省核,主程序加載成功
            if ( launched ) {
                gLinkContext.startedInitializingMainExecutable = true;
            // 拿到主程序的main函數(shù)稿辙,并返回result
                if (sSkipMain)
                    result = (uintptr_t)&fake_main;
                return result;
            }
            // 如果啟動失敗,就報響應(yīng)的錯誤信息
            else {
                if ( gLinkContext.verboseWarnings ) {
                    dyld::log("dyld: unable to use closure %p\n", mainClosure);
                }
                if ( !recoverable )
                    halt(diag.errorMessage());
            }

dyld2 的加載流程

    // 如果不是閉包模式 dyld2模式
    sLaunchModeUsed = 0;

    // 把 notifyGDB 跟 updateAllImages 回調(diào)放到 sBatchHandlers 數(shù)組
    sBatchHandlers)->push_back(notifyGDB);
    stateToHandlers(dyld_image_state_mapped, sSingleHandlers)->push_back(updateAllImages);

    // 分配一些初始化空間气忠,盡量空間分配的大一點邻储,保證后面夠用
    sImageRoots.reserve(16);
    sAddImageCallbacks.reserve(4);
    sRemoveImageCallbacks.reserve(4);
    sAddLoadImageCallbacks.reserve(4);
    sImageFilesNeedingTermination.reserve(16);
    sImageFilesNeedingDOFUnregistration.reserve(8);

    // 把dyld加入到UUIDList列表
    addDyldImageToUUIDList();


    // 開始加載主程序
    // mainExcutableAlreadyRebased:主程序Rebased狀態(tài)標(biāo)識
    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);
        }

    // 實例化主程序 image:可執(zhí)行文件(dyld第一個加載的image是主程序)
    sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
    gLinkContext.mainExecutable = sMainExecutable;
    // 代碼簽名
    gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

// 檢測主程序是否屬于當(dāng)前系統(tǒng),當(dāng)前設(shè)備版本
        {
            if ( ! isSimulatorBinary((uint8_t*)mainExecutableMH, sExecPath) ) {
                throwf("program was built for a platform that is not supported by this runtime");
            }
            uint32_t mainMinOS = sMainExecutable->minOSVersion();

            // dyld is always built for the current OS, so we can get the current OS version
            // from the load command in dyld itself.
            uint32_t dyldMinOS = ImageLoaderMachO::minOSVersion((const mach_header*)&__dso_handle);
            if ( mainMinOS > dyldMinOS ) {...}

// 相關(guān)配置
if (dyld::isTranslated()) {...}
// 設(shè)置動態(tài)庫的版本
checkVersionedPaths();


// 先判斷有沒有DYLD_INSERT_LIBRARIES這個環(huán)境變量旧噪,有的話插入所有動態(tài)庫
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                loadInsertedDylib(*lib);
        }

// 記錄插入動態(tài)庫
        gLinkContext.linkingMainExecutable = true;
// 鏈接主程序吨娜,在此函數(shù)開始時記錄開始時間,然后遞歸加載主程序依賴的庫淘钟,加載完畢后會發(fā)送通知宦赠,修正ASLR,綁定NoLazy符號,綁定弱符號勾扭,遞歸應(yīng)用插入的動態(tài)庫毡琉,注冊,最后記錄結(jié)束時間
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);


if ( sInsertedDylibCount > 0 ) {
// 循環(huán)遍歷把動態(tài)庫加入到allImage
            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();
            }
            if ( gLinkContext.allowInterposing ) {
                // 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);
                }
            }
        }

// 循環(huán)拿出image妙色,綁定插入的動態(tài)庫
if ( sInsertedDylibCount > 0 ) {
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                ImageLoader* image = sAllImages[i+1];
                //綁定插入的動態(tài)庫!
                image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true, nullptr);
            }
        }
// 弱符號綁定
sMainExecutable->weakBind(gLinkContext);

實例化主程序的過程

這里其實就是實例化一個iamge的過程桅滋,其他動態(tài)庫跟三方庫的實例化過程跟主程序的實例化過程一樣。

    // 實例化一個iamge的過程
    static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path) {
        // 加載主程序的MachO文件(header身辨,LoadCommands等)
        ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
        // 把iamge加入到allImage數(shù)組
        addImage(image);
        return (ImageLoaderMachO*)image;
    }

// 進(jìn)入到instantiateMainExecutable函數(shù)
ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
{
    //dyld::log("ImageLoader=%ld, ImageLoaderMachO=%ld, ImageLoaderMachOClassic=%ld, ImageLoaderMachOCompressed=%ld\n",
    //  sizeof(ImageLoader), sizeof(ImageLoaderMachO), sizeof(ImageLoaderMachOClassic), sizeof(ImageLoaderMachOCompressed));
    bool compressed;
    unsigned int segCount;
    unsigned int libCount;
    const linkedit_data_command* codeSigCmd;
    const encryption_info_command* encryptCmd;
    sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
    // 根據(jù)compressed的值選擇不同的子類來實例化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
}

進(jìn)入到sniffLoadCommands函數(shù)
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)
{
        *compressed = false;
    *segCount = 0;
    *libCount = 0;
    *codeSigCmd = NULL; // 代碼簽名
    *encryptCmd = NULL; // 代碼加密

    // segCount丐谋,libCount的數(shù)量限制
    if ( *segCount > 255 )
        dyld::throwf("malformed mach-o image: more than 255 segments in %s", path);

    // fSegmentsArrayCount is only 8-bits
    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;
}

initializeMainExecutable 函數(shù)

initializeMainExecutable 函數(shù)之前都是做一些初始化與加載準(zhǔn)備工作,這里才是開始真正進(jìn)入到主程序煌珊。

  1. 進(jìn)入到 initializeMainExecutable 函數(shù)
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 ) {
        for(size_t i=1; i < rootCount; ++i) {
            sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
        }
    }
    
    // run initializers for main executable and everything it brings up 
    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]);
}
  1. 通過函數(shù)調(diào)用棧我們可以看到号俐,initializeMainExecutable函數(shù)之后進(jìn)入到 ImageLoader 函數(shù),不管dyld2還是dyld3都會走到這
    dyld函數(shù)調(diào)用棧.png
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);
}
  1. 同樣根據(jù)函數(shù)調(diào)用棧來到 recursiveInitialization 函數(shù)定庵,看一下最終是怎么調(diào)到 load_images 函數(shù)的吏饿。
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
                                          InitializerTimingList& timingInfo, UninitedUpwards& uninitUps) 
{
  context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
}
  1. 進(jìn)到 notifySingle 函數(shù), 但是在這里我們并沒有找到 load_images 函數(shù)的調(diào)用,因為 load_images 是objc這個庫里面的洗贰,那他們是怎么調(diào)用的呢找岖?
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);
        }
    }
}
  1. load_images 調(diào)用過程

在notifySingle函數(shù)的1019行我們可以看到這個地方有一個回調(diào)指針,在這里會有一個判斷敛滋,sNotifyObjCInit不為空就會調(diào)用這個回調(diào),那sNotifyObjCInit在哪里賦值的呢兴革?

我們在當(dāng)前文件搜索會在 registerObjCNotifiers 函數(shù)看到是在這里賦值的, 賦值為init绎晃,那我們來看一下是哪里調(diào)用了 registerObjCNotifiers 函數(shù),并傳了一個 init杂曲。我們?nèi)炙阉饕幌隆?/p>

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;
}
registerObjCNotifiers函數(shù)調(diào)用的地方.png

在這里我們看到registerObjCNotifiers在這被調(diào)用了庶艾,并傳入了一個init。

接著再搜索 _dyld_objc_notify_register 會發(fā)現(xiàn)找不到了擎勘。那我們用符號斷點的方式咱揍,看看誰調(diào)用了 _dyld_objc_notify_register

_dyld_objc_notify_register符號斷點.png

_dyld_objc_notify_register斷點函數(shù)調(diào)用棧.png

運(yùn)行之后棚饵,通過打印堆棧信息我們可以看到 _dyld_objc_notify_register 函數(shù)是被libobjc文件里面的 _objc_init 函數(shù)調(diào)用了煤裙。這里我們需要看objc的源碼,找到 _objc_init 函數(shù)噪漾,并看看這個地方給 _dyld_objc_notify_register 方法傳了哪些值硼砰。

// _objc_init函數(shù)
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();
// 在這里我們看到了_dyld_objc_notify_register函數(shù)的調(diào)用,且第二個參數(shù)是load_images
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

在這里我們找到了 _dyld_objc_notify_register 的調(diào)用欣硼,并看到了第二個參數(shù)是 load_images,也就是init是 load_images题翰,也就找到了在 notifySingle 函數(shù)的1019行調(diào)用的是 load_images 函數(shù)。這里就看到了從 startload_images 的一個完整過程。

call_load_methods 函數(shù)

_objc_init 函數(shù)進(jìn)的 load_images 函數(shù)豹障,接著進(jìn)入到 call_load_methods 函數(shù)冯事,在這里我們可以看到353行 call_class_loads ,類的 load方法在這個時候被調(diào)用了血公。

// 1.從 `_objc_init` 函數(shù)進(jìn)的 `load_images` 函數(shù)
void
load_images(const char *path __unused, const struct mach_header *mh)
{
    call_load_methods();
}

// 2.進(jìn)入到 `call_load_methods` 函數(shù)
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();

    do {
        // 1. 類的load方法調(diào)用
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. 分類的load方法調(diào)用
        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;
}

C++全局構(gòu)造函數(shù)的加載

看完了 load_images 函數(shù)的調(diào)用過程之后昵仅,我們來到Demo,試一個現(xiàn)象坞笙。

#import "AppDelegate.h"
#import "ViewController.h"

__attribute__((constructor)) void func1() {
    printf("func1 執(zhí)行了");
}

__attribute__((constructor)) void func2() {
    printf("func2 執(zhí)行了");
}

@interface AppDelegate ()
MachO文件結(jié)構(gòu).png
printf打印順序.png

AppDelegate.m 文件添加 __attribute__((constructor)) void func1__attribute__((constructor)) void func2 函數(shù)岩饼,這兩個函數(shù)是全局C++構(gòu)造函數(shù),工程編譯之后薛夜,把編譯文件拖到MachOView應(yīng)用之后籍茧,可以看到文件結(jié)構(gòu)里面多了__mod_init_func文件。且工程運(yùn)行的時候梯澜,通過 printf 打印順序我們可以看到寞冯,func1func12load 之后 main 之前執(zhí)行,那是哪塊代碼來控制 func1func12 類型的函數(shù)的加載呢晚伙?

現(xiàn)在我們回到 dyld 源碼吮龄,來的 recursiveInitialization 函數(shù),并最終來到 ImageLoaderMachO 函數(shù)咆疗。在這里可以看到 doModInitFunctions 函數(shù)其實就是負(fù)責(zé)構(gòu)造函數(shù)的加載漓帚。

// 1.進(jìn)入到ImageLoaderMachO函數(shù)
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
    CRSetCrashLogMessage2(this->getPath());

    // mach-o has -init and static initializers
    doImageInit(context);
// 加載init構(gòu)造方法
    doModInitFunctions(context);
    
    CRSetCrashLogMessage2(NULL);
    
    return (fHasDashInit || fHasInitializers);
}

initializeMainExecutable函數(shù)之后

現(xiàn)在我們回到 dyld 的 main 函數(shù),在 之后我們找到7114行午磁,在這里調(diào)用了 (uintptr_t)gLibSystemHelpers->startGlueToCallExit 函數(shù)尝抖,獲取主程序的 main。把主程序的 main 函數(shù)的地址賦值給 result 迅皇,并判斷 result 是否有值昧辽,最后在 dyld main函數(shù)的結(jié)尾返回 result

{
            // 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;
            }
        }
    }

// dyld main函數(shù)的結(jié)尾
return result;

最后總結(jié)

  • DYLD : 動態(tài)鏈接器登颓,加載所有的庫和可執(zhí)行文件搅荞。
  • dyld 加載流程
    • 程序執(zhí)行從_dyld_start開始 -> dyld`dyldbootstrap::start
    • 進(jìn)入 dyld: main 函數(shù)
    • 配置一些環(huán)境 : rebase_dyld
    • 加載共享緩存
    • 判斷加載模式 DYLD2/DYLD3(閉包模式)
      • 實例化主程序
      • 加載動態(tài)庫(首先是插入動態(tài)庫) (主程序和動態(tài)庫都會添加到 allImages 里面)
      • 鏈接主程序、綁定符號(這里綁定的都非懶加載的框咙、弱符號)等等
      • 最關(guān)鍵的: 初始化方法 initializeMainExecutable (初始化主程序)
        • dyld`ImageLoader::runInitializers
          • dyld`ImageLoader::processInitializers:
            • dyld`ImageLoader::recursiveInitialization:
              • dyld`dyld::notifySingle:函數(shù)

                • 此函數(shù)執(zhí)行一個回調(diào)
                • 通過斷點調(diào)試: 此函數(shù)是_objc_init初始化賦值的一個函數(shù)Load_images
                  • Load_images里面執(zhí)行class_load_methods函數(shù)
                    • call_class_loads函數(shù): 循環(huán)調(diào)用各個類的 load 方法
              • doModInitFunction 函數(shù)

                • 內(nèi)部會調(diào)動全局 C++對象的構(gòu)造函數(shù)attribute((constructor))的 C 函數(shù)
      • 返回主程序的入口函數(shù)咕痛,開始進(jìn)入主程序的 main 函數(shù)

不足之處,還請指正......

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扁耐,一起剝皮案震驚了整個濱河市暇检,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌婉称,老刑警劉巖块仆,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件构蹬,死亡現(xiàn)場離奇詭異,居然都是意外死亡悔据,警方通過查閱死者的電腦和手機(jī)庄敛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來科汗,“玉大人藻烤,你說我怎么就攤上這事⊥诽希” “怎么了怖亭?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長坤检。 經(jīng)常有香客問我兴猩,道長,這世上最難降的妖魔是什么早歇? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任倾芝,我火速辦了婚禮,結(jié)果婚禮上箭跳,老公的妹妹穿的比我還像新娘晨另。我一直安慰自己,他們只是感情好谱姓,可當(dāng)我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布借尿。 她就那樣靜靜地躺著,像睡著了一般屉来。 火紅的嫁衣襯著肌膚如雪垛玻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天奶躯,我揣著相機(jī)與錄音,去河邊找鬼亿驾。 笑死嘹黔,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的莫瞬。 我是一名探鬼主播儡蔓,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼疼邀!你這毒婦竟也來了喂江?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤旁振,失蹤者是張志新(化名)和其女友劉穎获询,沒想到半個月后涨岁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡吉嚣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年梢薪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尝哆。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡秉撇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出秋泄,到底是詐尸還是另有隱情琐馆,我是刑警寧澤,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布恒序,位于F島的核電站瘦麸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏奸焙。R本人自食惡果不足惜瞎暑,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望与帆。 院中可真熱鬧了赌,春花似錦、人聲如沸玄糟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阵翎。三九已至逢并,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間郭卫,已是汗流浹背砍聊。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留贰军,地道東北人玻蝌。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓播歼,卻偏偏與公主長得像妻怎,于是被迫代替她去往敵國和親屠阻。 傳聞我的和親對象是個殘疾皇子没讲,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,665評論 2 354

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