iOS底層-dyld加載分析

引言:

眾所周知巢音,我們的iOS應(yīng)用是通過(guò)Dyld進(jìn)行加載的累澡,那么Dyld是如何加載我們的應(yīng)用的,它的流程是怎樣的鞍匾,下面我們把dyld的加載分為幾個(gè)步驟做個(gè)簡(jiǎn)短的分析交洗。

1 dyld的start啟動(dòng)

首先我們創(chuàng)建一個(gè)Demo工程,在我們的AppDelegate.m文件里加入+(load)方法并斷點(diǎn)橡淑,如下圖所示:

1

運(yùn)行Demo App后构拳,可以得到所下圖

2

從圖2中我們可以看到,我們的App是從_dyld_start開(kāi)始的梁棠,我們點(diǎn)擊dyld_start,看到匯編的第18行代碼置森,這里調(diào)用了dyldbootstrap::start(macho_header const, int, char const, long, macho_header const, unsigned long*)這行代碼,我們?cè)偈褂胋t命令看下詳細(xì)的堆棧符糊,如下圖

3

從這里可以看出一切的開(kāi)始是從_dyld_start開(kāi)始的凫海,這個(gè)dyld_start在可以在dyld的源碼里找(注:這里使用的dyld的源碼的版本是dyld-832.7.3),下面我們打開(kāi)dyld的源碼進(jìn)行分析

我們?cè)赿yld的源碼里找 dyldbootstrap這個(gè)命名空間,按住shift+comand+j進(jìn)入文件男娄,所下圖所示

4

從這里可以看到是在dyldInitialization.cpp文件里,然后我們?cè)谖募锼阉鱯tart行贪,找到uintptr_tstart(constdyld3::MachOLoaded* appsMachHeader,intargc,constchar* argv[],

constdyld3::MachOLoaded* dyldsMachHeader,uintptr_t* startGlue)函數(shù),如圖所示:

image

我們從start函數(shù)逐步往下分析整個(gè)流程模闲,我們先看下參數(shù)建瘫,appsMachHeader是我們App的MachHeader,dyldsMachHeader是dyld的MachHeader。

第121行代碼是告訴我們的degbugServer尸折,我的dyld開(kāi)始起動(dòng)了啰脚。

第125行代碼rebaseDyld(dyldsMachHeader) 重定位我們的dyld。

第136实夹,143行是棧溢出保護(hù)和初始化dyld,之后就是調(diào)用dyld的main函數(shù)(這是最核心的)拣播,我們著重分析下dyld的main函數(shù)流程。

5

main函數(shù)的前幾行代碼都是代碼檢測(cè)相關(guān)的收擦,不是核心內(nèi)容

我們下面來(lái)看主程序的配置相關(guān)贮配,如圖

6

這些是配置主程序的MachHeader(就是Macho的頭),主程序的Slide(就是主程序的ASLR的偏移值塞赂,每次啟動(dòng)都是不一樣的)

下面調(diào)用setContext(mainExecutableMH, argc, argv, envp, apple);保存我們配置的信息

7

然后通過(guò)configureProcessRestrictions(mainExecutableMH, envp)這個(gè)函數(shù)配置進(jìn)程受限制(AMFI相關(guān)(Apple Mobile File Integrity蘋果移動(dòng)文件保護(hù)))泪勒,下圖都是進(jìn)程受限相關(guān)的配置,比如是否強(qiáng)制使用dyld3(dyld是在iOS11推出來(lái)的,加載高效)

8

下圖打印我們的環(huán)境變量圆存,這個(gè)環(huán)境變理可通過(guò) Environment Variables配置

9

以下都是dyld的啟動(dòng)叼旋,配置,以及主程序的相關(guān)配置和一些代碼檢測(cè)的流程沦辙,下面我們來(lái)分析共享緩存

2 dyld加載共享緩存

10

點(diǎn)擊進(jìn)去checkSharedRegionDisable發(fā)現(xiàn)有一個(gè)“iOS cannot run without shared region”說(shuō)明夫植,這是表明我們的iOS是一定有共享緩存的。

