一、應(yīng)用程序加載原理
在分析dyld
加載應(yīng)用程序之前蚜厉,先清楚以下基本概念。
庫(kù):可執(zhí)行的二進(jìn)制文件畜眨,可以被系統(tǒng)加載到內(nèi)存昼牛。庫(kù)分為靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)术瓮,動(dòng)態(tài)和靜態(tài)庫(kù)的區(qū)別是鏈接的區(qū)別。
編譯過(guò)程
源文件->預(yù)編譯->編譯->匯編->鏈接->可執(zhí)行文件(MachO
格式)贰健。
動(dòng)態(tài)庫(kù):動(dòng)態(tài)鏈接胞四。只會(huì)存在一份,在內(nèi)存中共享伶椿。減少了包的體積大小辜伟。這里有完全動(dòng)態(tài)特性的就是系統(tǒng)的動(dòng)態(tài)庫(kù)了。
靜態(tài)庫(kù):靜態(tài)鏈接脊另。靜態(tài)庫(kù)在裝載的時(shí)候會(huì)重復(fù)游昼,浪費(fèi)了空間。
那么這些庫(kù)是怎么加載到內(nèi)存中的呢尝蠕?
是通過(guò)dyld
動(dòng)態(tài)鏈接器加載到內(nèi)存中的烘豌。整個(gè)過(guò)程大概如下:
dyld
(the dynamic link editor
)動(dòng)態(tài)鏈接器,是蘋(píng)果操作系統(tǒng)一個(gè)重要組成部分看彼,在系統(tǒng)內(nèi)核做好程序準(zhǔn)備工作之后交由dyld
負(fù)責(zé)余下的工作廊佩。
這篇文章將詳細(xì)分析整個(gè)dyld
加載過(guò)程。
二靖榕、dyld 初探
既然是dyld
加載的庫(kù)标锄,那么在加載完成后肯定會(huì)進(jìn)入main
函數(shù),那么在main
函數(shù)上打個(gè)斷點(diǎn)看下調(diào)用:
可以看到是libdyld.dylib start:
調(diào)用的main
函數(shù)茁计。給start
下個(gè)符號(hào)斷點(diǎn)并沒(méi)有進(jìn)入斷點(diǎn)料皇。那么證明在底層的符號(hào)不是start
。實(shí)現(xiàn)一個(gè)+ load
方法打個(gè)斷點(diǎn)發(fā)現(xiàn)如下調(diào)用棧:
可以看到是
dyld _dyld_start
發(fā)起的調(diào)用星压。opensoure
上直接下載dyld-852
源碼践剂。
搜索_dyld_start
發(fā)現(xiàn)這個(gè)入口是在匯編中,其中主要是調(diào)用了dyldbootstrap::start
:
最終跳轉(zhuǎn)了返回的
LC_MAIN
娜膘。
也可以通過(guò)斷點(diǎn)查看匯編調(diào)用確定:
dyldbootstrap
是c++
的命名空間逊脯,start
是其中的函數(shù)。搜索后發(fā)現(xiàn)dyldbootstrap::start
在dyldInitialization.cpp
中竣贪,這也就是函數(shù)開(kāi)始的地方军洼。接下來(lái)結(jié)合源碼分析怎么從start
調(diào)用到load
和main
方法,以及dyld
是如何加載images
的演怎。
三匕争、dyld源碼分析
3.1 dyldbootstrap::start(dyldInitialization.cpp
)
可以通過(guò)搜索dyldbootstrap
命名空間找到start
源碼。核心代碼如下:
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{
//告訴debug server dyld啟動(dòng)
dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);
//重定位dyld
rebaseDyld(dyldsMachHeader);
//棧溢出保護(hù)
__guard_setup(apple);
//初始化dyld
_subsystem_init(apple);
//偏移
uintptr_t appsSlide = appsMachHeader->getSlide();
//調(diào)用dyld main函數(shù)
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
start
主要做了以下幾件事:
- 告訴
debug server
dyld
啟動(dòng) - 重定位
dyld
- 棧溢出保護(hù)
- 初始化
dyld
- 調(diào)用
dyld main
函數(shù)
其中start
只做了一些配置和初始化的工作爷耀,核心邏輯在main
函數(shù)中甘桑,start
返回了main
函數(shù)的返回值。
3.2 dyld::_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)核檢測(cè)代碼
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);
}
……
//主程序可執(zhí)行文件 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;
}
//獲取主程序Header,Slide(ASLR的偏移值)
getHostInfo(mainExecutableMH, mainExecutableSlide);
……
CRSetCrashLogMessage("dyld: launch started");
//配置環(huán)境 將信息放入 gLinkContext 中( notifySingle函數(shù) 賦值在其中)
setContext(mainExecutableMH, argc, argv, envp, apple);
……
//根據(jù)環(huán)境變量 envp 配置進(jìn)程是否受限制扇住,AMFI相關(guān)(Apple Mobile File Integrity蘋(píng)果移動(dòng)文件保護(hù))
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
//又設(shè)置一次上下文,在文件受限的時(shí)候可能更改了envp盗胀。
setContext(mainExecutableMH, argc, argv, envp, apple);
}
else
#endif
{
//檢測(cè)環(huán)境變量并設(shè)置默認(rèn)值艘蹋,這個(gè)時(shí)候還沒(méi)有加載數(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);
……
//檢查共享緩存是否可用,到了這里只讀了主程序還沒(méi)有加載主程序女阀。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后動(dòng)態(tài)庫(kù)和三方庫(kù)都使用ClosureMode加載。
if ( sClosureMode == ClosureMode::Off ) {
//ClosureMode off打印log惹盼,往 if-else 后面走了
if ( gLinkContext.verboseWarnings )
dyld::log("dyld: not using closures\n");
} else {
//啟動(dòng)模式 閉包模式 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 ) {
//先從共享緩存找實(shí)例閉包
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 )
//如果拿到設(shè)置狀態(tài)
sLaunchModeUsed |= DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
}
……
//拿到閉包 && 驗(yàn)證閉包庸汗,如果閉包失效
if ( (mainClosure != nullptr) && !closureValid(mainClosure, mainFileInfo, mainExecutableCDHash, true, envp) ) {
mainClosure = nullptr;
//閉包失效設(shè)置狀態(tài)
sLaunchModeUsed &= ~DYLD_LAUNCH_MODE_CLOSURE_FROM_OS;
}
……
//判斷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)建一個(gè),一直拿 mainClosure 是為了拿他創(chuàng)建主程序手报。
mainClosure = buildLaunchClosure(mainExecutableCDHash, mainFileInfo, envp, bootToken);
if ( mainClosure != nullptr )
//創(chuàng)建失敗則設(shè)置狀態(tài)
sLaunchModeUsed |= DYLD_LAUNCH_MODE_BUILT_CLOSURE_AT_LAUNCH;
}
}
……
// 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;
//啟動(dòng)主程序蚯舱,mainClosure 相當(dāng)于加載器
bool launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
//啟動(dòng)失敗或者過(guò)期 允許重建
if ( !launched && closureOutOfDate && allowClosureRebuilds ) {
// closure is out of date, build new one
//再創(chuàng)建一個(gè)
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;
//啟動(dòng)
launched = launchWithClosure(mainClosure, sSharedCacheLoadInfo.loadAddress, (dyld3::MachOLoaded*)mainExecutableMH,
mainExecutableSlide, argc, argv, envp, apple, diag, &result, startGlue, &closureOutOfDate, &recoverable);
}
}
if ( launched ) {
//啟動(dòng)成功保存狀態(tài),主程序加載成功
gLinkContext.startedInitializingMainExecutable = true;
if (sSkipMain)
//主程序main函數(shù)掩蛤,dyld的main執(zhí)行完畢返回主程序的main
result = (uintptr_t)&fake_main;
return result;
}
else {
//失敗報(bào)錯(cuò)
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
//兩個(gè)回調(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
……
//主程序還沒(méi)有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揍鸟,這里相當(dāng)于是個(gè)標(biāo)簽兄裂。會(huì)循環(huán)。
reloadAllImages:
#endif
……
//實(shí)例化主程序阳藻,加入到allImages(第一個(gè)靠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
//檢查主程序是否屬于當(dāng)前系統(tǒng)
{……}
#endif
……
#if defined(__x86_64__) && !TARGET_OS_SIMULATOR
//設(shè)置加載動(dòng)態(tài)庫(kù)版本
if (dyld::isTranslated()) {……}
#endif
// Now that shared cache is loaded, setup an versioned dylib overrides
#if SUPPORT_VERSIONED_PATHS
//檢查版本路徑
checkVersionedPaths();
#endif
……
//DYLD_INSERT_LIBRARIES 插入動(dòng)態(tài)庫(kù)
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
//遍歷加載插入動(dòng)態(tài)庫(kù)
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
//-1為了排除主程序
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 ( sInsertedDylibCount > 0 ) {
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
//i+1因?yàn)橹鞒绦蛭保迦氲膇mage在主程序后面
ImageLoader* image = sAllImages[i+1];
//鏈接插入動(dòng)態(tài)庫(kù)
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
image->setNeverUnloadRecursive();
}
……
}
……
#if SUPPORT_ACCELERATE_TABLES
//判斷條件不滿(mǎn)足,持續(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
……
//綁定主程序
sMainExecutable->recursiveBindWithAccounting(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
……
// 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];
//綁定插入動(dòng)態(tài)庫(kù)
image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true, nullptr);
}
}
// <rdar://problem/12186933> do weak binding only after all inserted images linked
//弱引用綁定主程序腥泥,所有鏡像文件綁定完成后進(jìn)行畅涂。
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
//初始化主程序,到目前為止還沒(méi)有執(zhí)行到主程序中的代碼道川。
initializeMainExecutable();
……
{
// 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;
}
}
}
……
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
臊岸。這個(gè)時(shí)候只讀了主程序還沒(méi)有加載主程序。iOS必須有共享緩存尊流。
在checkSharedRegionDisable
方法中說(shuō)明了iOS
必須有共享緩存:
-
dyld2/dyld3
(ClosureMode
閉包模式)加載程序:iOS11
引入dyld3
閉包模式帅戒,以回調(diào)的方式加載。閉包模式加載速度更快,效率更高逻住。iOS13
后動(dòng)態(tài)庫(kù)和三方庫(kù)都使ClosureMode
加載钟哥。-
dyld3
:- 使用
mainClosure
來(lái)加載。 - 找到/創(chuàng)建
mainClosure
后瞎访,通過(guò)launchWithClosure
啟動(dòng)主程序腻贰,啟動(dòng)失敗后會(huì)有重新創(chuàng)建mainClosure
重新啟動(dòng)的邏輯。成功后返回result
(主程序入口main
)扒秸。launchWithClosure
中的邏輯和dyld2
啟動(dòng)主程序邏輯基本相同播演。
- 使用
-
dyld2
:?jiǎn)?dòng)主程序- 實(shí)例化主程序
instantiateFromLoadedImage
。sMainExecutable
是通過(guò)instantiateFromLoadedImage
賦值的伴奥,也就是把主程序加入allImages
中写烤。 - 插入&加載動(dòng)態(tài)庫(kù)
loadInsertedDylib
。加載在loadInsertedDylib
中調(diào)用load
(主程序和動(dòng)態(tài)庫(kù)都會(huì)添加到allImages
中loadAllImages
) - 鏈接主程序和鏈接插入動(dòng)態(tài)庫(kù)(
link
拾徙,主程序鏈接在前)洲炊。在這個(gè)過(guò)程中記錄了dyld
加載的時(shí)長(zhǎng)∧岱龋可以通過(guò)配置環(huán)境變量打印出來(lái)选浑。 - 綁定符號(hào)(非懶加載、弱符號(hào))玄叠,懶加載在調(diào)用時(shí)綁定古徒。
- 初始化主程序
initializeMainExecutable
,這個(gè)時(shí)候還沒(méi)有執(zhí)行到主程序中的代碼读恃。 - 找到主程序入口
LC_MAIN
隧膘,然后返回主程序。
- 實(shí)例化主程序
-
DYLD_PRINT_OPTS
寺惫,DYLD_PRINT_ENV
環(huán)境變量配置疹吃,可以打印環(huán)境變量配置(在"Scheme -> Arguments -> Environment Variables"中配置):
ASLR
:image list
第0
個(gè)主程序第一個(gè)地址。
關(guān)于dyld2/dyld3
更多信息將在后面做進(jìn)一步總結(jié)西雀。
3.3 mapSharedCache加載共享緩存
共享緩存專(zhuān)門(mén)緩存系統(tǒng)動(dòng)態(tài)庫(kù)萨驶,如:UIKit
、Foundation
等艇肴。(自己的庫(kù)腔呜、三方庫(kù)不行)
mapSharedCache
真正調(diào)用的是loadDyldCache
:
static void mapSharedCache(uintptr_t mainExecutableSlide)
{
……
//真正調(diào)用的是loadDyldCache
loadDyldCache(opts, &sSharedCacheLoadInfo);
……
}
3.3.1 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
//僅加載到當(dāng)前進(jìn)程
return mapCachePrivate(options, results);
}
else {
// fast path: when cache is already mapped into shared region
bool hasError = false;
//已經(jīng)加載不進(jìn)行任何處理
if ( reuseExistingCache(options, results) ) {
hasError = (results->errorMessage != nullptr);
} else {
// slow path: this is first process to load cache
//當(dāng)前進(jìn)程第一次加載
hasError = mapCacheSystemWide(options, results);
}
return hasError;
}
#endif
}
loadDyldCache
有3
個(gè)邏輯:
1.僅加載到當(dāng)前進(jìn)程調(diào)用mapCachePrivate
。不放入共享緩存再悼,僅自己使用核畴。
2.已經(jīng)加載過(guò)不進(jìn)行任何處理。
3.當(dāng)前進(jìn)程第一次加載調(diào)用mapCacheSystemWide
動(dòng)態(tài)庫(kù)的共享緩存在整個(gè)應(yīng)用的啟動(dòng)過(guò)程中是最先被加載的冲九。
3.4 instantiateFromLoadedImage 實(shí)例化主程序(創(chuàng)建image
)
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
//實(shí)例化image
ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
//將image添加到all Images中
addImage(image);
return (ImageLoaderMachO*)image;
// throw "main executable not a known format";
}
- 傳入主程序的
Header
谤草、ASLR
、path
實(shí)例化主程序生成image
。 - 將
image
加入all images
中丑孩。
實(shí)際上實(shí)例化真正調(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)
{
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 確定用哪個(gè)子類(lèi)進(jìn)行加載image冀宴,ImageLoader是個(gè)抽象類(lèi),根據(jù)值選擇對(duì)應(yīng)的子類(lèi)實(shí)例化image温学。
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
生成相關(guān)信息略贮,比如compressed
。 - 根據(jù)
compressed
確定用哪個(gè)子類(lèi)進(jìn)行加載image
枫浙,ImageLoader
是個(gè)抽象類(lèi),根據(jù)值選擇對(duì)應(yīng)的子類(lèi)實(shí)例化主程序古拴。
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 來(lái)獲取的
*compressed = false;
//segment數(shù)量
*segCount = 0;
//lib數(shù)量
*libCount = 0;
//代碼簽名和加密
*codeSigCmd = NULL;
*encryptCmd = NULL;
……
// fSegmentsArrayCount is only 8-bits
//segCount 最多 256 個(gè)
if ( *segCount > 255 )
dyld::throwf("malformed mach-o image: more than 255 segments in %s", path);
// fSegmentsArrayCount is only 8-bits
//libCount最多 4096 個(gè)
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
來(lái)獲取的箩帚。 -
segCount
最多256
個(gè)。 -
libCount
最多4096
個(gè)黄痪。
3.5 loadInsertedDylib 插入&加載動(dòng)態(tài)庫(kù)
static void loadInsertedDylib(const char* path)
{
unsigned cacheIndex;
try {
……
//調(diào)用load紧帕,加載動(dòng)態(tài)庫(kù)的真正函數(shù)
load(path, context, cacheIndex);
}
……
}
- 根據(jù)上下文初始化配置調(diào)用
load
加載動(dòng)態(tài)庫(kù)。
3.6 ImageLoader::link鏈接主程序/動(dòng)態(tài)庫(kù)
void link(ImageLoader* image, bool forceLazysBound, bool neverUnload, const ImageLoader::RPathChain& loaderRPaths, unsigned cacheIndex)
{
// add to list of known images. This did not happen at creation time for bundles
if ( image->isBundle() && !image->isLinked() )
addImage(image);
// we detect root images as those not linked in yet
if ( !image->isLinked() )
addRootImage(image);
// process images
try {
const char* path = image->getPath();
#if SUPPORT_ACCELERATE_TABLES
if ( image == sAllCacheImagesProxy )
path = sAllCacheImagesProxy->getIndexedPath(cacheIndex);
#endif
//最終會(huì)調(diào)用到image的link
image->link(gLinkContext, forceLazysBound, false, neverUnload, loaderRPaths, path);
}
}
-
link
最終調(diào)用的是ImageLoader::link
桅打。
ImageLoader::link
void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath)
{
// clear error strings
(*context.setErrorStrings)(0, NULL, NULL, NULL);
//起始時(shí)間是嗜。用于記錄時(shí)間間隔
uint64_t t0 = mach_absolute_time();
//遞歸加載主程序依賴(lài)的庫(kù),完成之后發(fā)通知挺尾。
this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
context.notifyBatch(dyld_image_state_dependents_mapped, preflightOnly);
……
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符號(hào)
this->recursiveBindWithAccounting(context, forceLazysBound, neverUnload);
t4 = mach_absolute_time();
if ( !context.linkingMainExecutable )
//綁定弱符號(hào)
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);
//遞歸應(yīng)用插入的動(dòng)態(tài)庫(kù)
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);
//注冊(cè)
context.registerDOFs(dofs);
}
//計(jì)算結(jié)束時(shí)間.
uint64_t t7 = mach_absolute_time();
// clear error strings
//配置環(huán)境變量鹅搪,就可以看到dyld應(yīng)用加載的時(shí)長(zhǎng)。
(*context.setErrorStrings)(0, NULL, NULL, NULL);
fgTotalLoadLibrariesTime += t1 - t0;
fgTotalRebaseTime += t3 - t2;
fgTotalBindTime += t4 - t3;
fgTotalWeakBindTime += t5 - t4;
fgTotalDOF += t7 - t6;
// done with initial dylib loads
fgNextPIEDylibAddress = 0;
}
- 修正
ASLR
遭铺。 - 綁定
NoLazy
符號(hào)丽柿。 - 綁定弱符號(hào)。
- 注冊(cè)魂挂。
- 記錄時(shí)間甫题,可以通過(guò)配置看到
dyld
應(yīng)用加載時(shí)長(zhǎng)。
3.7 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開(kāi)始到最后涂召。(第0個(gè)為主程序)
for(size_t i=1; i < rootCount; ++i) {
//image初始化坠非,調(diào)用 +load 和 構(gòu)造函數(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
,下標(biāo)從1
開(kāi)始果正,然后再初始化主程序(下標(biāo)0
)runInitializers
炎码。 - 可以配置環(huán)境變量
DYLD_PRINT_STATISTICS
和DYLD_PRINT_STATISTICS_DETAILS
打印相關(guān)信息。
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
值設(shè)置為1
然后調(diào)用processInitializers
秋泳。
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;
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
辅肾。
ImageLoader::recursiveInitialization(ImageLoader.cpp
)
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
……
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
//先初始化下級(jí)lib
for(unsigned int i=0; i < libraryCount(); ++i) {
ImageLoader* dependentImage = libImage(i);
if ( dependentImage != NULL ) {
……
else if ( dependentImage->fDepth >= fDepth ) {
//依賴(lài)文件遞歸初始化
dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
}
}
}
……
fState = dyld_image_state_dependents_initialized;
oldState = fState;
//這里調(diào)用傳遞的狀態(tài)是dyld_image_state_dependents_initialized,image傳遞的是自己轮锥。也就是最后調(diào)用了自己的+load矫钓。從libobjc.A.dylib開(kāi)始調(diào)用。
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// initialize this image
//初始化鏡像文件,調(diào)用c++構(gòu)造函數(shù)新娜。libSystem的libSystem_initializer就是在這里調(diào)用的赵辕。會(huì)調(diào)用到objc_init中。_dyld_objc_notify_register 中會(huì)調(diào)用自身的+load方法概龄,然后c++構(gòu)造函數(shù)汇四。
//1.調(diào)用libSystem_initializer->objc_init 注冊(cè)回調(diào)。
//2._dyld_objc_notify_register中調(diào)用 map_images站故,load_images泌豆,這里是首先初始化一些系統(tǒng)庫(kù),調(diào)用系統(tǒng)庫(kù)的load_images衰粹。比如libdispatch.dylib锣光,libsystem_featureflags.dylib,libsystem_trace.dylib铝耻,libxpc.dylib誊爹。
//3.自身的c++構(gòu)造函數(shù)
bool hasInitializers = this->doInitialization(context);
// let anyone know we finished initializing this image
fState = dyld_image_state_initialized;
oldState = fState;
//這里調(diào)用不到+load方法。 notifySingle內(nèi)部fState==dyld_image_state_dependents_initialized 才調(diào)用+load瓢捉。
context.notifySingle(dyld_image_state_initialized, this, NULL);
……
}
……
}
recursiveSpinUnLock();
}
- 整個(gè)過(guò)程是一個(gè)遞歸的過(guò)程频丘,先調(diào)用依賴(lài)庫(kù)的,再調(diào)用自己的泡态。
- 調(diào)用
notifySingle
最終調(diào)用到了objc
中所有的+ load
方法搂漠。這里第一個(gè)notifySingle
調(diào)用的是+load
方法,第二個(gè)notifySingle
由于參數(shù)是dyld_image_state_initialized
不會(huì)調(diào)用到+load
方法某弦。這里的dyld_image_state_dependents_initialized
意思是依賴(lài)文件初始化完畢了状答,可以初始化自己了。 - 調(diào)用
doInitialization
最終調(diào)用了c++
的系統(tǒng)構(gòu)造函數(shù)刀崖。先調(diào)用的是libSystem_initializer -> objc_init
進(jìn)行注冊(cè)回調(diào)惊科。在回調(diào)中調(diào)用了map_images
、load_images
(+load
)亮钦。這里的load_images
是調(diào)用一些加載一些系統(tǒng)庫(kù)馆截,比如:libdispatch.dylib,libsystem_featureflags.dylib蜂莉,libsystem_trace.dylib蜡娶,libxpc.dylib
。
c++
系統(tǒng)構(gòu)造函數(shù)__attribute__((constructor)) void func() { printf("\n ---func--- \n"); }
??這里也就說(shuō)明了對(duì)于同一個(gè)
image
而言映穗,+ load
方法是比c++
構(gòu)造函數(shù)更早調(diào)用的窖张。
dyld::notifySingle(dyld2.cpp
)
notifySingle
對(duì)應(yīng)一個(gè)函數(shù),在setContext
的時(shí)候賦值:
//調(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í)行會(huì)跑到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
)宿接。 - 這個(gè)函數(shù)執(zhí)行一個(gè)回調(diào)
sNotifyObjCInit
赘淮,條件是state
為dyld_image_state_dependents_initialized
。
搜索下回調(diào)sNotifyObjCInit
的賦值操作睦霎,發(fā)現(xiàn)是在registerObjCNotifiers
中賦值的
registerObjCNotifiers
//誰(shuí)調(diào)用的 registerObjCNotifiers 梢卸? _dyld_objc_notify_register。這里賦值了三個(gè)參數(shù) _dyld_objc_notify_mapped副女,_dyld_objc_notify_init蛤高,_dyld_objc_notify_unmapped
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
//第一個(gè)參數(shù) map_images
sNotifyObjCMapped = mapped;
//第二個(gè)參數(shù) load_images
sNotifyObjCInit = init;
//第三個(gè)參數(shù) unmap_image
sNotifyObjCUnmapped = unmapped;
// call 'mapped' function with all images mapped so far
try {
//賦值后馬上回調(diào) map_images
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);
//調(diào)用一些系統(tǒng)庫(kù)的 load_images。
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
}
}
}
-
registerObjCNotifiers
賦值來(lái)自于第二個(gè)參數(shù)_dyld_objc_notify_init
碑幅。 - 賦值后里面調(diào)用了
notifyBatchPartial
(內(nèi)部調(diào)用了sNotifyObjCMapped
)戴陡。 - 循環(huán)調(diào)用
load_images
,這里調(diào)用的是依賴(lài)的系統(tǒng)庫(kù)的libdispatch.dylib沟涨,libsystem_featureflags.dylib恤批,libsystem_trace.dylib,libxpc.dylib
拷窜。
搜索發(fā)現(xiàn)是_dyld_objc_notify_register
調(diào)用的registerObjCNotifiers
开皿。
_dyld_objc_notify_register(dyldAPIs.cpp
)
//_objc_init中調(diào)用的涧黄。
//單個(gè)鏡像文件的加載來(lái)到了這里->_dyld_objc_notify_register,打符號(hào)斷點(diǎn)查看被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
中找不到。
打符號(hào)斷點(diǎn)_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,第二個(gè)參數(shù)是load_images
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
- 證實(shí)是
_objc_init
調(diào)用了_dyld_objc_notify_register
春宣。 - 第一個(gè)參數(shù)是
map_images
酵颁,賦值給sNotifyObjCMapped
。 - 第二個(gè)參數(shù)是
load_images
月帝,賦值給sNotifyObjCInit
躏惋。 - 第三個(gè)參數(shù)是
unmap_image
,賦值給sNotifyObjCUnmapped
嚷辅。
這三個(gè)參數(shù)將在后面詳細(xì)介紹是如何與dyld
進(jìn)行交互的簿姨。
ImageLoaderMachO::doInitialization(ImageLoaderMachO.cpp
)
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
CRSetCrashLogMessage2(this->getPath());
// mach-o has -init and static initializers
doImageInit(context);
//加載c++構(gòu)造函數(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");
}
會(huì)發(fā)現(xiàn)MachO
中多了__mod_init_func
- 調(diào)用
doModInitFunctions
函數(shù)加載c++
構(gòu)造函數(shù)(__attribute__((constructor))
修飾的c
函數(shù))
ImageLoaderMachO::doModInitFunctions
- 內(nèi)部是對(duì)
macho
文件的一些讀取操作。 - 會(huì)進(jìn)行
__mod_init_func
section
的確認(rèn)簸搞,與上面的驗(yàn)證符合扁位。 - 加載前必須加載完
libSystem
庫(kù)。
四趁俊、反推objc與dyld的關(guān)聯(lián)
在上面的符號(hào)斷點(diǎn)過(guò)程中可以看到在_dyld_objc_notify_register
與doModInitFunctions
之間還有非dyld
的庫(kù)域仇。
在_objc_init
中打個(gè)斷點(diǎn)有如下調(diào)用棧:
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
* frame #0: 0x00000001002d2d44 libobjc.A.dylib`_objc_init at objc-os.mm:925:9
frame #1: 0x000000010044b0bc libdispatch.dylib`_os_object_init + 13
frame #2: 0x000000010045bafc libdispatch.dylib`libdispatch_init + 282
frame #3: 0x00007fff69543791 libSystem.B.dylib`libSystem_initializer + 220
frame #4: 0x000000010002f1d3 dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 535
frame #5: 0x000000010002f5de dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
frame #6: 0x0000000100029ffb dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 493
frame #7: 0x0000000100029f66 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 344
frame #8: 0x00000001000280b4 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
frame #9: 0x0000000100028154 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
frame #10: 0x0000000100016662 dyld`dyld::initializeMainExecutable() + 129
frame #11: 0x000000010001bbba dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 6667
frame #12: 0x0000000100015227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
frame #13: 0x0000000100015025 dyld`_dyld_start + 37
對(duì)于doModInitFunctions
后面的流程是未知的。從doModInitFunctions->_objc_init
流程是未知的寺擂。那么最好的方式就是從_objc_init
反推調(diào)用到它的整個(gè)流程暇务。
4.1 _os_object_init
_objc_init
是被_os_object_init
調(diào)用的泼掠,這個(gè)函數(shù)在libdispatch.dylib
中。下載libdispatch
最新源碼1271.120.2
直接搜索_os_object_init
:
void
_os_object_init(void)
{
//_objc_init調(diào)用
_objc_init();
Block_callbacks_RR callbacks = {
sizeof(Block_callbacks_RR),
(void (*)(const void *))&objc_retain,
(void (*)(const void *))&objc_release,
(void (*)(const void *))&_os_objc_destructInstance
};
_Block_use_RR2(&callbacks);
#if DISPATCH_COCOA_COMPAT
const char *v = getenv("OBJC_DEBUG_MISSING_POOLS");
if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
v = getenv("DISPATCH_DEBUG_MISSING_POOLS");
if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
v = getenv("LIBDISPATCH_DEBUG_MISSING_POOLS");
if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
#endif
}
發(fā)現(xiàn)確實(shí)是在_os_object_init
中直接調(diào)用了_objc_init()
般卑。
接著在libdispatch_init
中發(fā)現(xiàn)了_os_object_init
的調(diào)用:
- 其中進(jìn)行了
TLS
鍵值處理以及線(xiàn)程處理武鲁。
4.2 libSystem_initializer
libSystem_initializer
是在libSystem
庫(kù)中,下載libSystem
最新源碼1292.120.1
蝠检。
同樣直接搜索libSystem_initializer
:
- 其中直接調(diào)用了
libdispatch_init
沐鼠,同樣還調(diào)用了__malloc_init
、_dyld_initializer
以及_libtrace_init
叹谁。
libSystem_initializer
是ImageLoaderMachO::doModInitFunctions
調(diào)用的饲梭,這樣整個(gè)流程就回到了dyld
中。整個(gè)流程就串起來(lái)了焰檩。
在doModInitFunctions
中發(fā)現(xiàn)了如下代碼:
-
libSystem
庫(kù)必須第一個(gè)被初始化憔涉。這也能被理解,因?yàn)橐跏蓟?code>dispatch以及objc
析苫。其它image
都依賴(lài)它兜叨。 -
func
是對(duì)c++
構(gòu)造函數(shù)的調(diào)用。
那么libSystem_initializer
是在哪里調(diào)用的呢衩侥?在doModInitFunctions
中并沒(méi)有看到libSystem_initializer
的調(diào)用国旷。但是斷點(diǎn)讀取確實(shí)讀取到了:
前面已經(jīng)分析過(guò)了
doModInitFunctions
中是對(duì)c++
構(gòu)造函數(shù)的調(diào)用。libSystem_initializer
正好是c++
構(gòu)造函數(shù):這樣整個(gè)流程就通了茫死。只不過(guò)
libSystem_initializer
這個(gè)c++
構(gòu)造函數(shù)被先調(diào)用跪但。
libSystem
的c++
構(gòu)造函數(shù)在dyld
、libobjc
峦萎、Foundation
的c++
構(gòu)造函數(shù)之后屡久,主程序之前執(zhí)行。
五爱榔、 dyld注冊(cè)objc回調(diào)簡(jiǎn)單分析
通過(guò)上面的分析在_objc_init
中調(diào)用了_dyld_objc_notify_register
進(jìn)行回調(diào)注冊(cè)被环,有如下賦值:
//第一個(gè)參數(shù) map_images
sNotifyObjCMapped = mapped;
//第二個(gè)參數(shù) load_images
sNotifyObjCInit = init;
//第三個(gè)參數(shù) unmap_image
sNotifyObjCUnmapped = unmapped;
接下來(lái)將詳細(xì)分析這3
個(gè)回調(diào)的邏輯。
5.1 sNotifyObjCMapped(map_images)
sNotifyObjCMapped
在dyld
中的調(diào)用只在notifyBatchPartial
中:
而notifyBatchPartial
的調(diào)用是在registerObjCNotifiers
详幽、registerImageStateBatchChangeHandler
筛欢、以及notifyBatch
中。那么根據(jù)之前的分析這里的調(diào)用就是registerObjCNotifiers
注冊(cè)回調(diào)后就在里面調(diào)用了妒潭。
在objc
源碼map_images
中打個(gè)斷點(diǎn):
可以驗(yàn)證在注冊(cè)回調(diào)后立馬調(diào)用了
map_images
悴能。
map_images
中直接加鎖調(diào)用了map_images_nolock
,其中進(jìn)行了類(lèi)的加載相關(guān)的操作雳灾。這塊邏輯將單獨(dú)寫(xiě)篇文章進(jìn)行分析漠酿。
5.2 sNotifyObjCInit(load_images)
sNotifyObjCInit
在dyld
中的調(diào)用分為以下情況:
1.notifySingleFromCache
中。
2.notifySingle
中谎亩。
3.registerObjCNotifiers
炒嘲。
notifySingleFromCache
與notifySingle
邏輯基本相同宇姚,無(wú)非就是有沒(méi)有緩存的區(qū)別。
registerObjCNotifiers
是在注冊(cè)回調(diào)函數(shù)的時(shí)候直接進(jìn)行的回調(diào)夫凸。直接在load_images
中打個(gè)斷點(diǎn)可以跟蹤到如下信息:
可以看到系統(tǒng)的基礎(chǔ)庫(kù)在注冊(cè)回調(diào)后就馬上進(jìn)行了
load_images
的調(diào)用浑劳。
而對(duì)于其他庫(kù)是通過(guò)notifySingle
走的回調(diào)邏輯:
5.2.1 load_images(objc-runtime-new.mm
)
sNotifyObjCInit
其實(shí)就是load_images
,它的實(shí)現(xiàn)如下:
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
//加載所有分類(lèi)
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);
//準(zhǔn)備所有l(wèi)oad方法
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
//調(diào)用 + load方法
call_load_methods();
}
- 加載所有分類(lèi)夭拌。
- 準(zhǔn)備所有
load
方法魔熏。 - 最終調(diào)用了
call_load_methods
。
prepare_load_methods
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
//添加主類(lèi)的load方法
schedule_class_load(remapClass(classlist[i]));
}
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
//分類(lèi)準(zhǔn)備好
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
//實(shí)現(xiàn)類(lèi)
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
//添加分類(lèi)的load方法鸽扁。
add_category_to_loadable_list(cat);
}
}
- 添加主類(lèi)的
load
方法蒜绽。 - 添加分類(lèi)的
load
方法。
schedule_class_load
static void schedule_class_load(Class cls)
{
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
//調(diào)度類(lèi)的load方法桶现,遞歸到nil
schedule_class_load(cls->getSuperclass());
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
- 遞歸調(diào)度類(lèi)的
load
方法躲雅,直到父類(lèi)為nil
。
add_class_to_loadable_list & add_category_to_loadable_list
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
//load方法
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
//空間不足開(kāi)辟空間
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
//將load方法添加到loadable_classes中骡和。相當(dāng)于一個(gè)下標(biāo)中存儲(chǔ)的是cls-method
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
//獲取load方法
method = _category_getLoadMethod(cat);
// Don't bother if cat has no +load method
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load",
_category_getClassName(cat), _category_getName(cat));
}
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
//分類(lèi)添加到loadable_categories中
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
- 通過(guò)字符出那比較獲取
load
方法相赁。 - 空間不足的情況下開(kāi)辟空間嗎,每次開(kāi)辟的空間大小為(
2
倍+16
)* 16 字節(jié)慰于。
struct loadable_class { Class cls; // may be nil IMP method; };
- 將對(duì)應(yīng)的數(shù)據(jù)添加進(jìn)
loadable_classes
與loadable_categories
中钮科。
??加載過(guò)程中類(lèi)和分類(lèi)是有區(qū)分的。為什么區(qū)分將在后續(xù)的文章中詳細(xì)分析东囚。
getLoadMethod
IMP
objc_class::getLoadMethod()
{
runtimeLock.assertLocked();
const method_list_t *mlist;
//遞歸所有的baseMethods跺嗽,查找load方法战授。
mlist = ISA()->data()->ro()->baseMethods();
if (mlist) {
for (const auto& meth : *mlist) {
const char *name = sel_cname(meth.name());
//匹配load
if (0 == strcmp(name, "load")) {
return meth.imp(false);
}
}
}
return nil;
}
IMP
_category_getLoadMethod(Category cat)
{
runtimeLock.assertLocked();
const method_list_t *mlist;
mlist = cat->classMethods;
if (mlist) {
for (const auto& meth : *mlist) {
const char *name = sel_cname(meth.name());
if (0 == strcmp(name, "load")) {
return meth.imp(false);
}
}
}
return nil;
}
-
load
方法獲取是通過(guò)字符出那比較獲取的页藻。
5.2.2 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,類(lèi)的load方法在這一刻被調(diào)用
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
//調(diào)用每個(gè)類(lèi)的load
call_class_loads();
}
// 2. Call category +loads ONCE
//調(diào)用分類(lèi)load,這里也就說(shuō)明分類(lèi)的 load 在所有類(lèi)的load方法調(diào)用后才調(diào)用植兰。(針對(duì)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
加載類(lèi)的+ load
份帐。 - 接著調(diào)用
call_category_loads
加載分類(lèi)的+ load
。這里也就說(shuō)明分類(lèi)的load
在所有類(lèi)的load
方法調(diào)用后才調(diào)用楣导。(針對(duì)image
而言)废境。
在這里也就調(diào)用到了+ load
方法,這也就是+ load
在main
之前被調(diào)用的原因筒繁。
call_class_loads
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
//清空值
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
//從classes中獲取method
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
//調(diào)用load
(*load_method)(cls, @selector(load));
}
// Destroy the detached list.
if (classes) free(classes);
}
- 內(nèi)部也是從
loadable_classes
中循環(huán)取到load
方法進(jìn)行調(diào)用噩凹。
call_category_loads
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
//從cats中取出load
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
(*load_method)(cls, @selector(load));
cats[i].cat = nil;
}
}
……
}
- 分類(lèi)
load
的調(diào)用也是從loadable_categories
循環(huán)取load
方法進(jìn)行調(diào)用。分類(lèi)中內(nèi)部處理邏輯更多一些毡咏。
所以在調(diào)用完+ load
以及c++
構(gòu)造函數(shù)才返回LC_MAIN
進(jìn)行main
函數(shù)的調(diào)用驮宴。可以通過(guò)匯編斷點(diǎn)驗(yàn)證:
這樣就和開(kāi)頭的時(shí)候?qū)?yīng)上了呕缭。那么如果修改
main
函數(shù)的名稱(chēng)堵泽,編譯的時(shí)候就報(bào)錯(cuò)了修己。主程序的入口main
是寫(xiě)死的,可以通過(guò)Hook
去操作main
隱藏自己的邏輯迎罗。
根據(jù)以上分析可以看到
dyld
是按image list
順序從第1
個(gè)image
調(diào)用runInitializers
(可以看做是以image
分組)睬愤。再調(diào)用下一個(gè)image
的runInitializers
最后再調(diào)用主程序(下標(biāo)為0
)的runInitializers
。在runInitializers
內(nèi)部先調(diào)用所有類(lèi)的+load
纹安,再調(diào)用所有分類(lèi)的+ load
尤辱,最后調(diào)用c++
的構(gòu)造函數(shù)。
objc
中調(diào)用load
厢岂,dyld
中調(diào)用doModInitFunctions
啥刻。
??如果在+ load
中做了防護(hù),那么可以通過(guò)在+ load
執(zhí)行前斷住外部符號(hào)做處理咪笑。這樣就可以繞過(guò)防護(hù)了可帽。
防護(hù)最重要的就是不讓別人找到防護(hù)的邏輯,只要能找到那么破解就很容易了窗怒。
案例分析:你真的了解dyld么映跟?
5.3 sNotifyObjCUnmapped(unmap_image)
sNotifyObjCUnmapped
在dyld
中只有removeImage
進(jìn)行了調(diào)用:
removeImage
被checkandAddImage
、garbageCollectImages
扬虚、_dyld_link_module
調(diào)用努隙。
-
garbageCollectImages
:在link
等其它異常以及回收的時(shí)候調(diào)用。 -
checkandAddImage
:檢測(cè)加載的image
不在鏡像列表中的時(shí)候直接刪除辜昵。 -
_dyld_link_module
:暫時(shí)不確定是哪里調(diào)用的荸镊。
5.3.1 unmap_image
unmap_image
中調(diào)用了unmap_image_nolock
,核心代碼如下:
void
unmap_image_nolock(const struct mach_header *mh)
{
……
header_info *hi;
……
//釋放類(lèi)堪置,分類(lèi)相關(guān)資源躬存。
_unload_image(hi);
// Remove header_info from header list
//移除remove Header
removeHeader(hi);
free(hi);
}
- 移除釋放類(lèi),分類(lèi)相關(guān)資源舀锨。
- 移除
Header
信息岭洲。
六 、dyld3閉包模式分析
關(guān)于閉包模式在開(kāi)啟閉包模式的情況下就直接return
了坎匿,所以核心邏輯就在launchWithClosure
中了:
static bool launchWithClosure(const dyld3::closure::LaunchClosure* mainClosure,
const DyldSharedCache* dyldCache,
const dyld3::MachOLoaded* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[], Diagnostics& diag,
uintptr_t* entry, uintptr_t* startGlue, bool* closureOutOfDate, bool* recoverable)
{
……
libDyldEntry->runInitialzersBottomUp((mach_header*)mainExecutableMH);
……
}
在launchWithClosure
中發(fā)現(xiàn)了runInitialzersBottomUp
的調(diào)用:
void AllImages::runInitialzersBottomUp(const closure::Image* topImage)
{
// walk closure specified initializer list, already ordered bottom up
topImage->forEachImageToInitBefore(^(closure::ImageNum imageToInit, bool& stop) {
// get copy of LoadedImage about imageToInit, but don't keep reference into _loadedImages, because it may move if initialzers call dlopen()
uint32_t indexHint = 0;
LoadedImage loadedImageCopy = findImageNum(imageToInit, indexHint);
// skip if the image is already inited, or in process of being inited (dependency cycle)
if ( (loadedImageCopy.state() == LoadedImage::State::fixedUp) && swapImageState(imageToInit, indexHint, LoadedImage::State::fixedUp, LoadedImage::State::beingInited) ) {
// tell objc to run any +load methods in image
if ( (_objcNotifyInit != nullptr) && loadedImageCopy.image()->mayHavePlusLoads() ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)loadedImageCopy.loadedAddress(), 0, 0);
const char* path = imagePath(loadedImageCopy.image());
log_notifications("dyld: objc-init-notifier called with mh=%p, path=%s\n", loadedImageCopy.loadedAddress(), path);
//+load
(*_objcNotifyInit)(path, loadedImageCopy.loadedAddress());
}
// run all initializers in image
//c++構(gòu)造函數(shù)
runAllInitializersInImage(loadedImageCopy.image(), loadedImageCopy.loadedAddress());
// advance state to inited
swapImageState(imageToInit, indexHint, LoadedImage::State::beingInited, LoadedImage::State::inited);
}
});
}
-
_objcNotifyInit
最終調(diào)用到了+ load
方法盾剩。 -
runAllInitializersInImage
調(diào)用c++
構(gòu)造函數(shù),其中包括注冊(cè)回調(diào)替蔬。
void AllImages::runAllInitializersInImage(const closure::Image* image, const MachOLoaded* ml)
{
image->forEachInitializer(ml, ^(const void* func) {
Initializer initFunc = (Initializer)func;
#if __has_feature(ptrauth_calls)
initFunc = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)initFunc, 0, 0);
#endif
{
ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)ml, (uint64_t)func, 0);
//c++構(gòu)造函數(shù)
initFunc(NXArgc, NXArgv, environ, appleParams, _programVars);
}
log_initializers("dyld: called initialzer %p in %s\n", initFunc, image->path());
});
}
在真機(jī)/模擬器調(diào)試中對(duì)_dyld_objc_notify_register
下符號(hào)斷點(diǎn)發(fā)現(xiàn)_dyld_objc_notify_register()
的注冊(cè)回調(diào)是dyld3::_dyld_objc_notify_register
調(diào)用的:
但是最終的回調(diào)以及調(diào)用方確是
dyld2
的邏輯告私。看下源碼:
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped)
{
if ( gUseDyld3 )
return dyld3::_dyld_objc_notify_register(mapped, init, unmapped);
DYLD_LOCK_THIS_BLOCK;
typedef bool (*funcType)(_dyld_objc_notify_mapped, _dyld_objc_notify_init, _dyld_objc_notify_unmapped);
static funcType __ptrauth_dyld_function_ptr p = NULL;
if(p == NULL)
dyld_func_lookup_and_resign("__dyld_objc_notify_register", &p);
p(mapped, init, unmapped);
}
那么就說(shuō)明gUseDyld3
為NULL
承桥,走了dyld2
的邏輯驻粟。但是如果走dyld3
可以得到以下信息,注冊(cè)的三個(gè)回調(diào)函數(shù)指針與dyld2
名稱(chēng)不同:
_objcNotifyMapped = map;
_objcNotifyInit = init;
_objcNotifyUnmapped = unmap;
-
_objcNotifyInit
已經(jīng)清楚了是在runInitialzersBottomUp
中調(diào)用的快毛。 -
_objcNotifyUnmapped
是在garbageCollectImages ->removeImages
中調(diào)用的格嗅。 -
_objcNotifyMapped
是在runImageCallbacks
中調(diào)用的番挺,它有兩個(gè)調(diào)用方applyInitialImages
以及loadImage
。-
applyInitialImages
是被_dyld_initializer
調(diào)用的屯掖。_dyld_initializer
在第四部分已經(jīng)明確了是在libSystem_initializer
中調(diào)用的玄柏。而由于_dyld_initializer
是在libdispatch_init
之前調(diào)用的,所以這個(gè)時(shí)候應(yīng)該還沒(méi)有注冊(cè)回調(diào)贴铜。 -
loadImage
是在dlopen
中調(diào)用的粪摘。
-
由于真機(jī)和模擬器以及mac
都沒(méi)有辦法進(jìn)入閉包模式調(diào)試驗(yàn)證。并且閉包模式代碼邏輯可讀性比較差绍坝,所以這里只是根據(jù)源碼得出的結(jié)論徘意,不一定成立。
七轩褐、dyld簡(jiǎn)介
啟動(dòng)時(shí)間(Startup Time
):main
函數(shù)執(zhí)行之前所用的時(shí)間椎咧。
啟動(dòng)收尾(Lacunch Closure
):?jiǎn)?dòng)應(yīng)用程序必須的所有信息。
dyld
發(fā)展到如今已經(jīng)有3個(gè)大版本了把介,接下來(lái)將對(duì)dyld
的演進(jìn)過(guò)程做簡(jiǎn)單總結(jié)勤讽。
7.1 dyld 1.0 (1996–2004)
- 包含在
NeXTStep 3.3
中一起發(fā)布,在這之前NeXT
使用靜態(tài)二進(jìn)制數(shù)據(jù)拗踢。 -
dyld1
的歷史早于標(biāo)準(zhǔn)化POSIX dlopen()
調(diào)用脚牍。 -
dyld1
是在大多數(shù)使用c++
動(dòng)態(tài)庫(kù)的系統(tǒng)之前編寫(xiě)的。
c++
有許多的特性比如其初始化器排序方式等在靜態(tài)環(huán)境中工作良好巢墅,但是在動(dòng)態(tài)環(huán)境中可能降低性能诸狭。因此大型c++
代碼庫(kù)導(dǎo)致dyld
需要完成大量的工作,速度變慢君纫。 - 在
macOS Cheetah(10)
中增加了預(yù)綁定技術(shù)驯遇。
預(yù)綁定為系統(tǒng)中所有的dylib
和你的程序找到固定地址。dyld
將會(huì)加載這些地址的所有內(nèi)容庵芭。加載成功會(huì)編輯所有這些二進(jìn)制數(shù)據(jù)以獲得所有預(yù)計(jì)算地址妹懒。然后下次當(dāng)它將所有數(shù)據(jù)放入相同地址時(shí)不必進(jìn)行任何其它額外的工作雀监。這樣會(huì)大幅提高速度双吆,但是這也意味著每次啟動(dòng)時(shí)會(huì)編輯你的二進(jìn)制數(shù)據(jù)。從安全性來(lái)說(shuō)這樣并不是很好的做法会前。
7.2 dyld2.0 (2004-2007)
- 隨著
macOS Tiger
發(fā)布好乐。 -
dyld2
是dyld
的完全重寫(xiě)版本。 - 正確支持
c++
初始化器語(yǔ)義瓦宜,擴(kuò)展了mach-o
格式并且更新了dyld
蔚万。 - 具有完整的本機(jī)
dlopen
和dlsym
實(shí)現(xiàn),具有正確的語(yǔ)義临庇,棄用了舊版API
(舊版API
仍然僅位于macOS
中)反璃。 -
dyld2
的設(shè)計(jì)目標(biāo)是提高速度昵慌,因此僅進(jìn)行有限的健全性檢查(以前惡意程序并不多)。 -
dyld
有一些安全性問(wèn)題淮蜈,對(duì)一些功能性改進(jìn)提高它在現(xiàn)在平臺(tái)上的安全性斋攀。 - 由于速度大幅提升可以減少預(yù)綁定工作量。不同于
dyld1
編輯你的程序數(shù)據(jù)梧田,dyld2
僅編輯系統(tǒng)庫(kù)淳蔼。可以?xún)H在軟件更新時(shí)做這些事情裁眯。因此在軟件更新時(shí)可能會(huì)看到“優(yōu)化系統(tǒng)性能”之類(lèi)的文字鹉梨,這時(shí)就是在更新預(yù)綁定。
7.2.1dyld2.x(2007-2017)
- 增加更多的架構(gòu)和平臺(tái)穿稳。
-
x86
存皂、x86_64
、arm64
-
iOS
逢艘、tvOS
艰垂、watchOS
-
- 提升安全性
- 增加代碼簽名和
ASLR
-
mach-o header
邊界檢查,避免惡意二進(jìn)制數(shù)據(jù)的加入埋虹。
- 增加代碼簽名和
- 提升性能
- 使用共享緩存替換預(yù)綁定猜憎。
7.2.2 共享緩存(shared cache)
共享緩存(dyld
預(yù)連接器)最早被引入iOS3.1
& macOS Snow Leopard
,完全取代預(yù)綁定搔课。
-
它是一個(gè)單文件胰柑,含有大多數(shù)系統(tǒng)
dylib
。
由于合并成一個(gè)文件爬泥,因此可以進(jìn)行優(yōu)化- 重新調(diào)整二進(jìn)制數(shù)據(jù)以提高加載速度(重新調(diào)整所有文本段和所有數(shù)據(jù)段重寫(xiě)整個(gè)符號(hào)表以減小大屑硖帧)。
- 允許打包二進(jìn)制數(shù)據(jù)段節(jié)省大量
ram
- 預(yù)生成數(shù)據(jù)結(jié)構(gòu)供
dyld
和objc
使用袍啡,在運(yùn)行時(shí)使用讓我們不必在應(yīng)用啟動(dòng)時(shí)做這些事情踩官。這樣也會(huì)節(jié)約更多ram
和時(shí)間。
共享緩存在
macOS
上本地生成運(yùn)行dyld
共享代碼大幅優(yōu)化系統(tǒng)性能境输。其它平臺(tái)由Apple
提供蔗牡。
7.3 dyld3.0(2017-)
dyld3
是全新的動(dòng)態(tài)連接器,2017(iOS11
)年所有系統(tǒng)程序都默認(rèn)使用dyld3
嗅剖,第三方在2019(iOS13
)年完全取代dyld2
辩越。
dyld3
主要做了以下三方面的改進(jìn):
- 性能,提高啟動(dòng)速度信粮。
dyld3
可以幫助我們獲得更快的程序啟動(dòng)和運(yùn)行速度黔攒。 - 安全性。
dyld2
增加的安全性很難跟隨現(xiàn)實(shí)情形增強(qiáng)安全性。 - 可測(cè)試性和可靠性督惰。
XCTest
依賴(lài)于dyld
的底層功能不傅,將它們的庫(kù)插入進(jìn)程。因此不能用于測(cè)試現(xiàn)有的dyld
代碼赏胚。這讓難以測(cè)試安全性和性能水平蛤签。
dyld3
將大多數(shù)dyld
移出進(jìn)程,現(xiàn)在大多數(shù)dyld
只是普通的后臺(tái)程序栅哀≌鸢梗可以使用標(biāo)準(zhǔn)測(cè)試工具進(jìn)行測(cè)試。另外也允許部分dyld
駐留在進(jìn)程中留拾,駐留部分盡可能小戳晌,從而減少程序的受攻擊面積。
7.4 dyld2與dyld3加載對(duì)比
7.4.1 dyld2流程
-
Parse mach-o headers & Find dependencies
:分析macho headers
痴柔,確認(rèn)需要哪些庫(kù)沦偎。遞歸分析依賴(lài)的庫(kù)直到獲得所有的dylib庫(kù)。普通iOS
程序需要3-600
個(gè)dylib
咳蔚,數(shù)據(jù)龐大需要進(jìn)行大量處理豪嚎。 -
Map mach-o files
:映射所有macho
文件將他們放入地址空間(映射進(jìn)內(nèi)存)。 -
Perform symbol lookups
:執(zhí)行符號(hào)查找谈火。 比如使用printf
函數(shù)侈询,將會(huì)查找printf
是否在庫(kù)系統(tǒng)中,然后找到它的地址糯耍,將它復(fù)制給應(yīng)用程序中的函數(shù)指針扔字。 -
Bind and rebase
:綁定和基址重置。復(fù)制這些指針温技,所有指針必須使用基地址(ASLR
的存在)革为。 -
Run initializers
:運(yùn)行所有初始化器。這之后就開(kāi)始準(zhǔn)備執(zhí)行main
函數(shù)舵鳞。
7.4.2 dyld3流程
dyld3
整個(gè)被分成了3個(gè)流程:
-
dyld3
是一個(gè)進(jìn)程外macho
分析器和編譯器(對(duì)應(yīng)上圖中紅色部分)震檩。- 解析所有搜索路徑、
rpaths
蜓堕、環(huán)境變量抛虏。 - 分析
macho
二進(jìn)制數(shù)據(jù)。 - 執(zhí)行所有符號(hào)查找俩滥。
- 利用上面的這些結(jié)果創(chuàng)建閉包處理嘉蕾。
- 它是一個(gè)普通的后臺(tái)程序,可以進(jìn)行正常測(cè)試霜旧。
- 大多數(shù)程序啟動(dòng)會(huì)使用緩存,始終不需要調(diào)用進(jìn)程外
macho
分析器或編譯器。 - 啟動(dòng)閉包比
macho
更簡(jiǎn)單挂据,它們是內(nèi)存映射文件以清,不需要用復(fù)雜的方式進(jìn)行分析,可以簡(jiǎn)單的驗(yàn)證它們崎逃,作用是為了提高速度。
- 解析所有搜索路徑、
-
dyld3
也是一個(gè)進(jìn)程內(nèi)引擎。- 檢查閉包是否正確收叶。
- 使用閉包映射所有
dylibs
写穴。 - 綁定和基址重置。
- 運(yùn)行所有初始化器巴柿,然后跳轉(zhuǎn)主程序
main()
??
dyld3
不需要分析macho Headers
或者執(zhí)行符號(hào)查找凛虽。在App
啟動(dòng)時(shí)沒(méi)有這個(gè)過(guò)程,因此極大的提升了程序的啟動(dòng)速度广恢。 - 啟動(dòng)閉包緩存服務(wù)
- 系統(tǒng)
app
閉包模式構(gòu)建在共享緩存中凯旋。 - 第三方應(yīng)用在安裝時(shí)構(gòu)建,在軟件更新時(shí)重新構(gòu)建钉迷。
- 在
macOS
上后臺(tái)進(jìn)程引擎可以在后臺(tái)進(jìn)程被調(diào)用至非,在其它平臺(tái)上不需要這么做。
- 系統(tǒng)
詳細(xì)情況參考官方:wwdc2017-413(App Startup Time: Past, Present, and Future)