iOS開發(fā) - 程序加載過程之dyld流程分析

程序真實(shí)第一步在哪糠排?

我們都知道傍衡,網(wǎng)上普遍流行的一種說法就是,一個(gè)iOS程序的入口就是我們的main函數(shù)区匠,那么在main函數(shù)真的就沒別的了嗎干像?答案是否的,下面一個(gè)小案例探究一下:

1.1程序入口探究

在程序main.m文件中寫下一個(gè)C方法kcFunc()驰弄,在main函數(shù)打下斷點(diǎn)麻汰,然后在ViewController.m文件中寫下一個(gè)+load()
1.2程序入口探究

運(yùn)行程序,來到斷點(diǎn)處揩懒,點(diǎn)擊左側(cè)的堆棧信息查看什乙,在main之前還有兩個(gè)start挽封,點(diǎn)擊查看發(fā)現(xiàn)是匯編 libdyld.dylib start
1.3查看程序運(yùn)行堆棧信息

或者利用lldb 命令bt 也可查看程序的堆棧信息:
1.4查看程序運(yùn)行堆棧信息

兩種方式查看堆棧信息可知已球,在main函數(shù)調(diào)用之前libdyld.dylibstart就已經(jīng)被調(diào)用,所以說libdyld.dylibstart 才是程序的第一步辅愿,那么libdyld.dylib 又是啥呢智亮?

dylib 源碼分析

dyld簡介

dyld (the dynamic link editor) 是蘋果的動(dòng)態(tài)鏈接器,是蘋果操作系統(tǒng)一個(gè)重要組成部分点待,在系統(tǒng)內(nèi)核做好程序準(zhǔn)備工作之后阔蛉,交由dyld負(fù)責(zé)余下的工作。而且它是開源的癞埠,任何人可以通過蘋果官網(wǎng)下載它的源碼來閱讀理解它的運(yùn)作方式状原,了解系統(tǒng)加載動(dòng)態(tài)庫的細(xì)節(jié)。

dyld源碼下載地址 筆者下載的是750.6版本

在已知程序入口函數(shù)main()之前的libdyld.dylib start之后苗踪,我們打開源碼在dyldStartup.s文件中找到__dyld_start:

__dyld_start:
    popl    %edx        # edx = mh of app
    pushl   $0      # push a zero for debugger end of frames marker
    movl    %esp,%ebp   # pointer to base of kernel frame
    andl    $-16,%esp       # force SSE alignment
    subl    $32,%esp    # room for locals and outgoing parameters

    call    L__dyld_start_picbase
L__dyld_start_picbase:
    popl    %ebx        # set %ebx to runtime value of picbase

        // app_mh    MachOLoaded* appsMachHeader
        // argc 和 argv  main函數(shù)帶進(jìn)來的
        // dyld_mh   MachOLoaded* dyldsMachHeader
    # call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
    subl    $L__dyld_start_picbase-__dyld_start, %ebx # ebx = &__dyld_start
    subl    $0x1000, %ebx   # ebx = load address of dyld
    movl    %edx,(%esp) # param1 = app_mh
    movl    4(%ebp),%eax
    movl    %eax,4(%esp)    # param2 = argc
    lea     8(%ebp),%eax
    movl    %eax,8(%esp)    # param3 = argv
    movl    %ebx,12(%esp)   # param4 = dyld load address
    lea 28(%esp),%eax
    movl    %eax,16(%esp)   # param5 = &startGlue
    call    __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
    movl    28(%esp),%edx
    cmpl    $0,%edx
    jne Lnew

        # clean up stack and jump to "start" in main executable
    movl    %ebp,%esp   # restore the unaligned stack pointer
    addl    $4,%esp     # remove debugger end frame marker
    movl    $0,%ebp     # restore ebp back to zero
    jmp *%eax       # jump to the entry point

    # LC_MAIN case, set up stack for call to main()
Lnew:   movl    4(%ebp),%ebx
    movl    %ebx,(%esp) # main param1 = argc
    leal    8(%ebp),%ecx
    movl    %ecx,4(%esp)    # main param2 = argv
    leal    0x4(%ecx,%ebx,4),%ebx
    movl    %ebx,8(%esp)    # main param3 = env
Lapple: movl    (%ebx),%ecx # look for NULL ending env[] array
    add $4,%ebx
    testl   %ecx,%ecx
    jne Lapple      # once found, next pointer is "apple" parameter now in %ebx
    movl    %ebx,12(%esp)   # main param4 = apple
    pushl   %edx        # simulate return address into _start in libdyld
    jmp *%eax       # jump to main(argc,argv,env,apple) with return address set to _start

能看到注釋里面有call dyldbootstrap::start方法(我們可以全局搜索start(const)颠区,我們可以跟著注釋方法進(jìn)去一探究竟:

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

    // Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536>
    dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);

    // if kernel had to slide dyld, we need to fix up load sensitive locations
    // we have to do this before using any global variables
    // dyld 重定位
    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;

    // set up random value for stack canary
    // 棧溢出保護(hù)
    __guard_setup(apple);

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

    // now that we are done bootstrapping dyld, call dyld's main
    uintptr_t appsSlide = appsMachHeader->getSlide();
    return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

start方法主要做了很多dyld初始化工作:

  • rebaseDyld dyld重定位
  • __guard_setup 棧溢出保護(hù)

