iOS底層探索 -- dyld 流程分析

前言

之前耕蝉,我們研究了很多關(guān)于iOS底層相對零碎的知識宛徊。
而iOS對用戶來說佛嬉,最重要的就是每一個APP。
今天岩调,我們來研究一下APP的啟動入口巷燥。

  1. 首先,我們新建一個SingleViewApp
  2. 我們在main()函數(shù)處打一個斷點号枕。
  3. 我們在ViewController.m中實現(xiàn)一個load方法
+ (void)load{
    NSLog(@"%s",__func__);
}

同時在此處也打一個斷點缰揪。

通常意義上,我們說一個APP的入口就是main()或者+(void)load
所以葱淳,我們在此處打斷點看看在入口前钝腺,系統(tǒng)為我們做了什么

在兩個斷點處,可以看見先進入了+(void)load方法赞厕。

使用lldb bt 打印棧信息

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
  * frame #0: 0x00000001060e3e77 002-應(yīng)用程加載分析`+[ViewController load](self=ViewController, _cmd="load") at ViewController.m:17:5
    frame #1: 0x00000001069be317 libobjc.A.dylib`call_load_methods + 691
    frame #2: 0x00000001069bf814 libobjc.A.dylib`load_images + 77
    frame #3: 0x00000001060f207e dyld_sim`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 420
    frame #4: 0x00000001060ff3e7 dyld_sim`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 303
    frame #5: 0x00000001060fe5d3 dyld_sim`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 133
    frame #6: 0x00000001060fe665 dyld_sim`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 73
    frame #7: 0x00000001060f2379 dyld_sim`dyld::initializeMainExecutable() + 199
    frame #8: 0x00000001060f6434 dyld_sim`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 4384
    frame #9: 0x00000001060f1630 dyld_sim`start_sim + 136
    frame #10: 0x000000010775785c dyld`dyld::useSimulatorDyld(int, macho_header const*, char const*, int, char const**, char const**, char const**, unsigned long*, unsigned long*) + 2308
    frame #11: 0x00000001077554f4 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 837
    frame #12: 0x0000000107750227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
    frame #13: 0x0000000107750025 dyld`_dyld_start + 37

隨后跳過斷點繼續(xù)運行艳狐,斷點走到main()

使用lldb bt 打印棧信息

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x00000001060e40fd 002-應(yīng)用程加載分析`main(argc=1, argv=0x00007ffee9b1b180) at main.m:16:16
    frame #1: 0x0000000108dfe541 libdyld.dylib`start + 1

通過兩處的bt打印,我們可以看到皿桑,入口都是在dyld

對照前面的棧方法信息毫目,我們來研究一下dyld的流程

dyld簡介

dyld(the dynamic link editor)是蘋果的動態(tài)鏈接器,是蘋果操作系統(tǒng)一個重要組成部分诲侮,在系統(tǒng)內(nèi)核做好程序準(zhǔn)備工作之后镀虐,交由dyld負(fù)責(zé)余下的工作。

dyld下載地址:http://opensource.apple.com/tarballs/dyld沟绪。

這里刮便,我們下載一份官方的開源代碼來參考研究,這里的版本是750.6.0.

dyld代碼分析

從之前的bt方法打印中绽慈,我們能看到dyldload加載前的大概流程

bt方法打印簡圖.png

現(xiàn)在恨旱,我們就按照這個流程來研究一下dyld源碼

_dyld_start:

#if __arm64__
    .text
    .align 2
    .globl __dyld_start
