一、dyld簡介
在iOS系統(tǒng)中,幾乎所有的程序都會(huì)用到動(dòng)態(tài)庫刃榨,靜態(tài)庫等弹砚,而這些庫在加載的時(shí)候都需要用到dyld程序進(jìn)行鏈接,dyld是蘋果的動(dòng)態(tài)鏈接器枢希,是蘋果操作系統(tǒng)的一個(gè)重要組成部分桌吃,在系統(tǒng)內(nèi)核做好程序準(zhǔn)備工作之后,交由dyld負(fù)責(zé)余下的工作苞轿。
二茅诱、準(zhǔn)備
創(chuàng)建一個(gè)空的項(xiàng)目,在ViewController這個(gè)類中添加一個(gè)load方法搬卒,打個(gè)斷點(diǎn)瑟俭,然后在xcode左側(cè)查看項(xiàng)目的堆棧調(diào)用信息,如下:
我們發(fā)現(xiàn)在load方法之前契邀,加載了好多函數(shù)呀摆寄,這些函數(shù)都干了什么啦,這些是我們接下來需要探索的東西坯门。
補(bǔ)充
我們也可以通過lldb進(jìn)行查看如下:
dyld初探
下載最新的dyld源碼本篇文章基于dyld-750.6進(jìn)行分析,我們?cè)谠创a中搜索_dyld_start微饥,然后發(fā)現(xiàn)在dyldStartup.s文件中有具體的調(diào)用,無論模擬器古戴、真機(jī)都會(huì)調(diào)用dyldbootstrap::start這個(gè)方法欠橘,我們繼續(xù)查找,找到如下:
部分源碼如下:
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{
// 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
rebaseDyld(dyldsMachHeader);
// kernel sets up env pointer to be just past end of agv array
const char** envp = &argv[argc+1];
// kernel sets up apple pointer to be just past end of envp array
const char** apple = envp;
while(*apple != NULL) { ++apple; }
++apple;
// set up random value for stack canary
__guard_setup(apple);
#if DYLD_INITIALIZER_SUPPORT
// run all C++ initializers inside dyld
runDyldInitializers(argc, argv, envp, apple);
#endif
// now that we are done bootstrapping dyld, call dyld's main
uintptr_t appsSlide = appsMachHeader->getSlide();
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
在進(jìn)入dyld的__main函數(shù)之前现恼,會(huì)進(jìn)行dyld的重定向肃续、dyld內(nèi)部加載所有的c++初始化器、地址偏移等工作叉袍。
我們進(jìn)行dyld的__main函數(shù)中始锚,發(fā)現(xiàn)里面干了好多事情,流程如下:
1.我們發(fā)現(xiàn)__main函數(shù)中喳逛,主要會(huì)對(duì)環(huán)境以及平臺(tái)信息進(jìn)行處理疼蛾,比如我們xcode設(shè)置了一些環(huán)境變量之類的,都可以打印出來艺配。
2.getHostInfo函數(shù):獲取cpu相關(guān)信息
3.checkSharedRegionDisable函數(shù):判斷是否可以加載系統(tǒng)共享緩存庫察郁,加載共享緩存庫
4.instantiateFromLoadedImage函數(shù):實(shí)例化主程序,也就是machO這個(gè)可執(zhí)行文件转唉、鏈接動(dòng)態(tài)庫和插入庫
5.loadInsertedDylib:加載所有插入庫
6.weakBind:符號(hào)綁定
7.initializeMainExecutable:初始化依賴庫皮钠、三方庫、load赠法、c++構(gòu)造函數(shù)麦轰,這個(gè)函數(shù)內(nèi)部源碼如下:
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 ) {
for(size_t i=1; i < rootCount; ++i) {
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
}
}
// run initializers for main executable and everything it brings up
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
// register cxa_atexit() handler to run static terminators in all loaded images when this process exits
if ( gLibSystemHelpers != NULL )
(*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);
// dump info if requested
if ( sEnv.DYLD_PRINT_STATISTICS )
ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}
里面關(guān)鍵的函數(shù)是runInitializers,順著這個(gè)函數(shù)我們可以找到notifySingle乔夯,doInitialization
查找如下:runInitializers->processInitializers->recursiveInitialization->notifySingle(加載load方法)
doInitialization:內(nèi)部會(huì)調(diào)用全局C++對(duì)象的構(gòu)造函數(shù),即attribute((constructor))這樣的函數(shù)款侵。
最后通過notifyMonitoringDyldMain:通知main函數(shù)dyld引導(dǎo)已經(jīng)完成了末荐,可以進(jìn)入main函數(shù)了。
補(bǔ)充
我們發(fā)現(xiàn)notifySingle這個(gè)函數(shù)中會(huì)調(diào)用sNotifyObjCInit新锈,它是在registerObjCNotifiers函數(shù)中進(jìn)行賦值,_dyld_objc_notify_register函數(shù)中又調(diào)用了registerObjCNotifiers妹笆,但是我們卻沒有找到_dyld_objc_notify_register函數(shù)調(diào)用的位置,最后在objc的源碼中發(fā)現(xiàn)了我們想要的結(jié)果墩新。
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
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();
cache_init();
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
注釋中有解釋,Called by libSystem BEFORE library initialization time切省,library初始化之前被libSystem庫調(diào)用帕胆。
查看load_images源碼實(shí)現(xiàn)
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
最后一行代碼是調(diào)用所有的+load方法
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();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
call_load_methods函數(shù)中循環(huán)調(diào)用類的+load方法
然后會(huì)調(diào)用分類的+load方法