接著調(diào)用mapSharedCache傳進(jìn)去主程序的Slide油讯,這個(gè)函數(shù)調(diào)用了loadDyldCache加載我們的dyld緩存,如下圖所示

11

滿足options.forcePrivate 這個(gè)條件的話详民,只加載當(dāng)前進(jìn)程

reuseExistingCache如果緩存已經(jīng)加載不再處理,如果第一次加載執(zhí)行mapCacheSystemWide這個(gè)函數(shù)

通過(guò)以上分析陌兑,可以得出結(jié)論沈跨,動(dòng)態(tài)庫(kù)的共享緩存是最先被加載的(我們自己開(kāi)發(fā)的動(dòng)態(tài)庫(kù)不可以)。從iOS11引入了dyld3的ClosureMode(閉包模式加載更快)兔综,下面我們來(lái)分析一下

3 dyld3的閉包模式

12

這里先判斷閉包模式是否打開(kāi)饿凛,如果沒(méi)有的話將會(huì)走dyld2的流程,打開(kāi)走dyld3的流程(dyld2,dyld3的加載流程一致),下面我們來(lái)分析dyld3的閉包模式

13

先從共享緩存中查找這個(gè)實(shí)例软驰,如果拿到就先驗(yàn)證

14

這里判斷是否查找成功涧窒,并且驗(yàn)證閉包的有效性,如果失效锭亏,sLaunchModeUsed設(shè)置為NULL

15

這里如果沒(méi)找到纠吴,再去緩存中查一次,如果mainClosure為空贰镣,這里就調(diào)用buildLaunchClosure創(chuàng)建閉包實(shí)例

最終拿到這個(gè)mainClosure實(shí)例啟動(dòng)這個(gè)實(shí)例呜象,如下圖所示

16

如果啟動(dòng)失敗或者閉包過(guò)期,這里就再重新調(diào)用buildLaunchClosure創(chuàng)建并調(diào)用launchWithClosure重新啟動(dòng)一次碑隆。

啟動(dòng)成功后設(shè)置gLinkContext.startedInitializingMainExecutable = true;這個(gè)主程序加載成功了恭陡。

同時(shí)返回結(jié)果result(即主程序的main),如下圖所示:

17

接著就會(huì)實(shí)例化我們的主程序了上煤,下面我們來(lái)分析是如何加載的休玩。

4 dyld加載主程序

接下看下怎么實(shí)例化主程序的,如下圖所示

18

第6862行代碼會(huì)調(diào)用instantiateFromLoadedImage函數(shù)實(shí)例化我們的主程序劫狠,我們來(lái)看下instantiateFromLoadedImage這個(gè)函數(shù)拴疤,下圖所示:

19

這里通過(guò)ImageLoaderMachO這個(gè)函數(shù)傳image的macho_header,ASLR的偏移值,路徑生成ImageLoader對(duì)象独泞,然后調(diào)用addImage這個(gè)函數(shù)加入我們的鏡像文件呐矾,同時(shí)返回ImageLoader這個(gè)對(duì)象。(通過(guò)dyld加載的第一個(gè)鏡像是我們的主程序),我們來(lái)看下instantiateMainExecutable的這個(gè)函數(shù)的流程懦砂,如圖所示:

20

這里調(diào)用sniffLoadCommands獲取loadCommands,如圖:

21

compressed是根據(jù)Macho中的LG_DYLD_INFO_ONLY和LG_LOAD_DYLINKER來(lái)獲取的蜒犯。

segCount是SEGMENT的數(shù)量组橄,最大不能超過(guò)255。

libCount是LC_LOAD_DYLIB加載動(dòng)態(tài)庫(kù)的個(gè)數(shù),最大不能超過(guò)4095罚随。

*codeSigCmd是代碼簽名玉工。

*encryptCmd是代碼加密信息。