__dyld_start:
    mov     x28, sp
    and     sp, x28, #~15       // force 16-byte alignment of stack
    mov x0, #0
    mov x1, #0
    stp x1, x0, [sp, #-16]! // make aligned terminating frame
    mov fp, sp          // set up fp to point to terminating frame
    sub sp, sp, #16             // make room for local variables
#if __LP64__
    ldr     x0, [x28]               // get app's mh into x0
    ldr     x1, [x28, #8]           // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment)
    add     x2, x28, #16            // get argv into x2
#else
    ldr     w0, [x28]               // get app's mh into x0
    ldr     w1, [x28, #4]           // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment)
    add     w2, w28, #8             // get argv into x2
#endif
    adrp    x3,___dso_handle@page
    add     x3,x3,___dso_handle@pageoff // get dyld's mh in to x4
    mov x4,sp                   // x5 has &startGlue

    // call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
    bl  __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
    mov x16,x0                  // save entry point address in x16
#if __LP64__
    ldr     x1, [sp]
#else
    ldr     w1, [sp]
#endif
    cmp x1, #0
    b.ne    Lnew

    // LC_UNIXTHREAD way, clean up stack and jump to result
#if __LP64__
    add sp, x28, #8             // restore unaligned stack pointer without app mh
#else
    add sp, x28, #4             // restore unaligned stack pointer without app mh
#endif
#if __arm64e__
    braaz   x16                     // jump to the program's entry point
#else
    br      x16                     // jump to the program's entry point
#endif

    // LC_MAIN case, set up stack for call to main()
Lnew:   mov lr, x1          // simulate return address into _start in libdyld.dylib
#if __LP64__
    ldr x0, [x28, #8]       // main param1 = argc
    add x1, x28, #16        // main param2 = argv
    add x2, x1, x0, lsl #3
    add x2, x2, #8          // main param3 = &env[0]
    mov x3, x2
Lapple: ldr x4, [x3]
    add x3, x3, #8
#else
    ldr w0, [x28, #4]       // main param1 = argc
    add x1, x28, #8         // main param2 = argv
    add x2, x1, x0, lsl #2
    add x2, x2, #4          // main param3 = &env[0]
    mov x3, x2
Lapple: ldr w4, [x3]
    add x3, x3, #4
#endif
    cmp x4, #0
    b.ne    Lapple          // main param4 = apple
#if __arm64e__
    braaz   x16
#else
    br      x16
#endif

#endif // __arm64__

在匯編源碼中辈毯,我們可以看到

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

指向了dyldbootstrap::start

dyldbootstrap::start

全局搜索dyldbootstrap,找到其作用域搜贤,然后找到其中的start方法

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
    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
    __guard_setup(apple);

#if DYLD_INITIALIZER_SUPPORT
    // run all C++ initializers inside 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()函數(shù)中做了很多dyld初始化相關(guān)的工作谆沃,包括:

  • rebaseDyld() dyld重定位。
  • mach_init() mach消息初始化入客。
  • __guard_setup() 棧溢出保護管毙。

完成初始化后腿椎,我們直接翻到return

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

能看見這里桌硫,返回了dyldmain方法

dyld::_main()

跳轉(zhuǎn)到dyld::_main后,

//
// Entry point for dyld.  The kernel loads dyld and jumps to __dyld_start which
// sets up some registers and call this function.
//
// Returns address of main() in target program which __dyld_start jumps to
//
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è)置運行環(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
    sExecShortName = ::strrchr(sExecPath, '/');
    if ( sExecShortName != NULL )
        ++sExecShortName;
    else
        sExecShortName = sExecPath;
    // 配置進程受限模式
    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
    // 如果設(shè)置了DYLD_PRINT_OPTS則調(diào)用printOptions()打印參數(shù)
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    // 如果設(shè)置了DYLD_PRINT_ENV則調(diào)用printEnvironmentVariables()打印環(huán)境變量
    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;
    // 獲取當(dāng)前程序架構(gòu)
    getHostInfo(mainExecutableMH, mainExecutableSlide);
    //-------------第一步結(jié)束-------------

    // load shared cache
    // 第二步铆隘,加載共享緩存
    // 檢查共享緩存是否開啟,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
        // 第三步 實例化主程序
        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
        // 第四步 加載插入的動態(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.
        // 記錄插入的動態(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
        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
        // 第六步 鏈接插入的動態(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 SUPPORT_ACCELERATE_TABLES
        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
        // 第七步 執(zhí)行弱符號綁定
        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()
        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;
}

其中重要的地方加了一些注釋南用,而整個流程大概分為九步

  1. 設(shè)置運行環(huán)境膀钠。
  2. 加載共享緩存
  3. 實例化主程序裹虫。
  4. 加載插入的動態(tài)庫肿嘲。
  5. 鏈接主程序
  6. 鏈接插入的動態(tài)庫筑公。
  7. 執(zhí)行弱符號綁定雳窟。
  8. 執(zhí)行初始化方法祝懂。
  9. 查找入口點并返回唯笙。

設(shè)置運行環(huán)境

這一步主要是設(shè)置運行參數(shù)環(huán)境變量等杏慰。代碼在開始的時候捣作,將入?yún)?code>mainExecutableMH賦值給了sMainExecutableMachHeader誉结,這是一個macho_header結(jié)構(gòu)體,表示的是當(dāng)前主程序的Mach-O頭部信息券躁,加載器依據(jù)Mach-O頭部信息就可以解析整個Mach-O文件信息惩坑。接著調(diào)用setContext()設(shè)置上下文信息,包括一些回調(diào)函數(shù)也拜、參數(shù)以舒、標(biāo)志信息

分段代碼
setContext

static void setContext(const macho_header* mainExecutableMH, int argc, const char* argv[], const char* envp[], const char* apple[])
{
    gLinkContext.loadLibrary            = &libraryLocator;
    gLinkContext.terminationRecorder    = &terminationRecorder;
    gLinkContext.flatExportFinder       = &flatFindExportedSymbol;
    gLinkContext.coalescedExportFinder  = &findCoalescedExportedSymbol;
    gLinkContext.getCoalescedImages     = &getCoalescedImages;
    gLinkContext.undefinedHandler       = &undefinedHandler;
    gLinkContext.getAllMappedRegions    = &getMappedRegions;
    gLinkContext.bindingHandler         = NULL;
    gLinkContext.notifySingle           = &notifySingle;
    gLinkContext.notifyBatch            = &notifyBatch;
    gLinkContext.removeImage            = &removeImage;
    gLinkContext.registerDOFs           = dyld3::Loader::dtraceUserProbesEnabled() ? &registerDOFs : NULL;
    gLinkContext.clearAllDepths         = &clearAllDepths;
    gLinkContext.printAllDepths         = &printAllDepths;
    gLinkContext.imageCount             = &imageCount;
    gLinkContext.setNewProgramVars      = &setNewProgramVars;
    gLinkContext.inSharedCache          = &inSharedCache;
    gLinkContext.setErrorStrings        = &setErrorStrings;
#if SUPPORT_OLD_CRT_INITIALIZATION
    gLinkContext.setRunInitialzersOldWay= &setRunInitialzersOldWay;
#endif
    gLinkContext.findImageContainingAddress = &findImageContainingAddress;
    gLinkContext.addDynamicReference    = &addDynamicReference;
#if SUPPORT_ACCELERATE_TABLES
    gLinkContext.notifySingleFromCache  = &notifySingleFromCache;
    gLinkContext.getPreInitNotifyHandler= &getPreInitNotifyHandler;
    gLinkContext.getBoundBatchHandler   = &getBoundBatchHandler;
#endif
    gLinkContext.bindingOptions         = ImageLoader::kBindingNone;
    gLinkContext.argc                   = argc;
    gLinkContext.argv                   = argv;
    gLinkContext.envp                   = envp;
    gLinkContext.apple                  = apple;
    gLinkContext.progname               = (argv[0] != NULL) ? basename(argv[0]) : "";
    gLinkContext.programVars.mh         = mainExecutableMH;
    gLinkContext.programVars.NXArgcPtr  = &gLinkContext.argc;
    gLinkContext.programVars.NXArgvPtr  = &gLinkContext.argv;
    gLinkContext.programVars.environPtr = &gLinkContext.envp;
    gLinkContext.programVars.__prognamePtr=&gLinkContext.progname;
    gLinkContext.mainExecutable         = NULL;
    gLinkContext.imageSuffix            = NULL;
    gLinkContext.dynamicInterposeArray  = NULL;
    gLinkContext.dynamicInterposeCount  = 0;
    gLinkContext.prebindUsage           = ImageLoader::kUseAllPrebinding;
    gLinkContext.sharedRegionMode       = ImageLoader::kUseSharedRegion;
}

configureProcessRestrictions()用來配置進程是否受限

static void configureProcessRestrictions(const macho_header* mainExecutableMH, const char* envp[])
{
    uint64_t amfiInputFlags = 0;
#if TARGET_OS_SIMULATOR
    amfiInputFlags |= AMFI_DYLD_INPUT_PROC_IN_SIMULATOR;
#elif __MAC_OS_X_VERSION_MIN_REQUIRED
    if ( hasRestrictedSegment(mainExecutableMH) )
        amfiInputFlags |= AMFI_DYLD_INPUT_PROC_HAS_RESTRICT_SEG;
#elif __IPHONE_OS_VERSION_MIN_REQUIRED
    if ( isFairPlayEncrypted(mainExecutableMH) )
        amfiInputFlags |= AMFI_DYLD_INPUT_PROC_IS_ENCRYPTED;
#endif
    uint64_t amfiOutputFlags = 0;
    const char* amfiFake = nullptr;
    if ( dyld3::internalInstall() && dyld3::BootArgs::enableDyldTestMode() ) {
        amfiFake = _simple_getenv(envp, "DYLD_AMFI_FAKE");
    }
    if ( amfiFake != nullptr ) {
        amfiOutputFlags = hexToUInt64(amfiFake, nullptr);
    }
    if ( (amfiFake != nullptr) || (amfi_check_dyld_policy_self(amfiInputFlags, &amfiOutputFlags) == 0) ) {
        gLinkContext.allowAtPaths               = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_AT_PATH);
        gLinkContext.allowEnvVarsPrint          = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_PRINT_VARS);
        gLinkContext.allowEnvVarsPath           = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_PATH_VARS);
        gLinkContext.allowEnvVarsSharedCache    = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_CUSTOM_SHARED_CACHE);
        gLinkContext.allowClassicFallbackPaths  = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_FALLBACK_PATHS);
        gLinkContext.allowInsertFailures        = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_FAILED_LIBRARY_INSERTION);
