应用程序的加载 - ShenYj/ShenYj.github.io GitHub Wiki

应用程序的加载

.

通过设置断点,在调用堆栈处验证:app启动从dyld_dyld_start函数开始

dyld加载流程

dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统的重要组成部分,在app被编译打包成可执行文件格式的 Mach-O 文件后,交由dyld负责连接,加载程序

本文此时参阅的是 dyld-750.6 代码

  • 启动环节关键函数执行顺序
    • _dyld_start
    • dyldbootstrap::start
    • dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue)

程序的启动从c++函数_dyld_start开始,下层调用了汇编的__dyld_start函数

  • dylbStartup.s源码

    源码
    #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

    此处为汇编实现,对不同的指令集做了适配,然后调用了dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)函数

  • dyldInitialization.cppstart(app_mh, argc, argv, dyld_mh, &startGlue)函数源码

    源码
    //
    //  This is code to bootstrap dyld.  This work in normally done for a program by dyld and crt.
    //  In dyld we have to do this manually.
    //
    uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
                    const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
    {
    
        // 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);
    }
    

    其核心是返回值的调用了dyld_main函数,其中macho_headerMach-O的头部,而dyld加载的文件就是Mach-O类型的,即Mach-O类型是可执行文件类型,由四部分组成:Mach-O头部、Load Commandsection、Other Data

  • dyld2.cpp_main函数源码

    源码
    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
        uint8_t mainExecutableCDHashBuffer[20];
        const uint8_t* mainExecutableCDHash = nullptr;
        if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) )
            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;
        sMainExecutableMachHeader = mainExecutableMH;
        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");
    
        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
        {
            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;
        getHostInfo(mainExecutableMH, mainExecutableSlide);
    
        // load shared cache
        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
            if	( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
                for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                    loadInsertedDylib(*lib);
            }
            // record count of inserted libraries so that a flat search will look at 
            // inserted libraries, then main, then others.
            sInsertedDylibCount = sAllImages.size()-1;
    
            // link main executable
            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
            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
            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
            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;
    }
    • 代码解读

    _main函数代码很长,在_main函数中主要做了以下几件事情:

    • 环境变量配置:根据环境变量设置相应的值以及获取当前运行架构
    • 共享缓存:检查是否开启了共享缓存,以及共享缓存是否映射到共享区域,例如UIKitCoreFoundation
    • 主程序的初始化:调用instantiateFromLoadedImage函数实例化了一个ImageLoader对象
    • 插入动态库:遍历DYLD_INSERT_LIBRARIES环境变量,调用loadInsertedDylib加载
    • link 主程序:link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1)
    • link 动态库:link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1)
    • 弱符号绑定
    • 执行初始化方法: initializeMainExecutable()
    • 寻找主程序入口即main函数:从Load Command读取LC_MAIN入口,如果没有,就读取LC_UNIXTHREAD,这样就来到了日常开发中熟悉的main函数了

    sMainExecutable表示主程序变量

    • initializeMainExecutable函数

      源码
      void initializeMainExecutable()
      {
          // record that we've reached this step
          gLinkContext.startedInitializingMainExecutable = true;
      
          // run initialzers for any inserted dylibs
          ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
          initializerTimes[0].count = 0;
          const size_t rootCount = sImageRoots.size();
          if ( rootCount > 1 ) {
              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]);
      }

      其中的关键代码是调用了runInitializers函数

    • runInitializers函数

      源码
      void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
      {
          uint64_t t1 = mach_absolute_time();
          mach_port_t thisThread = mach_thread_self();
          ImageLoader::UninitedUpwards up;
          up.count = 1;
          up.imagesAndPaths[0] = { this, this->getPath() };
          processInitializers(context, thisThread, timingInfo, up);
          context.notifyBatch(dyld_image_state_initialized, false);
          mach_port_deallocate(mach_task_self(), thisThread);
          uint64_t t2 = mach_absolute_time();
          fgTotalInitTime += (t2 - t1);
      }

      关键代码是调用了processInitializers(context, thisThread, timingInfo, up);函数

    • processInitializers函数

      源码
      // <rdar://problem/14412057> upward dylib initializers can be run too soon
      // To handle dangling dylibs which are upward linked but not downward, all upward linked dylibs
      // have their initialization postponed until after the recursion through downward dylibs
      // has completed.
      void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
                                          InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
      {
          uint32_t maxImageCount = context.imageCount()+2;
          ImageLoader::UninitedUpwards upsBuffer[maxImageCount];
          ImageLoader::UninitedUpwards& ups = upsBuffer[0];
          ups.count = 0;
          // Calling recursive init on all images in images list, building a new list of
          // uninitialized upward dependencies.
          for (uintptr_t i=0; i < images.count; ++i) {
              images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
          }
          // If any upward dependencies remain, init them.
          if ( ups.count > 0 )
              processInitializers(context, thisThread, timingInfo, ups);
      }

      其中对镜像列表调用recursiveInitialization函数进行递归实例化

    • 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函数

      • doInitialization函数,调用了

        • doImageInit: 其核心主要是for循环加载方法的调用,这里需要注意的一点是,libSystem的初始化必须先运行(注释中已强调,后面也会解释说明)
        • doModInitFunctions: 加载了所有Cxx文件
        源码
        // doInitialization 源码
        bool ImageLoaderMachO::doInitialization(const LinkContext& context)
        {
            CRSetCrashLogMessage2(this->getPath());
        
            // mach-o has -init and static initializers
            doImageInit(context);
            doModInitFunctions(context);
            
            CRSetCrashLogMessage2(NULL);
            
            return (fHasDashInit || fHasInitializers);
        }
        
        
        // doImageInit 源码
        void ImageLoaderMachO::doImageInit(const LinkContext& context)
        {
            if ( fHasDashInit ) {
                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) {
                    switch (cmd->cmd) {
                        case LC_ROUTINES_COMMAND:
                            Initializer func = (Initializer)(((struct macho_routines_command*)cmd)->init_address + fSlide);
        #if __has_feature(ptrauth_calls)
                            func = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0);
        #endif
                            // <rdar://problem/8543820&9228031> verify initializers are in image
                            if ( ! this->containsAddress(stripPointer((void*)func)) ) {
                                dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
                            }
                            if ( ! dyld::gProcessInfo->libSystemInitialized ) {
                                // <rdar://problem/17973316> libSystem initializer must run first
                                dyld::throwf("-init function in image (%s) that does not link with libSystem.dylib\n", this->getPath());
                            }
                            if ( context.verboseInit )
                                dyld::log("dyld: calling -init function %p in %s\n", func, this->getPath());
                            {
                                dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
                                func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
                            }
                            break;
                    }
                    cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
                }
            }
        }
        
        // doModInitFunctions源码
        void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
        {
            if ( fHasInitializers ) {
                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_SEGMENT_COMMAND ) {
                        const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
                        const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
                        const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
                        for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
                            const uint8_t type = sect->flags & SECTION_TYPE;
                            if ( type == S_MOD_INIT_FUNC_POINTERS ) {
                                Initializer* inits = (Initializer*)(sect->addr + fSlide);
                                const size_t count = sect->size / sizeof(uintptr_t);
                                // <rdar://problem/23929217> Ensure __mod_init_func section is within segment
                                if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) )
                                    dyld::throwf("__mod_init_funcs section has malformed address range for %s\n", this->getPath());
                                for (size_t j=0; j < count; ++j) {
                                    Initializer func = inits[j];
                                    // <rdar://problem/8543820&9228031> verify initializers are in image
                                    if ( ! this->containsAddress(stripPointer((void*)func)) ) {
                                        dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
                                    }
                                    if ( ! dyld::gProcessInfo->libSystemInitialized ) {
                                        // <rdar://problem/17973316> libSystem initializer must run first
                                        const char* installPath = getInstallPath();
                                        if ( (installPath == NULL) || (strcmp(installPath, libSystemPath(context)) != 0) )
                                            dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n", this->getPath());
                                    }
                                    if ( context.verboseInit )
                                        dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
                                    bool haveLibSystemHelpersBefore = (dyld::gLibSystemHelpers != NULL);
                                    {
                                        dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
                                        func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
                                    }
                                    bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers != NULL);
                                    if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) {
                                        // now safe to use malloc() and other calls in libSystem.dylib
                                        dyld::gProcessInfo->libSystemInitialized = true;
                                    }
                                }
                            }
                            else if ( type == S_INIT_FUNC_OFFSETS ) {
                                const uint32_t* inits = (uint32_t*)(sect->addr + fSlide);
                                const size_t count = sect->size / sizeof(uint32_t);
                                // Ensure section is within segment
                                if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) )
                                    dyld::throwf("__init_offsets section has malformed address range for %s\n", this->getPath());
                                if ( seg->initprot & VM_PROT_WRITE )
                                    dyld::throwf("__init_offsets section is not in read-only segment %s\n", this->getPath());
                                for (size_t j=0; j < count; ++j) {
                                    uint32_t funcOffset = inits[j];
                                    // verify initializers are in image
                                    if ( ! this->containsAddress((uint8_t*)this->machHeader() + funcOffset) ) {
                                        dyld::throwf("initializer function offset 0x%08X not in mapped image for %s\n", funcOffset, this->getPath());
                                    }
                                    if ( ! dyld::gProcessInfo->libSystemInitialized ) {
                                        // <rdar://problem/17973316> libSystem initializer must run first
                                        const char* installPath = getInstallPath();
                                        if ( (installPath == NULL) || (strcmp(installPath, libSystemPath(context)) != 0) )
                                            dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n", this->getPath());
                                    }
                                    Initializer func = (Initializer)((uint8_t*)this->machHeader() + funcOffset);
                                    if ( context.verboseInit )
                                        dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
        #if __has_feature(ptrauth_calls)
                                    func = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0);
        #endif
                                    bool haveLibSystemHelpersBefore = (dyld::gLibSystemHelpers != NULL);
                                    {
                                        dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
                                        func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
                                    }
                                    bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers != NULL);
                                    if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) {
                                        // now safe to use malloc() and other calls in libSystem.dylib
                                        dyld::gProcessInfo->libSystemInitialized = true;
                                    }
                                }
                            }
                        }
                    }
                    cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
                }
            }
        }
        
    • notifySingle函数,其重点是(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());这行代码

      源码
      static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
      {
          //dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());
          std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers);
          if ( handlers != NULL ) {
              dyld_image_info info;
              info.imageLoadAddress	= image->machHeader();
              info.imageFilePath		= image->getRealPath();
              info.imageFileModDate	= image->lastModified();
              for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it != handlers->end(); ++it) {
                  const char* result = (*it)(state, 1, &info);
                  if ( (result != NULL) && (state == dyld_image_state_mapped) ) {
                      //fprintf(stderr, "  image rejected by handler=%p\n", *it);
                      // make copy of thrown string so that later catch clauses can free it
                      const char* str = strdup(result);
                      throw str;
                  }
              }
          }
          if ( state == dyld_image_state_mapped ) {
              // <rdar://problem/7008875> Save load addr + UUID for images from outside the shared cache
              if ( !image->inSharedCache() ) {
                  dyld_uuid_info info;
                  if ( image->getUUID(info.imageUUID) ) {
                      info.imageLoadAddress = image->machHeader();
                      addNonSharedCacheImageUUID(info);
                  }
              }
          }
          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);
              // MARK: 重点
              (*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);
              }
          }
          // mach message csdlc about dynamically unloaded images
          if ( image->addFuncNotified() && (state == dyld_image_state_terminated) ) {
              notifyKernel(*image, false);
              const struct mach_header* loadAddress[] = { image->machHeader() };
              const char* loadPath[] = { image->getPath() };
              notifyMonitoringDyld(true, 1, loadAddress, loadPath);
          }
      }

      sNotifyObjCInit的真实类型是: typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh);

      registerObjCNotifiers函数被调用时,存储了这个回调函数

      源码
      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());
              }
          }
      }

      registerObjCNotifiers函数是在_dyld_objc_notify_register函数中被调用的

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

      _dyld_objc_notify_register的函数是在libobjc源码中被调用的,也就是dyld暴露给libobjc的对外api

