应用程序的加载 - ShenYj/ShenYj.github.io GitHub Wiki
通过设置断点,在调用堆栈处验证:app启动从dyld
的_dyld_start
函数开始
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.cpp
中start(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_header
是Mach-O
的头部,而dyld
加载的文件就是Mach-O
类型的,即Mach-O
类型是可执行文件类型,由四部分组成:Mach-O
头部、Load Command
、section、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
函数中主要做了以下几件事情:- 环境变量配置:根据环境变量设置相应的值以及获取当前运行架构
- 共享缓存:检查是否开启了共享缓存,以及共享缓存是否映射到共享区域,例如
UIKit
、CoreFoundation
等 - 主程序的初始化:调用
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 = §ionsStart[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加载流程
在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
, 知道了sNotifyObjCInit
与load_images
(对内对外相当于一个形参, 一个实参)
load_images
是在notifySingle
函数中,通过sNotifyObjCInit
调用的
而_objc_init
的调用流程需要借助符号断点查看堆栈信息
定位到libsystem
,在libsystem
的init.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
在libdispatch
的queue.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的关联
- 加载dyld到App进程
- 加载动态库(包括所依赖的所有动态库)
- Rebase
- Bind
- 初始化Objective C Runtime
- 其它的初始化代码
dyld
会首先读取 mach-o
文件的 Header
和 load commands
接着就知道了这个可执行文件依赖的动态库。例如加载动态库A到内存,接着检查A所依赖的动态库,就这样的递归加载,直到所有的动态库加载完毕。通常一个App所依赖的动态库在100-400个左右,其中大多数都是系统的动态库,它们会被缓存到 dsc(dyld shared cache)
,这样读取的效率会很高。
有两种主要的技术来保证应用的安全:ASLR 和 Code 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
修正外部指针指向
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之类的符号
- 把上述结果写入缓存
这样,在应用启动的时候,就可以直接从缓存中读取数据,加快加载速度