#ifdef AMFI_RETURNS_INTERPOSING_FLAG
        gLinkContext.allowInterposing           = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_LIBRARY_INTERPOSING);
#else
        gLinkContext.allowInterposing           = true;
#endif
    }
    else {
#if __MAC_OS_X_VERSION_MIN_REQUIRED
        // support chrooting from old kernel
        bool isRestricted = false;
        bool libraryValidation = false;
        // any processes with setuid or setgid bit set or with __RESTRICT segment is restricted
        if ( issetugid() || hasRestrictedSegment(mainExecutableMH) ) {
            isRestricted = true;
        }
        bool usingSIP = (csr_check(CSR_ALLOW_TASK_FOR_PID) != 0);
        uint32_t flags;
        if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) != -1 ) {
            // On OS X CS_RESTRICT means the program was signed with entitlements
            if ( ((flags & CS_RESTRICT) == CS_RESTRICT) && usingSIP ) {
                isRestricted = true;
            }
            // Library Validation loosens searching but requires everything to be code signed
            if ( flags & CS_REQUIRE_LV ) {
                isRestricted = false;
                libraryValidation = true;
            }
        }
        gLinkContext.allowAtPaths                = !isRestricted;
        gLinkContext.allowEnvVarsPrint           = !isRestricted;
        gLinkContext.allowEnvVarsPath            = !isRestricted;
        gLinkContext.allowEnvVarsSharedCache     = !libraryValidation || !usingSIP;
        gLinkContext.allowClassicFallbackPaths   = !isRestricted;
        gLinkContext.allowInsertFailures         = false;
        gLinkContext.allowInterposing            = true;