完整的dyld加载流程

.

dyld与objc

objc4-781源码中搜索_dyld_objc_notify_register函数

发现在_objc_init源码中调用了该方法,并传入了参数,所以sNotifyObjCInit的赋值的就是objc中的load_images

load_images会调用所有的+load方法

综上所述,notifySingle是一个回调函数,也就是:

libobjc_objc_init函数中调用了dyld_dyld_objc_notify_register函数,并将load_images传给dyld中以sNotifyObjCInit回调函数的方式存储起来

sNotifyObjCMapped == mapped == map_images
sNotifyObjCInit == init == load_images
sNotifyObjCUnmapped == unmapped == unmap_image
  • objc-os.mm_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();
        // 关于线程key的绑定,比如:线程数据的析构函数
        // 主要作用是: 本地线程池的初始化和析构
        tls_init();
        // 运行C++静态构造函数。在dyld调用我们的静态构造函数之前,libc 会调用 _objc_init()
        static_init();
        // runtime运行时环境初始化
        runtime_init();
        // libobjc异常处理系统初始化
        exception_init();
        // 缓存条件初始化
        cache_init();
        // 启动回调机制。通常不会做什么,因为所有的初始化都是惰性的
        _imp_implementationWithBlock_init();
        /*
        
        _dyld_objc_notify_register -- dyld 注册的地方
    
        - 仅供objc运行时使用
        - 注册处理程序,以便在映射、取消映射 和初始化objc镜像文件时使用,dyld将使用包含objc_image_info的镜像文件数组,回调 mapped 函数
        
        map_images:dyld将image镜像文件加载进内存时,会触发该函数
        load_images:dyld初始化image会触发该函数
        unmap_image:dyld将image移除时会触发该函数
        */
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    
    #if __OBJC2__
        didCallDyldNotifyRegister = true;
    #endif
    }