ImageLoaderMachO這個(gè)函數(shù)調(diào)用sniffLoadCommands這個(gè)之后會(huì)根據(jù)compressed這個(gè)變量判斷調(diào)用ImageLoaderMachOCompressed或者ImageLoaderMachOClassic這兩個(gè)函數(shù)實(shí)例化淘菩。

實(shí)例化完畢之后添加到AllImage中遵班。

22

接著檢測(cè)當(dāng)前主程否是當(dāng)前設(shè)備的,如上圖所示,到這里我們的主程序?qū)嵗Y(jié)束潮改,接著我們來(lái)分析動(dòng)態(tài)庫(kù)的加載狭郑。

5 dyld加載動(dòng)態(tài)庫(kù)

23

這里先檢查動(dòng)態(tài)庫(kù)的版本和路徑,接著加載動(dòng)態(tài)庫(kù),如圖所示:

24

這里先根據(jù)環(huán)境變量判斷動(dòng)態(tài)插入庫(kù)不為空进陡,接著遍歷loadInsertedDylib

25

這里調(diào)用load插入動(dòng)態(tài)庫(kù)愿阐,接著開(kāi)始鏈接主程序微服,

26

先配置gLinkContext.linkingMainExecutable = true;這個(gè)變量為true.

接著調(diào)用link函數(shù)進(jìn)行鏈接趾疚,我們來(lái)看看是如何鏈接的:

27
28
29

這里先記錄起始時(shí)間,在最后在記錄結(jié)束時(shí)間以蕴,把加載時(shí)間記錄下來(lái)糙麦,這個(gè)就是dyld加載應(yīng)用的時(shí)長(zhǎng)。

這里鏈接插入動(dòng)態(tài)庫(kù)完成了丛肮。

30

之后把這些實(shí)例化的鏡像文件加入到AllImages中(這里是從i+1開(kāi)始的赡磅,因?yàn)橹鞒绦蛞呀?jīng)先加載了),之后再調(diào)用link進(jìn)行鏈接宝与,這里跟主程序的鏈接是一樣的焚廊。

31

這里的條件不滿足的話,將會(huì)持續(xù)的調(diào)用reloadAllImage习劫,這里執(zhí)行之后咆瘟,就開(kāi)始綁定動(dòng)態(tài)庫(kù)了,如圖所示:

32

這里遍歷AllImages綁定插入動(dòng)態(tài)庫(kù)诽里,之后進(jìn)行弱符號(hào)綁定袒餐。

接著調(diào)用initializeMainExecutable初始化主程序的Main方法,如圖所示:

33

下面我們來(lái)分析主程序Main方法加載的流程。

6 load方法與初始化方法的加載

我們先進(jìn)入initializeMainExecutable()這個(gè)函數(shù)谤狡,看下它的實(shí)現(xiàn)

34

這里有一個(gè)runInitializers函數(shù)灸眼,我們?cè)龠M(jìn)去

35

這里會(huì)調(diào)用processInitializers這個(gè)函數(shù),我們?cè)俑M(jìn)去看看

36

接著我們?cè)俑聄ecursiveInitialization這個(gè)函數(shù)

37
38

這里調(diào)用了notifySingle這個(gè)函數(shù)墓懂,我們需要再跟進(jìn)去一下焰宣,

39
40

而這里沒(méi)有找到loadImge的函數(shù)調(diào)用,這里到底是怎么回事捕仔,我們通過(guò)匯編可以看到load_image是在libobjc.dylib中匕积,也就是說(shuō)在objc的源碼中佛嬉,那它是怎么調(diào)用的,我們來(lái)看下代碼闸天。

在1019行中 (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()) 這個(gè)回調(diào)進(jìn)行關(guān)聯(lián)的暖呕。

這里首先判斷sNotifyObjCInit這個(gè)是否為空,我們?cè)谶@文件里搜下苞氮,發(fā)現(xiàn)是在registerObjCNotifiers這里調(diào)用的時(shí)候賦值的湾揽,如下圖:

41

我們搜下registerObjCNotifiers這個(gè)函數(shù)發(fā)現(xiàn)是在dyldAPIS.cpp中的