在結(jié)束dyld初始化工作后,函數(shù)調(diào)用 dyld::_main() 函數(shù)通铲,再將返回值傳遞給__dyld_start去調(diào)用真正的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)
{
    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);
    }

    //Check and see if there are any kernel flags
    dyld3::BootArgs::setFlags(hexToUInt64(_simple_getenv(apple, "dyld_flags"), nullptr));

    // Grab the cdHash of the main executable from the environment
    // 第一步:設(shè)置運(yùn)行環(huán)境
    uint8_t mainExecutableCDHashBuffer[20];
    const uint8_t* mainExecutableCDHash = nullptr;
    if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) )
        // 獲取主程序的hash
        mainExecutableCDHash = mainExecutableCDHashBuffer;

#if !TARGET_OS_SIMULATOR
    // Trace dyld's load
    notifyKernelAboutImage((macho_header*)&__dso_handle, _simple_getenv(apple, "dyld_file"));
    // Trace the main executable's load
    notifyKernelAboutImage(mainExecutableMH, _simple_getenv(apple, "executable_file"));
#endif

    uintptr_t result = 0;
    // 獲取主程序的macho_header結(jié)構(gòu)
    sMainExecutableMachHeader = mainExecutableMH;
    // 獲取主程序的slide值
    sMainExecutableSlide = mainExecutableSlide;


    // Set the platform ID in the all image infos so debuggers can tell the process type
    // FIXME: This can all be removed once we make the kernel handle it in rdar://43369446
    if (gProcessInfo->version >= 16) {
        __block bool platformFound = false;
        ((dyld3::MachOFile*)mainExecutableMH)->forEachSupportedPlatform(^(dyld3::Platform platform, uint32_t minOS, uint32_t sdk) {
            if (platformFound) {
                halt("MH_EXECUTE binaries may only specify one platform");
            }
            gProcessInfo->platform = (uint32_t)platform;
            platformFound = true;
        });
        if (gProcessInfo->platform == (uint32_t)dyld3::Platform::unknown) {
            // There were no platforms found in the binary. This may occur on macOS for alternate toolchains and old binaries.
            // It should never occur on any of our embedded platforms.
#if __MAC_OS_X_VERSION_MIN_REQUIRED
            gProcessInfo->platform = (uint32_t)dyld3::Platform::macOS;
#else
            halt("MH_EXECUTE binaries must specify a minimum supported OS version");
#endif
        }
    }

#if __MAC_OS_X_VERSION_MIN_REQUIRED
    // Check to see if we need to override the platform
    const char* forcedPlatform = _simple_getenv(envp, "DYLD_FORCE_PLATFORM");
    if (forcedPlatform) {
        if (strncmp(forcedPlatform, "6", 1) != 0) {
            halt("DYLD_FORCE_PLATFORM is only supported for platform 6");
        }
        const dyld3::MachOFile* mf = (dyld3::MachOFile*)sMainExecutableMachHeader;
        if (mf->allowsAlternatePlatform()) {
            gProcessInfo->platform = PLATFORM_IOSMAC;
        }
    }

    // if this is host dyld, check to see if iOS simulator is being run
    const char* rootPath = _simple_getenv(envp, "DYLD_ROOT_PATH");
    if ( (rootPath != NULL) ) {
        // look to see if simulator has its own dyld
        char simDyldPath[PATH_MAX]; 
        strlcpy(simDyldPath, rootPath, PATH_MAX);
        strlcat(simDyldPath, "/usr/lib/dyld_sim", PATH_MAX);
        int fd = my_open(simDyldPath, O_RDONLY, 0);
        if ( fd != -1 ) {
            const char* errMessage = useSimulatorDyld(fd, mainExecutableMH, simDyldPath, argc, argv, envp, apple, startGlue, &result);
            if ( errMessage != NULL )
                halt(errMessage);
            return result;
        }
    }
    else {
        ((dyld3::MachOFile*)mainExecutableMH)->forEachSupportedPlatform(^(dyld3::Platform platform, uint32_t minOS, uint32_t sdk) {
            if ( dyld3::MachOFile::isSimulatorPlatform(platform) )
                halt("attempt to run simulator program outside simulator (DYLD_ROOT_PATH not set)");
        });
    }
#endif

    CRSetCrashLogMessage("dyld: launch started");
    // 設(shè)置上下文信息
    setContext(mainExecutableMH, argc, argv, envp, apple);

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

    // <rdar://problem/13868260> Remove interim apple[0] transition code from dyld
    if (!sExecPath) sExecPath = apple[0];

#if __IPHONE_OS_VERSION_MIN_REQUIRED && !TARGET_OS_SIMULATOR
    // <rdar://54095622> kernel is not passing a real path for main executable
    if ( strncmp(sExecPath, "/var/containers/Bundle/Application/", 35) == 0 ) {
        if ( char* newPath = (char*)malloc(strlen(sExecPath)+10) ) {
            strcpy(newPath, "/private");
            strcat(newPath, sExecPath);
            sExecPath = newPath;
        }
    }