#else
        halt("amfi_check_dyld_policy_self() failed\n");
#endif
    }
}

checkEnvironmentVariables()檢測環(huán)境變量,如果sEnvModeenvNone就直接返回搪泳,否則調(diào)用processDyldEnvironmentVariable()處理并設(shè)置環(huán)境變量

static void checkEnvironmentVariables(const char* envp[])
{
    if ( !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsPrint )
        return;
    const char** p;
    for(p = envp; *p != NULL; p++) {
        const char* keyEqualsValue = *p;
        if ( strncmp(keyEqualsValue, "DYLD_", 5) == 0 ) {
            const char* equals = strchr(keyEqualsValue, '=');
            if ( equals != NULL ) {
                strlcat(sLoadingCrashMessage, "\n", sizeof(sLoadingCrashMessage));
                strlcat(sLoadingCrashMessage, keyEqualsValue, sizeof(sLoadingCrashMessage));
                const char* value = &equals[1];
                const size_t keyLen = equals-keyEqualsValue;
                char key[keyLen+1];
                strncpy(key, keyEqualsValue, keyLen);
                key[keyLen] = '\0';
                if ( (strncmp(key, "DYLD_PRINT_", 11) == 0) && !gLinkContext.allowEnvVarsPrint )
                    continue;
                processDyldEnvironmentVariable(key, value, NULL);
            }
        }
        else if ( strncmp(keyEqualsValue, "LD_LIBRARY_PATH=", 16) == 0 ) {
            const char* path = &keyEqualsValue[16];
            sEnv.LD_LIBRARY_PATH = parseColonList(path, NULL);
        }
    }

#if SUPPORT_LC_DYLD_ENVIRONMENT
    checkLoadCommandEnvironmentVariables();
#endif // SUPPORT_LC_DYLD_ENVIRONMENT   
    
#if SUPPORT_ROOT_PATH
    // <rdar://problem/11281064> DYLD_IMAGE_SUFFIX and DYLD_ROOT_PATH cannot be used together
    if ( (gLinkContext.imageSuffix != NULL && *gLinkContext.imageSuffix != NULL) && (gLinkContext.rootPaths != NULL) ) {
        dyld::warn("Ignoring DYLD_IMAGE_SUFFIX because DYLD_ROOT_PATH is used.\n");
        gLinkContext.imageSuffix = NULL; // this leaks allocations from parseColonList
    }
#endif
}

隨后是getHostInfo()獲取cpu的框架信息

