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開始的,我們點(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

從這里可以看出一切的開始是從_dyld_start開始的塔猾,這個(gè)dyld_start在可以在dyld的源碼里找(注:這里使用的dyld的源碼的版本是dyld-832.7.3),下面我們打開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ù)糯俗,如圖所示:



5

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

第121行代碼是告訴我們的degbugServer,我的dyld開始起動(dòng)了顿仇。

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

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


6

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

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


7

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

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


8

然后通過(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)的恕刘,加載高效)

9

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

10

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

2 dyld加載共享緩存

11

點(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庫(kù)存,如下圖所示


12

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

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

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

3 dyld3的閉包模式


13

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


14

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


15

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


16

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

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


17

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

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

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

18

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

4 dyld加載主程序

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


19

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


20

這里通過(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ù)的流程气忠,如圖所示:


21

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


22

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中。


23

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

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

24

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


25

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


26

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


27

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

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


28


29
30

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

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

31

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


32

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


33

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

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

34

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

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

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

35

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


36

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


37

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

38
39

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


40
41

而這里沒(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í)候賦值的,如下圖:


42

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

43

這個(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)用的坞笙,下面打開objc的的源代碼岩饼,按信shift+command+o找到定義,再按住shift+command+j找到源文件是在objc-os.mm文件中薛夜,如圖所示:


44

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


45

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


46

這里會(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)去看下


47

這里doModInitFunctions調(diào)用這個(gè)函數(shù)民傻,這個(gè)函數(shù)的作用是什么胰默,我們來(lái)看下,


48

這里就是在加載我們的構(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ù),找到這里喧半,如圖


49

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

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


50

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末婉称,一起剝皮案震驚了整個(gè)濱河市块仆,隨后出現(xiàn)的幾起案子构蹬,更是在濱河造成了極大的恐慌,老刑警劉巖悔据,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庄敛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡科汗,警方通過(guò)查閱死者的電腦和手機(jī)藻烤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)头滔,“玉大人隐绵,你說(shuō)我怎么就攤上這事∽竞粒” “怎么了依许?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)缀蹄。 經(jīng)常有香客問(wèn)我峭跳,道長(zhǎng),這世上最難降的妖魔是什么缺前? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任蛀醉,我火速辦了婚禮,結(jié)果婚禮上衅码,老公的妹妹穿的比我還像新娘拯刁。我一直安慰自己,他們只是感情好逝段,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布垛玻。 她就那樣靜靜地躺著,像睡著了一般奶躯。 火紅的嫁衣襯著肌膚如雪帚桩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天嘹黔,我揣著相機(jī)與錄音账嚎,去河邊找鬼。 笑死儡蔓,一個(gè)胖子當(dāng)著我的面吹牛郭蕉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播喂江,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼召锈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了开呐?” 一聲冷哼從身側(cè)響起烟勋,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎筐付,沒(méi)想到半個(gè)月后卵惦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓦戚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年沮尿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片较解。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡畜疾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出印衔,到底是詐尸還是另有隱情啡捶,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布奸焙,位于F島的核電站瞎暑,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏与帆。R本人自食惡果不足惜了赌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望玄糟。 院中可真熱鬧勿她,春花似錦、人聲如沸阵翎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)郭卫。三九已至筒狠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間箱沦,已是汗流浹背辩恼。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谓形,地道東北人灶伊。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像寒跳,于是被迫代替她去往敵國(guó)和親聘萨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

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