#endif

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

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

    // 進(jìn)程的頭環(huán)境配置
    configureProcessRestrictions(mainExecutableMH, envp);

    // Check if we should force dyld3.  Note we have to do this outside of the regular env parsing due to AMFI
    if ( dyld3::internalInstall() ) {
        if (const char* useClosures = _simple_getenv(envp, "DYLD_USE_CLOSURES")) {
            if ( strcmp(useClosures, "0") == 0 ) {
                sClosureMode = ClosureMode::Off;
            } else if ( strcmp(useClosures, "1") == 0 ) {
#if __MAC_OS_X_VERSION_MIN_REQUIRED

#if __i386__
                // don't support dyld3 for 32-bit macOS
#else
                // Also don't support dyld3 for iOSMac right now
                if ( gProcessInfo->platform != PLATFORM_IOSMAC ) {
                    sClosureMode = ClosureMode::On;
                }
#endif // __i386__

#else
                sClosureMode = ClosureMode::On;
#endif // __MAC_OS_X_VERSION_MIN_REQUIRED
            } else {
                dyld::warn("unknown option to DYLD_USE_CLOSURES.  Valid options are: 0 and 1\n");
            }

        }
    }

#if __MAC_OS_X_VERSION_MIN_REQUIRED
    if ( !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache ) {
        pruneEnvironmentVariables(envp, &apple);
        // set again because envp and apple may have changed or moved
        setContext(mainExecutableMH, argc, argv, envp, apple);
    }
    else
#endif
    {
        // 檢測環(huán)境變量
        checkEnvironmentVariables(envp);
        defaultUninitializedFallbackPaths(envp);
    }
#if __MAC_OS_X_VERSION_MIN_REQUIRED
    if ( gProcessInfo->platform == PLATFORM_IOSMAC ) {
        gLinkContext.rootPaths = parseColonList("/System/iOSSupport", NULL);
        gLinkContext.iOSonMac = true;
        if ( sEnv.DYLD_FALLBACK_LIBRARY_PATH == sLibraryFallbackPaths )
            sEnv.DYLD_FALLBACK_LIBRARY_PATH = sRestrictedLibraryFallbackPaths;
        if ( sEnv.DYLD_FALLBACK_FRAMEWORK_PATH == sFrameworkFallbackPaths )
            sEnv.DYLD_FALLBACK_FRAMEWORK_PATH = sRestrictedFrameworkFallbackPaths;
    }
    else if ( ((dyld3::MachOFile*)mainExecutableMH)->supportsPlatform(dyld3::Platform::driverKit) ) {
        gLinkContext.driverKit = true;
        gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion;
    }
#endif
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);

    // Parse this envirionment variable outside of the regular logic as we want to accept
    // this on binaries without an entitelment
#if !TARGET_OS_SIMULATOR
    if ( _simple_getenv(envp, "DYLD_JUST_BUILD_CLOSURE") != nullptr ) {
#if TARGET_OS_IPHONE
        const char* tempDir = getTempDir(envp);
        if ( (tempDir != nullptr) && (geteuid() != 0) ) {
            // Use realpath to prevent something like TMPRIR=/tmp/../usr/bin
            char realPath[PATH_MAX];
            if ( realpath(tempDir, realPath) != NULL )
                tempDir = realPath;
            if (strncmp(tempDir, "/private/var/mobile/Containers/", strlen("/private/var/mobile/Containers/")) == 0) {
                sJustBuildClosure = true;
            }
        }
#endif
        // If we didn't like the format of TMPDIR, just exit.  We don't want to launch the app as that would bring up the UI
        if (!sJustBuildClosure) {
            _exit(EXIT_SUCCESS);
        }
    }
#endif

    if ( sJustBuildClosure )
        sClosureMode = ClosureMode::On;
    // 獲取主機(jī)信息 可理解為 程序結(jié)構(gòu)
    getHostInfo(mainExecutableMH, mainExecutableSlide);
    
//--- 第一步結(jié)束

    // load shared cache
    // 第二步:加載共享緩存
    // 檢查緩存共享區(qū)域是否開啟,iOS必須開啟
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
        if ( sSharedCacheOverrideDir)
            mapSharedCache();
#else
        // 共享緩存加載
        mapSharedCache();
#endif
    }

    // If we haven't got a closure mode yet, then check the environment and cache type
    if ( sClosureMode == ClosureMode::Unset ) {
        // First test to see if we forced in dyld2 via a kernel boot-arg
        if ( dyld3::BootArgs::forceDyld2() ) {
            sClosureMode = ClosureMode::Off;
        } else if ( inDenyList(sExecPath) ) {
            sClosureMode = ClosureMode::Off;
        } else if ( sEnv.hasOverride ) {
            sClosureMode = ClosureMode::Off;
        } else if ( dyld3::BootArgs::forceDyld3() ) {
            sClosureMode = ClosureMode::On;
        } else {
            sClosureMode = getPlatformDefaultClosureMode();
        }
    }

