13-dyld

2021-05-12  本文已影响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的整体流程

总结

上一篇下一篇

猜你喜欢

热点阅读