static void getHostInfo(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide)
{
#if CPU_SUBTYPES_SUPPORTED
#if __ARM_ARCH_7K__
    sHostCPU        = CPU_TYPE_ARM;
    sHostCPUsubtype = CPU_SUBTYPE_ARM_V7K;
#elif __ARM_ARCH_7A__
    sHostCPU        = CPU_TYPE_ARM;
    sHostCPUsubtype = CPU_SUBTYPE_ARM_V7;
#elif __ARM_ARCH_6K__
    sHostCPU        = CPU_TYPE_ARM;
    sHostCPUsubtype = CPU_SUBTYPE_ARM_V6;
#elif __ARM_ARCH_7F__
    sHostCPU        = CPU_TYPE_ARM;
    sHostCPUsubtype = CPU_SUBTYPE_ARM_V7F;
#elif __ARM_ARCH_7S__
    sHostCPU        = CPU_TYPE_ARM;
    sHostCPUsubtype = CPU_SUBTYPE_ARM_V7S;
#elif __ARM64_ARCH_8_32__
    sHostCPU        = CPU_TYPE_ARM64_32;
    sHostCPUsubtype = CPU_SUBTYPE_ARM64_32_V8;
#elif __arm64e__
    sHostCPU        = CPU_TYPE_ARM64;
    sHostCPUsubtype = CPU_SUBTYPE_ARM64E;
#elif __arm64__
    sHostCPU        = CPU_TYPE_ARM64;
    sHostCPUsubtype = CPU_SUBTYPE_ARM64_V8;
#else
    struct host_basic_info info;
    mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
    mach_port_t hostPort = mach_host_self();
    kern_return_t result = host_info(hostPort, HOST_BASIC_INFO, (host_info_t)&info, &count);
    if ( result != KERN_SUCCESS )
        throw "host_info() failed";
    sHostCPU        = info.cpu_type;
    sHostCPUsubtype = info.cpu_subtype;
    mach_port_deallocate(mach_task_self(), hostPort);
  #if __x86_64__
      // host_info returns CPU_TYPE_I386 even for x86_64.  Override that here so that
      // we don't need to mask the cpu type later.
      sHostCPU = CPU_TYPE_X86_64;
    #if !TARGET_OS_SIMULATOR
      sHaswell = (sHostCPUsubtype == CPU_SUBTYPE_X86_64_H);
      // <rdar://problem/18528074> x86_64h: Fall back to the x86_64 slice if an app requires GC.
      if ( sHaswell ) {
        if ( isGCProgram(mainExecutableMH, mainExecutableSlide) ) {
            // When running a GC program on a haswell machine, don't use and 'h slices
            sHostCPUsubtype = CPU_SUBTYPE_X86_64_ALL;
            sHaswell = false;
            gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion;
        }
      }
    #endif
  #endif
#endif
#endif
}

加載共享緩存

  • 首先調(diào)用checkSharedRegionDisable()檢查是否禁用了共享緩存
static void checkSharedRegionDisable(const dyld3::MachOLoaded* mainExecutableMH, uintptr_t mainExecutableSlide)
{
#if __MAC_OS_X_VERSION_MIN_REQUIRED
    // if main executable has segments that overlap the shared region,
    // then disable using the shared region
    if ( mainExecutableMH->intersectsRange(SHARED_REGION_BASE, SHARED_REGION_SIZE) ) {
        gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion;
        if ( gLinkContext.verboseMapping )
            dyld::warn("disabling shared region because main executable overlaps\n");
    }
#if __i386__
    if ( !gLinkContext.allowEnvVarsPath ) {
        // <rdar://problem/15280847> use private or no shared region for suid processes
        gLinkContext.sharedRegionMode = ImageLoader::kUsePrivateSharedRegion;
    }
#endif
#endif
    // iOS cannot run without shared region
}

根據(jù)注釋內(nèi)容稀轨,我們判斷iOS的程序時,共享緩存必須開啟岸军。

  • 然后調(diào)用mapSharedCache()加載共享緩存
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;
    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(opts, &sSharedCacheLoadInfo)

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)前進程
        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
}

可見加載共享緩存分三種情況

  1. 僅加載到當(dāng)前進程 調(diào)用 mapCachePrivate(options, results)
  2. 快速路徑奋刽, 共享緩存已加載
  3. 慢速路徑瓦侮, 首次當(dāng)前進程首次加載共享緩存 mapCacheSystemWide(options, results)

實例化主程序

將主程序的Mach-O加載進內(nèi)存,并實例化一個ImageLoader佣谐。

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";
}
  • 首先會判斷Mach-O頭部的magic肚吏、cputypecpusubtype的屬性是否兼容