#if !TARGET_OS_SIMULATOR
    if ( sClosureMode == ClosureMode::Off ) {
        if ( gLinkContext.verboseWarnings )
            dyld::log("dyld: not using closure because of DYLD_USE_CLOSURES or -force_dyld2=1 override\n");
    } else {
        const dyld3::closure::LaunchClosure* mainClosure = nullptr;
        dyld3::closure::LoadedFileInfo mainFileInfo;
        mainFileInfo.fileContent = mainExecutableMH;
        mainFileInfo.path = sExecPath;
        // FIXME: If we are saving this closure, this slice offset/length is probably wrong in the case of FAT files.
        mainFileInfo.sliceOffset = 0;
        mainFileInfo.sliceLen = -1;
        struct stat mainExeStatBuf;
        if ( ::stat(sExecPath, &mainExeStatBuf) == 0 ) {
            mainFileInfo.inode = mainExeStatBuf.st_ino;
            mainFileInfo.mtime = mainExeStatBuf.st_mtime;
        }
        // check for closure in cache first
        if ( sSharedCacheLoadInfo.loadAddress != nullptr ) {
            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());
        }

        // We only want to try build a closure at runtime if its an iOS third party binary, or a macOS binary from the shared cache
        bool allowClosureRebuilds = false;
        if ( sClosureMode == ClosureMode::On ) {
            allowClosureRebuilds = true;
        } else if ( (sClosureMode == ClosureMode::PreBuiltOnly) && (mainClosure != nullptr) ) {
            allowClosureRebuilds = true;
        }

        if ( (mainClosure != nullptr) && !closureValid(mainClosure, mainFileInfo, mainExecutableCDHash, true, envp) )
            mainClosure = nullptr;

        // If we didn't find a valid cache closure then try build a new one
        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);
            if ( mainClosure == nullptr ) {
                // if  no cached closure found, build new one
                mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp);
            }
        }

        // exit dyld after closure is built, without running program
        if ( sJustBuildClosure )
            _exit(EXIT_SUCCESS);

        // try using launch closure
        if ( mainClosure != nullptr ) {
            CRSetCrashLogMessage("dyld3: launch started");
            bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
                                              mainExecutableSlide, argc, argv, envp, apple, &result, startGlue);
            if ( !launched && allowClosureRebuilds ) {
                // closure is out of date, build new one
                mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp);
                if ( mainClosure != nullptr ) {
                    launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
                                                 mainExecutableSlide, argc, argv, envp, apple, &result, startGlue);
                }
            }
            if ( launched ) {
                gLinkContext.startedInitializingMainExecutable = true;
#if __has_feature(ptrauth_calls)
                // start() calls the result pointer as a function pointer so we need to sign it.
                result = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)result, 0, 0);
#endif
                if (sSkipMain)
                    result = (uintptr_t)&fake_main;
                return result;
            }
            else {
                if ( gLinkContext.verboseWarnings ) {
                    dyld::log("dyld: unable to use closure %p\n", mainClosure);
                }
            }
        }
    }
#endif // TARGET_OS_SIMULATOR
    // could not use closure info, launch old way



    // install gdb notifier
    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);

#if !TARGET_OS_SIMULATOR
#ifdef WAIT_FOR_SYSTEM_ORDER_HANDSHAKE
    // <rdar://problem/6849505> Add gating mechanism to dyld support system order file generation process
    WAIT_FOR_SYSTEM_ORDER_HANDSHAKE(dyld::gProcessInfo->systemOrderFlag);
#endif
#endif


    try {
        // add dyld itself to UUID list
        addDyldImageToUUIDList();

#if SUPPORT_ACCELERATE_TABLES
#if __arm64e__
        // Disable accelerator tables when we have threaded rebase/bind, which is arm64e executables only for now.
        if (sMainExecutableMachHeader->cpusubtype == CPU_SUBTYPE_ARM64E)
            sDisableAcceleratorTables = true;
#endif
        bool mainExcutableAlreadyRebased = false;
        if ( (sSharedCacheLoadInfo.loadAddress != nullptr) && !dylibsCanOverrideCache() && !sDisableAcceleratorTables && (sSharedCacheLoadInfo.loadAddress->header.accelerateInfoAddr != 0) ) {
            struct stat statBuf;
            if ( ::stat(IPHONE_DYLD_SHARED_CACHE_DIR "no-dyld2-accelerator-tables", &statBuf) != 0 )
                sAllCacheImagesProxy = ImageLoaderMegaDylib::makeImageLoaderMegaDylib(&sSharedCacheLoadInfo.loadAddress->header, sSharedCacheLoadInfo.slide, mainExecutableMH, gLinkContext);
        }

reloadAllImages:
#endif


    #if __MAC_OS_X_VERSION_MIN_REQUIRED
        gLinkContext.strictMachORequired = false;
        // <rdar://problem/22805519> be less strict about old macOS mach-o binaries
        ((dyld3::MachOFile*)mainExecutableMH)->forEachSupportedPlatform(^(dyld3::Platform platform, uint32_t minOS, uint32_t sdk) {
            if ( (platform == dyld3::Platform::macOS) && (sdk >= DYLD_PACKED_VERSION(10,15,0)) ) {
                gLinkContext.strictMachORequired = true;
            }
        });
        if ( gLinkContext.iOSonMac )
            gLinkContext.strictMachORequired = true;
    #else
        // simulators, iOS, tvOS, watchOS, are always strict
        gLinkContext.strictMachORequired = true;
    #endif


        CRSetCrashLogMessage(sLoadingCrashMessage);
        // instantiate ImageLoader for main executable
        // 第三步:實(shí)例化主程序
        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
        {
            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 ) {
    #if TARGET_OS_WATCH
                throwf("app was built for watchOS %d.%d which is newer than this simulator %d.%d",
                        mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF),
                        dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF));
    #elif TARGET_OS_TV
                throwf("app was built for tvOS %d.%d which is newer than this simulator %d.%d",
                        mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF),
                        dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF));
    #else
                throwf("app was built for iOS %d.%d which is newer than this simulator %d.%d",
                        mainMinOS >> 16, ((mainMinOS >> 8) & 0xFF),
                        dyldMinOS >> 16, ((dyldMinOS >> 8) & 0xFF));
    #endif
            }
        }