dyld跳到了libobjc, 知道了sNotifyObjCInitload_images(对内对外相当于一个形参, 一个实参)

load_images是在notifySingle函数中,通过sNotifyObjCInit调用的

_objc_init的调用流程需要借助符号断点查看堆栈信息

.

定位到libsystem,在libsysteminit.c中找到实现部分源码

源码
#define _libSystem_ktrace_init_func(what) \
    _libSystem_ktrace1(ARIADNE_LIFECYCLE_libsystem_init | DBG_FUNC_NONE, INIT_##what)

// libsyscall_initializer() initializes all of libSystem.dylib
// <rdar://problem/4892197>
__attribute__((constructor))
static void
libSystem_initializer(int argc,
            const char* argv[],
            const char* envp[],
            const char* apple[],
            const struct ProgramVars* vars)
{
    static const struct _libkernel_functions libkernel_funcs = {
        .version = 4,
        // V1 functions
#if !TARGET_OS_DRIVERKIT
        .dlsym = dlsym,
#endif
        .malloc = malloc,
        .free = free,
        .realloc = realloc,
        ._pthread_exit_if_canceled = _pthread_exit_if_canceled,
        // V2 functions (removed)
        // V3 functions
        .pthread_clear_qos_tsd = _pthread_clear_qos_tsd,
        // V4 functions
        .pthread_current_stack_contains_np = pthread_current_stack_contains_np,
    };

    static const struct _libpthread_functions libpthread_funcs = {
        .version = 2,
        .exit = exit,
        .malloc = malloc,
        .free = free,
    };

    static const struct _libc_functions libc_funcs = {
        .version = 1,
        .atfork_prepare = libSystem_atfork_prepare,
        .atfork_parent = libSystem_atfork_parent,
        .atfork_child = libSystem_atfork_child,
#if defined(HAVE_SYSTEM_CORESERVICES)
        .dirhelper = _dirhelper,
#endif
    };
    
    static const struct _malloc_functions malloc_funcs = {
        .version = 1,
#if !TARGET_OS_DRIVERKIT
        .dlopen = dlopen,
        .dlsym = dlsym,
#endif
    };
    
    _libSystem_ktrace0(ARIADNE_LIFECYCLE_libsystem_init | DBG_FUNC_START);

    __libkernel_init(&libkernel_funcs, envp, apple, vars);
    _libSystem_ktrace_init_func(KERNEL);

    __libplatform_init(NULL, envp, apple, vars);
    _libSystem_ktrace_init_func(PLATFORM);

    __pthread_init(&libpthread_funcs, envp, apple, vars);
    _libSystem_ktrace_init_func(PTHREAD);

    _libc_initializer(&libc_funcs, envp, apple, vars);
    _libSystem_ktrace_init_func(LIBC);

    // TODO: Move __malloc_init before __libc_init after breaking malloc's upward link to Libc
    __malloc_init(apple);
    _libSystem_ktrace_init_func(MALLOC);

#if TARGET_OS_OSX
    /* <rdar://problem/9664631> */
    __keymgr_initializer();
    _libSystem_ktrace_init_func(KEYMGR);
#endif

    // No ASan interceptors are invoked before this point. ASan is normally initialized via the malloc interceptor:
    // _dyld_initializer() -> tlv_load_notification -> wrap_malloc -> ASanInitInternal

    _dyld_initializer();
    _libSystem_ktrace_init_func(DYLD);

    libdispatch_init();
    _libSystem_ktrace_init_func(LIBDISPATCH);

#if !TARGET_OS_DRIVERKIT
    _libxpc_initializer();
    _libSystem_ktrace_init_func(LIBXPC);

#if CURRENT_VARIANT_asan
    setenv("DT_BYPASS_LEAKS_CHECK", "1", 1);
#endif
#endif // !TARGET_OS_DRIVERKIT

    // must be initialized after dispatch
    _libtrace_init();
    _libSystem_ktrace_init_func(LIBTRACE);

#if !TARGET_OS_DRIVERKIT
#if defined(HAVE_SYSTEM_SECINIT)
    _libsecinit_initializer();
    _libSystem_ktrace_init_func(SECINIT);
#endif

#if defined(HAVE_SYSTEM_CONTAINERMANAGER)
    _container_init(apple);
    _libSystem_ktrace_init_func(CONTAINERMGR);
#endif

    __libdarwin_init();
    _libSystem_ktrace_init_func(DARWIN);
#endif // !TARGET_OS_DRIVERKIT

    __stack_logging_early_finished(&malloc_funcs);

#if !TARGET_OS_IPHONE
    /* <rdar://problem/22139800> - Preserve the old behavior of apple[] for
    * programs that haven't linked against newer SDK.
    */
#define APPLE0_PREFIX "executable_path="
    if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_11){
        if (strncmp(apple[0], APPLE0_PREFIX, strlen(APPLE0_PREFIX)) == 0){
            apple[0] = apple[0] + strlen(APPLE0_PREFIX);
        }
    }
#endif

    _libSystem_ktrace0(ARIADNE_LIFECYCLE_libsystem_init | DBG_FUNC_END);

    /* <rdar://problem/11588042>
    * C99 standard has the following in section 7.5(3):
    * "The value of errno is zero at program startup, but is never set
    * to zero by any library function."
    */
    errno = 0;
}

