一城菊、dyld概述
dyld
(the dynamic link editor
)動態(tài)鏈接器,是蘋果操作系統(tǒng)一個重要組成部分朽砰,在系統(tǒng)內(nèi)核做好程序準備工作之后交由dyld
負責余下的工作。
二、dyld 源碼分析
新建一個工程拟枚,寫一個+load
方法,分別在+load
和main
函數(shù)上打斷點众弓,然后運行在真機上恩溅。
這個時候函數(shù)的調(diào)用棧如下:
匯編代碼如下:
在dyld
源碼中(dyld-832.7.3
)找到dyldbootstrap::start
函數(shù)(dyldInitialization.cpp
),這也就是函數(shù)開始的地方谓娃。
接下來結合源碼分析怎么從start
調(diào)用到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);
//調(diào)用dyld main函數(shù)
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
start
主要做了以下幾件事:
- 告訴
debug server
dyld
啟動 - 重定位
dyld
- 棧溢出保護
- 初始化
dyld
- 調(diào)用
dyld main
函數(shù)
其中start
只做了一些配置和初始化的工作,核心邏輯在main
函數(shù)中奶稠,start
返回了main
函數(shù)的返回值俯艰。
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)
{
//內(nèi)核檢測代碼
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
//主程序可執(zhí)行文件
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];
……
//根據(jù)環(huán)境變量 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
{
//檢測環(huán)境變量并設置默認值啦辐,這個時候還沒有加載數(shù)據(jù)。
checkEnvironmentVariables(envp);
defaultUninitializedFallbackPaths(envp);
}
……
//打印環(huán)境變量蜈项,可以在"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后動態(tài)庫和三方庫都使用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 )
//如果拿到設置狀態(tài)
sLaunchModeUsed |= DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
}
……
//拿到閉包 && 驗證閉包聋亡,如果閉包失效
if ( (mainClosure != nullptr) && !closureValid(mainClosure, mainFileInfo, mainExecutableCDHash, true, envp) ) {
mainClosure = nullptr;
//閉包失效設置狀態(tài)
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
//緩存中找不到則創(chuàng)建一個肘习,一直拿 mainClosure 是為了拿他創(chuàng)建主程序。
mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
if ( mainClosure != nullptr )
//創(chuàng)建失敗則設置狀態(tài)
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
//再創(chuàng)建一個
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函數(shù),dyld的main執(zhí)行完畢返回主程序的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
//兩個回調(diào)地址放入stateToHandlers數(shù)組中
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);
}
//加載所有的可執(zhí)行文件 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
//檢查主程序是否屬于當前系統(tǒng)
{
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
//設置加載動態(tài)庫版本
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 插入動態(tài)庫
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
//遍歷加載插入動態(tài)庫
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];
//插入動態(tài)庫的鏈接
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
//判斷條件不滿足瘩缆,持續(xù) 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];
//綁定插入動態(tài)庫
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
//初始化主程序,到目前為止還沒有執(zhí)行到主程序中的代碼佃蚜。
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
函數(shù)主要是返回了主程序的函數(shù)入口(
main`):
- 配置環(huán)境庸娱,獲取主程序
Header
,Slide
(ASLR
) - 加載共享緩存:
mapSharedCache
谐算。這個時候只讀了主程序還沒有加載主程序熟尉。iOS必須有共享緩存。
在checkSharedRegionDisable
方法中說明了iOS
必須有共享緩存:
-
dyld2/dyld3
(ClosureMode
閉包模式)加載程序:iOS11
引入dyld3
閉包模式洲脂,以回調(diào)的方式加載斤儿。閉包模式加載速度更快,效率更高。iOS13
后動態(tài)庫和三方庫都使ClosureMode
加載往果。-
dyld3
:- 使用
mainClosure
來加載疆液。 - 找到/創(chuàng)建
mainClosure
后,通過launchWithClosure
啟動主程序棚放,啟動失敗后會有重新創(chuàng)建mainClosure
重新啟動的邏輯枚粘。成功后返回result
(主程序入口main
)。launchWithClosure
中的邏輯和dyld2
啟動主程序邏輯基本相同飘蚯。
- 使用
-
dyld2
:啟動主程序- 實例化主程序
instantiateFromLoadedImage
馍迄。 - 插入&加載動態(tài)庫
loadInsertedDylib
。加載在loadInsertedDylib
中調(diào)用load
(主程序和動態(tài)庫都會添加到allImages
中loadAllImages
) - 鏈接主程序和鏈接插入動態(tài)庫(
link
局骤,主程序鏈接在前)攀圈。在這個過程中記錄了dyld
加載的時長÷退Γ可以通過配置環(huán)境變量打印出來赘来。 - 綁定符號(非懶加載、弱符號)凯傲,懶加載在調(diào)用時綁定犬辰。
- 初始化主程序
initializeMainExecutable
,這個時候還沒有執(zhí)行到主程序中的代碼冰单。 - 找到主程序入口
LC_MAIN
幌缝,然后返回主程序。
- 實例化主程序
-
DYLD_PRINT_OPTS
诫欠,DYLD_PRINT_ENV
環(huán)境變量配置涵卵,可以打印環(huán)境變量配置(在"Scheme -> Arguments -> Environment Variables"中配置):
ASLR
:image list
第0
個主程序第一個地址。
2.1.1.1mapSharedCache加載共享緩存
共享緩存專門緩存系統(tǒng)動態(tài)庫荒叼,如:UIKit
轿偎、Foundation
等。(自己的庫被廓、三方庫不行)
2.1.1.1.1 mapSharedCache
mapSharedCache
真正調(diào)用的是loadDyldCache
:
static void mapSharedCache(uintptr_t mainExecutableSlide)
{
……
//真正調(diào)用的是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;
//已經(jīng)加載不進行任何處理
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.僅加載到當前進程調(diào)用mapCachePrivate
坏晦。不放入共享緩存,僅自己使用嫁乘。
2.已經(jīng)加載過不進行任何處理英遭。
3.當前進程第一次加載調(diào)用mapCacheSystemWide
動態(tài)庫的共享緩存在整個應用的啟動過程中是最先被加載的。
2.1.1.2 instantiateFromLoadedImage 實例化主程序(創(chuàng)建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
中法精。
實際上實例化真正調(diào)用的是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
//根據(jù) compressed 確定用哪個子類進行加載,ImageLoader是個抽象類,根據(jù)值選擇對應的子類實例化主程序
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
}
- 調(diào)用
sniffLoadCommands
生成相關信息搂蜓,比如compressed
狼荞。 - 根據(jù)
compressed
確定用哪個子類進行加載,ImageLoader
是個抽象類帮碰,根據(jù)值選擇對應的子類實例化主程序相味。
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)
{
//根據(jù)LC_DYLIB_INFO 和 LC_DYLD_INFO_ONLY 來獲取的
*compressed = false;
//segment數(shù)量
*segCount = 0;
//lib數(shù)量
*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
是根據(jù)LC_DYLIB_INFO
和LC_DYLD_INFO_ONLY
來獲取的。 -
segCount
最多256
個殉挽。 -
libCount
最多4096
個丰涉。
2.1.1.3 loadInsertedDylib 插入&加載動態(tài)庫
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;
//調(diào)用load,加載動態(tài)庫的真正函數(shù)
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));
}
}
- 根據(jù)上下文初始化配置調(diào)用
load
加載動態(tài)庫斯碌。
2.1.1.4ImageLoader::link鏈接主程序/動態(tài)庫
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();
//遞歸加載主程序依賴的庫,完成之后發(fā)通知傻唾。
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);
//遞歸應用插入的動態(tài)庫
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);
//配置環(huán)境變量投慈,就可以看到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初始化蝙砌,調(diào)用 +load 和 構造函數(shù)
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
}
}
// run initializers for main executable and everything it brings up
//調(diào)用主程序初始化
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
。 - 可以配置環(huán)境變量
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
然后調(diào)用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);
}
- 最終調(diào)用了
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;
//最終調(diào)用到objc里面了加載了所有l(wèi)oad方法
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// initialize this image
//調(diào)用c++構造函數(shù)
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();
}
- 調(diào)用
notifySingle
最終調(diào)用到了objc
中調(diào)用了所有的+ load
方法拭卿。 - 調(diào)用
doInitialization
最終調(diào)用了c++
的系統(tǒng)構造函數(shù)骡湖。
c++
系統(tǒng)構造函數(shù)__attribute__((constructor)) void func() { printf("\n ---func--- \n"); }
dyld dyld::notifySingle(dyld2.cpp
)
//調(diào)用到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);
//回調(diào)指針 sNotifyObjCInit 是在 registerObjCNotifiers 中賦值的。這里執(zhí)行會跑到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
的調(diào)用(從堆棧信息中可以看到notifySingle
之后是load image
)峻厚。 - 這個函數(shù)執(zhí)行一個回調(diào)
sNotifyObjCInit
响蕴。
搜索下回調(diào)sNotifyObjCInit
的賦值操作,發(fā)現(xiàn)是在registerObjCNotifiers
中賦值的
registerObjCNotifiers
//那么誰調(diào)用的 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浦夷,傳進來的參數(shù)辖试。
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
賦值來自于第二個參數(shù)_dyld_objc_notify_init
。 - 搜索發(fā)現(xiàn)是
_dyld_objc_notify_register
調(diào)用的registerObjCNotifiers
劈狐。
** _dyld_objc_notify_register**(dyldAPIs.cpp
)
// _dyld_objc_notify_register 調(diào)用 registerObjCNotifiers
//這里找不到 _dyld_objc_notify_register 調(diào)用者罐孝。打符號斷點查看被objc-os.mm中 _objc_init 調(diào)用
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
的調(diào)用者找不到。
打符號斷點_dyld_objc_notify_register
排查調(diào)用情況:
可以看到是被
_objc_init
調(diào)用的肥缔。_objc_init
的調(diào)用在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 調(diào)用dyldAPIs.cpp 中_dyld_objc_notify_register,第二個參數(shù)是load_images
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
- 證實是
_objc_init
調(diào)用了_dyld_objc_notify_register
续膳。 - 第二個參數(shù)是
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)
//調(diào)用 call_load_methods
call_load_methods();
}
- 最終調(diào)用了
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();
//循環(huán)調(diào)用 call_class_loads姑宽,類的load方法在這一刻被調(diào)用
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
//調(diào)用每個類的load
call_class_loads();
}
// 2. Call category +loads ONCE
//調(diào)用分類load,這里也就說明分類的 load 在所有類的load方法調(diào)用后才調(diào)用遣耍。(針對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;
}
- 調(diào)用
call_class_loads
加載類的+ load
。 - 接著調(diào)用
call_category_loads
加載分類的+ load
炮车。這里也就說明分類的load
在所有類的load
方法調(diào)用后才調(diào)用舵变。(針對image
而言)。
在這里也就調(diào)用到了+ load
方法瘦穆,這也就是+ load
在main
之前被調(diào)用的原因纪隙。
ImageLoaderMachO::doInitialization(ImageLoaderMachO.cpp
)
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
CRSetCrashLogMessage2(this->getPath());
// mach-o has -init and static initializers
doImageInit(context);
//加載c++構造函數(shù)
doModInitFunctions(context);
CRSetCrashLogMessage2(NULL);
return (fHasDashInit || fHasInitializers);
}
加上以下代碼查看MachO
文件:
__attribute__((constructor)) void func1() {
printf("\n ---func1--- \n");
}
__attribute__((constructor)) void func2() {
printf("\n ---func2--- \n");
}
會發(fā)現(xiàn)MachO
中多了__mod_init_func
- 調(diào)用
doModInitFunctions
函數(shù)加載c++
構造函數(shù)(__attribute__((constructor))
修飾的c
函數(shù))
根據(jù)以上分析可以看到
dyld
是按image list
順序從第1
個image
調(diào)用runInitializers
(可以看做是以image
分組)。再調(diào)用下一個image
的runInitializers
最后再調(diào)用主程序(下標為0
)的runInitializers
扛或。在runInitializers
內(nèi)部先調(diào)用所有類的+load
绵咱,再調(diào)用所有分類的+ load
,最后調(diào)用c++
的構造函數(shù)熙兔。
objc
中調(diào)用load
悲伶,dyld
中調(diào)用doModInitFunctions
。
??如果在+ load
中做了防護住涉,那么我們可以通過在+ load
執(zhí)行前斷住外部符號做處理麸锉。這樣就可以繞過防護了。
防護最重要的就是不讓別人找到防護的邏輯舆声,只要能找到那么破解就很容易了花沉。
案例分析:你真的了解dyld么?