#endif


    #if SUPPORT_ACCELERATE_TABLES
        sAllImages.reserve((sAllCacheImagesProxy != NULL) ? 16 : INITIAL_IMAGE_COUNT);
    #else
        sAllImages.reserve(INITIAL_IMAGE_COUNT);
    #endif

        // Now that shared cache is loaded, setup an versioned dylib overrides
    #if SUPPORT_VERSIONED_PATHS
        checkVersionedPaths();
    #endif


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

        // load any inserted libraries
        // 第四步:加載插入的動(dòng)態(tài)庫
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                loadInsertedDylib(*lib);
        }
        // record count of inserted libraries so that a flat search will look at 
        // inserted libraries, then main, then others.
        // 記錄插入的動(dòng)態(tài)庫的數(shù)量
        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
        // 第五步:鏈接主程序        Executable:可執(zhí)行的意思
        link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
        sMainExecutable->setNeverUnloadRecursive();
        if ( sMainExecutable->forceFlat() ) {
            gLinkContext.bindFlat = true;
            gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
        }

        // link any inserted libraries
        // do this after linking main executable so that any dylibs pulled in by inserted 
        // dylibs (e.g. libSystem) will not be in front of dylibs the program uses
        // 第六步:鏈接插入的動(dòng)態(tài)庫
        if ( sInsertedDylibCount > 0 ) {
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                ImageLoader* image = sAllImages[i+1];
                link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
                image->setNeverUnloadRecursive();
            }
            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);
                }
            }
        }

        if ( gLinkContext.allowInterposing ) {
            // <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES
            for (long i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {
                ImageLoader* image = sAllImages[i];
                if ( image->inSharedCache() )
                    continue;
                image->registerInterposing(gLinkContext);
            }
        }
    #if g
        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

        // apply interposing to initial set of images
        for(int i=0; i < sImageRoots.size(); ++i) {
            sImageRoots[i]->applyInterposing(gLinkContext);
        }
        ImageLoader::applyInterposingToDyldCache(gLinkContext);

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

        // Bind and notify for the inserted images now interposing has been registered
        if ( sInsertedDylibCount > 0 ) {
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                ImageLoader* image = sAllImages[i+1];
                image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
            }
        }
        
        // <rdar://problem/12186933> do weak binding only after all inserted images linked
        // 第七步:弱引用綁定主程序
        sMainExecutable->weakBind(gLinkContext);
        gLinkContext.linkingMainExecutable = false;

        sMainExecutable->recursiveMakeDataReadOnly(gLinkContext);

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

        // notify any montoring proccesses that this process is about to enter main()
        // 第九步:查找程序入口函數(shù)main并返回
        notifyMonitoringDyldMain();
        if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
            dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 2);
        }
        ARIADNEDBG_CODE(220, 1);

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

    CRSetCrashLogMessage("dyld2 mode");
#if !TARGET_OS_SIMULATOR
    if (sLogClosureFailure) {
        // We failed to launch in dyld3, but dyld2 can handle it. synthesize a crash report for analytics
        dyld3::syntheticBacktrace("Could not generate launchClosure, falling back to dyld2", true);
    }
#endif

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

這代碼也太長了颅夺。朋截。。代碼規(guī)范呢吧黄?哪去了部服?
dyld流程可總結(jié)為九個(gè)步驟:

  • 第一步:設(shè)置運(yùn)行環(huán)境
  • 第二步:加載共享緩存
  • 第三步:實(shí)例化主程序
  • 第四步:加載插入的動(dòng)態(tài)庫
  • 第五步:鏈接主程序
  • 第六步:鏈接插入的動(dòng)態(tài)庫
  • 第七步:執(zhí)行弱引用綁定
  • 第八步:執(zhí)行初始化方法
  • 第九步:查找程序入口main然后返回

第一步:設(shè)置運(yùn)行環(huán)境

這一步主要是設(shè)置程序的運(yùn)行環(huán)境運(yùn)行條件等準(zhǔn)備工作拗慨,包括環(huán)境廓八,平臺(tái)厦酬,版本路徑瘫想,主機(jī)信息仗阅,設(shè)置程序上下文信息

// 獲取主程序的hash
    mainExecutableCDHash = mainExecutableCDHashBuffer;
// 獲取主程序的macho_header結(jié)構(gòu)
    sMainExecutableMachHeader = mainExecutableMH;
// 獲取主程序的slide值
    sMainExecutableSlide = mainExecutableSlide;
// 設(shè)置上下文信息
    setContext(mainExecutableMH, argc, argv, envp, apple);
// 獲取主程序路徑
    sExecPath = _simple_getenv(apple, "executable_path");
// 進(jìn)程的頭環(huán)境配置
    configureProcessRestrictions(mainExecutableMH, envp);
// 檢測環(huán)境變量
    checkEnvironmentVariables(envp);
    defaultUninitializedFallbackPaths(envp);
// 獲取主機(jī)信息 可理解為 程序結(jié)構(gòu)
    getHostInfo(mainExecutableMH, mainExecutableSlide);

第二步:加載共享緩存

首先檢查dyld共享緩存區(qū)是否禁用iOS必須開啟国夜,在checkSharedRegionDisable里面iOS環(huán)境下注釋:

// iOS cannot run without shared region   
// 檢查緩存共享區(qū)域是否開啟
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
// 共享緩存加載
    mapSharedCache();
