赞
踩
在了解应用程序加载顺序之前,我们需要先了解一下runtime
中比较常用的方法load
以及C++
方法、main
函数的执行顺序
ViewController.m
文件中添加load
方法,并在方法中打断点+ (void)load {
NSLog(@"%s",__func__);
}
main.m
文件中增加C++
方法,并且在方法中打断点__attribute__((constructor)) void kcFunc() {
printf("来了 : %s \n",__func__);
}
main
函数中打断点int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
NSLog(@"1223333");
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
由打印结果可知,这三个函数的加载顺序为:load -> C++ -> main
,可仍然会有疑问
思考:1. 按照之前的理解,main是程序的第一个入口,但load比mian先执行,那么入口到底是什么?2. load函数是谁在调用
在分析App自动之前,我们需要先了解iOSApp代码的编译过程
以及动/静态库
其中编译过程
如下所示,主要分为以下几个步骤
源文件
:载入.h
、.m
、.cpp
等文件预处理
:替换宏,删除注释,展开头文件,产生.i
文件编译
:将.i文件转换为汇编语言,产生.s
文件汇编
:将汇编文件转换为机器码文件,产生.o
文件链接
:对.o文件中引用其他库的地方进行引用,生成可执行
文件静态库
:.a
和.framework
文件,在链接时,静态库会被完整的复制
到可执行文件中,被多次使用就有多份冗余拷贝
。动态库
:.dylib
和.framework
文件,链接时动态库不复制
,在程序运行时
由系统动态的加载到内存,供程序调用,系统只加载一次
,多个程序共用
,节省内存
。带着上述疑问,我们在load
函数内加断点,并使用bt
命令查看堆栈信息
使用bt
命令打印堆栈信息,可知:_dyld_start
为整个项目的第一个函数入口,该函数在dyld
框架内,从dyld源码地址下载源码,改源码无法运行起来,只能打开查看
如果读bt
命令打印出的堆栈信息,可以直接查看左侧的堆栈目录也是一样的
在dyld
源码中全局搜索_dyld_start
方法,在dyld底层,该方法是做了底层环境区分的,虽然每个环境的编译大致相同,但还是建议查看arm64
的源码
#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 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 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) // 调用 dyldbootstrap::start方法 bl __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm mov x16,x0 // save entry point address in x16 ldr x1, [sp] cmp x1, #0 b.ne Lnew // LC_UNIXTHREAD way, clean up stack and jump to result add sp, x28, #8 // restore unaligned stack pointer without app mh braaz x16 // jump to the program's entry point // LC_MAIN case, set up stack for call to main() Lnew: mov lr, x1 // simulate return address into _start in libdyld.dylib #if __LP64__ ldr x0, [x28, #8] // main param1 = argc add x1, x28, #16 // main param2 = argv add x2, x1, x0, lsl #3 add x2, x2, #8 // main param3 = &env[0] mov x3, x2 Lapple: ldr x4, [x3] add x3, x3, #8 #endif cmp x4, #0 b.ne Lapple // main param4 = apple #if __arm64e__ braaz x16 #endif
源码有点长,我对代码做了一些删减,删减部分主要是if-else
部分,可暂时不去查看,但是apple
的注释写的还是不错的,英文好的可以直接查看,不好的用翻译软件也是可以的。在源码中可以看到call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
注释,调用 dyldbootstrap::start
方法,根据堆栈信息可知该方法为_dyld_start
方法执行后的第二个方法,全局搜索,找到该方法
全局搜索start
方法,最终我们在dyldbootstrap.cpp
文件中找到该函数。
仍然查看源码以及注释,源码如下
// 这是引导dyld的代码。这项工作通常是有dyld和crt完成的。在dyld中我们必须手动执行此操作 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_start
中进行调用没有
运行C++
的初始化
程序时,dyld调用runDyldInitializers(argc, argv, envp, apple);
方法进行所有C++初始化程序的运行。dyld::_main
方法进行下一步操作Simulator模拟器
,而查看的信息为arm64真机
环境。该函数的的源码有点长,而且基本删减不了什么,需要耐心查看,源码如下
// dyld的入口点,内核加载dyld并跳转到 __dyld_start , 后者设置一些寄存器并调用此函数 // 返回目标程序 __dyld_start 跳转到 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); } // 检查是否有内核标志 dyld3::BootArgs::setFlags(hexToUInt64(_simple_getenv(apple, "dyld_flags"), nullptr)); // 从环境中获取 main 可执行文件的 cdHash uint8_t mainExecutableCDHashBuffer[20]; const uint8_t* mainExecutableCDHash = nullptr; if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) ) mainExecutableCDHash = mainExecutableCDHashBuffer; #if !TARGET_OS_SIMULATOR // 通知 dyld 的加载 notifyKernelAboutImage((macho_header*)&__dso_handle, _simple_getenv(apple, "dyld_file")); // 通知 main 可执行文件的加载 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 // 给所有的image设置平台ID,以便调试器可以告诉进行类型 // 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. // 它永远不会再我们任何嵌入式平台上发生,只能发生于macOS上 } } CRSetCrashLogMessage("dyld: launch started"); // 获取上下文 -- 这个函数中是对 gLinkContent 进行一系列的配置 setContext(mainExecutableMH, argc, argv, envp, apple); // Pickup the pointer to the exec path. // 获取执行exec路径的指针,exec就是编译后的可执行文件。 sExecPath = _simple_getenv(apple, "executable_path"); // <rdar://problem/13868260> Remove interim apple[0] transition code from dyld // 从 dyld 移除临时 apple[0] 过渡代码 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 // 内核没有为 main 可执行文件传递真实路径 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] != '/' ) { // 有相对路径,需要使用 cmd 获取绝对路径 char cwdbuff[MAXPATHLEN]; if ( getcwd(cwdbuff, MAXPATHLEN) != NULL ) { // maybe use static buffer to avoid calling malloc so early... // 可用静态缓冲区内容来避免过早的调用 malloc char* s = new char[strlen(cwdbuff) + strlen(sExecPath) + 2]; strcpy(s, cwdbuff); strcat(s, "/"); strcat(s, sExecPath); sExecPath = s; } } // 记录进场简称,以便以后记录 sExecShortName = ::strrchr(sExecPath, '/'); if ( sExecShortName != NULL ) ++sExecShortName; else sExecShortName = sExecPath; // 配置主流程 -- 内容仍然是配置 gLinkContext 内容 configureProcessRestrictions(mainExecutableMH, envp); // Check if we should force dyld3. Note we have to do this outside of the regular env parsing due to AMFI // 检查是否应强制使用dyld3 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 ) { // 只有macOS能够设置 } else { // 报警告 dyld::warn("unknown option to DYLD_USE_CLOSURES. Valid options are: 0 and 1\n"); } } } // 检查环境变量 checkEnvironmentVariables(envp); // 默认未初始化的后背路径 defaultUninitializedFallbackPaths(envp); 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) ) { // 使用真实路径,防止出现类似 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 ) // 模式设置:on意味着我们设置DYLD_USE_CLOUSURES = 1,有env变量或iOS上的客户缓存 sClosureMode = ClosureMode::On; // 获取Host信息 getHostInfo(mainExecutableMH, mainExecutableSlide); // 加载共享缓存,iOS无法在没有共享缓存的情况下运行 checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide); if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) { #if TARGET_OS_SIMULATOR if ( sSharedCacheOverrideDir) mapSharedCache(); #else // 共享缓存映射 mapSharedCache(); #endif } // 如果没有设置ClosureMode,需要检查环境和缓存类型 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; } // 检查缓存是否关闭 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 // 我们只想尝试在运行时构建闭包(如果它是iOS第三方二进制文件或者来自共享缓存的macOS二进制文件) // 是否允许Closure重建 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; // 如果没有找到有效的closure缓存,将尝试构建一个新的closure 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); } } // 生成闭包后退出dyld,不运行程序 if ( sJustBuildClosure ) _exit(EXIT_SUCCESS); // try using launch closure if ( mainClosure != nullptr ) { CRSetCrashLogMessage("dyld3: launch started"); // 构建所有已知的 image 列表 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 // chosure超时,创建新的 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. // start() 将结果指针作为函数指针调用,因此我们需要对其进行签名。 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 // 无法使用closure信息,以旧方式启动 // 安装gdb通知程序 stateToHandlers(dyld_image_state_dependents_mapped, sBatchHandlers)->push_back(notifyGDB); stateToHandlers(dyld_image_state_mapped, sSingleHandlers)->push_back(updateAllImages); // 使初始分配足够大,以至于不太可能需要重新分配 sImageRoots.reserve(16); sAddImageCallbacks.reserve(4); sRemoveImageCallbacks.reserve(4); sAddLoadImageCallbacks.reserve(4); sImageFilesNeedingTermination.reserve(16); sImageFilesNeedingDOFUnregistration.reserve(8); #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 { // 将dyld本身添加到UUID列表 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 // simulators, iOS, tvOS, watchOS, are always strict gLinkContext.strictMachORequired = true; CRSetCrashLogMessage(sLoadingCrashMessage); // 为main可执行文件实例化ImageLoader 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 // 运行所有初始化程序 initializeMainExecutable(); #endif // 通知dyld可以进入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); { // 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; }
大致流程:
mapSharedCache
stateToHandlers(dyld_image_state_dependents_mapped, sBatchHandlers)->push_back(notifyGDB)
instantiateFromLoadedImage
loadInsertedDylib
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1)
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1)
weakBind
initializeMainExecutable
notifyMonitoringDyldMain
根据_main
方法源码以及LLDB
的堆栈信息可知,该方法是_main内部执行的,源码如下
void initializeMainExecutable() { // record that we've reached this step gLinkContext.startedInitializingMainExecutable = true; // run initialzers for any inserted dylibs // 加载并插入所有的dylib 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 // 运行main可执行文件 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
源码,源码如下
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() };
// 递归加载dylib
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);
}
源码如下:
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); }
源码如下
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(); }
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
image初始化方法doModInitFunctions
源码如下:
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); } } }
libSystem.dylib
必须先运行源码如下:
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); (*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); } }
dyld_start
dyld入口dyldbootstrap::start
dyld::_main
dyld的main入口mapSharedCache
共享缓存加载instantiateFromLoadedImage
实例化主程序loadInsertedDylib
加载插入的动态库link
主程序link
插入的动态库weakBind
弱引用绑定主程序initializeMainExecutable
初始化主程序notifyMonitoringDyldMain
通知dyld可以进mian()
函数Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。