// This is used to validate if a non-fat (aka thin or raw) mach-o file can be used
// on the current processor. //
bool isCompatibleMachO(const uint8_t* firstPage, const char* path)
{
#if CPU_SUBTYPES_SUPPORTED
    // It is deemed compatible if any of the following are true:
    //  1) mach_header subtype is in list of compatible subtypes for running processor
    //  2) mach_header subtype is same as running processor subtype
    //  3) mach_header subtype runs on all processor variants
    //如果符合以下條件狭魂,則視為兼容:
    //1)mach_header subtype在運行處理器的兼容子類型列表中
    //2)mach_header subtype與運行處理器子類型相同
    //3)mach_header subtype在所有處理器變體上運行
    const mach_header* mh = (mach_header*)firstPage;
    if ( mh->magic == sMainExecutableMachHeader->magic ) {
        if ( mh->cputype == sMainExecutableMachHeader->cputype ) {
            if ( mh->cputype == sHostCPU ) {
                // get preference ordered list of subtypes that this machine can use
                const cpu_subtype_t* subTypePreferenceList = findCPUSubtypeList(mh->cputype, sHostCPUsubtype);
                if ( subTypePreferenceList != NULL ) {
                    // if image's subtype is in the list, it is compatible
                    for (const cpu_subtype_t* p = subTypePreferenceList; *p != CPU_SUBTYPE_END_OF_LIST; ++p) {
                        if ( *p == mh->cpusubtype )
                            return true;
                    }
                    // have list and not in list, so not compatible
                    throwf("incompatible cpu-subtype: 0x%08X in %s", mh->cpusubtype, path);
                }
                // unknown cpu sub-type, but if exact match for current subtype then ok to use
                if ( mh->cpusubtype == sHostCPUsubtype ) 
                    return true;
            }
            
            // cpu type has no ordered list of subtypes
            switch (mh->cputype) {
                case CPU_TYPE_I386:
                case CPU_TYPE_X86_64:
                    // subtypes are not used or these architectures
                    return true;
            }
        }
    }
#else
    // For architectures that don't support cpu-sub-types
    // this just check the cpu type.
    const mach_header* mh = (mach_header*)firstPage;
    if ( mh->magic == sMainExecutableMachHeader->magic ) {
        if ( mh->cputype == sMainExecutableMachHeader->cputype ) {
            return true;
        }
    }
#endif
    return false;
}
  • 然后調(diào)用ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext)實例化主程序的ImageLoader罚攀。ImageLoader是抽象類,其子類負(fù)責(zé)把Mach-O文件實例化為image雌澄,當(dāng)sniffLoadCommands()解析完以后斋泄,根據(jù)compressed的值來決定調(diào)用哪個子類進行實例化
// 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
}

  • 在調(diào)用完ImageLoaderMachO::instantiateMainExecutable()后繼續(xù)調(diào)用addImage(),將image加入到sAllImages全局鏡像列表镐牺,并將image映射到申請的內(nèi)存中
static void addImage(ImageLoader* image)
{
    // add to master list
    allImagesLock();
        sAllImages.push_back(image);
    allImagesUnlock();
    
    // update mapped ranges
    uintptr_t lastSegStart = 0;
    uintptr_t lastSegEnd = 0;
    for(unsigned int i=0, e=image->segmentCount(); i < e; ++i) {
        if ( image->segUnaccessible(i) ) 
            continue;
        uintptr_t start = image->segActualLoadAddress(i);
        uintptr_t end = image->segActualEndAddress(i);
        if ( start == lastSegEnd ) {
            // two segments are contiguous, just record combined segments
            lastSegEnd = end;
        }
        else {
            // non-contiguous segments, record last (if any)
            if ( lastSegEnd != 0 )
                addMappedRange(image, lastSegStart, lastSegEnd);
            lastSegStart = start;
            lastSegEnd = end;
        }       
    }
    if ( lastSegEnd != 0 )
        addMappedRange(image, lastSegStart, lastSegEnd);

    
    if ( gLinkContext.verboseLoading || (sEnv.DYLD_PRINT_LIBRARIES_POST_LAUNCH && (sMainExecutable!=NULL) && sMainExecutable->isLinked()) ) {
        const char *imagePath = image->getPath();
        uuid_t imageUUID;
        if ( image->getUUID(imageUUID) ) {
            uuid_string_t imageUUIDStr;
            uuid_unparse_upper(imageUUID, imageUUIDStr);
            dyld::log("dyld: loaded: <%s> %s\n", imageUUIDStr, imagePath);
        }
        else {
            dyld::log("dyld: loaded: %s\n", imagePath);
        }
    }
    
}

到這里實例化主程序完成

加載插入的動態(tài)庫

這一步是加載環(huán)境變量DYLD_INSERT_LIBRARIES中配置的動態(tài)庫炫掐,先判斷是否存在需要加載的動態(tài)庫,如果有睬涧,則調(diào)用loadInsertedDylib()

if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
    for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
        loadInsertedDylib(*lib);
}

loadInsertedDylib內(nèi)部實現(xiàn)

通過設(shè)置一個LoadContext參數(shù)募胃,然后調(diào)用load()實現(xiàn)

static void loadInsertedDylib(const char* path)
{
    unsigned cacheIndex;
    try {
        LoadContext context;
        context.useSearchPaths      = false;
        context.useFallbackPaths    = false;
        context.useLdLibraryPath    = false;
        context.implicitRPath       = false;
        context.matchByInstallName  = false;
        context.dontLoad            = false;
        context.mustBeBundle        = false;
        context.mustBeDylib         = true;
        context.canBePIE            = false;
        context.origin              = NULL; // can't use @loader_path with DYLD_INSERT_LIBRARIES
        context.rpath               = NULL;
        load(path, context, cacheIndex);
    }
    catch (const char* msg) {
        if ( gLinkContext.allowInsertFailures )
            dyld::log("dyld: warning: could not load inserted library '%s' into hardened process because %s\n", path, msg);
        else
            halt(dyld::mkstringf("could not load inserted library '%s' because %s\n", path, msg));
    }
    catch (...) {
        halt(dyld::mkstringf("could not load inserted library '%s'\n", path));
    }
}