// mapSharedCache 函數(shù)
static void mapSharedCache()
{
    dyld3::SharedCacheOptions opts;
    opts.cacheDirOverride   = sSharedCacheOverrideDir;
    opts.forcePrivate       = (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion);


#if __x86_64__ && !TARGET_OS_SIMULATOR
    opts.useHaswell         = sHaswell;
#else
    opts.useHaswell         = false;
#endif
    opts.verbose            = gLinkContext.verboseMapping;
//--- 加載dyld緩存主函數(shù)
    loadDyldCache(opts, &sSharedCacheLoadInfo);

    // update global state
    if ( sSharedCacheLoadInfo.loadAddress != nullptr ) {
        gLinkContext.dyldCache                              = sSharedCacheLoadInfo.loadAddress;
        dyld::gProcessInfo->processDetachedFromSharedRegion = opts.forcePrivate;
        dyld::gProcessInfo->sharedCacheSlide                = sSharedCacheLoadInfo.slide;
        dyld::gProcessInfo->sharedCacheBaseAddress          = (unsigned long)sSharedCacheLoadInfo.loadAddress;
        sSharedCacheLoadInfo.loadAddress->getUUID(dyld::gProcessInfo->sharedCacheUUID);
        dyld3::kdebug_trace_dyld_image(DBG_DYLD_UUID_SHARED_CACHE_A, sSharedCacheLoadInfo.path, (const uuid_t *)&dyld::gProcessInfo->sharedCacheUUID[0], {0,0}, {{ 0, 0 }}, (const mach_header *)sSharedCacheLoadInfo.loadAddress);
    }
}
// loadDyldCache函數(shù)
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
        return mapCachePrivate(options, results);
    }
    else {
        // fast path: when cache is already mapped into shared region
        bool hasError = false;
        if ( reuseExistingCache(options, results) ) {
            hasError = (results->errorMessage != nullptr);
        } else {
            // slow path: this is first process to load cache
            hasError = mapCacheSystemWide(options, results);
        }
        return hasError;
    }
#endif
}

mapSharedCache函數(shù)主要調(diào)用loadDyldCache 函數(shù)减噪,loadDyldCache函數(shù)主要有三種方式加載共享緩存:

  • mapCachePrivate() 僅加載到當(dāng)前進(jìn)程
  • 共享緩存已經(jīng)加載過,不做任何處理
  • mapCacheSystemWide() 未加載過车吹,首次加載

第三步:實(shí)例化主程序

這一步主要是將主程序的Mach-O加載進(jìn)內(nèi)存筹裕,并實(shí)例化一個(gè)ImageLoaderinstantiateFromLoadedImage() 首先調(diào)用isCompatibleMachO() 函數(shù)檢測當(dāng)前進(jìn)程的magic窄驹、cputype朝卒、cpusubtype 等相關(guān)屬性,判斷Mach-O文件的兼容性乐埠,如果兼容性滿足抗斤,就調(diào)用ImageLoaderMachO::instantiateMainExecutable()實(shí)例化主程序的ImageLoader

// 第三步:實(shí)例化主程序
        sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
        gLinkContext.mainExecutable = sMainExecutable;
        gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
// The kernel maps in main executable before dyld gets control.  We need to 
// make an ImageLoader* for the already mapped in main executable.
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
    // try mach-o loader
    if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
        ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
        addImage(image);
        return (ImageLoaderMachO*)image;
    }
    
    throw "main executable not a known format";
}
// create image for main executable
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);
    // instantiate concrete class based on content of load commands
    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
}

instantiateMainExecutable() 函數(shù)中通過sniffLoadCommands() 判斷這個(gè)mach-o文件是普通的還是壓縮的LINKEDIT,以及它有多少段丈咐。
最后根據(jù)compressed 是否壓縮來實(shí)例化最后返回的ImageLoader瑞眼。

第四步:加載插入的動(dòng)態(tài)庫

// 第四步:加載插入的動(dòng)態(tài)庫
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                loadInsertedDylib(*lib);
        }

loadInsertedDylib() 函數(shù)中設(shè)置了一個(gè)LoadContext,并為它配置一些參數(shù)后棵逊,調(diào)用load() 方法:

ImageLoader* load(const char* path, const LoadContext& context, unsigned& cacheIndex)
{
    ......
// try all path permutations and check against existing loaded images

    ImageLoader* image = loadPhase0(path, orgPath, context, cacheIndex, NULL);
    if ( image != NULL ) {
        CRSetCrashLogMessage2(NULL);
        return image;
    }

    // try all path permutations and try open() until first success
    std::vector<const char*> exceptions;
    image = loadPhase0(path, orgPath, context, cacheIndex, &exceptions);
#if !TARGET_OS_SIMULATOR
    // <rdar://problem/16704628> support symlinks on disk to a path in dyld shared cache
    if ( image == NULL)
        image = loadPhase2cache(path, orgPath, context, cacheIndex, &exceptions);
#endif
    CRSetCrashLogMessage2(NULL);
    if ( image != NULL ) {
        // <rdar://problem/6916014> leak in dyld during dlopen when using DYLD_ variables
        for (std::vector<const char*>::iterator it = exceptions.begin(); it != exceptions.end(); ++it) {
            free((void*)(*it));
        }
        // if loaded image is not from cache, but original path is in cache
        // set gSharedCacheOverridden flag to disable some ObjC optimizations
        if ( !gSharedCacheOverridden && !image->inSharedCache() && image->isDylib() && dyld3::MachOFile::isSharedCacheEligiblePath(path) && inSharedCache(path) ) {
            gSharedCacheOverridden = true;
        }
        return image;
    }
    ......
}

