前言
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
进行了处理,分别针对macOS
和iOS模拟器
👇
#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的加载流程
- 找到/创建
mainClosure
后,通过launchWithClosure
启动主程序,启动失败
后会有重新创建mainClosure
,接着重复重新启动的逻辑;成功
后返回result(主程序入口main)
。 -
launchWithClosure
中的逻辑和dyld2
启动主程序逻辑基本相同。
1.2.2 dyld2的加载流程
dyld2的加载流程之前iOS应用程序加载大致流程分析中是分析过的👇
- 主程序表初始化 👉
instantiateFromLoadedImage
- 插入动态库 👉
loadInsertedDylib
,其中主程序
和动态库
都会添加到allImages
中,并执行loadAllImages
- 链接主程序表 和 所有动态库 👉
link
- 符号绑定 👉 包括非懒加载符号和弱符号
- 初始化所有 👉
initializeMainExecutable
- 主程序入口处理
网友评论