一、dyld概述
dyld
(the dynamic link editor
)动态链接器,是苹果操作系统一个重要组成部分,在系统内核做好程序准备工作之后交由dyld
负责余下的工作。
二、dyld 源码分析
新建一个工程,写一个+load
方法,分别在+load
和main
函数上打断点,然后运行在真机上。
这个时候函数的调用栈如下:
汇编代码如下:
在dyld
源码中(dyld-832.7.3
)找到dyldbootstrap::start
函数(dyldInitialization.cpp
),这也就是函数开始的地方。
接下来结合源码分析怎么从start
调用到load
和main
方法。
2.1dyldbootstrap::start(dyldInitialization.cpp
)
可以通过搜索dyldbootstrap
命名空间找到start
源码。核心代码如下:
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{
//告诉debug server dyld启动
// 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
//重定位dyld
rebaseDyld(dyldsMachHeader);
//栈溢出保护
__guard_setup(apple);
//初始化dyld
_subsystem_init(apple);
//调用dyld main函数
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
start
主要做了以下几件事:
- 告诉
debug server
dyld
启动 - 重定位
dyld
- 栈溢出保护
- 初始化
dyld
- 调用
dyld main
函数
其中start
只做了一些配置和初始化的工作,核心逻辑在main
函数中,start
返回了main
函数的返回值。
2.1.1dyld::_main(dyld2.cpp
)
核心逻辑如下:
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 ( const char* mainExeCdHashStr = _simple_getenv(apple, "executable_cdhash") ) {
unsigned bufferLenUsed;
if ( hexStringToBytes(mainExeCdHashStr, mainExecutableCDHashBuffer, sizeof(mainExecutableCDHashBuffer), bufferLenUsed) )
mainExecutableCDHash = mainExecutableCDHashBuffer;
}
//获取主程序Header,Slide(ASLR的偏移值)
getHostInfo(mainExecutableMH, mainExecutableSlide);
……
uintptr_t result = 0;
sMainExecutableMachHeader = mainExecutableMH;
sMainExecutableSlide = mainExecutableSlide;
……
CRSetCrashLogMessage("dyld: launch started");
//设置上下文,将信息放入 gLinkContext 中
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];
……
//根据环境变量 envp 配置进程是否受限制,AMFI相关(Apple Mobile File Integrity苹果移动文件保护)
configureProcessRestrictions(mainExecutableMH, envp);
……
#if TARGET_OS_OSX
if ( !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache ) {
pruneEnvironmentVariables(envp, &apple);
// set again because envp and apple may have changed or moved
//又设置一次上下文,在文件受限的时候可能更改了envp。
setContext(mainExecutableMH, argc, argv, envp, apple);
}
else
#endif
{
//检测环境变量并设置默认值,这个时候还没有加载数据。
checkEnvironmentVariables(envp);
defaultUninitializedFallbackPaths(envp);
}
……
//打印环境变量,可以在"Scheme -> Arguments -> Environment Variables"中配置
if ( sEnv.DYLD_PRINT_OPTS )
printOptions(argv);
if ( sEnv.DYLD_PRINT_ENV )
printEnvironmentVariables(envp);
……
// load shared cache,加载共享缓存,只读了主程序还没有加载主程序。iOS必须有共享缓存。
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
if ( sSharedCacheOverrideDir)
mapSharedCache(mainExecutableSlide);
#else
//加载共享缓存方法
mapSharedCache(mainExecutableSlide);
#endif
}
……
#if !TARGET_OS_SIMULATOR
//dyld3 ClosureMode模式,iOS11引入ClosureMode,iOS13后动态库和三方库都使用ClosureMode加载。
if ( sClosureMode == ClosureMode::Off ) {
//ClosureMode off打印log,往 if-else 后面走了
if ( gLinkContext.verboseWarnings )
dyld::log("dyld: not using closures\n");
} else {
//ClosureMode on
//启动模式 闭包模式 DYLD_LAUNCH_MODE_USING_CLOSURE
sLaunchModeUsed = DYLD_LAUNCH_MODE_USING_CLOSURE;
const dyld3::closure::LaunchClosure* mainClosure = nullptr;
//主程序 info 和 Header
dyld3::closure::LoadedFileInfo mainFileInfo;
mainFileInfo.fileContent = mainExecutableMH;
mainFileInfo.path = sExecPath;
……
//第一次从共享缓存找闭包
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());
if ( mainClosure != nullptr )
//如果拿到设置状态
sLaunchModeUsed |= DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
}
……
//拿到闭包 && 验证闭包,如果闭包失效
if ( (mainClosure != nullptr) && !closureValid(mainClosure, mainFileInfo, mainExecutableCDHash, true, envp) ) {
mainClosure = nullptr;
//闭包失效设置状态
sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
}
……
// If we didn't find a valid cache closure then try build a new one
//判断mainClosure是否为空
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, bootToken);
if ( mainClosure == nullptr ) {
// if no cached closure found, build new one
//缓存中找不到则创建一个,一直拿 mainClosure 是为了拿他创建主程序。
mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
if ( mainClosure != nullptr )
//创建失败则设置状态
sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH;
}
}
// 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");
if ( mainClosure->topImage()->fixupsNotEncoded() )
sLaunchModeUsed |= DYLD_LAUNCH_MODE_MINIMAL_CLOSURE;
Diagnostics diag;
bool closureOutOfDate;
bool recoverable;
//启动主程序,mainClosure 相当于加载器
bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
//启动失败或者过期 允许重建
if ( !launched && closureOutOfDate && allowClosureRebuilds ) {
// closure is out of date, build new one
//再创建一个
mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
if ( mainClosure != nullptr ) {
diag.clearError();
sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH;
if ( mainClosure->topImage()->fixupsNotEncoded() )
sLaunchModeUsed |= DYLD_LAUNCH_MODE_MINIMAL_CLOSURE;
else
sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_MINIMAL_CLOSURE;
//启动
launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
}
}
if ( launched ) {
//启动成功保存白能量,主程序加载成功
gLinkContext.startedInitializingMainExecutable = true;
if (sSkipMain)
//主程序main函数,dyld的main执行完毕返回主程序的main
result = (uintptr_t)&fake_main;
return result;
}
else {
//失败报错
if ( gLinkContext.verboseWarnings ) {
dyld::log("dyld: unable to use closure %p\n", mainClosure);
}
if ( !recoverable )
halt(diag.errorMessage());
}
}
}
#endif // TARGET_OS_SIMULATOR
// could not use closure info, launch old way
//dyld2模式
sLaunchModeUsed = 0;
// install gdb notifier
//两个回调地址放入stateToHandlers数组中
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);
……
try {
// add dyld itself to UUID list
//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_MASK) == CPU_SUBTYPE_ARM64E)
sDisableAcceleratorTables = true;
#endif
//主程序还没有rebase
bool mainExcutableAlreadyRebased = false;
if ( (sSharedCacheLoadInfo.loadAddress != nullptr) && !dylibsCanOverrideCache() && !sDisableAcceleratorTables && (sSharedCacheLoadInfo.loadAddress->header.accelerateInfoAddr != 0) ) {
struct stat statBuf;
if ( dyld3::stat(IPHONE_DYLD_SHARED_CACHE_DIR "no-dyld2-accelerator-tables", &statBuf) != 0 )
sAllCacheImagesProxy = ImageLoaderMegaDylib::makeImageLoaderMegaDylib(&sSharedCacheLoadInfo.loadAddress->header, sSharedCacheLoadInfo.slide, mainExecutableMH, gLinkContext);
}
//加载所有的可执行文件 image list
reloadAllImages:
#endif
……
CRSetCrashLogMessage(sLoadingCrashMessage);
// instantiate ImageLoader for main executable
//实例化主程序,加入到allImages(第一个靠dyld加载的image就是主程序)
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
#if defined(__x86_64__) && !TARGET_OS_SIMULATOR
//设置加载动态库版本
if (dyld::isTranslated()) {……}
#endif
// Now that shared cache is loaded, setup an versioned dylib overrides
#if SUPPORT_VERSIONED_PATHS
//检查版本路径
checkVersionedPaths();
#endif
……
// load any inserted libraries
//DYLD_INSERT_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) {
//i+1因为主程序,插入的在主程序后面
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 SUPPORT_ACCELERATE_TABLES
//判断条件不满足,持续 goto reloadAllImages
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
……
// 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, nullptr);
}
}
// <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
……
{
// find entry point for main executable
//找到主程序入口 LC_MAIN
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;
}
}
}
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`):
- 配置环境,获取主程序
Header
,Slide
(ASLR
) - 加载共享缓存:
mapSharedCache
。这个时候只读了主程序还没有加载主程序。iOS必须有共享缓存。
在checkSharedRegionDisable
方法中说明了iOS
必须有共享缓存:
-
dyld2/dyld3
(ClosureMode
闭包模式)加载程序:iOS11
引入dyld3
闭包模式,以回调的方式加载。闭包模式加载速度更快,效率更高。iOS13
后动态库和三方库都使ClosureMode
加载。-
dyld3
:- 使用
mainClosure
来加载。 - 找到/创建
mainClosure
后,通过launchWithClosure
启动主程序,启动失败后会有重新创建mainClosure
重新启动的逻辑。成功后返回result
(主程序入口main
)。launchWithClosure
中的逻辑和dyld2
启动主程序逻辑基本相同。
- 使用
-
dyld2
:启动主程序- 实例化主程序
instantiateFromLoadedImage
。 - 插入&加载动态库
loadInsertedDylib
。加载在loadInsertedDylib
中调用load
(主程序和动态库都会添加到allImages
中loadAllImages
) - 链接主程序和链接插入动态库(
link
,主程序链接在前)。在这个过程中记录了dyld
加载的时长。可以通过配置环境变量打印出来。 - 绑定符号(非懒加载、弱符号),懒加载在调用时绑定。
- 初始化主程序
initializeMainExecutable
,这个时候还没有执行到主程序中的代码。 - 找到主程序入口
LC_MAIN
,然后返回主程序。
- 实例化主程序
-
DYLD_PRINT_OPTS
,DYLD_PRINT_ENV
环境变量配置,可以打印环境变量配置(在"Scheme -> Arguments -> Environment Variables"中配置):
ASLR
:image list
第0
个主程序第一个地址。
2.1.1.1mapSharedCache加载共享缓存
共享缓存专门缓存系统动态库,如:UIKit
、Foundation
等。(自己的库、三方库不行)
2.1.1.1.1 mapSharedCache
mapSharedCache
真正调用的是loadDyldCache
:
static void mapSharedCache(uintptr_t mainExecutableSlide)
{
……
//真正调用的是loadDyldCache
loadDyldCache(opts, &sSharedCacheLoadInfo);
……
}
2.1.1.1.2 loadDyldCache
bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results)
{
results->loadAddress = 0;
results->slide = 0;
results->errorMessage = nullptr;
#if TARGET_OS_SIMULATOR
// simulator only supports mmap()ing cache privately into process
return mapCachePrivate(options, results);
#else
if ( options.forcePrivate ) {
// mmap cache into this process only
//仅加载到当前进程
return mapCachePrivate(options, results);
}
else {
// fast path: when cache is already mapped into shared region
bool hasError = false;
//已经加载不进行任何处理
if ( reuseExistingCache(options, results) ) {
hasError = (results->errorMessage != nullptr);
} else {
// slow path: this is first process to load cache
//当前进程第一次加载
hasError = mapCacheSystemWide(options, results);
}
return hasError;
}
#endif
}
loadDyldCache
有3
个逻辑:
1.仅加载到当前进程调用mapCachePrivate
。不放入共享缓存,仅自己使用。
2.已经加载过不进行任何处理。
3.当前进程第一次加载调用mapCacheSystemWide
动态库的共享缓存在整个应用的启动过程中是最先被加载的。
2.1.1.2 instantiateFromLoadedImage 实例化主程序(创建image
)
//header aslr path
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
// try mach-o loader
// if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
//实例化主程序
ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
//将主程序加入到all images
addImage(image);
return (ImageLoaderMachO*)image;
// }
// throw "main executable not a known format";
}
- 传入主程序的
Header
、ASLR
、path
实例化主程序生成image
。 - 将
image
加入all images
中。
实际上实例化真正调用的是ImageLoaderMachO::instantiateMainExecutable
:
// create image for main executable
ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
{
//dyld::log("ImageLoader=%ld, ImageLoaderMachO=%ld, ImageLoaderMachOClassic=%ld, ImageLoaderMachOCompressed=%ld\n",
// sizeof(ImageLoader), sizeof(ImageLoaderMachO), sizeof(ImageLoaderMachOClassic), sizeof(ImageLoaderMachOCompressed));
bool compressed;
unsigned int segCount;
unsigned int libCount;
const linkedit_data_command* codeSigCmd;
const encryption_info_command* encryptCmd;
//获取Load Commands
sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
// instantiate concrete class based on content of load commands
//根据 compressed 确定用哪个子类进行加载,ImageLoader是个抽象类,根据值选择对应的子类实例化主程序
if ( compressed )
return ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
else
#if SUPPORT_CLASSIC_MACHO
return ImageLoaderMachOClassic::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
#else
throw "missing LC_DYLD_INFO load command";
#endif
}
- 调用
sniffLoadCommands
生成相关信息,比如compressed
。 - 根据
compressed
确定用哪个子类进行加载,ImageLoader
是个抽象类,根据值选择对应的子类实例化主程序。
sniffLoadCommands
:
void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* path, bool inCache, bool* compressed,
unsigned int* segCount, unsigned int* libCount, const LinkContext& context,
const linkedit_data_command** codeSigCmd,
const encryption_info_command** encryptCmd)
{
//根据LC_DYLIB_INFO 和 LC_DYLD_INFO_ONLY 来获取的
*compressed = false;
//segment数量
*segCount = 0;
//lib数量
*libCount = 0;
//代码签名和加密
*codeSigCmd = NULL;
*encryptCmd = NULL;
……
// fSegmentsArrayCount is only 8-bits
//segCount 最多 256 个
if ( *segCount > 255 )
dyld::throwf("malformed mach-o image: more than 255 segments in %s", path);
// fSegmentsArrayCount is only 8-bits
//libCount最多 4096 个
if ( *libCount > 4095 )
dyld::throwf("malformed mach-o image: more than 4095 dependent libraries in %s", path);
if ( needsAddedLibSystemDepency(*libCount, mh) )
*libCount = 1;
// dylibs that use LC_DYLD_CHAINED_FIXUPS have that load command removed when put in the dyld cache
if ( !*compressed && (mh->flags & MH_DYLIB_IN_CACHE) )
*compressed = true;
}
-
compressed
是根据LC_DYLIB_INFO
和LC_DYLD_INFO_ONLY
来获取的。 -
segCount
最多256
个。 -
libCount
最多4096
个。
2.1.1.3 loadInsertedDylib 插入&加载动态库
static void loadInsertedDylib(const char* path)
{
unsigned cacheIndex;
try {
LoadContext context;
context.useSearchPaths = false;
context.useFallbackPaths = false;
context.useLdLibraryPath = false;
context.implicitRPath = false;
context.matchByInstallName = false;
context.dontLoad = false;
context.mustBeBundle = false;
context.mustBeDylib = true;
context.canBePIE = false;
context.origin = NULL; // can't use @loader_path with DYLD_INSERT_LIBRARIES
context.rpath = NULL;
//调用load,加载动态库的真正函数
load(path, context, cacheIndex);
}
catch (const char* msg) {
if ( gLinkContext.allowInsertFailures )
dyld::log("dyld: warning: could not load inserted library '%s' into hardened process because %s\n", path, msg);
else
halt(dyld::mkstringf("could not load inserted library '%s' because %s\n", path, msg));
}
catch (...) {
halt(dyld::mkstringf("could not load inserted library '%s'\n", path));
}
}
- 根据上下文初始化配置调用
load
加载动态库。
2.1.1.4ImageLoader::link链接主程序/动态库
void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath)
{
//dyld::log("ImageLoader::link(%s) refCount=%d, neverUnload=%d\n", imagePath, fDlopenReferenceCount, fNeverUnload);
// clear error strings
(*context.setErrorStrings)(0, NULL, NULL, NULL);
//起始时间。用于记录时间间隔
uint64_t t0 = mach_absolute_time();
//递归加载主程序依赖的库,完成之后发通知。
this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly);
// we only do the loading step for preflights
if ( preflightOnly )
return;
uint64_t t1 = mach_absolute_time();
context.clearAllDepths();
this->updateDepth(context.imageCount());
__block uint64_t t2, t3, t4, t5;
{
dyld3::ScopedTimer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0, 0, 0);
t2 = mach_absolute_time();
//Rebase修正ASLR
this->recursiveRebaseWithAccounting(context);
context.notifyBatch(dyld_image_state_rebased, false);
t3 = mach_absolute_time();
if ( !context.linkingMainExecutable )
//绑定NoLazy符号
this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);
t4 = mach_absolute_time();
if ( !context.linkingMainExecutable )
//绑定弱符号
this->weakBind(context);
t5 = mach_absolute_time();
}
// interpose any dynamically loaded images
if ( !context.linkingMainExecutable && (fgInterposingTuples.size() != 0) ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_APPLY_INTERPOSING, 0, 0, 0);
//递归应用插入的动态库
this->recursiveApplyInterposing(context);
}
// now that all fixups are done, make __DATA_CONST segments read-only
if ( !context.linkingMainExecutable )
this->recursiveMakeDataReadOnly(context);
if ( !context.linkingMainExecutable )
context.notifyBatch(dyld_image_state_bound, false);
uint64_t t6 = mach_absolute_time();
if ( context.registerDOFs != NULL ) {
std::vector<DOFInfo> dofs;
this->recursiveGetDOFSections(context, dofs);
//注册
context.registerDOFs(dofs);
}
//计算结束时间.
uint64_t t7 = mach_absolute_time();
// clear error strings
(*context.setErrorStrings)(0, NULL, NULL, NULL);
//配置环境变量,就可以看到dyld应用加载的时长。
fgTotalLoadLibrariesTime += t1 - t0;
fgTotalRebaseTime += t3 - t2;
fgTotalBindTime += t4 - t3;
fgTotalWeakBindTime += t5 - t4;
fgTotalDOF += t7 - t6;
// done with initial dylib loads
fgNextPIEDylibAddress = 0;
}
- 修正
ASLR
。 - 绑定
NoLazy
符号。 - 绑定弱符号。
- 注册。
- 记录时间,可以通过配置看到
dyld
应用加载时长。
2.1.1.5 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 ) {
//从1开始到最后。(第0个为主程序)
for(size_t i=1; i < rootCount; ++i) {
//image初始化,调用 +load 和 构造函数
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]);
}
- 初始化
images
,下标从1
开始,然后再初始化主程序(下标0
),runInitializers
。 - 可以配置环境变量
DYLD_PRINT_STATISTICS
和DYLD_PRINT_STATISTICS_DETAILS
打印相信信息。
2.1.1.5.1 dyld ImageLoader::runInitializers(ImageLoader.cpp
)
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);
}
-
up.count
值设置为1
然后调用processInitializers
。
dyld`ImageLoader::processInitializers
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.
//这里count为1
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
。
dyld ImageLoader::recursiveInitialization(ImageLoader.cpp
)
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;
//最终调用到objc里面了加载了所有load方法
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// initialize this image
//调用c++构造函数
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();
}
- 调用
notifySingle
最终调用到了objc
中调用了所有的+ load
方法。 - 调用
doInitialization
最终调用了c++
的系统构造函数。
c++
系统构造函数__attribute__((constructor)) void func() { printf("\n ---func--- \n"); }
dyld dyld::notifySingle(dyld2.cpp
)
//调用到objc里面去
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
……
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 是在 registerObjCNotifiers 中赋值的。这里执行会跑到objc的load_images中
(*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);
}
}
……
}
-
notifySingle
中找不到load image
的调用(从堆栈信息中可以看到notifySingle
之后是load image
)。 - 这个函数执行一个回调
sNotifyObjCInit
。
搜索下回调sNotifyObjCInit
的赋值操作,发现是在registerObjCNotifiers
中赋值的
registerObjCNotifiers
//那么谁调用的 registerObjCNotifiers ? _dyld_objc_notify_register
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
//赋值 sNotifyObjCInit,传进来的参数。
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_init
。 - 搜索发现是
_dyld_objc_notify_register
调用的registerObjCNotifiers
。
** _dyld_objc_notify_register**(dyldAPIs.cpp
)
// _dyld_objc_notify_register 调用 registerObjCNotifiers
//这里找不到 _dyld_objc_notify_register 调用者。打符号断点查看被objc-os.mm中 _objc_init 调用
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
的调用者找不到。
打符号断点_dyld_objc_notify_register
排查调用情况:
可以看到是被
_objc_init
调用的。_objc_init
的调用在objc-os.mm
中,查看源码:
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();
tls_init();
static_init();
runtime_init();
exception_init();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
//_objc_init 调用dyldAPIs.cpp 中_dyld_objc_notify_register,第二个参数是load_images
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
- 证实是
_objc_init
调用了_dyld_objc_notify_register
。 - 第二个参数是
load_images
。
load_images(objc-runtime-new.mm
)
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
//调用 call_load_methods
call_load_methods();
}
- 最终调用了
call_load_methods
。
call_load_methods (objc-loadmethod.mm
)
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
//循环调用 call_class_loads,类的load方法在这一刻被调用
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
//调用每个类的load
call_class_loads();
}
// 2. Call category +loads ONCE
//调用分类load,这里也就说明分类的 load 在所有类的load方法调用后才调用。(针对image而言)
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
- 调用
call_class_loads
加载类的+ load
。 - 接着调用
call_category_loads
加载分类的+ load
。这里也就说明分类的load
在所有类的load
方法调用后才调用。(针对image
而言)。
在这里也就调用到了+ load
方法,这也就是+ load
在main
之前被调用的原因。
ImageLoaderMachO::doInitialization(ImageLoaderMachO.cpp
)
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
CRSetCrashLogMessage2(this->getPath());
// mach-o has -init and static initializers
doImageInit(context);
//加载c++构造函数
doModInitFunctions(context);
CRSetCrashLogMessage2(NULL);
return (fHasDashInit || fHasInitializers);
}
加上以下代码查看MachO
文件:
__attribute__((constructor)) void func1() {
printf("\n ---func1--- \n");
}
__attribute__((constructor)) void func2() {
printf("\n ---func2--- \n");
}
会发现MachO
中多了__mod_init_func
- 调用
doModInitFunctions
函数加载c++
构造函数(__attribute__((constructor))
修饰的c
函数)
根据以上分析可以看到
dyld
是按image list
顺序从第1
个image
调用runInitializers
(可以看做是以image
分组)。再调用下一个image
的runInitializers
最后再调用主程序(下标为0
)的runInitializers
。在runInitializers
内部先调用所有类的+load
,再调用所有分类的+ load
,最后调用c++
的构造函数。
objc
中调用load
,dyld
中调用doModInitFunctions
。
⚠️如果在+ load
中做了防护,那么我们可以通过在+ load
执行前断住外部符号做处理。这样就可以绕过防护了。
防护最重要的就是不让别人找到防护的逻辑,只要能找到那么破解就很容易了。
案例分析:你真的了解dyld么?