鏈接主程序

這一步主要調(diào)用了link()函數(shù)

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
        image->link(gLinkContext, forceLazysBound, false, neverUnload, loaderRPaths, path);
    }
    catch (const char* msg) {
        garbageCollectImages();
        throw;
    }
}

作用是將實例化后的主程序進行動態(tài)化修正讓二進制變?yōu)榭烧?zhí)行的狀態(tài)。

其中load()內(nèi)部重要通過ImageLoaderlink方法

查看一下源碼

void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath)
{
    //dyld::log("ImageLoader::link(%s) refCount=%d, neverUnload=%d\n", imagePath, fDlopenReferenceCount, fNeverUnload);
    
    // clear error strings
    (*context.setErrorStrings)(0, NULL, NULL, NULL);

    uint64_t t0 = mach_absolute_time();
    // 遞歸加載加載主程序所需依賴庫
    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();
        // 遞歸進行rebase
        this->recursiveRebaseWithAccounting(context);
        context.notifyBatch(dyld_image_state_rebased, false);

        t3 = mach_absolute_time();
        if ( !context.linkingMainExecutable )
            // 遞歸綁定符號表
            this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);

        t4 = mach_absolute_time();
        if ( !context.linkingMainExecutable )
             // 弱符號綁定
            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);
        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);
        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;
}

其主要做的幾件事情

  • recursiveLoadLibraries() 根據(jù)LC_LOAD_DYLIB加載命令把所有依賴庫加載進內(nèi)存畦浓。
  • recursiveUpdateDepth() 遞歸刷新依賴庫的層級痹束。
  • recursiveRebaseWithAccounting() 由于ASLR的存在,必須遞歸對主程序以及依賴庫進行重定位操作讶请。
  • recursiveBindWithAccounting() 把主程序二進制和依賴進來的動態(tài)庫全部執(zhí)行符號表綁定祷嘶。
  • weakBind() 如果鏈接的不是主程序二進制的話,會在此時執(zhí)行弱符號綁定秽梅,主程序二進制則在link()完后再執(zhí)行弱符號綁定抹蚀,后面會進行分析。
  • recursiveGetDOFSections()企垦、context.registerDOFs() 注冊DOF(DTrace Object Format)節(jié)环壤。

鏈接插入的動態(tài)庫

和鏈接主程序一樣,調(diào)用link()函數(shù)去進行鏈接钞诡,不過這里需要從i+1個元素開始郑现,第一個是主程序

ImageLoader* image = sAllImages[i+1];

執(zhí)行弱符號綁定

sMainExecutable->weakBind(gLinkContext);
  1. 通過getCoalescedImages()合并所有動態(tài)庫的弱符號到一個列表里
  2. 調(diào)用initializeCoalIterator()對需要綁定的弱符號進行排序
  3. 調(diào)用incrementCoalIterator()讀取dyld_info_command結(jié)構(gòu)的weak_bind_offweak_bind_size字段,確定弱符號的數(shù)據(jù)偏移與大小

執(zhí)行初始化方法

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

這一步由initializeMainExecutable()完成荧降。

dyld會優(yōu)先初始化動態(tài)庫接箫,然后初始化主程序.

initializeMainExecutable()內(nèi)部主要方法為runInitializers()
再內(nèi)部跳轉(zhuǎn)為processInitializers()->recursiveInitialization()->ImageLoader::recursiveInitialization()

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

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

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

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

然后在recursiveInitialization()中我們看到了notifySingle()函數(shù)

context.notifySingle(dyld_image_state_initialized, this, NULL);

這個流程剛好對應(yīng)了我們之前bt打印的信息

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

其中,我們需要關(guān)心的東西是sNotifyObjCInit

下面全局搜索一下給他賦值的地方

void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    // record functions to call
    sNotifyObjCMapped   = mapped;
    sNotifyObjCInit     = init;
    sNotifyObjCUnmapped = unmapped;
    // call 'mapped' function with all images mapped so far
    try {
        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);
            (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        }
    }
}

接下來我們找一下誰調(diào)用了registerObjCNotifiers()

全局搜索后在dyldAPIs.cpp中找到方法

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_objc_notify_register并沒有找到調(diào)用了朵诫。

我們回到之前使用的objc源碼辛友,全局搜索后發(fā)現(xiàn)

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

這里注冊的init回調(diào)函數(shù)就是load_images(),回調(diào)里面調(diào)用了call_load_methods()來執(zhí)行所有的+ load方法

notifySingle()之后是doInitialization()

bool hasInitializers = this->doInitialization(context);
  • doInitialization()內(nèi)部首先調(diào)用doImageInit來執(zhí)行鏡像的初始化函數(shù)废累,也就是LC_ROUTINES_COMMAND中記錄的函數(shù)邓梅,
  • 然后再執(zhí)行doModInitFunctions()方法來解析并執(zhí)行_DATA_,__mod_init_func這個section中保存的函數(shù)

