13-dyld

作者: 深圳_你要的昵称 | 来源:发表于2021-05-12 14:07 被阅读0次

    前言

    dyld(the dynamic link editor)动态链接器,是苹果操作系统一个重要组成部分,在系统内核做好程序准备工作之后,交由dyld负责余下的工作。

    一、dyld加载流程

    dyld的加载流程,在我之前的文章iOS应用程序加载大致流程分析中已经分析了(当时版本是dyld-750.6)。

    dyld最新版本 👉 Apple Source中搜索dyld-832.7.3并下载。

    现在我们来看看最新版的代码的不同之处。

    1.1dyld-832.7.3的优化点

    1.1.1 主程序可执行文件

        // Grab the cdHash of the main executable from the environment
        // 从环境中获取主可执行文件的cdHash值
        uint8_t mainExecutableCDHashBuffer[20];
        const uint8_t* mainExecutableCDHash = nullptr;
        if ( const char* mainExeCdHashStr = _simple_getenv(apple, "executable_cdhash") ) {
            unsigned bufferLenUsed;
            if ( hexStringToBytes(mainExeCdHashStr, mainExecutableCDHashBuffer, sizeof(mainExecutableCDHashBuffer), bufferLenUsed) )
                mainExecutableCDHash = mainExecutableCDHashBuffer;
        }
        
        getHostInfo(mainExecutableMH, mainExecutableSlide);
    

    区别于dyld-750.6,新版本使用hexStringToBytes()替换hexToBytes(),同时调用getHostInfo获取主程序Header,Slide(ASLR的偏移值)等信息。

    1.1.2 ptrauth_calls

    #if __has_feature(ptrauth_calls)
        // Check and see if kernel disabled JOP pointer signing (which lets us load plain arm64 binaries)
        if ( const char* disableStr = _simple_getenv(apple, "ptrauth_disabled") ) {
            if ( strcmp(disableStr, "1") == 0 )
                sKeysDisabled = true;
        }
        else {
            // needed until kernel passes ptrauth_disabled for arm64 main executables
            if ( (mainExecutableMH->cpusubtype == CPU_SUBTYPE_ARM64_V8) || (mainExecutableMH->cpusubtype == CPU_SUBTYPE_ARM64_ALL) )
                sKeysDisabled = true;
        }
    #endif
    

    针对ptrauth_calls的处理,也是新版中才有的👇

    如果加载普通的arm64二进制文件 👉 检查内核是否禁用了JOP指针签名,如果未禁用,那么需要直到内核为arm64主可执行文件中传值ptrauth_disabled

    1.1.3 platform 的处理

    dyld-750.6中针对所有镜像文件,会关联platform ID信息,这样调试器就能告诉进程当前的平台类型(是iOS 还是 MacOS),相关代码👇

        // 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
        // 这块处理可以被删除,一旦我们在`内核`中处理了`platform`
        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
        // 如果是模拟器调试的情况:实际使用的是主机的dyld进行加载,那么此时要确保模拟器正处于运行的状态
        const char* rootPath = _simple_getenv(envp, "DYLD_ROOT_PATH");
        if ( (rootPath != NULL) ) {
            // look to see if simulator has its own dyld
            // 模拟器是否有自己的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 {
            // 在路径"DYLD_ROOT_PATH"中未找到dyld,如果是模拟器运行的程序,那么报错 👉 "DYLD_ROOT_PATH not set"
            ((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
    

    而在新版本dyld-832.7.3中并没有判断if (gProcessInfo->version >= 16),而是直接处理👇

        // 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
        // The host may not have the platform field in its struct, but there's space for it in the padding, so always set it
        // 主机可能在其结构中没有platform 字段,但在填充中有空间,所以总是设置它
        {
          // 这里的代码和dyld-750.6一样
        }
    

    1.1.4 arm64e的处理

    新版本的arm64e处理👇

    #if TARGET_OS_OSX && __has_feature(ptrauth_calls)
        // on Apple Silicon macOS, only Apple signed ("platform binary") arm64e can be loaded
        // 在Apple Silicon macOS上,只有Apple签名(“平台二进制”)arm64e可以被加载
        sOnlyPlatformArm64e = true;
    
        // internal builds, or if boot-arg is set, then non-platform-binary arm64e slices can be run
        // 内部构建,或者如果设置了boot-arg,则只有非平台二进制的arm64e架构可以运行
        if ( const char* abiMode = _simple_getenv(apple, "arm64e_abi") ) {
            if ( strcmp(abiMode, "all") == 0 )
                sOnlyPlatformArm64e = false;
        }
    #endif
    

    1.1.4 dyld3:闭包方式的加载流程

    新版本中,dyld3的闭包模式处理加载流程,增加了对useClosures == "2"这种情况的判👇

        // AMFI相关(Apple Mobile File Integrity苹果移动文件保护)
        // 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 !__i386__ // don't support dyld3 for 32-bit macOS
                    sClosureMode = ClosureMode::On;
                    sClosureKind = ClosureKind::full;
        #endif
                } else if ( strcmp(useClosures, "2") == 0 ) {
                    sClosureMode = ClosureMode::On;
                    sClosureKind = ClosureKind::minimal;
                } else {
                    dyld::warn("unknown option to DYLD_USE_CLOSURES.  Valid options are: 0 and 1\n");
                }
    
            }
        }
    
    

    同时,增加了针对PLATFORM_IOS(iOS系统下)ARM64架构的处理👇

    #if TARGET_OS_OSX
        switch (gProcessInfo->platform) {
    #if (TARGET_OS_OSX && TARGET_CPU_ARM64)
            case PLATFORM_IOS:
                sClosureMode = ClosureMode::On; // <rdar://problem/56792308> Run iOS apps on macOS in dyld3 mode
                [[clang::fallthrough]];
    #endif
            case PLATFORM_MACCATALYST:
                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;
                break;
            case PLATFORM_DRIVERKIT:
                gLinkContext.driverKit = true;
                gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion;
                break;
        }
    #endif
    

    dyld3对闭包的构建,也做了优化,封装buildClosureCachePath函数,交由闭包自己去处理路径👇

    #if !TARGET_OS_SIMULATOR
        if ( _simple_getenv(envp, "DYLD_JUST_BUILD_CLOSURE") != nullptr ) {
    #if TARGET_OS_IPHONE
            char tempClosurePath[PATH_MAX];
            if ( dyld3::closure::LaunchClosure::buildClosureCachePath(sExecPath, envp, false, tempClosurePath) )
                sJustBuildClosure = true;
    #endif
            // If the env vars for the data contain look wrong, don't want to launch the app as that would bring up the UI
            if (!sJustBuildClosure) {
                _exit(EXIT_SUCCESS);
            }
        }
    #endif
    

    而旧版本的是直接在_main函数中处理,其实这里并不关心👇

    #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
    

    1.1.5 boot-args处理

    新版本中特别对 boot-args进行了处理,分别针对macOSiOS模拟器👇

    #if !TARGET_OS_SIMULATOR
        if ( getpid() == 1 ) {
            // Get the value as set by the boot-args
            // 获取关于boot-args相关的参数值
            uint64_t commPageValue = 0;
            size_t commPageValueSize = sizeof(commPageValue);
            if ( sysctlbyname("kern.dyld_flags", &commPageValue, &commPageValueSize, nullptr, 0) != 0 ) {
                // Try again with the old name
                // TODO: Remove this when we are always on new enough kernels
                sysctlbyname("kern.dyld_system_flags", &commPageValue, &commPageValueSize, nullptr, 0);
            }
    
            commPageValue &= CommPageBootArgMask;
            // logToConsole("dyld: got comm page flags 0x%llx\n", commPageValue);
    
            // If we are PID 1 (launchd) and on macOS, then we should check if the simulator support dylibs
            // are roots or not.
            // If they are not roots at launchd time, and the file system is read-only, then we know for sure
            // they will not be roots later
            // 系统静态库root权限的处理
    #if DYLD_SIMULATOR_ROOTS_SUPPORT
            bool fileSystemIsWritable = true;
    
            // logToConsole("dyld: in launchd\n");
            struct statfs statBuffer;
            int statResult = statfs("/", &statBuffer);
            if ( statResult == 0 ) {
                if ( !strcmp(statBuffer.f_fstypename, "apfs") ) {
                    if ( (statBuffer.f_flags & (MNT_RDONLY | MNT_SNAPSHOT)) == (MNT_RDONLY | MNT_SNAPSHOT) ) {
                        // logToConsole("dyld: got statfs flags 0x%llx\n", statBuffer.f_flags);
                        fileSystemIsWritable = false;
                    }
                }
            } else {
                int error = errno;
                logToConsole("dyld: could not stat '/', errno = %d\n", error);
            }
    
            // If the file system is read-only, then we can check now whether any of the simulator support
            // dylibs are roots
            bool libsystemKernelIsRoot      = false;
            bool libsystemPlatformIsRoot    = false;
            bool libsystemPThreadIsRoot     = false;
            if ( !fileSystemIsWritable && (sSharedCacheLoadInfo.loadAddress != nullptr)) {
                dyld3::closure::FileSystemPhysical fileSystem;
                libsystemKernelIsRoot   = !dyld3::RootsChecker::uuidMatchesSharedCache("/usr/lib/system/libsystem_kernel.dylib",
                                                                                       &fileSystem, sSharedCacheLoadInfo.loadAddress);
                libsystemPlatformIsRoot = !dyld3::RootsChecker::uuidMatchesSharedCache("/usr/lib/system/libsystem_platform.dylib",
                                                                                       &fileSystem, sSharedCacheLoadInfo.loadAddress);
                libsystemPThreadIsRoot  = !dyld3::RootsChecker::uuidMatchesSharedCache("/usr/lib/system/libsystem_pthread.dylib",
                                                                                       &fileSystem, sSharedCacheLoadInfo.loadAddress);
            }
            commPageValue |= (fileSystemIsWritable ? CommPageFlags::fileSystemCanBeModified : CommPageFlags::None);
            commPageValue |= (libsystemKernelIsRoot ? CommPageFlags::libsystemKernelIsRoot : CommPageFlags::None);
            commPageValue |= (libsystemPlatformIsRoot ? CommPageFlags::libsystemPlatformIsRoot : CommPageFlags::None);
            commPageValue |= (libsystemPThreadIsRoot ? CommPageFlags::libsystemPThreadIsRoot : CommPageFlags::None);
    #endif // DYLD_SIMULATOR_ROOTS_SUPPORT
    
            logToConsole("dyld: setting comm page to 0x%llx\n", commPageValue);
            if ( sysctlbyname("kern.dyld_flags", nullptr, 0, &commPageValue, sizeof(commPageValue)) != 0 ) {
                // Try again with the old name
                // TODO: Remove this when we are always on new enough kernels
                sysctlbyname("kern.dyld_system_flags", nullptr, 0, &commPageValue, sizeof(commPageValue));
            }
        }
    
    #if DYLD_SIMULATOR_ROOTS_SUPPORT
        // Set the roots checker to the state from the comm page
        // comm page的状态的root权限检查
        {
            uint64_t dyldFlags = *((uint64_t*)_COMM_PAGE_DYLD_SYSTEM_FLAGS);
            bool fileSystemCanBeModified = dyldFlags & CommPageFlags::fileSystemCanBeModified;
            bool libsystemKernelIsRoot = dyldFlags & CommPageFlags::libsystemKernelIsRoot;
            bool libsystemPlatformIsRoot = dyldFlags & CommPageFlags::libsystemPlatformIsRoot;
            bool libsystemPThreadIsRoot = dyldFlags & CommPageFlags::libsystemPThreadIsRoot;
            sRootsChecker.setFileSystemCanBeModified(fileSystemCanBeModified);
            sRootsChecker.setLibsystemKernelIsRoot(libsystemKernelIsRoot);
            sRootsChecker.setLibsystemPlatformIsRoot(libsystemPlatformIsRoot);
            sRootsChecker.setLibsystemPThreadIsRoot(libsystemPThreadIsRoot);
        }
    #endif // DYLD_SIMULATOR_ROOTS_SUPPORT
    
    #endif // !TARGET_OS_SIMULATOR
    

    1.1.6 bootToken

            // <rdar://60333505> bootToken is a concat of boot-hash kernel passes down for app and dyld's uuid
            // bootToken是引导【app哈希值 + dyld的uuid的哈希值】,由内核传递
            uint8_t bootTokenBufer[128];
            unsigned bootTokenBufferLen = 0;
            if ( const char* bootHashStr = _simple_getenv(apple, "executable_boothash") ) {
                if ( hexStringToBytes(bootHashStr, bootTokenBufer, sizeof(bootTokenBufer), bootTokenBufferLen) ) {
                    if ( ((dyld3::MachOFile*)&__dso_handle)->getUuid(&bootTokenBufer[bootTokenBufferLen]) )
                        bootTokenBufferLen += sizeof(uuid_t);
                }
            }
            // 存储bootToken到dyld::Array中
            dyld3::Array<uint8_t> bootToken(bootTokenBufer, bootTokenBufferLen, bootTokenBufferLen);
    
    1.1.7 aot images
    #if defined(__x86_64__) && !TARGET_OS_SIMULATOR
            if (dyld::isTranslated()) {
                struct dyld_all_runtime_info {
                    uint32_t image_count;
                    dyld_image_info* images;
                    uint32_t uuid_count;
                    dyld_uuid_info* uuids;
                    uint32_t aot_image_count;
                    dyld_aot_image_info* aots;
                    dyld_aot_shared_cache_info aot_cache_info;
                };
    
                dyld_all_runtime_info* runtime_info;
                int ret = syscall(0x7000004, &runtime_info);
                if (ret == 0) {
                    for (int i = 0; i < runtime_info->uuid_count; i++) {
                        dyld_image_info image_info = runtime_info->images[i];
                        dyld_uuid_info uuid_info = runtime_info->uuids[i];
    
                        // add the arm64 cambria runtime to uuid info
                        addNonSharedCacheImageUUID(uuid_info);
    
                        struct stat sb;
                        if (stat(image_info.imageFilePath, &sb) == 0) {
                            fsid_t fsid = {{0, 0}};
                            fsobj_id_t fsobj = {0};
                            ino_t inode = sb.st_ino;
                            fsobj.fid_objno = (uint32_t)inode;
                            fsobj.fid_generation = (uint32_t)(inode>>32);
                            fsid.val[0] = sb.st_dev;
    
                            dyld3::kdebug_trace_dyld_image(DBG_DYLD_UUID_MAP_A, image_info.imageFilePath, &(uuid_info.imageUUID), fsobj, fsid, image_info.imageLoadAddress);
                        }
                    }
    
                    // add aot images to dyld_all_image_info
                    addAotImagesToAllAotImages(runtime_info->aot_image_count, runtime_info->aots);
    
                    // add the arm64 cambria runtime to dyld_all_image_info
                    addImagesToAllImages(runtime_info->image_count, runtime_info->images);
    
                    // set the aot shared cache info in dyld_all_image_info
                    dyld::gProcessInfo->aotSharedCacheBaseAddress = runtime_info->aot_cache_info.cacheBaseAddress;
                    memcpy(dyld::gProcessInfo->aotSharedCacheUUID, runtime_info->aot_cache_info.cacheUUID, sizeof(uuid_t));
                }
            }
    #endif
    

    新版本的添加了对aot images镜像文件的处理 👉 将aot images都添加到了所有镜像文件的表中,然后dyld统一按顺序加载。

    1.2 dyld3的优化点

    上述代码中可以看出,dyld3采用的是ClosureMode闭包模式,以回调的方式加载。闭包模式加载速度更快,效率更高iOS13动态库第三方库都使ClosureMode加载。

    1.2.1 dyld3的加载流程

    1. 找到/创建mainClosure后,通过launchWithClosure启动主程序,启动失败后会有重新创建mainClosure,接着重复重新启动的逻辑;成功后返回result(主程序入口main)
    2. launchWithClosure中的逻辑和dyld2启动主程序逻辑基本相同。

    1.2.2 dyld2的加载流程

    dyld2的加载流程之前iOS应用程序加载大致流程分析中是分析过的👇

    1. 主程序表初始化 👉 instantiateFromLoadedImage
    2. 插入动态库 👉 loadInsertedDylib,其中主程序动态库都会添加到allImages中,并执行loadAllImages
    3. 链接主程序表 和 所有动态库 👉 link
    4. 符号绑定 👉 包括非懒加载符号和弱符号
    5. 初始化所有 👉 initializeMainExecutable
    6. 主程序入口处理

    1.3 _main的整体流程

    总结

    相关文章

      网友评论

          本文标题:13-dyld

          本文链接:https://www.haomeiwen.com/subject/zmegdltx.html