在这里发现_dyld_initializer();libdispatch_init();被调用,与调用堆栈的结果一致

也说明了前面注释的原因,是在libsystem中初始化的dyld,同时初始化了libdispatch

libdispatchqueue.c中查看libdispatch_init函数的实现

源码
void
libdispatch_init(void)
{
	dispatch_assert(sizeof(struct dispatch_apply_s) <=
			DISPATCH_CONTINUATION_SIZE);

	if (_dispatch_getenv_bool("LIBDISPATCH_STRICT", false)) {
		_dispatch_mode |= DISPATCH_MODE_STRICT;
	}
#if HAVE_OS_FAULT_WITH_PAYLOAD && TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
	if (_dispatch_getenv_bool("LIBDISPATCH_NO_FAULTS", false)) {
		_dispatch_mode |= DISPATCH_MODE_NO_FAULTS;
	} else if (getpid() == 1 ||
			!os_variant_has_internal_diagnostics("com.apple.libdispatch")) {
		_dispatch_mode |= DISPATCH_MODE_NO_FAULTS;
	}
#endif // HAVE_OS_FAULT_WITH_PAYLOAD && TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR


#if DISPATCH_DEBUG || DISPATCH_PROFILE
#if DISPATCH_USE_KEVENT_WORKQUEUE
	if (getenv("LIBDISPATCH_DISABLE_KEVENT_WQ")) {
		_dispatch_kevent_workqueue_enabled = false;
	}
#endif
#endif

#if HAVE_PTHREAD_WORKQUEUE_QOS
	dispatch_qos_t qos = _dispatch_qos_from_qos_class(qos_class_main());
	_dispatch_main_q.dq_priority = _dispatch_priority_make(qos, 0);
#if DISPATCH_DEBUG
	if (!getenv("LIBDISPATCH_DISABLE_SET_QOS")) {
		_dispatch_set_qos_class_enabled = 1;
	}
#endif
#endif

#if DISPATCH_USE_THREAD_LOCAL_STORAGE
	_dispatch_thread_key_create(&__dispatch_tsd_key, _libdispatch_tsd_cleanup);
#else
	_dispatch_thread_key_create(&dispatch_priority_key, NULL);
	_dispatch_thread_key_create(&dispatch_r2k_key, NULL);
	_dispatch_thread_key_create(&dispatch_queue_key, _dispatch_queue_cleanup);
	_dispatch_thread_key_create(&dispatch_frame_key, _dispatch_frame_cleanup);
	_dispatch_thread_key_create(&dispatch_cache_key, _dispatch_cache_cleanup);
	_dispatch_thread_key_create(&dispatch_context_key, _dispatch_context_cleanup);
	_dispatch_thread_key_create(&dispatch_pthread_root_queue_observer_hooks_key,
			NULL);
	_dispatch_thread_key_create(&dispatch_basepri_key, NULL);
#if DISPATCH_INTROSPECTION
	_dispatch_thread_key_create(&dispatch_introspection_key , NULL);
#elif DISPATCH_PERF_MON
	_dispatch_thread_key_create(&dispatch_bcounter_key, NULL);
#endif
	_dispatch_thread_key_create(&dispatch_wlh_key, _dispatch_wlh_cleanup);
	_dispatch_thread_key_create(&dispatch_voucher_key, _voucher_thread_cleanup);
	_dispatch_thread_key_create(&dispatch_deferred_items_key,
			_dispatch_deferred_items_cleanup);
#endif

#if DISPATCH_USE_RESOLVERS // rdar://problem/8541707
	_dispatch_main_q.do_targetq = _dispatch_get_default_queue(true);
#endif

	_dispatch_queue_set_current(&_dispatch_main_q);
	_dispatch_queue_set_bound_thread(&_dispatch_main_q);

#if DISPATCH_USE_PTHREAD_ATFORK
	(void)dispatch_assume_zero(pthread_atfork(dispatch_atfork_prepare,
			dispatch_atfork_parent, dispatch_atfork_child));
#endif
	_dispatch_hw_config_init();
	_dispatch_time_init();
	_dispatch_vtable_init();
	_os_object_init();
	_voucher_init();
	_dispatch_introspection_init();
}

