iOS底層-dyld加載流程分析

一、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)用信息,如下:

圖1

我們發(fā)現(xiàn)在load方法之前契邀,加載了好多函數(shù)呀摆寄,這些函數(shù)都干了什么啦,這些是我們接下來需要探索的東西坯门。

補(bǔ)充
我們也可以通過lldb進(jìn)行查看如下:

堆棧調(diào)用信息圖

鏡像加載圖

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ù)查找,找到如下:

圖2

部分源碼如下:

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)里面干了好多事情,流程如下:


dyld流程分析圖.png

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方法

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驯用,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子记餐,更是在濱河造成了極大的恐慌薇正,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異审轮,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)篡诽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門杈女,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碧信,“玉大人街夭,你說我怎么就攤上這事板丽“<睿” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長辛萍。 經(jīng)常有香客問我羡藐,道長仆嗦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任谆甜,我火速辦了婚禮店印,結(jié)果婚禮上倒慧,老公的妹妹穿的比我還像新娘包券。我一直安慰自己溅固,他們只是感情好兰珍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布掠河。 她就那樣靜靜地躺著唠摹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪煮甥。 梳的紋絲不亂的頭發(fā)上藕赞,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天斧蜕,我揣著相機(jī)與錄音批销,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛骡技,可吹牛的內(nèi)容都是我干的羞反。 我是一名探鬼主播昼窗,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼澄惊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了没佑?” 一聲冷哼從身側(cè)響起蛤奢,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤啤贩,失蹤者是張志新(化名)和其女友劉穎痹屹,沒想到半個(gè)月后腹纳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嘲恍,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡佃牛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了爷速。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惫东。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡颓遏,死狀恐怖滞时,靈堂內(nèi)的尸體忽然破棺而出坪稽,到底是詐尸還是另有隱情,我是刑警寧澤弟孟,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布拂募,位于F島的核電站陈症,受9級(jí)特大地震影響录肯,放射性物質(zhì)發(fā)生泄漏论咏。R本人自食惡果不足惜厅贪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一养涮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜀变,春花似錦库北、人聲如沸洼专。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽官还。三九已至望伦,卻和暖如春屯伞,著一層夾襖步出監(jiān)牢的瞬間劣摇,已是汗流浹背末融。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國打工锨咙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酪刀,地道東北人眼滤。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓诅需,卻偏偏與公主長得像,于是被迫代替她去往敵國和親荧库。 傳聞我的和親對(duì)象是個(gè)殘疾皇子堰塌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355