第五步:鏈接主程序

// 第五步:鏈接主程序        Executable:可執(zhí)行的意思
        link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, 
        true, ImageLoader::RPathChain(NULL, NULL), -1);

調(diào)用link()函數(shù)將實(shí)例化后的主程序進(jìn)行動(dòng)態(tài)修正伤疙,讓二進(jìn)制變?yōu)榭烧?zhí)行的狀態(tài)。link()函數(shù)內(nèi)部調(diào)用了ImgaeLoader::link() 函數(shù)辆影,主要做了下面幾件事:

  • this->recursiveLoadLibraries() 遞歸加載依賴庫進(jìn)內(nèi)存
  • this->recursiveUpdateDepth() 遞歸更新依賴庫的路徑
  • this->recursiveRebaseWithAccounting() 遞歸重定位主程序和依賴庫
  • this->recursiveBindWithAccounting() 遞歸將主程序和依賴庫執(zhí)行符號(hào)表綁定(鏈接動(dòng)態(tài)庫使用)
  • this->weakBind() 如果不是正在鏈接主程序二進(jìn)制徒像,那就主程序弱符號(hào)綁定(鏈接動(dòng)態(tài)庫使用)
  • this->recursiveApplyInterposing() 遞歸申請可插入依賴庫權(quán)限
  • this->recursiveMakeDataReadOnly() 遞歸設(shè)置所有信息只讀(鏈接動(dòng)態(tài)庫使用)
  • this->recursiveGetDOFSections() 注冊DOF節(jié)

代碼如下所示:

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);
    uint64_t t0 = mach_absolute_time();
       // 遞歸加載依賴庫進(jìn)內(nèi)存
    this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
    context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly);

    // we only do the loading step for preflights
    if ( preflightOnly )
        return;

    uint64_t t1 = mach_absolute_time();
       // 遞歸更新依賴庫的路徑
    context.clearAllDepths();
    this->recursiveUpdateDepth(context.imageCount());

    __block uint64_t t2, t3, t4, t5;
    {
        dyld3::ScopedTimer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0, 0, 0);
        t2 = mach_absolute_time();
        // 遞歸重定位主程序和依賴庫
        this->recursiveRebaseWithAccounting(context);
        context.notifyBatch(dyld_image_state_rebased, false);

        t3 = mach_absolute_time();
        if ( !context.linkingMainExecutable )
            // 遞歸將主程序和依賴庫執(zhí)行符號(hào)表綁定
            this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);

        t4 = mach_absolute_time();
        if ( !context.linkingMainExecutable )
        // 如果不是正在鏈接主程序二進(jìn)制,那就主程序弱符號(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);
        // 遞歸申請可插入依賴庫權(quán)限
        this->recursiveApplyInterposing(context);
    }

    // now that all fixups are done, make __DATA_CONST segments read-only
    if ( !context.linkingMainExecutable )
      // 遞歸設(shè)置所有信息只讀
        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;
       //  注冊DOF節(jié)
        this->recursiveGetDOFSections(context, dofs);
        context.registerDOFs(dofs);
    }
    uint64_t t7 = mach_absolute_time();

    // clear error strings
    (*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;
}

第六步:鏈接插入的動(dòng)態(tài)庫

這一步跟鏈接主程序一樣蛙讥,將sAllImages中的ImageLoader遍歷出來锯蛀,然后調(diào)用link()進(jìn)行鏈接,需要注意的是键菱,sAllImages中保存的第一個(gè)是主程序的鏡像谬墙,所以要獲取所有的動(dòng)態(tài)庫的ImageLoader,就必須從i + 1 開始遍歷:

for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
    ImageLoader* image = sAllImages[i+1];
    link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
    image->setNeverUnloadRecursive();
}

第七步:執(zhí)行主程序弱符號(hào)綁定

weakBind()首先通過getCoalescedImages()合并所有動(dòng)態(tài)庫的弱符號(hào)到一個(gè)列表里经备,然后調(diào)用initializeCoalIterator()對需要綁定的弱符號(hào)進(jìn)行排序拭抬,接著調(diào)用incrementCoalIterator()讀取dyld_info_command結(jié)構(gòu)的weak_bind_offweak_bind_size字段,確定弱符號(hào)的數(shù)據(jù)偏移與大小侵蒙,最終進(jìn)行弱符號(hào)綁定
代碼略造虎,太多了。纷闺。算凿。

第八步:執(zhí)行初始化方法