而此刻要查找的_objc_init函数就在_os_object_init();函数中被调用

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

_objc_init的调用顺序:(调用堆栈中摘取关键函数)

  • _dyld_start
  • dyldbootstrap::start
  • dyld::_main
  • dyld::initializeMainExecutable
  • ImageLoader::runInitializers
  • ImageLoader::processInitializers
  • ImageLoader::recursiveInitialization
  • doInitialization
  • libSystem_initializer(libSystem.B.dylib)
  • _os_object_init(libdispatch.dylib)
  • _objc_init(libobjc.A.dylib)

dyld与objc的关联

.

概括

dyld2

  1. 加载dyld到App进程
  2. 加载动态库(包括所依赖的所有动态库)
  3. Rebase
  4. Bind
  5. 初始化Objective C Runtime
  6. 其它的初始化代码

加载动态库

dyld 会首先读取 mach-o 文件的 Headerload commands

接着就知道了这个可执行文件依赖的动态库。例如加载动态库A到内存,接着检查A所依赖的动态库,就这样的递归加载,直到所有的动态库加载完毕。通常一个App所依赖的动态库在100-400个左右,其中大多数都是系统的动态库,它们会被缓存到 dsc(dyld shared cache),这样读取的效率会很高。

Rebase && Bind

有两种主要的技术来保证应用的安全:ASLRCode Sign