_mod_init_funcs中保存的是全局C++對象的構(gòu)造函數(shù)以及所有帶__attribute__((constructor)的C函數(shù)。

我們之前在工程中添加了一個

__attribute__((constructor)) void kcFunc(){
    printf("來了 : %s \n",__func__);
}

現(xiàn)在我們在machOView中驗證一下

machOView驗證.jpg

這也是為什么這個方法在main()函數(shù)之前打印的原因邑滨。

查找入口點并返回

result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();

這一步調(diào)用主程序鏡像的getEntryFromLC_MAIN()日缨,從加載命令讀取LC_MAIN入口,如果沒有LC_MAIN就調(diào)用getEntryFromLC_UNIXTHREAD讀取LC_UNIXTHREAD掖看,找到后就跳到入口點指定的地址并返回

void* ImageLoaderMachO::getEntryFromLC_MAIN() const
{
    const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
    const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
    const struct load_command* cmd = cmds;
    for (uint32_t i = 0; i < cmd_count; ++i) {
        if ( cmd->cmd == LC_MAIN ) {
            entry_point_command* mainCmd = (entry_point_command*)cmd;
            void* entry = (void*)(mainCmd->entryoff + (char*)fMachOData);
            // <rdar://problem/8543820&9228031> verify entry point is in image
            if ( this->containsAddress(entry) )
                return entry;
            else
                throw "LC_MAIN entryoff is out of range";
        }
        cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
    }
    return NULL;
}
void* ImageLoaderMachO::getEntryFromLC_UNIXTHREAD() const
{
    const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
    const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
    const struct load_command* cmd = cmds;
    for (uint32_t i = 0; i < cmd_count; ++i) {
        if ( cmd->cmd == LC_UNIXTHREAD ) {
    #if __i386__
            const i386_thread_state_t* registers = (i386_thread_state_t*)(((char*)cmd) + 16);
            void* entry = (void*)(registers->eip + fSlide);
            // <rdar://problem/8543820&9228031> verify entry point is in image
            if ( this->containsAddress(entry) )
                return entry;
    #elif __x86_64__
            const x86_thread_state64_t* registers = (x86_thread_state64_t*)(((char*)cmd) + 16);
            void* entry = (void*)(registers->rip + fSlide);
            // <rdar://problem/8543820&9228031> verify entry point is in image
            if ( this->containsAddress(entry) )
                return entry;
    #elif __arm64__ && !__arm64e__
            // temp support until <rdar://39514191> is fixed
            const uint64_t* regs64 = (uint64_t*)(((char*)cmd) + 16);
            void* entry = (void*)(regs64[32] + fSlide); // arm_thread_state64_t.__pc
            // <rdar://problem/8543820&9228031> verify entry point is in image
            if ( this->containsAddress(entry) )
                return entry;
    #endif
        }
        cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
    }
    throw "no valid entry point";
}

總結(jié)

這次的分析大概就到這里匣距,源代碼太多,可讀性較差哎壳,主要流程可以直接按照之前的bt打印的流程來參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末毅待,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子耳峦,更是在濱河造成了極大的恐慌恩静,老刑警劉巖焕毫,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹲坷,死亡現(xiàn)場離奇詭異,居然都是意外死亡邑飒,警方通過查閱死者的電腦和手機循签,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疙咸,“玉大人县匠,你說我怎么就攤上這事∪雎郑” “怎么了乞旦?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長题山。 經(jīng)常有香客問我兰粉,道長,這世上最難降的妖魔是什么顶瞳? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任玖姑,我火速辦了婚禮,結(jié)果婚禮上慨菱,老公的妹妹穿的比我還像新娘焰络。我一直安慰自己,他們只是感情好符喝,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布闪彼。 她就那樣靜靜地躺著,像睡著了一般协饲。 火紅的嫁衣襯著肌膚如雪畏腕。 梳的紋絲不亂的頭發(fā)上课蔬,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音郊尝,去河邊找鬼二跋。 笑死,一個胖子當(dāng)著我的面吹牛流昏,可吹牛的內(nèi)容都是我干的扎即。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼况凉,長吁一口氣:“原來是場噩夢啊……” “哼谚鄙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起刁绒,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤闷营,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后知市,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體傻盟,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年嫂丙,在試婚紗的時候發(fā)現(xiàn)自己被綠了娘赴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡跟啤,死狀恐怖诽表,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情隅肥,我是刑警寧澤竿奏,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站腥放,受9級特大地震影響泛啸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捉片,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一平痰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伍纫,春花似錦宗雇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春舞虱,著一層夾襖步出監(jiān)牢的瞬間欢际,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工矾兜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留损趋,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓椅寺,卻偏偏與公主長得像浑槽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子返帕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355