42

這個(gè)函數(shù)調(diào)用的,這里有傳遞init進(jìn)來(lái)笼吟,那么又是誰(shuí)調(diào)用的_dyld_objc_notify_register這個(gè)函數(shù)呢库物,搜了之后,發(fā)現(xiàn)dyld里沒(méi)用調(diào)用的贷帮,那么怎么辦呢戚揭,我們可以在Demo工程中下一個(gè)符號(hào)斷點(diǎn)_dyld_objc_notify_register,結(jié)果發(fā)現(xiàn)是在libobjc.dylib中的_objc_init調(diào)用的撵枢,下面打開(kāi)objc的的源代碼民晒,按信shift+command+o找到定義,再按住shift+command+j找到源文件是在objc-os.mm文件中锄禽,如圖所示:

43

這里可以看到_dyld_objc_notify_register這個(gè)函數(shù)是在_objc_init調(diào)用的潜必,這里有一個(gè)load_images,我們?cè)倏聪?/p>

44

這里面有一個(gè)call_load_methods方法沃但,點(diǎn)進(jìn)去看下

45

這里會(huì)do while調(diào)用call_class_loads方法來(lái)加載所有類的+(void)load方法磁滚,load方法加載完成后調(diào)用了call_category_loads這個(gè)方法,加載類加的loads方法宵晚,這也是為什么類別的方法與原類的方法重名后垂攘,會(huì)覆蓋原類的方法。

我們回到dyld的源代碼找到ImageLoader.cpp文件中的recursiveInitialization函數(shù)中調(diào)用notifySingle這里走到了objc中淤刃,objc把所有的load加載完成后晒他,會(huì)調(diào)用doInitialization這個(gè)函數(shù),進(jìn)去看下

46

這里doModInitFunctions調(diào)用這個(gè)函數(shù)钝凶,這個(gè)函數(shù)的作用是什么仪芒,我們來(lái)看下,

47

這里就是在加載我們的構(gòu)造函數(shù),我們?cè)贒emo的main.m上面加入構(gòu)造函數(shù)

attribute((constructor)) void test1() {

printf("test調(diào)用了");

}

經(jīng)過(guò)調(diào)試耕陷,它比main函數(shù)先調(diào)用掂名。

我們?cè)倩氐絛yld的main函數(shù),找到這里哟沫,如圖

48

這里通過(guò)LC_MAIN找到程序入口給result饺蔑,最后返回主程序的main地址。dyld的加載就結(jié)束了嗜诀。

下面是dyld的initializeMainExecutable初始化主程序的思維導(dǎo)圖

49

結(jié)語(yǔ):到這里dyld的流程分析就結(jié)束了猾警,有所遺漏或者錯(cuò)誤的地方孔祸,請(qǐng)指正,大家可以相互學(xué)習(xí)交流发皿,共同進(jìn)步崔慧!

補(bǔ)充 objc與dyld是如何關(guān)聯(lián)?

接上文穴墅,我們知道notifySingle是在libobjc.dyld動(dòng)態(tài)庫(kù)中找到了惶室,那么它在dyld源碼中是怎么賦值的呢,經(jīng)過(guò)我們的一系列的搜索查找玄货,終于找到了皇钞,如圖:

50

第4803行代碼中,我們看到了notifySingle的函數(shù)指針給了gLinkContext.notifySingle,我們貼一下這個(gè)函數(shù)的代碼:

static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
    //dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());
    std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers);
    if ( handlers != NULL ) {
        dyld_image_info info;
        info.imageLoadAddress   = image->machHeader();
        info.imageFilePath      = image->getRealPath();
        info.imageFileModDate   = image->lastModified();
        for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it != handlers->end(); ++it) {
            const char* result = (*it)(state, 1, &info);
            if ( (result != NULL) && (state == dyld_image_state_mapped) ) {
                //fprintf(stderr, "  image rejected by handler=%p\n", *it);
                // make copy of thrown string so that later catch clauses can free it
                const char* str = strdup(result);
                throw str;
            }
        }
    }
    if ( state == dyld_image_state_mapped ) {
        // <rdar://problem/7008875> Save load addr + UUID for images from outside the shared cache
        // <rdar://problem/50432671> Include UUIDs for shared cache dylibs in all image info when using private mapped shared caches
        if (!image->inSharedCache()
            || (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion)) {
            dyld_uuid_info info;
            if ( image->getUUID(info.imageUUID) ) {
                info.imageLoadAddress = image->machHeader();
                addNonSharedCacheImageUUID(info);
            }
        }
    }
    if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
        uint64_t t0 = mach_absolute_time();
        dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
        (*sNotifyObjCInit)(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);
        }
    }
    // mach message csdlc about dynamically unloaded images
    if ( image->addFuncNotified() && (state == dyld_image_state_terminated) ) {
        notifyKernel(*image, false);
        const struct mach_header* loadAddress[] = { image->machHeader() };
        const char* loadPath[] = { image->getPath() };
        notifyMonitoringDyld(true, 1, loadAddress, loadPath);
    }
}

其中(sNotifyObjCInit)(image->getRealPath(), image->machHeader());*
這行代碼有一個(gè)sNotifyObjCInit這個(gè)類型松捉,我們?nèi)炙阉饕幌录薪纾鐖D:

51

發(fā)現(xiàn)它是一個(gè)_dyld_objc_notify_init這個(gè)類型 ,我們接著往下搜索隘世,如圖:
52

發(fā)現(xiàn)sNotifyObjCInit是在這里通過(guò)函數(shù)的參數(shù)進(jìn)行的初始化可柿。
我們?cè)偎阉饕幌?strong>registerObjCNotifiers這個(gè)函數(shù)是在什么時(shí)候調(diào)用的,如圖所示:

53

發(fā)現(xiàn)是在_dyld_objc_notify_register這個(gè)函數(shù)調(diào)用的以舒,我們?cè)偎阉飨逻@個(gè)函數(shù)趾痘,沒(méi)有找到它的定義慢哈,只有調(diào)用蔓钟,這是怎么回事?
從這里找不到的時(shí)候,我們通過(guò)匯編可以看到是在libObjc.dylib中卵贱,我們打開(kāi)objc的源碼滥沫,搜索,看到如下圖的代碼:
54

在這里我們看到了_dyld_objc_notify_register這個(gè)函數(shù)的調(diào)用键俱,我們斷點(diǎn)調(diào)試一下兰绣,看下堆棧,如圖:
55

我們經(jīng)過(guò)一系列的分析编振,frame13到fame7的流程我們是知道的缀辩,而從frame0到fame6這個(gè)流程是怎么回事,我們還不得而知踪央,我們接著分析臀玄,從堆棧我們可以看到_objc_init的是由_os_object_init發(fā)起的,是在libdispatch.dylib這個(gè)動(dòng)態(tài)庫(kù)中畅蹂,我們打開(kāi)libdispatch.dylib這個(gè)庫(kù)的源碼健无,通過(guò)搜索_os_object_init,如圖:

56

這里有調(diào)用_objc_init這個(gè)函數(shù)通過(guò)搜索發(fā)現(xiàn)是引入objc庫(kù)中的_objc_init液斜,如圖:
57

通過(guò)之前的堆椑巯停可以看出libdispatch_init調(diào)用了_os_object_init叠穆,我們?cè)偎严?strong>libdispatch_init這個(gè)函數(shù),找到如下代碼:

void
libdispatch_init(void)
{
    dispatch_assert(sizeof(struct dispatch_apply_s) <=
            DISPATCH_CONTINUATION_SIZE);

    if (_dispatch_getenv_bool("LIBDISPATCH_STRICT", false)) {
        _dispatch_mode |= DISPATCH_MODE_STRICT;
    }


#if DISPATCH_DEBUG || DISPATCH_PROFILE
#if DISPATCH_USE_KEVENT_WORKQUEUE
    if (getenv("LIBDISPATCH_DISABLE_KEVENT_WQ")) {
        _dispatch_kevent_workqueue_enabled = false;
    }
#endif
#endif

#if HAVE_PTHREAD_WORKQUEUE_QOS
    dispatch_qos_t qos = _dispatch_qos_from_qos_class(qos_class_main());
    _dispatch_main_q.dq_priority = _dispatch_priority_make(qos, 0);
#if DISPATCH_DEBUG
    if (!getenv("LIBDISPATCH_DISABLE_SET_QOS")) {
        _dispatch_set_qos_class_enabled = 1;
    }
#endif
#endif

#if DISPATCH_USE_THREAD_LOCAL_STORAGE
    _dispatch_thread_key_create(&__dispatch_tsd_key, _libdispatch_tsd_cleanup);
#else
    _dispatch_thread_key_create(&dispatch_priority_key, NULL);
    _dispatch_thread_key_create(&dispatch_r2k_key, NULL);
    _dispatch_thread_key_create(&dispatch_queue_key, _dispatch_queue_cleanup);
    _dispatch_thread_key_create(&dispatch_frame_key, _dispatch_frame_cleanup);
    _dispatch_thread_key_create(&dispatch_cache_key, _dispatch_cache_cleanup);
    _dispatch_thread_key_create(&dispatch_context_key, _dispatch_context_cleanup);
    _dispatch_thread_key_create(&dispatch_pthread_root_queue_observer_hooks_key,
            NULL);
    _dispatch_thread_key_create(&dispatch_basepri_key, NULL);
#if DISPATCH_INTROSPECTION
    _dispatch_thread_key_create(&dispatch_introspection_key , NULL);
#elif DISPATCH_PERF_MON
    _dispatch_thread_key_create(&dispatch_bcounter_key, NULL);
#endif
    _dispatch_thread_key_create(&dispatch_wlh_key, _dispatch_wlh_cleanup);
    _dispatch_thread_key_create(&dispatch_voucher_key, _voucher_thread_cleanup);
    _dispatch_thread_key_create(&dispatch_deferred_items_key,
            _dispatch_deferred_items_cleanup);
#endif
    pthread_key_create(&_os_workgroup_key, _os_workgroup_tsd_cleanup);
#if DISPATCH_USE_RESOLVERS // rdar://problem/8541707
    _dispatch_main_q.do_targetq = _dispatch_get_default_queue(true);
#endif

    _dispatch_queue_set_current(&_dispatch_main_q);
    _dispatch_queue_set_bound_thread(&_dispatch_main_q);

#if DISPATCH_USE_PTHREAD_ATFORK
    (void)dispatch_assume_zero(pthread_atfork(dispatch_atfork_prepare,
            dispatch_atfork_parent, dispatch_atfork_child));
#endif
    _dispatch_hw_config_init();
    _dispatch_time_init();
    _dispatch_vtable_init();
    _os_object_init();
    _voucher_init();
    _dispatch_introspection_init();
}

這里有調(diào)用_os_object_init這個(gè)函數(shù)臼膏。
libdispatch_init又由誰(shuí)調(diào)起的硼被,我們通過(guò)堆棧分析發(fā)現(xiàn),是在libSystem.B.dylib這個(gè)庫(kù)中調(diào)用渗磅,我們打開(kāi)這個(gè)庫(kù)的源碼祷嘶,搜索發(fā)現(xiàn),如下圖代碼:

58

這里有調(diào)用libdispatch_init這個(gè)函數(shù)夺溢,這個(gè)流程是通的论巍,那么libSystem_initializer是由誰(shuí)調(diào)起的呢,我們接著分析风响。
從通過(guò)之前的堆棧分析嘉汰,下面就來(lái)到了dylddoModInitFunctions這個(gè)函數(shù),我們先貼下這個(gè)函數(shù)的代碼:

void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
    if ( fHasInitializers ) {
        const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
        const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
        const struct load_command* cmd = cmds;
        for (uint32_t i = 0; i < cmd_count; ++i) {
            if ( cmd->cmd == LC_SEGMENT_COMMAND ) {
                const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
                const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
                const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
                for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
                    const uint8_t type = sect->flags & SECTION_TYPE;
                    if ( type == S_MOD_INIT_FUNC_POINTERS ) {
                        Initializer* inits = (Initializer*)(sect->addr + fSlide);
                        const size_t count = sect->size / sizeof(uintptr_t);
                        // <rdar://problem/23929217> Ensure __mod_init_func section is within segment
                        if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) )
                            dyld::throwf("__mod_init_funcs section has malformed address range for %s\n", this->getPath());
                        for (size_t j=0; j < count; ++j) {
                            Initializer func = inits[j];
                            // <rdar://problem/8543820&9228031> verify initializers are in image
                            if ( ! this->containsAddress(stripPointer((void*)func)) ) {
                                dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
                            }
                            if ( ! dyld::gProcessInfo->libSystemInitialized ) {
                                // <rdar://problem/17973316> libSystem initializer must run first
                                const char* installPath = getInstallPath();
                                if ( (installPath == NULL) || (strcmp(installPath, libSystemPath(context)) != 0) )
                                    dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n", this->getPath());
                            }
                            if ( context.verboseInit )
                                dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
                            bool haveLibSystemHelpersBefore = (dyld::gLibSystemHelpers != NULL);
                            {
                                dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
                                func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
                            }
                            bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers != NULL);
                            if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) {
                                // now safe to use malloc() and other calls in libSystem.dylib
                                dyld::gProcessInfo->libSystemInitialized = true;
                            }
                        }
                    }
                    else if ( type == S_INIT_FUNC_OFFSETS ) {
                        const uint32_t* inits = (uint32_t*)(sect->addr + fSlide);
                        const size_t count = sect->size / sizeof(uint32_t);
                        // Ensure section is within segment
                        if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) )
                            dyld::throwf("__init_offsets section has malformed address range for %s\n", this->getPath());
                        if ( seg->initprot & VM_PROT_WRITE )
                            dyld::throwf("__init_offsets section is not in read-only segment %s\n", this->getPath());
                        for (size_t j=0; j < count; ++j) {
                            uint32_t funcOffset = inits[j];
                            // verify initializers are in image
                            if ( ! this->containsAddress((uint8_t*)this->machHeader() + funcOffset) ) {
                                dyld::throwf("initializer function offset 0x%08X not in mapped image for %s\n", funcOffset, this->getPath());
                            }
                            if ( ! dyld::gProcessInfo->libSystemInitialized ) {
                                // <rdar://problem/17973316> libSystem initializer must run first
                                const char* installPath = getInstallPath();
                                if ( (installPath == NULL) || (strcmp(installPath, libSystemPath(context)) != 0) )
                                    dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n", this->getPath());
                            }
                            Initializer func = (Initializer)((uint8_t*)this->machHeader() + funcOffset);
                            if ( context.verboseInit )
                                dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
#if __has_feature(ptrauth_calls)
                            func = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0);
#endif
                            bool haveLibSystemHelpersBefore = (dyld::gLibSystemHelpers != NULL);
                            {
                                dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
                                func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
                            }
                            bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers != NULL);
                            if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) {
                                // now safe to use malloc() and other calls in libSystem.dylib
                                dyld::gProcessInfo->libSystemInitialized = true;
                            }
                        }
                    }
                }
            }
            cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
        }
    }
}

其中有一行if ( (installPath == NULL) || (strcmp(installPath, libSystemPath(context)) != 0) )這行代碼就是所有文件加載之前状勤,必須把libSystem加載進(jìn)來(lái)鞋怀,它的優(yōu)先級(jí)最高,這行也就是libSystem.B.dylib這個(gè)庫(kù)的加載持搜,Initializer func = (Initializer)((uint8_t)this->machHeader() + funcOffset);這行代碼就是libSystem_initializer這個(gè)函數(shù)的加載密似。
我們接著再搜索下
doModInitFunctions這個(gè)函數(shù),找到它的調(diào)用葫盼,如圖:

59

是在
doInitialization*這個(gè)函數(shù)調(diào)用的残腌,我們?cè)偎严逻@個(gè)函數(shù),找到如下代碼:

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;
            context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
            
            // initialize this image
            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();
}

發(fā)現(xiàn)這行代碼調(diào)用了bool hasInitializers = this->doInitialization(context);贫导,
doInitialization函數(shù)抛猫,這行就是初始化我們鏡像文件的。
notifySingledoInitialization是怎么關(guān)聯(lián)的呢孩灯,我們接著分析闺金。
我們知道notifySingle會(huì)間接調(diào)用_dyld_objc_notify_register這個(gè)函數(shù),
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;

會(huì)初始化這些參數(shù)峰档。
doInitialization必然會(huì)走到_objc_init這個(gè)函數(shù)败匹,而_objc_init又必然走到_dyld_objc_notify_register這個(gè)函數(shù),它的一個(gè)參數(shù)map_images讥巡,那么map_images(sNotifyObjCMapped這個(gè)是dyld的叫法掀亩,其實(shí)是一回事)這個(gè)函數(shù)又是什么時(shí)候調(diào)用的,我們繼續(xù)往下分析尚卫。
我們?cè)赿yld的源碼中搜下sNotifyObjCMapped這個(gè)归榕,如圖所示:

60

是在notifyBatchPartial這個(gè)函數(shù)調(diào)起的,我們?cè)偎阉?strong>notifyBatchPartial這個(gè)函數(shù)吱涉,如圖所示:

61

是在registerObjCNotifiers這個(gè)函數(shù)發(fā)起的調(diào)用刹泄,經(jīng)過(guò)我們的再次分析sNotifyObjCInit這個(gè)函數(shù)是在notifySingle發(fā)起的調(diào)用外里,而它又是在recursiveInitialization被調(diào)用的(recursiveInitialization是遞歸調(diào)用)。
doInitialization一旦初始化特石,就會(huì)對(duì)map_images初始化盅蝗,同時(shí)load也會(huì)初始化,緊接著就會(huì)對(duì)map_images的調(diào)用姆蘸,接著對(duì)就走notifySingle墩莫,notifySingle就會(huì)對(duì)load調(diào)用

以上就是個(gè)人的一些見(jiàn)解,請(qǐng)多多指證逞敷。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末狂秦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子推捐,更是在濱河造成了極大的恐慌裂问,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牛柒,死亡現(xiàn)場(chǎng)離奇詭異堪簿,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)皮壁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門椭更,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蛾魄,你說(shuō)我怎么就攤上這事虑瀑。” “怎么了畏腕?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵缴川,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我描馅,道長(zhǎng),這世上最難降的妖魔是什么而线? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任铭污,我火速辦了婚禮,結(jié)果婚禮上膀篮,老公的妹妹穿的比我還像新娘嘹狞。我一直安慰自己,他們只是感情好誓竿,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布磅网。 她就那樣靜靜地躺著,像睡著了一般筷屡。 火紅的嫁衣襯著肌膚如雪涧偷。 梳的紋絲不亂的頭發(fā)上簸喂,一...
    開(kāi)封第一講書(shū)人閱讀 49,829評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音燎潮,去河邊找鬼喻鳄。 笑死,一個(gè)胖子當(dāng)著我的面吹牛确封,可吹牛的內(nèi)容都是我干的除呵。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼爪喘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼颜曾!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起秉剑,我...
    開(kāi)封第一講書(shū)人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤泛啸,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后秃症,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體候址,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年种柑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了岗仑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡聚请,死狀恐怖荠雕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情驶赏,我是刑警寧澤炸卑,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站煤傍,受9級(jí)特大地震影響盖文,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蚯姆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一五续、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧龄恋,春花似錦疙驾、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春扳肛,著一層夾襖步出監(jiān)牢的瞬間傻挂,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工敞峭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留踊谋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓旋讹,卻偏偏與公主長(zhǎng)得像殖蚕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子沉迹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容