App被启动的时候,程序会被影射到逻辑的地址空间,这个逻辑的地址空间有一个起始地址,而 ASLR 技术使得这个起始地址是随机的。如果是固定的,那么黑客很容易就可以由起始地址+偏移量找到函数的地址。

在进行 Code sign 的时候,加密哈希不是针对于整个文件,而是针对于每一个Page的。这就保证了在 dyld 进行加载的时候,可以对每一个 page 进行独立的验证。

mach-o 中有很多符号,有指向当前 mach-o 的,也有指向其他 dylib 的,比如 printf

mach-o 中采用了 PIC(Position Independ code) 技术, 当你的程序要调用 printf 的时候,会先在 __DATA 段中建立一个指针指向 printf,在通过这个指针实现间接调用。dyld 这时候需要做一些 fix-up 工作,即帮助应用程序找到这些符号的实际地址。主要包括两部分

这里我理解的应该是指间接符号表

  • Rebase 修正内部(指向当前mach-o文件)的指针指向
  • Bind 修正外部指针指向

dyld3

.

dyld 3 并不是 WWDC19 推出来的新技术,早在2017年就被引入至 iOS 11,当时主要用来优化系统库。

现在,在iOS 13 中它也将用于启动第三方APP,将完全替代 dyld 2。由于 dyld 3 的代码并未开源,目前仅能通过官方披露的资料来了解到底做了什么改进。

dyld 3 最大的特点就是部分是进程外的且有缓存的,在打开APP时,实际上已经有不少工作都完成了

dyld2 是纯粹的 in-process,也就是在程序进程内执行的,也就意味着只有当应用程序被启动的时候,dyld2 才能开始执行任务。

dyld3 则是部分 out-of-process,部分 in-process。图中,虚线之上的部分是 out-of-process 的,在App下载安装和版本更新的时候会去执行,out-of-process 会做如下事情:

  • 分析Mach-o Headers
  • 分析依赖的动态库
  • 查找需要Rebase & Bind之类的符号
  • 把上述结果写入缓存

这样,在应用启动的时候,就可以直接从缓存中读取数据,加快加载速度

外链

⚠️ **GitHub.com Fallback** ⚠️