這一步就開始進(jìn)行初始化工作了份蝴,initializeMainExecutable() 函數(shù)中調(diào)用 runInitializers() 函數(shù),接著依次調(diào)用 processInitializers() 函數(shù)進(jìn)行一些初始化準(zhǔn)備工作氓轰,接著recursiveInitialization() 函數(shù)調(diào)用進(jìn)行初始化工作婚夫,接著全局搜索recursiveInitialization(,找到ImageLoader.cpp中的此方法定義署鸡,看重點(diǎn)案糙,函數(shù)里面我們看到noffitySingle()單個(gè)通知注入

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

    if ( fState < dyld_image_state_dependents_initialized-1 ) {
        uint8_t oldState = fState;
        // break cycles
        fState = dyld_image_state_dependents_initialized-1;
        try {
            // initialize lower level libraries first
            // 先初始化底層依賴庫
            for(unsigned int i=0; i < libraryCount(); ++i) {
                ImageLoader* dependentImage = libImage(i);
                if ( dependentImage != NULL ) {
                    // don't try to initialize stuff "above" me yet
                    if ( libIsUpward(i) ) {
                        uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };
                        uninitUps.count++;
                    }
                    else if ( dependentImage->fDepth >= fDepth ) {
                        dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
                    }
                }
            }
            
            // record termination order
            // 記錄終端序號(hào)
            if ( this->needsTermination() )
                context.terminationRecorder(this);

            // let objc know we are about to initialize this image
            // 單個(gè)通知注入 通知告知大家我要開始初始化啦,你們趕緊去做初始化的工作
            uint64_t t1 = mach_absolute_time();
            fState = dyld_image_state_dependents_initialized;
            oldState = fState;
            context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
            
            // initialize this image
            bool hasInitializers = this->doInitialization(context);

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

我們跟蹤notifySingle方法進(jìn)來靴庆,我們發(fā)現(xiàn)下面一段代碼:

if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
        uint64_t t0 = mach_absolute_time();
        dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
        (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        uint64_t t1 = mach_absolute_time();
        uint64_t t2 = mach_absolute_time();
        uint64_t timeInObjC = t1-t0;
        uint64_t emptyTime = (t2-t1)*100;
        if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {
            timingInfo->addTime(image->getShortName(), timeInObjC);
        }
    }

sNotifyObjCInit關(guān)鍵詞时捌,搜索查找此關(guān)鍵詞,發(fā)現(xiàn)一個(gè)賦值的地方:

void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    // record functions to call
    sNotifyObjCMapped   = mapped;
    sNotifyObjCInit     = init;
    sNotifyObjCUnmapped = unmapped;
    .....
}

發(fā)現(xiàn)在registerObjCNotifiers 方法中對sNotifyObjCInit 進(jìn)行了賦值操作,registerObjCNotifiers 可以理解為注冊objc通知炉抒,繼續(xù)查找調(diào)用registerObjCNotifiers()的地方:

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

那么問題來了奢讨,繼續(xù)查找下去,dyld代碼全局搜索找不到_dyld_objc_notify_register() 函數(shù)調(diào)用的方法了焰薄。拿诸。。那咋辦蛤奥?
我們不妨利用工程及斷點(diǎn)來查找佳镜,_dyld_objc_notify_register() 下斷點(diǎn)繼續(xù)觀察一下僚稿,在xcode 新建 Symbolic Breakpoint.. 符號(hào)斷點(diǎn):

_dyld_objc_notify_register調(diào)用探索

可以看到凡桥,在_dyld_objc_notify_register方法調(diào)用前,_objc_init方法調(diào)用了蚀同,我們不妨試著看看是不是在_objc_init里面有調(diào)用_dyld_objc_notify_register方法:
_objc_initobjc源碼里面缅刽,所以我們在先前獲取的objc4 - 781找到_objc_init源碼如下:

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();
    cache_init();
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

的確,在_objc_init 方法中調(diào)用了_dyld_objc_notify_register() 函數(shù)蠢络∷ッ停可以看出此處注冊的就是load_images(),回調(diào)函數(shù)里調(diào)用了call_load_methods()來執(zhí)行所有的+load方法:

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
       // 加載所有的分類信息
        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);
    
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    // 調(diào)用所有的load方法
    call_load_methods();
}

現(xiàn)在回到文章剛開始的例子處刹孔,我們在ViewController.m 文件中的+load() 方法中打下斷點(diǎn)來查看調(diào)用堆棧信息:


調(diào)用堆棧信息展示

從堆棧信息可以看出啡省,從下往上,因?yàn)槲矣玫氖悄M器髓霞,所以會(huì)有start_sim 方法的調(diào)用卦睹。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市方库,隨后出現(xiàn)的幾起案子结序,更是在濱河造成了極大的恐慌,老刑警劉巖纵潦,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徐鹤,死亡現(xiàn)場離奇詭異垃环,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)返敬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門遂庄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人劲赠,你說我怎么就攤上這事涧团。” “怎么了经磅?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵泌绣,是天一觀的道長。 經(jīng)常有香客問我预厌,道長阿迈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任轧叽,我火速辦了婚禮苗沧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘炭晒。我一直安慰自己待逞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布网严。 她就那樣靜靜地躺著识樱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪震束。 梳的紋絲不亂的頭發(fā)上怜庸,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音垢村,去河邊找鬼割疾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛嘉栓,可吹牛的內(nèi)容都是我干的宏榕。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼侵佃,長吁一口氣:“原來是場噩夢啊……” “哼麻昼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起趣钱,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對情侶失蹤涌献,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后首有,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體燕垃,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枢劝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了卜壕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片您旁。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖轴捎,靈堂內(nèi)的尸體忽然破棺而出鹤盒,到底是詐尸還是另有隱情,我是刑警寧澤侦副,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布侦锯,位于F島的核電站,受9級(jí)特大地震影響秦驯,放射性物質(zhì)發(fā)生泄漏尺碰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一译隘、第九天 我趴在偏房一處隱蔽的房頂上張望亲桥。 院中可真熱鬧,春花似錦固耘、人聲如沸题篷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽番枚。三九已至,卻和暖如春璧瞬,著一層夾襖步出監(jiān)牢的瞬間户辫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工嗤锉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人墓塌。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓瘟忱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親苫幢。 傳聞我的和親對象是個(gè)殘疾皇子访诱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344