【IOS開(kāi)發(fā)高級(jí)系列】dyld專題

1 dyld

1.1 dyld簡(jiǎn)介

??????? 在iOS系統(tǒng)中图谷,幾乎所有的程序都會(huì)用到動(dòng)態(tài)庫(kù)呻惕,而動(dòng)態(tài)庫(kù)在加載的時(shí)候都需要用dyld(位于/usr/lib/dyld)程序進(jìn)行鏈接贰谣。很多系統(tǒng)庫(kù)幾乎都是每個(gè)程序都要用到的胀葱,與其在每個(gè)程序運(yùn)行的時(shí)候一個(gè)一個(gè)將這些動(dòng)態(tài)庫(kù)都加載進(jìn)來(lái)誓禁,還不如先把它們打包好雅任,一次加載進(jìn)來(lái)來(lái)的快风范。

??????? 動(dòng)態(tài)庫(kù)不能直接運(yùn)行,而是需要通過(guò)系統(tǒng)的動(dòng)態(tài)鏈接加載器進(jìn)行加載到內(nèi)存后執(zhí)行沪么,動(dòng)態(tài)鏈接加載器在系統(tǒng)中以一個(gè)用戶態(tài)的可執(zhí)行文件形式存在硼婿,一般應(yīng)用程序會(huì)在Mach-O文件部分指定一個(gè)LC_LOAD_DYLINKER的加載命令,此加載命令指定了dyld的路徑禽车,通常它的默認(rèn)值是“/usr/lib/dyld”寇漫。系統(tǒng)內(nèi)核在加載Mach-O文件時(shí),會(huì)使用該路徑指定的程序作為動(dòng)態(tài)庫(kù)的加載器來(lái)加載dylib殉摔。

1.2 Dyld緩存

????? dyld加載時(shí)州胳,為了優(yōu)化程序啟動(dòng),啟用了共享緩存(shared cache)技術(shù)逸月。共享緩存會(huì)在進(jìn)程啟動(dòng)時(shí)被dyld映射到內(nèi)存中栓撞,之后,當(dāng)任何Mach-O映像加載時(shí)碗硬,dyld首先會(huì)檢查該Mach-O映像與所需的動(dòng)態(tài)庫(kù)是否在共享緩存中瓤湘,如果存在,則直接將它在共享內(nèi)存中的內(nèi)存地址映射到進(jìn)程的內(nèi)存地址空間恩尾。在程序依賴的系統(tǒng)動(dòng)態(tài)庫(kù)很多的情況下弛说,這種做法對(duì)程序啟動(dòng)性能是有明顯提升的。

????? update_dyld_shared_cache程序確保了dyld的共享緩存是最新的翰意,它會(huì)掃描 /var/db/dyld/shared_region_roots/目錄下paths路徑文件剃浇,這些paths文件包含了需要加入到共享緩存的Mach-O文件路徑列表巾兆,update_dyld_shared_cache()會(huì)挨個(gè)將這些Mach-O文件及其依賴的dylib都加共享緩存中去。

????? 共享緩存是以文件形式存放在/var/db/dyld/目錄下的虎囚,生成共享緩存的update_dyld_shared_cache程序位于是/usr/bin/目錄下角塑,該工具會(huì)為每種系統(tǒng)加構(gòu)生成一個(gè)緩存文件與對(duì)應(yīng)的內(nèi)存地址map表,如下所示:

ls -l/var/db/dyld/

total1741296

-rw-r--r--? ?1root? wheel? 333085108 Apr 22 15:02 dyld_shared_cache_i386

-rw-r--r--?? 1 root?wheel????? 65378 Apr 22 15:02dyld_shared_cache_i386.map

-rw-r--r--?? 1 root?wheel? 558259294 Apr 25 16:18dyld_shared_cache_x86_64h

-rw-r--r--?? 1 root?wheel???? 129633 Apr 25 16:18dyld_shared_cache_x86_64h.map

drwxr-xr-x? 10 root?wheel??????? 340 Apr? 7 09:19 shared_region_roots

????? 生成的共享緩存可以使用工具dyld_shared_cache_util查看它的信息淘讥,該工具位于dyld源碼中的launch-cache\dyld_shared_cache_util.cpp文件圃伶,需要自己手動(dòng)編譯。另外蒲列,也可以使用dyld提供的兩個(gè)函數(shù)dyld_shared_cache_extract_dylibs()與dyld_shared_cache_extract_dylibs_progress()來(lái)自己解開(kāi)cache文件窒朋,代碼位于dyld源碼的launch-cache\dsc_extractor.cpp文件中。

????? update_dyld_shared_cache通常它只在系統(tǒng)的安裝器安裝軟件與系統(tǒng)更新時(shí)調(diào)用蝗岖,當(dāng)然侥猩,可以手動(dòng)運(yùn)行“sudoupdate_dyld_shared_cache”來(lái)更新共享緩存。新的共享緩存會(huì)在系統(tǒng)下次啟動(dòng)后自動(dòng)更新抵赢。

????? dyld緩存在系統(tǒng)中位于“/System/Library/Caches/com.apple.dyld/”目錄下欺劳,文件名是以“dyld_shared_cache_”開(kāi)頭,再加上這個(gè)dyld緩存文件所支持的指令集铅鲤。在這個(gè)目錄下划提,有可能有多個(gè)dyld緩存文件,對(duì)應(yīng)所支持的不同指令集邢享。比如鹏往,在iPadAir 2上,該目錄下就存在兩個(gè)緩存文件:

? ? ? 因?yàn)閕Pad Air 2是64位的ARM(ARM v8)處理器骇塘,同時(shí)它也兼容32位的ARM應(yīng)用伊履,所以就要有兩個(gè)緩存文件。dyld_shared_cache_arm64對(duì)應(yīng)64位的版本款违,而dyld_shared_cache_armv7s對(duì)應(yīng)32位的版本湾碎。到目前為止,所有iOS支持的ARM指令集有以下四種:

1)armv6

2)armv7

3)armv7s

4)arm64

1.3 dyld緩存提取工具

????? 沒(méi)有了系統(tǒng)庫(kù)的原始二進(jìn)制版本是不是就沒(méi)法分析了呢奠货?當(dāng)然不是介褥,我們還可以從dyld緩存文件中將系統(tǒng)庫(kù)的原始二進(jìn)制文件給解出來(lái)。目前递惋,有兩個(gè)工具可以做到這點(diǎn)柔滔,一是dyld_decache,還有一個(gè)就是jtool萍虽。

????? 使用dyld_decache可以整體提取dyld緩存文件中的所有庫(kù)原始二進(jìn)制文件:

dyld_decache [-o folder] [-f name [-f name] ...]path/to/dyld_shared_cache_armvX

????? -o用來(lái)指定提取出來(lái)的文件所要保存的路徑睛廊,如果不指定,默認(rèn)就在當(dāng)前目錄下創(chuàng)建一個(gè)叫做“l(fā)ibrary”的目錄保存杉编。-f用來(lái)說(shuō)明要提取庫(kù)的名字超全,如果要提取的庫(kù)不止一個(gè)咆霜,那么每個(gè)庫(kù)的名字前面都要帶上-f。如果不指定默認(rèn)行為就是把緩存文件中所有的庫(kù)文件全部都提取出來(lái)嘶朱。例如蛾坯,如果想要解壓Security庫(kù),可以使用下面的命令:

dyld_decache –o./Security -f Security ./dyld_shared_cache_armv7s

????? 前面也提到了疏遏,還可以用jtool來(lái)達(dá)到提取指定庫(kù)文件的目的:

jtool –extractname path/to/dyld_shared_cache_armvx

????? -extract用來(lái)指定要提取庫(kù)的名字脉课。jtool默認(rèn)不支持提取全部緩存中庫(kù)文件的功能,只能一個(gè)一個(gè)提取财异。

????? 以上就是iOS中dyld緩存的相關(guān)使用方法倘零,大家可以在系統(tǒng)中去找找對(duì)應(yīng)的緩存文件,結(jié)合本文的分享戳寸,深入研究下呈驶。


????相關(guān)文章:《iOSCornerstone工具操作方法詳解

2 IOS程序啟動(dòng)過(guò)程

? ? ? ? 系統(tǒng)先讀取App的可執(zhí)行文件(Mach-O文件),從里面獲得dyld的路徑疫鹊,然后加載dyld袖瞻,dyld去初始化運(yùn)行環(huán)境,開(kāi)啟緩存策略订晌,加載程序相關(guān)依賴庫(kù)(其中也包含我們的可執(zhí)行文件),并對(duì)這些庫(kù)進(jìn)行鏈接蚌吸,最后調(diào)用每個(gè)依賴庫(kù)的初始化方法锈拨,在這一步,runtime被初始化羹唠。當(dāng)所有依賴庫(kù)的初始化后奕枢,輪到最后一位(程序可執(zhí)行文件)進(jìn)行初始化,在這時(shí)runtime會(huì)對(duì)項(xiàng)目中所有類(lèi)進(jìn)行類(lèi)結(jié)構(gòu)初始化佩微,然后調(diào)用所有的load方法缝彬。最后dyld返回main函數(shù)地址,main函數(shù)被調(diào)用哺眯,我們便來(lái)到了熟悉的程序入口谷浅。

2.1 Mach-O文件加載

??????? 這里先說(shuō)下Mach-O文件。

??????? Mach-O文件格式是OS X與iOS系統(tǒng)上的可執(zhí)行文件格式奶卓,像我們編譯過(guò)程產(chǎn)生的.O文件一疯,以及程序的可執(zhí)行文件,動(dòng)態(tài)庫(kù)等都是Mach-O文件夺姑。它的結(jié)構(gòu)如下:

Mach-O文件結(jié)構(gòu)

? ? ? ? mach-o文件有如下幾個(gè)部分組成:

? ? ? ?Header:保存了一些基本信息墩邀,包括了該文件運(yùn)行的平臺(tái)、文件類(lèi)型盏浙、LoadCommands的個(gè)數(shù)等等眉睹。

? ? ? ? LoadCommands:可以理解為加載命令荔茬,在加載Mach-O文件時(shí)會(huì)使用這里的數(shù)據(jù)來(lái)確定內(nèi)存的分布以及相關(guān)的加載命令。比如我們的main函數(shù)的加載地址竹海,程序所需的dyld的文件路徑慕蔚,以及相關(guān)依賴庫(kù)的文件路徑。

? ? ? ? Data: 這里包含了具體的代碼站削、數(shù)據(jù)等等坊萝。

??????? 我們可以通過(guò)Mach-O文件查看器MachOView查看一個(gè)測(cè)試項(xiàng)目(這里放上地址)編譯后的可執(zhí)行文件內(nèi)容:

Mach-O文件內(nèi)容

??????? 這里可以看到,程序需要的dyld的路徑在LC_LOAD_DYLINKER命令里许起,一般都是在/usr/lib/dyld路徑下十偶。這里的LC_MAIN指的是程序main函數(shù)加載地址,下面還有寫(xiě)LC_LOAD_DYLIB指向的都是程序依賴庫(kù)加載信息园细,如果我們程序里使用到了AFNetworking惦积,這里就會(huì)多一條名為L(zhǎng)C_LOAD_DYLIB(AFNetworking)的命令,如下圖:

??? 三方庫(kù)

??????? 這里可以看到一些我們比較常用的三方庫(kù):AFNetworking,IQKeyboard等猛频。

??????? 系統(tǒng)加載程序可執(zhí)行文件后狮崩,通過(guò)分析文件來(lái)獲得dyld所在路徑來(lái)加載dyld,然后就將后面的事情甩給dyld了鹿寻。

2.2 dyld加載

??? dyld: (the dynamiclink editor)動(dòng)態(tài)鏈接器睦柴,其源碼是開(kāi)源的。

??????? ImageLoader:用于輔助加載特定可執(zhí)行文件格式的類(lèi)毡熏,程序中對(duì)應(yīng)實(shí)例可簡(jiǎn)稱為image(如程序可執(zhí)行文件莱找,F(xiàn)ramework庫(kù)趟卸,bundle文件)殴穴。

??????? dyld接手后得做很多事情陵且,主要負(fù)責(zé)初始化程序環(huán)境,將可執(zhí)行文件以及相應(yīng)的依賴庫(kù)與插入庫(kù)加載進(jìn)內(nèi)存生成對(duì)應(yīng)的ImageLoader類(lèi)的image(鏡像文件)對(duì)象财搁,對(duì)這些image進(jìn)行鏈接蘸炸,調(diào)用各image的初始化方法等等(注:這里多數(shù)事情都是遞歸的,從底向上的方法調(diào)用)尖奔,其中runtime也是在這個(gè)過(guò)程中被初始化搭儒,這些事情大多數(shù)在dyld:_mian方法中被發(fā)生,我們可以看段簡(jiǎn)潔的代碼:

2.2.1 dyld::_main函數(shù)代碼

??????? 這里的_main函數(shù)是dyld的函數(shù)提茁,并非我們程序里的main函數(shù)仗嗦。

????1. sMainExecutable = instantiateFromLoadedImage(....)與loadInsertedDylib(...)

??????? 這一步dyld將我們可執(zhí)行文件以及插入的lib加載進(jìn)內(nèi)存,生成對(duì)應(yīng)的image。

sMainExecutable對(duì)應(yīng)著我們的可執(zhí)行文件甘凭,里面包含了我們項(xiàng)目中所有新建的類(lèi)稀拐。

InsertDylib一些插入的庫(kù),他們配置在全局的環(huán)境變量sEnv中丹弱,我們可以在項(xiàng)目中設(shè)置環(huán)境變量DYLD_PRINT_ENV為1來(lái)打印該sEnv的值德撬。

環(huán)境變量設(shè)置

??????? 運(yùn)行程序Log如下:

??? 打印出插入庫(kù)的log

??????? 可以看到插入的庫(kù)為:libBacktraceRecording.dylib和libViewDebuggerSupport.有時(shí)我們會(huì)在三方App的Mach-O文件中通過(guò)修改DYLD_INSERT_LIBRARIES的值來(lái)加入我們自己的動(dòng)態(tài)庫(kù)铲咨,從而注入代碼,hook別人的App(相關(guān)資料)蜓洪。

2.2.2 link(sMainExecutable,...)和link(image纤勒,....)

????2.?link(sMainExecutable,...)和link(image,....)

??????? 對(duì)上面生成的Image進(jìn)行鏈接隆檀。其主要做的事有對(duì)image進(jìn)行l(wèi)oad(加載), rebase(基地址復(fù)位)摇天,bind(外部符號(hào)綁定),我們可以查看源碼:

? ? link方法

??? recursiveLoadLibraries(context, preflightOnly, loaderRPaths)

??????? 遞歸加載所有依賴庫(kù)進(jìn)內(nèi)存恐仑。

??? recursiveRebase(context)

??????? 遞歸對(duì)自己以及依賴庫(kù)進(jìn)行復(fù)基位操作泉坐。在以前,程序每次加載其在內(nèi)存中的堆椛哑停基地址都是一樣的腕让,這意味著你的方法、變量等地址每次都一樣的歧斟,這使得程序很不安全纯丸,后面就出現(xiàn)ASLR(Address space layout randomization,地址空間配置隨機(jī)加載),程序每次啟動(dòng)后地址都會(huì)隨機(jī)變化静袖,這樣程序里所有的代碼地址都是錯(cuò)的觉鼻,需要重新對(duì)代碼地址進(jìn)行計(jì)算修復(fù)才能正常訪問(wèn)。

??? ?recursiveBind(context, forceLazysBound, neverUnload)

??????? 對(duì)庫(kù)中所有nolazy的符號(hào)進(jìn)行bind,一般的情況下多數(shù)符號(hào)都是lazybind的队橙,他們?cè)诘谝淮问褂玫臅r(shí)候才進(jìn)行bind坠陈。

2.2.3 initializeMainExecutable方法

????3.?initializeMainExecutable()

??????? 這一步主要是調(diào)用所有image的Initalizer方法進(jìn)行初始化。這里的Initalizers方法并非名為Initalizers的方法喘帚,而是C++靜態(tài)對(duì)象初始化構(gòu)造器畅姊,atribute((constructor))進(jìn)行修飾的方法咒钟,在LmageLoader類(lèi)中initializer函數(shù)指針?biāo)赶蛟摮跏蓟椒ǖ牡刂贰?/p>

? ? ? ? initiallizer函數(shù)指針

??????? 我們可以在程序中設(shè)置環(huán)境變量DYLD_PRINT_INITIALIZERS為1來(lái)打印出程序的各種依賴庫(kù)的initializer方法:

? ? ? ?可以打印出調(diào)用了Initalizers的image的運(yùn)行程序吹由,系統(tǒng)Log打印如下:

??? ????lnitializer調(diào)用log(由于打印的比較長(zhǎng),這樣就截取開(kāi)頭的log)可以看到每個(gè)依賴庫(kù)對(duì)應(yīng)著一個(gè)初始化方法朱嘴,名稱各有不同倾鲫。

??????? 這里最開(kāi)始調(diào)用的libSystem.dylib的initializer function比較特殊,因?yàn)閞untime初始化就在這一階段萍嬉,而這個(gè)方法其實(shí)很簡(jiǎn)單乌昔,我們可以在這里看到init.c源碼,主要方法如下:

2.2.4 libSystem_initializer方法

??????? 其中l(wèi)ibdispatch_init里調(diào)用了到了runtime初始化方法_objc_init.我們可以壤追、在程序中打個(gè)符號(hào)斷點(diǎn)來(lái)驗(yàn)證:

??? _objc_init符號(hào)斷點(diǎn)

??????? 運(yùn)行程序磕道,然后斷點(diǎn)命中,我們來(lái)看下調(diào)用棧:

2.2.5 objc_init調(diào)用棧

??????? 這里可以看到_objc_init調(diào)用的順序行冰,先libSystem_initializer調(diào)用libdispatch_init再到_objc_init初始化runtime溺蕉。

??????? runtime初始化后不會(huì)閑著伶丐,在_objc_init中注冊(cè)了幾個(gè)通知,從dyld這里接手了幾個(gè)活疯特,其中包括負(fù)責(zé)初始化相應(yīng)依賴庫(kù)里的類(lèi)結(jié)構(gòu)哗魂,調(diào)用依賴庫(kù)里所有的load方法。

? ? ? ? 就拿sMainExcuatable來(lái)說(shuō)漓雅,它的initializer方法是最后調(diào)用的录别,當(dāng)initializer方法被調(diào)用前dyld會(huì)通知runtime進(jìn)行類(lèi)結(jié)構(gòu)初始化,然后再通知調(diào)用load方法邻吞,這些目前還發(fā)生在main函數(shù)前组题,但由于lazy bind機(jī)制,依賴庫(kù)多數(shù)都是在使用時(shí)才進(jìn)行bind吃衅,所以這些依賴庫(kù)的類(lèi)結(jié)構(gòu)初始化都是發(fā)生在程序里第一次使用到該依賴庫(kù)時(shí)才進(jìn)行的往踢。

2.3 main函數(shù)被調(diào)用

??????? 當(dāng)所有的依賴庫(kù)庫(kù)的lnitializer都調(diào)用完后,dyld::main函數(shù)會(huì)返回程序的main函數(shù)地址徘层,main函數(shù)被調(diào)用峻呕,從而代碼來(lái)到了我們熟悉的程序入口。

main函數(shù)入口

? ??結(jié)語(yǔ)

??????? 這里只是簡(jiǎn)單概括了從程序啟動(dòng)->dyld加載依賴庫(kù)->runtime初始化->main的過(guò)程趣效。但這階段還有很多事情未講瘦癌,如果想深入了解還得結(jié)合源碼來(lái)學(xué)習(xí),這里我已經(jīng)將dyld和runtime源碼都放在這了跷敬,大家可直接下載讯私,也可以從opensource-apple下載。

2.4 源碼解析

2.4.1 主函數(shù)解析

??????? dyld是蘋(píng)果操作系統(tǒng)一個(gè)重要組成部分西傀,而且令人興奮的是斤寇,它是開(kāi)源的,任何人可以通過(guò)蘋(píng)果官網(wǎng)下載它的源碼來(lái)閱讀理解它的運(yùn)作方式(下載地址:Source Browser)拥褂,了解系統(tǒng)加載動(dòng)態(tài)庫(kù)的細(xì)節(jié)娘锁。

??????? 系統(tǒng)內(nèi)核在加載動(dòng)態(tài)庫(kù)前,會(huì)加載dyld饺鹃,然后調(diào)用去執(zhí)行__dyld_start()莫秆,該函數(shù)會(huì)執(zhí)行dyldbootstrap::start(),后者會(huì)執(zhí)行_main()函數(shù)悔详,dyld的加載動(dòng)態(tài)庫(kù)的代碼就是從_main()開(kāi)始執(zhí)行的镊屎。下面以dyld源碼的360.18版本為藍(lán)本進(jìn)行分析:

uintptr_t_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,

??????? int argc, const char* argv[], const char* envp[], const char* apple[],

??????? uintptr_t* startGlue)

{

??? //第一步,設(shè)置運(yùn)行環(huán)境茄螃,處理環(huán)境變量

??? uintptr_t result = 0;

??? sMainExecutableMachHeader = mainExecutableMH;

??? ......


??? CRSetCrashLogMessage("dyld: launch started");

??? ......


??? setContext(mainExecutableMH, argc, argv, envp, apple);


??? // Pickup the pointer to the exec path.

??? sExecPath = _simple_getenv(apple, "executable_path");


??? // Remove interim apple[0] transition code from dyld

??? if (!sExecPath) sExecPath = apple[0];


??? ......


??? sExecShortName = ::strrchr(sExecPath, '/');

??? if ( sExecShortName != NULL )

??????? ++sExecShortName;

??? else

??????? sExecShortName = sExecPath;

??? sProcessIsRestricted = processRestricted(mainExecutableMH, &ignoreEnvironmentVariables, &sProcessRequiresLibraryValidation);

??? if ( sProcessIsRestricted ) {

#if SUPPORT_LC_DYLD_ENVIRONMENT

??????? checkLoadCommandEnvironmentVariables();

#endif????

????????pruneEnvironmentVariables(envp, &apple);

??????? setContext(mainExecutableMH, argc, argv, envp, apple);

??? }

??? else {

??????? if ( !ignoreEnvironmentVariables )

??????????? checkEnvironmentVariables(envp);

??????? defaultUninitializedFallbackPaths(envp);

??? }

??? if ( sEnv.DYLD_PRINT_OPTS )

??????? printOptions(argv);

??? if ( sEnv.DYLD_PRINT_ENV )

??????? printEnvironmentVariables(envp);

??? getHostInfo(mainExecutableMH, mainExecutableSlide);


??? ......


??? //第二步缝驳,初始化主程序

??? try {

??????? // add dyld itself to UUID list

??????? addDyldImageToUUIDList();

??????? CRSetCrashLogMessage(sLoadingCrashMessage);

??????? // instantiate ImageLoader for main executable

??????? sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);

??????? gLinkContext.mainExecutable = sMainExecutable;

??????? gLinkContext.processIsRestricted = sProcessIsRestricted;

??????? gLinkContext.processRequiresLibraryValidation = sProcessRequiresLibraryValidation;

??????? gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);


??????? ......


??????? //第三步,加載共享緩存

??????? checkSharedRegionDisable();

??? #if DYLD_SHARED_CACHE_SUPPORT

??????? if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion )

??????????? mapSharedCache();

??? #endif


??????? // Now that shared cache is loaded, setup an versioned dylib overrides

??? #if SUPPORT_VERSIONED_PATHS

??????? checkVersionedPaths();

??? #endif


??????? //第四步,加載插入的動(dòng)態(tài)庫(kù)

??????? if??? ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {

??????????? for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)

??? ????????????loadInsertedDylib(*lib);

??????? }

??????? sInsertedDylibCount = sAllImages.size()-1;


??????? //第五步用狱,鏈接主程序

??????? gLinkContext.linkingMainExecutable = true;

??????? link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL));

??????? sMainExecutable->setNeverUnloadRecursive();

??????? if ( sMainExecutable->forceFlat() ) {

??????????? gLinkContext.bindFlat = true;

??????????? gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;

??????? }


??????? //第六步萎庭,鏈接插入的動(dòng)態(tài)庫(kù)

??????? if ( sInsertedDylibCount > 0 ) {

??????????? for(unsigned int i=0; i < sInsertedDylibCount; ++i) {

??????????????? ImageLoader* image = sAllImages[i+1];

??????????????? link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL));

??????????????? image->setNeverUnloadRecursive();

??????????? }

??????????? // only INSERTED libraries can interpose

??????????? for(unsigned int i=0; i < sInsertedDylibCount; ++i) {

??????????????? ImageLoader* image = sAllImages[i+1];

??????????????? image->registerInterposing();

??????????? }

??????? }


??????? // dyld should support interposition even without DYLD_INSERT_LIBRARIES

??????? for (int i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {

??????????? ImageLoader* image = sAllImages[i];

??????????? if ( image->inSharedCache() )

??????????????? continue;

??????????? image->registerInterposing();

??????? }


??????? // apply interposing to initial set of images

??????? for(int i=0; i < sImageRoots.size(); ++i) {

??????????? sImageRoots[i]->applyInterposing(gLinkContext);

??????? }


??????? //第七步,執(zhí)行弱符號(hào)綁定

??????? gLinkContext.linkingMainExecutable = false;

???? ???// do weak binding only after all inserted images linked

??????? sMainExecutable->weakBind(gLinkContext);


??????? //第八步齿拂,執(zhí)行初始化方法

??????? 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

??????? initializeMainExecutable();

??? #endif


??????? //第九步驳规,查找入口點(diǎn)并返回

??????? result = (uintptr_t)sMainExecutable->getThreadPC();

??????? if ( result != 0 ) {

??????????? // main executable uses LC_MAIN, needs to return to glue in libdyld.dylib

??????????? 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->getMain();

??????????? *startGlue = 0;

??????? }

??? }

??? catch(const char* message) {

??????? syncAllImages();

??????? halt(message);

??? }

??? catch(...) {

??????? dyld::log("dyld: launch failed\n");

??? }


??? CRSetCrashLogMessage(NULL);


??? return result;

}

????????整個(gè)方法的代碼比較長(zhǎng),將它按功能分成九個(gè)步驟進(jìn)行講解:

2.4.2 第一步署海,設(shè)置運(yùn)行環(huán)境吗购,處理環(huán)境變量

??????? 代碼在開(kāi)始時(shí)候,將傳入的變量mainExecutableMH賦值給了sMainExecutableMachHeader砸狞,這是一個(gè)macho_header類(lèi)型的變量捻勉,其結(jié)構(gòu)體內(nèi)容就是本章前面介紹的mach_header結(jié)構(gòu)體,表示的是當(dāng)前主程序的Mach-O頭部信息刀森,有了頭部信息踱启,加載器就可以從頭開(kāi)始,遍歷整個(gè)Mach-O文件的信息研底。

????????接著執(zhí)行了setContext()埠偿,此方法設(shè)置了全局一個(gè)鏈接上下文,包括一些回調(diào)函數(shù)榜晦、參數(shù)與標(biāo)志設(shè)置信息冠蒋,代碼片斷如下:

static void setContext(const macho_header* mainExecutableMH, int argc, const char* argv[], const char* envp[], const char* apple[])

{

??? gLinkContext.loadLibrary = &libraryLocator;

??? gLinkContext.terminationRecorder = &terminationRecorder;

??? gLinkContext.flatExportFinder = &flatFindExportedSymbol;

??? gLinkContext.coalescedExportFinder = &findCoalescedExportedSymbol;

??? gLinkContext.getCoalescedImages = &getCoalescedImages;


??? ......


??? gLinkContext.bindingOptions??????????? = ImageLoader::kBindingNone;

??? gLinkContext.argc??????????????????? = argc;

??? gLinkContext.argv??????????????????? = argv;

??? gLinkContext.envp??????????????????? = envp;

??? gLinkContext.apple??????????????????? = apple;

??? gLinkContext.progname??????????????? = (argv[0] != NULL) ? basename(argv[0]) : "";

??? gLinkContext.programVars.mh??????????? = mainExecutableMH;

??? gLinkContext.programVars.NXArgcPtr??? = &gLinkContext.argc;

??? gLinkContext.programVars.NXArgvPtr??? = &gLinkContext.argv;

??? gLinkContext.programVars.environPtr??? = &gLinkContext.envp;

??? gLinkContext.programVars.__prognamePtr=&gLinkContext.progname;

??? gLinkContext.mainExecutable??????????? = NULL;

??? gLinkContext.imageSuffix??????????? = NULL;

??? gLinkContext.dynamicInterposeArray??? = NULL;

??? gLinkContext.dynamicInterposeCount??? = 0;

??? gLinkContext.prebindUsage??????????? = ImageLoader::kUseAllPrebinding;

#if TARGET_IPHONE_SIMULATOR

??? gLinkContext.sharedRegionMode??????? = ImageLoader::kDontUseSharedRegion;

#else

??? gLinkContext.sharedRegionMode??????? = ImageLoader::kUseSharedRegion;

#endif

}

??????? 設(shè)置的回調(diào)函數(shù)都是dyld本模塊實(shí)現(xiàn)的,如loadLibrary方法就是本模塊的libraryLocator()方法乾胶,負(fù)責(zé)加載動(dòng)態(tài)庫(kù)抖剿。

????????在設(shè)置完這些信息后,執(zhí)行processRestricted()方法判斷進(jìn)程是否受限识窿。代碼如下:

static bool processRestricted(const macho_header* mainExecutableMH, bool* ignoreEnvVars, bool* processRequiresLibraryValidation)

{

#if TARGET_IPHONE_SIMULATOR

??? gLinkContext.codeSigningEnforced = true;

#else

??? // ask kernel if code signature of program makes it restricted

??? uint32_t flags;

??? if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) != -1 ) {

??????? if (flags & CS_REQUIRE_LV)

??????????? *processRequiresLibraryValidation = true;


? #if __MAC_OS_X_VERSION_MIN_REQUIRED

??????? if ( flags & CS_ENFORCEMENT ) {

??????????? gLinkContext.codeSigningEnforced = true;

??????? }

??????? if ( ((flags & CS_RESTRICT) == CS_RESTRICT) && (csr_check(CSR_ALLOW_TASK_FOR_PID) != 0) ) {

??????????? sRestrictedReason = restrictedByEntitlements;

??????????? return true;

??????? }

? #else

??????? if ((flags & CS_ENFORCEMENT) && !(flags & CS_GET_TASK_ALLOW)) {

??????????? *ignoreEnvVars = true;

??????? }

??????? gLinkContext.codeSigningEnforced = true;

? #endif

??? }

#endif


??? // all processes with set uid or set gid bit set are restricted

??? if ( issetugid() ) {

??????? sRestrictedReason = restrictedBySetGUid;

??????? return true;

??? }


??? // Respect __RESTRICT,__restrict section for root processes

??? if ( hasRestrictedSegment(mainExecutableMH) ) {

??????? // existence of __RESTRICT/__restrict section make process restricted

??????? sRestrictedReason = restrictedBySegment;

??????? return true;

??? }

??? return false;

}

??????? 進(jìn)程受限會(huì)是以下三種可能:

??? ????restrictedByEntitlements:在macOS系統(tǒng)上斩郎,在需要驗(yàn)證代碼簽名(Gatekeeper開(kāi)啟)的情況下,且csr_check(CSR_ALLOW_TASK_FOR_PID)返回為真(表示Rootless開(kāi)啟了TASK_FOR_PID標(biāo)志)時(shí)喻频,進(jìn)程才不會(huì)受限缩宜,在macOS版本10.12系統(tǒng)上,默認(rèn)Gatekeeper是開(kāi)啟的半抱,并且Rootless是關(guān)閉了CSR_ALLOW_TASK_FOR_PID標(biāo)志位的脓恕,這意味著膜宋,默認(rèn)情況下窿侈,系統(tǒng)上運(yùn)行的進(jìn)程是受限的。

??? ????restrictedBySetGUid:當(dāng)進(jìn)程的setuid與setgid位被設(shè)置時(shí)秋茫,進(jìn)程會(huì)被設(shè)置成受限史简。這樣做是出于安全的考慮,受限后的進(jìn)程無(wú)法訪問(wèn)DYLD_開(kāi)頭的環(huán)境變量,一種典型的系統(tǒng)攻擊就是針對(duì)這種情況而發(fā)生的圆兵,在macOS版本10.10系統(tǒng)上跺讯,一個(gè)由DYLD_PRINT_TO_FILE環(huán)境變量引發(fā)的系統(tǒng)本地提權(quán)漏洞,就是通過(guò)向DYLD_PRINT_TO_FILE環(huán)境變量傳入擁有SUID權(quán)限的受限文件殉农,而系統(tǒng)沒(méi)做安全檢測(cè)刀脏,而這些文件是直接有向系統(tǒng)創(chuàng)建與寫(xiě)入文件權(quán)限的。關(guān)于漏洞的具體細(xì)節(jié)可以參看:OS X 10.10 DYLD_PRINT_TO_FILE

Local Privilege Escalation Vulnerability

??? ????restrictedBySegment:段名受限超凳。當(dāng)Mach-O包含一個(gè)__RESTRICT/__restrict段時(shí)愈污,進(jìn)程會(huì)被設(shè)置成受限。


??? 在進(jìn)程受限后轮傍,執(zhí)行了以下三個(gè)方法:

??? ????checkLoadCommandEnvironmentVariables():遍歷Mach-O中所有的LC_DYLD_ENVIRONMENT加載命令暂雹,然后調(diào)用processDyldEnvironmentVariable()對(duì)不同的環(huán)境變量做相應(yīng)的處理。

??? ????pruneEnvironmentVariables():刪除進(jìn)程的LD_LIBRARY_PATH與所有以DYLD_開(kāi)頭的環(huán)境變量创夜,這樣以后創(chuàng)建的子進(jìn)程就不包含這些環(huán)境變量了杭跪。

??? ????setContext():重新設(shè)置鏈接上下文。這一步執(zhí)行的主要目的是由于環(huán)境變量發(fā)生變化了驰吓,需要更新進(jìn)程的envp與apple參數(shù)涧尿。

2.4.3 第二步,初始化主程序

??????? 這一步主要執(zhí)行了instantiateFromLoadedImage()檬贰。它的代碼如下:

static ImageLoader* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)

{

??? // try mach-o loader

??? if ( isCompatibleMachO((const uint8_t*)mh, path) ) {

??????? ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);

??????? addImage(image);

??????? return image;

??? }


??? throw "main executable not a known format";

}

??????? isCompatibleMachO()主要檢查Mach-O的頭部的cputype與cpusubtype來(lái)判斷程序與當(dāng)前的系統(tǒng)是否兼容现斋。如果兼容接下來(lái)就調(diào)用instantiateMainExecutable()實(shí)例化主程序,代碼如下:

ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)

{

??? //dyld::log("ImageLoader=%ld, ImageLoaderMachO=%ld, ImageLoaderMachOClassic=%ld, ImageLoaderMachOCompressed=%ld\n",

??? //??? sizeof(ImageLoader), sizeof(ImageLoaderMachO), sizeof(ImageLoaderMachOClassic), sizeof(ImageLoaderMachOCompressed));

??? bool compressed;

??? unsigned int segCount;

??? unsigned int libCount;

??? const linkedit_data_command* codeSigCmd;

??? const encryption_info_command* encryptCmd;

??? sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);

??? // instantiate concrete class based on content of load commands

??? 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

}

??????? sniffLoadCommands()主要獲取了加載命令中的如下信息:

??????? compressed:判斷Mach-O的Compressed還是Classic類(lèi)型偎蘸。判斷的依據(jù)是Mach-O是否包含LC_DYLD_INFO或LC_DYLD_INFO_ONLY加載命令庄蹋。這2個(gè)加載命令記錄了Mach-O的動(dòng)態(tài)庫(kù)加載信息,使用結(jié)構(gòu)體dyld_info_command表示:

struct dyld_info_command {

?? uint32_t?? cmd;??????? /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */

?? uint32_t?? cmdsize;??????? /* sizeof(struct dyld_info_command) */

?? uint32_t?? rebase_off;??? /* file offset to rebase info? */

?? uint32_t?? rebase_size;??? /* size of rebase info?? */

?? uint32_t?? bind_off;??? /* file offset to binding info?? */

?? uint32_t?? bind_size;??? /* size of binding info? */

?? uint32_t?? weak_bind_off;??? /* file offset to weak binding info?? */

?? uint32_t?? weak_bind_size;? /* size of weak binding info? */

?? uint32_t?? lazy_bind_off;??? /* file offset to lazy binding info */

?? uint32_t?? lazy_bind_size;? /* size of lazy binding infs */

?? uint32_t?? export_off;??? /* file offset to lazy binding info */

?? uint32_t?? export_size;??? /* size of lazy binding infs */

};

??????? rebase_off與大小rebase_size存儲(chǔ)了rebase(重設(shè)基址)相關(guān)信息迷雪,當(dāng)Mach-O加載到內(nèi)存中的地址不是指定的首選地址時(shí)限书,就需要對(duì)當(dāng)前的映像數(shù)據(jù)進(jìn)行rebase(重設(shè)基址)。

??????? bind_off與bind_size存儲(chǔ)了進(jìn)程的符號(hào)綁定信息章咧,當(dāng)進(jìn)程啟動(dòng)時(shí)必須綁定這些符號(hào)倦西,典型的有dyld_stub_binder,該符號(hào)被dyld用來(lái)做遲綁定加載符號(hào)赁严,一般動(dòng)態(tài)庫(kù)都包含該符號(hào)扰柠。

??????? weak_bind_off與weak_bind_size存儲(chǔ)了進(jìn)程的弱綁定符號(hào)信息。弱符號(hào)主要用于面向?qū)ρZ(yǔ)言中的符號(hào)重載疼约,典型的有c++中使用new創(chuàng)建對(duì)象卤档,默認(rèn)情況下會(huì)綁定ibstdc++.dylib,如果檢測(cè)到某個(gè)映像使用弱符號(hào)引用重載了new符號(hào)程剥,dyld則會(huì)重新綁定該符號(hào)并調(diào)用重載的版本劝枣。

??????? lazy_bind_off與lazy_bind_size存儲(chǔ)了進(jìn)程的延遲綁定符號(hào)信息。有些符號(hào)在進(jìn)程啟動(dòng)時(shí)不需要馬上解析,它們會(huì)在第一次調(diào)用時(shí)被解析舔腾,這類(lèi)符號(hào)叫延遲綁定符號(hào)(LazySymbol)溪胶。

??????? export_off與export_size存儲(chǔ)了進(jìn)程的導(dǎo)出符號(hào)綁定信息。導(dǎo)出符號(hào)可以被外部的Mach-O訪問(wèn)稳诚,通常動(dòng)態(tài)庫(kù)會(huì)導(dǎo)出一個(gè)或多個(gè)符號(hào)供外部使用哗脖,而可執(zhí)行程序由導(dǎo)出_main與_mh_execute_header符號(hào)供dyld使用。

??????? segCount:段的數(shù)量扳还。sniffLoadCommands()通過(guò)遍歷所有的LC_SEGMENT_COMMAND加載命令來(lái)獲取段的數(shù)量懒熙。

??????? libCount:需要加載的動(dòng)態(tài)庫(kù)的數(shù)量。Mach-O中包含的每一條LC_LOAD_DYLIB普办、LC_LOAD_WEAK_DYLIB工扎、LC_REEXPORT_DYLIB、LC_LOAD_UPWARD_DYLIB加載命令衔蹲,都表示需要加載一個(gè)動(dòng)態(tài)庫(kù)肢娘。

??????? codeSigCmd:通過(guò)解析LC_CODE_SIGNATURE來(lái)獲取代碼簽名的加載命令。

?????? ?encryptCmd:通過(guò)LC_ENCRYPTION_INFO與LC_ENCRYPTION_INFO_64來(lái)獲取段加密信息舆驶。

??????? 獲取compressed后橱健,根據(jù)Mach-O是否compressed來(lái)分別調(diào)用ImageLoaderMachOCompressed::instantiateMainExecutable()與ImageLoaderMachOClassic::instantiateMainExecutable()。ImageLoaderMachOCompressed::instantiateMainExecutable()代碼如下:

// create image for main executable

ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path,

????????????????????????????????????????????????????? ??????????????????unsigned int segCount, unsigned int libCount, const LinkContext& context)

{

??? ImageLoaderMachOCompressed* image = ImageLoaderMachOCompressed::instantiateStart(mh, path, segCount, libCount);


??? // set slide for PIE programs

??? image->setSlide(slide);


??? // for PIE record end of program, to know where to start loading dylibs

??? if ( slide != 0 )

??????? fgNextPIEDylibAddress = (uintptr_t)image->getEnd();


??? image->disableCoverageCheck();

??? image->instantiateFinish(context);

??? image->setMapped(context);


??? if ( context.verboseMapping ) {

??????? dyld::log("dyld: Main executable mapped %s\n", path);

??????? for(unsigned int i=0, e=image->segmentCount(); i < e; ++i) {

??????????? const char* name = image->segName(i);

?????? ?????if ( (strcmp(name, "__PAGEZERO") == 0) || (strcmp(name, "__UNIXSTACK") == 0)? )

??????????????? dyld::log("%18s at 0x%08lX->0x%08lX\n", name, image->segPreferredLoadAddress(i), image->segPreferredLoadAddress(i)+image->segSize(i));

??????????? else

?? ?????????????dyld::log("%18s at 0x%08lX->0x%08lX\n", name, image->segActualLoadAddress(i), image->segActualEndAddress(i));

??????? }

??? }


??? return image;

}

??????? ImageLoaderMachOCompressed::instantiateStart()使用主程序Mach-O信息構(gòu)造了一個(gè)ImageLoaderMachOCompressed對(duì)象沙廉。disableCoverageCheck()禁用覆蓋率檢查拘荡。instantiateFinish()調(diào)用parseLoadCmds()解析其它所有的加載命令,后者會(huì)填充完ImageLoaderMachOCompressed的一些保護(hù)成員信息撬陵,最后調(diào)用setDyldInfo()設(shè)置動(dòng)態(tài)庫(kù)鏈接信息珊皿,然后調(diào)用setSymbolTableInfo()設(shè)置符號(hào)表信息。

??????? instantiateFromLoadedImage()調(diào)用完了ImageLoaderMachO::instantiateMainExecutable()后巨税,接著調(diào)用addImage()蟋定,代碼如下:

static void addImage(ImageLoader* image)

{

??? // add to master list

??? allImagesLock();

??????? sAllImages.push_back(image);

??? allImagesUnlock();


??? // update mapped ranges

??? uintptr_t lastSegStart = 0;

??? uintptr_t lastSegEnd = 0;

??? for(unsigned int i=0, e=image->segmentCount(); i < e; ++i) {

??????? if ( image->segUnaccessible(i) )

??????????? continue;

??????? uintptr_t start = image->segActualLoadAddress(i);

??????? uintptr_t end = image->segActualEndAddress(i);

??????? if ( start == lastSegEnd ) {

??????????? // two segments are contiguous, just record combined segments

??????????? lastSegEnd = end;

??????? }

??????? else {

??????????? // non-contiguous segments, record last (if any)

??????????? if ( lastSegEnd != 0 )

??????????????? addMappedRange(image, lastSegStart, lastSegEnd);

??????????? lastSegStart = start;

??????????? lastSegEnd = end;

??????? }???????

????}

??? if ( lastSegEnd != 0 )

??????? addMappedRange(image, lastSegStart, lastSegEnd);


??? if ( sEnv.DYLD_PRINT_LIBRARIES || (sEnv.DYLD_PRINT_LIBRARIES_POST_LAUNCH && (sMainExecutable!=NULL) && sMainExecutable->isLinked()) ) {

??????? dyld::log("dyld: loaded: %s\n", image->getPath());

??? }

}

??????? 這段代碼將實(shí)例化好的主程序添加到全局主列表sAllImages中,最后調(diào)用addMappedRange()申請(qǐng)內(nèi)存草添,更新主程序映像映射的內(nèi)存區(qū)驶兜。做完這些工作,第二步初始化主程序就算完成了远寸。

2.4.4 第三步抄淑,加載共享緩存

??????? 這一步主要執(zhí)行mapSharedCache()來(lái)映射共享緩存。該函數(shù)先通過(guò)_shared_region_check_np()來(lái)檢查緩存是否已經(jīng)映射到了共享區(qū)域了驰后,如果已經(jīng)映射了肆资,就更新緩存的slide與UUID,然后返回倡怎。反之迅耘,判斷系統(tǒng)是否處于安全啟動(dòng)模式(safe-bootmode)下,如果是就刪除緩存文件并返回监署,正常啟動(dòng)的情況下颤专,接下來(lái)調(diào)用openSharedCacheFile()打開(kāi)緩存文件,該函數(shù)在sSharedCacheDir路徑下钠乏,打開(kāi)與系統(tǒng)當(dāng)前cpu架構(gòu)匹配的緩存文件栖秕,也就是/var/db/dyld/dyld_shared_cache_x86_64h,接著讀取緩存文件的前8192字節(jié)晓避,解析緩存頭dyld_cache_header的信息簇捍,將解析好的緩存信息存入mappings變量,最后調(diào)用_shared_region_map_and_slide_np()完成真正的映射工作俏拱。部分代碼片斷如下:

static void mapSharedCache()

{

??? uint64_t cacheBaseAddress = 0;

??? if ( _shared_region_check_np(&cacheBaseAddress) == 0 ) {

??????? sSharedCache = (dyld_cache_header*)cacheBaseAddress;

#if __x86_64__

??????? const char* magic = (sHaswell ? ARCH_CACHE_MAGIC_H : ARCH_CACHE_MAGIC);

#else

??????? const char* magic = ARCH_CACHE_MAGIC;

#endif

??????? if ( strcmp(sSharedCache->magic, magic) != 0 ) {

??????????? sSharedCache = NULL;

??????????? if ( gLinkContext.verboseMapping ) {

??????????????? dyld::log("dyld: existing shared cached in memory is not compatible\n");

??????????????? return;

??????????? }

??????? }

??????? const dyld_cache_header* header = sSharedCache;

??????? ......

??????? // if cache has a uuid, copy it

??????? if ( header->mappingOffset >= 0x68 ) {

??????????? memcpy(dyld::gProcessInfo->sharedCacheUUID, header->uuid, 16);

??????? }

??????? ......

??? }

??? else {

#if __i386__ || __x86_64__

??????? uint32_t??? safeBootValue = 0;

??????? size_t??????? safeBootValueSize = sizeof(safeBootValue);

??????? if ( (sysctlbyname("kern.safeboot", &safeBootValue, &safeBootValueSize, NULL, 0) == 0) && (safeBootValue != 0) ) {

??????????? struct stat dyldCacheStatInfo;

??????????? if ( my_stat(MACOSX_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME, &dyldCacheStatInfo) == 0 ) {

??????????????? struct timeval bootTimeValue;

??????????????? size_t bootTimeValueSize = sizeof(bootTimeValue);

??????????????? if ( (sysctlbyname("kern.boottime", &bootTimeValue, &bootTimeValueSize, NULL, 0) == 0) && (bootTimeValue.tv_sec != 0) ) {

??????????????????? if ( dyldCacheStatInfo.st_mtime < bootTimeValue.tv_sec ) {

??????????????????????? ::unlink(MACOSX_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME);

??????????????????????? gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion;

??????????????????????? return;

??????????????????? }

??????????????? }

??????????? }

??????? }

#endif

??????? // map in shared cache to shared region

??????? int fd = openSharedCacheFile();

??????? if ( fd != -1 ) {

??????????? uint8_t firstPages[8192];

??????????? if ( ::read(fd, firstPages, 8192) == 8192 ) {

??????????????? dyld_cache_header* header = (dyld_cache_header*)firstPages;

??????? #if __x86_64__

??????????????? const char* magic = (sHaswell ? ARCH_CACHE_MAGIC_H : ARCH_CACHE_MAGIC);

??????? #else

??????????????? const char* magic = ARCH_CACHE_MAGIC;

??????? #endif

??????????????? if ( strcmp(header->magic, magic) == 0 ) {

??????????????????? const dyld_cache_mapping_info* const fileMappingsStart = (dyld_cache_mapping_info*)&firstPages[header->mappingOffset];

??????????????????? const dyld_cache_mapping_info* const fileMappingsEnd = &fileMappingsStart[header->mappingCount];

??????????????????? shared_file_mapping_np??? mappings[header->mappingCount+1]; // add room for code-sig


??????????????????? ......


??????????????????????? if (_shared_region_map_and_slide_np(fd, mappingCount, mappings, codeSignatureMappingIndex, cacheSlide, slideInfo, slideInfoSize) == 0) {

??????????????????????????? sSharedCache = (dyld_cache_header*)mappings[0].sfm_address;

??????????????????????????? sSharedCacheSlide = cacheSlide;

??????????????????????????? dyld::gProcessInfo->sharedCacheSlide = cacheSlide;

??????????????????????????? ......

??????????????????????? }

??????????????????????? else {

#if __IPHONE_OS_VERSION_MIN_REQUIRED

??????????????????????????? throw "dyld shared cache could not be mapped";

#endif

??????????????????????????? if ( gLinkContext.verboseMapping )

??????????????????????????????? dyld::log("dyld: shared cached file could not be mapped\n");

??????????????????????? }

??????????????????? }

??????????????? }

??????????????? else {

?????????? ?????????if ( gLinkContext.verboseMapping )

??????????????????????? dyld::log("dyld: shared cached file is invalid\n");

??????????????? }

??????????? }

??????????? else {

??????????????? if ( gLinkContext.verboseMapping )

??????????????????? dyld::log("dyld: shared cached file cannot be read\n");

??????????? }

??????????? close(fd);

??????? }

??????? else {

??????????? if ( gLinkContext.verboseMapping )

??????????????? dyld::log("dyld: shared cached file cannot be opened\n");

??????? }

??? }

??? ......

}

??????? 共享緩存加載完畢后暑塑,接著進(jìn)行動(dòng)態(tài)庫(kù)的版本化重載,這主要通過(guò)函數(shù)checkVersionedPaths()完成锅必。該函數(shù)讀取DYLD_VERSIONED_LIBRARY_PATH與DYLD_VERSIONED_FRAMEWORK_PATH環(huán)境變量事格,將指定版本的庫(kù)比當(dāng)前加載的庫(kù)的版本做比較,如果當(dāng)前的庫(kù)版本更高的話搞隐,就使用新版本的庫(kù)來(lái)替換掉舊版本的驹愚。

2.4.5 第四步,加載插入的動(dòng)態(tài)庫(kù)

??????? 這一步循環(huán)遍歷DYLD_INSERT_LIBRARIES環(huán)境變量中指定的動(dòng)態(tài)庫(kù)列表劣纲,并調(diào)用loadInsertedDylib()將其加載逢捺。該函數(shù)調(diào)用load()完成加載工作。load()會(huì)調(diào)用loadPhase0()嘗試從文件加載癞季,loadPhase0()會(huì)向下調(diào)用下一層phase來(lái)查找動(dòng)態(tài)庫(kù)的路徑劫瞳,直到loadPhase6(),查找的順序?yàn)镈YLD_ROOT_PATH->LD_LIBRARY_PATH->DYLD_FRAMEWORK_PATH->原始路徑->DYLD_FALLBACK_LIBRARY_PATH绷柒,找到后調(diào)用ImageLoaderMachO::instantiateFromFile()來(lái)實(shí)例化一個(gè)ImageLoader柠新,之后調(diào)用checkandAddImage()驗(yàn)證映像并將其加入到全局映像列表中。如果loadPhase0()返回為空辉巡,表示在路徑中沒(méi)有找到動(dòng)態(tài)庫(kù)恨憎,就嘗試從共享緩存中查找,找到就調(diào)用ImageLoaderMachO::instantiateFromCache()從緩存中加載郊楣,否則就拋出沒(méi)找到映像的異常憔恳。部分代碼片斷如下:

ImageLoader* load(const char* path, const LoadContext& context)

{

??? ......

??? if ( context.useSearchPaths && ( gLinkContext.imageSuffix != NULL) ) {

??????? if ( realpath(path, realPath) != NULL )

??????????? path = realPath;

??? }


??? ImageLoader* image = loadPhase0(path, orgPath, context, NULL);

??? if ( image != NULL ) {

??????? CRSetCrashLogMessage2(NULL);

??????? return image;

??? }

??? ......

??? image = loadPhase0(path, orgPath, context, &exceptions);

#if __IPHONE_OS_VERSION_MIN_REQUIRED && DYLD_SHARED_CACHE_SUPPORT && !TARGET_IPHONE_SIMULATOR

??? // support symlinks on disk to a path in dyld shared cache

??? if ( (image == NULL) && cacheablePath(path) && !context.dontLoad ) {

??????? ......

??????? if ( (myerr == ENOENT) || (myerr == 0) )

??????? {

??????????? const macho_header* mhInCache;

??????????? const char*??????????? pathInCache;

??????????? long??????????????? slideInCache;

??????????? if ( findInSharedCacheImage(resolvedPath, false, NULL, &mhInCache, &pathInCache, &slideInCache) ) {

?????? ?????????struct stat stat_buf;

??????????????? bzero(&stat_buf, sizeof(stat_buf));

??????????????? try {

??????????????????? image = ImageLoaderMachO::instantiateFromCache(mhInCache, pathInCache, slideInCache, stat_buf, gLinkContext);

??????????????????? image = checkandAddImage(image, context);

??????????????? }

??????????????? catch (...) {

??????????????????? image = NULL;

??????????????? }

??????????? }

??????? }

??? }

#endif

??? ......

??? else {

??????? const char* msgStart = "no suitable image found.? Did find:";

??????? ......

??????? throw (const char*)fullMsg;

??? }

}

2.4.6 第五步,鏈接主程序

??????? 這一步執(zhí)行l(wèi)ink()完成主程序的鏈接操作净蚤。該函數(shù)調(diào)用了ImageLoader自身的link()函數(shù)钥组,主要的目的是將實(shí)例化的主程序的動(dòng)態(tài)數(shù)據(jù)進(jìn)行修正,達(dá)到讓進(jìn)程可用的目的今瀑,典型的就是主程序中的符號(hào)表修正操作程梦,它的代碼片斷如下:

void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths)

{

??? ......

??? this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths);

??? ......

??? context.clearAllDepths();

??? this->recursiveUpdateDepth(context.imageCount());


???? this->recursiveRebase(context);

??? ......

???? this->recursiveBind(context, forceLazysBound, neverUnload);


??? if ( !context.linkingMainExecutable )

??????? this->weakBind(context);??? //現(xiàn)在是鏈接主程序点把,這里現(xiàn)在不會(huì)執(zhí)行

??? ......

??? std::vector dofs;

??? this->recursiveGetDOFSections(context, dofs);

??? context.registerDOFs(dofs);

??? ......

??? if ( !context.linkingMainExecutable && (fgInterposingTuples.size() != 0) ) {

??????? this->recursiveApplyInterposing(context);??? //現(xiàn)在是鏈接主程序,這里現(xiàn)在不會(huì)執(zhí)行

??? }

??? ......

}

??????? recursiveLoadLibraries()采用遞歸的方式來(lái)加載程序依賴的動(dòng)態(tài)庫(kù)屿附,加載的方法是調(diào)用context的loadLibrary指針?lè)椒ɡ商樱摲椒ㄔ谇懊婵吹竭^(guò),是setContext()設(shè)置的libraryLocator()挺份,該函數(shù)只是調(diào)用了load()來(lái)完成加載褒翰,load()加載動(dòng)態(tài)庫(kù)的過(guò)程在上一步已經(jīng)分析過(guò)了。

????? ??接著調(diào)用recursiveUpdateDepth()對(duì)映像及其依賴庫(kù)按列表方式進(jìn)行排序匀泊。recursiveRebase()則對(duì)映像完成遞歸rebase操作优训,該函數(shù)只是調(diào)用了虛函數(shù)doRebase(),doRebase()被ImageLoaderMachO重載各聘,實(shí)際只是將代碼段設(shè)置成可寫(xiě)后調(diào)用了rebase()揣非,在ImageLoaderMachOCompressed中,該函數(shù)讀取映像動(dòng)態(tài)鏈接信息的rebase_off與rebase_size來(lái)確定需要rebase的數(shù)據(jù)偏移與大小躲因,然后挨個(gè)修正它們的地址信息妆兑。

??????? recursiveBind()完成遞歸綁定符號(hào)表的操作。此處的符號(hào)表針對(duì)的是非延遲加載的符號(hào)表毛仪,它的核心是調(diào)用了doBind()搁嗓,在ImageLoaderMachOCompressed中,該函數(shù)讀取映像動(dòng)態(tài)鏈接信息的bind_off與bind_size來(lái)確定需要綁定的數(shù)據(jù)偏移與大小箱靴,然后挨個(gè)對(duì)它們進(jìn)行綁定腺逛,綁定操作具體使用bindAt()函數(shù),它主要通過(guò)調(diào)用resolve()解析完符號(hào)表后衡怀,調(diào)用bindLocation()完成最終的綁定操作棍矛,需要綁定的符號(hào)信息有三種:

BIND_TYPE_POINTER:需要綁定的是一個(gè)指針。直接將計(jì)算好的新值嶼值即可抛杨。

BIND_TYPE_TEXT_ABSOLUTE32:一個(gè)32位的值够委。取計(jì)算的值的低32位賦值過(guò)去。

BIND_TYPE_TEXT_PCREL32:重定位符號(hào)怖现。需要使用新值減掉需要修正的地址值來(lái)計(jì)算出重定位值茁帽。

??????? recursiveGetDOFSections()與registerDOFs()主要注冊(cè)程序的DOF節(jié)區(qū),供dtrace使用屈嗤。

2.4.7 第六步潘拨,鏈接插入的動(dòng)態(tài)庫(kù)

??????? 鏈接插入的動(dòng)態(tài)庫(kù)與鏈接主程序一樣,都是使用的link(),插入的動(dòng)態(tài)庫(kù)列表是前面調(diào)用addImage()保存到sAllImages中的饶号,之后铁追,循環(huán)獲取每一個(gè)動(dòng)態(tài)庫(kù)的ImageLoader,調(diào)用link()對(duì)其進(jìn)行鏈接茫船,注意:sAllImages中保存的第一項(xiàng)是主程序的映像琅束。接下來(lái)調(diào)用每個(gè)映像的registerInterposing()方法來(lái)注冊(cè)動(dòng)態(tài)庫(kù)插入與調(diào)用applyInterposing()應(yīng)用插入操作扭屁。registerInterposing()查找__DATA段的__interpose節(jié)區(qū),找到需要應(yīng)用插入操作(也可以叫作符號(hào)地址替換)的數(shù)據(jù)涩禀,然后做一些檢查后料滥,將要替換的符號(hào)與被替換的符號(hào)信息存入fgInterposingTuples列表中,供以后具體符號(hào)替換時(shí)查詢埋泵。applyInterposing()調(diào)用了虛方法doInterpose()來(lái)做符號(hào)替換操作幔欧,在ImageLoaderMachOCompressed中實(shí)際是調(diào)用了eachBind()與eachLazyBind()分別對(duì)常規(guī)的符號(hào)與延遲加載的符號(hào)進(jìn)行應(yīng)用插入操作,具體使用的是interposeAt(),該方法調(diào)用interposedAddress()在fgInterposingTuples中查找要替換的符號(hào)地址香罐,找到后然后進(jìn)行最終的符號(hào)地址替換蔬将。

2.4.8 第七步,執(zhí)行弱符號(hào)綁定

??????? weakBind()函數(shù)執(zhí)行弱符號(hào)綁定嚼摩。首先通過(guò)調(diào)用context的getCoalescedImages()將sAllImages中所有含有弱符號(hào)的映像合并成一個(gè)列表,合并完后調(diào)用initializeCoalIterator()對(duì)映像進(jìn)行排序,排序完成后調(diào)用incrementCoalIterator()收集需要進(jìn)行綁定的弱符號(hào)霉撵,后者是一個(gè)虛函數(shù),在ImageLoaderMachOCompressed中洪囤,該函數(shù)讀取映像動(dòng)態(tài)鏈接信息的weak_bind_off與weak_bind_size來(lái)確定弱符號(hào)的數(shù)據(jù)偏移與大小徒坡,然后挨個(gè)計(jì)算它們的地址信息。之后調(diào)用getAddressCoalIterator()瘤缩,按照映像的加載順序在導(dǎo)出表中查找符號(hào)的地址喇完,找到后調(diào)用updateUsesCoalIterator()執(zhí)行最終的綁定操作,執(zhí)行綁定的是bindLocation()剥啤,前面有講過(guò)锦溪,此處不再贅述。

2.4.9 第八步府怯,執(zhí)行初始化方法

??????? 執(zhí)行初始化的方法是initializeMainExecutable()刻诊。該函數(shù)主要執(zhí)行runInitializers(),后者調(diào)用了ImageLoader的runInitializers()方法牺丙,最終迭代執(zhí)行了ImageLoaderMachO的doInitialization()方法则涯,后者主要調(diào)用doImageInit()與doModInitFunctions()執(zhí)行映像與模塊中設(shè)置為init的函數(shù)與靜態(tài)初始化方法,代碼如下:

bool ImageLoaderMachO::doInitialization(const LinkContext& context)

{

??? CRSetCrashLogMessage2(this->getPath());

??? doImageInit(context);

??? doModInitFunctions(context);

??? CRSetCrashLogMessage2(NULL);


??? return (fHasDashInit || fHasInitializers);

}

2.4.10 第九步冲簿,查找入口點(diǎn)并返回

??????? 這一步調(diào)用主程序映像的getThreadPC()函數(shù)來(lái)查找主程序的LC_MAIN加載命令獲取程序的入口點(diǎn)是整,沒(méi)找到就調(diào)用getMain()到LC_UNIXTHREAD加載命令中去找,找 到后就跳到入口點(diǎn)指定的地址并返回了民假。

??????? 到這里浮入,dyld整個(gè)加載動(dòng)態(tài)庫(kù)的過(guò)程就算完成了。

??????? 另外再討論下延遲符號(hào)加載的技術(shù)細(xì)節(jié)羊异。在所有擁有延遲加載符號(hào)的Mach-O文件里事秀,它的符號(hào)表中一定有一個(gè)dyld_stub_helper符號(hào)彤断,它是延遲符號(hào)加載的關(guān)鍵!延遲綁定符號(hào)的修正工作就是由它完成的易迹。綁定符號(hào)信息可以使用XCode提供的命令行工具dyldinfo來(lái)查看宰衙,執(zhí)行以下命令可以查看python的綁定信息:

xcrun dyldinfo -bind /usr/bin/python

for arch i386:

bind information:

segment section????????? address??????? type??? addend dylib??????????? symbol

__DATA? __cfstring?????? 0x000040F0??? pointer????? 0 CoreFoundation?? ___CFConstantStringClassReference

__DATA? __cfstring?????? 0x00004100??? pointer????? 0 CoreFoundation?? ___CFConstantStringClassReference

__DATA? __nl_symbol_ptr? 0x00004010??? pointer????? 0 CoreFoundation?? _kCFAllocatorNull

__DATA? __nl_symbol_ptr? 0x00004008??? pointer????? 0 libSystem??????? ___stack_chk_guard

__DATA? __nl_symbol_ptr? 0x0000400C??? pointer????? 0 libSystem??????? _environ

__DATA? __nl_symbol_ptr? 0x00004000??? pointer????? 0 libSystem??????? dyld_stub_binder

bind information:

segment section????????? address??????? type??? addend dylib??????????? symbol

__DATA? __cfstring?????? 0x1000031D8??? pointer????? 0 CoreFoundation?? ___CFConstantStringClassReference

__DATA? __cfstring?????? 0x1000031F8??? pointer????? 0 CoreFoundation?? ___CFConstantStringClassReference

__DATA? __got??????????? 0x100003010??? pointer????? 0 CoreFoundation?? _kCFAllocatorNull

__DATA? __got??????????? 0x100003000??? pointer????? 0 libSystem??????? ___stack_chk_guard

__DATA? __got??????????? 0x100003008??? pointer?????0 libSystem??????? _environ

__DATA? __nl_symbol_ptr? 0x100003018??? pointer????? 0 libSystem??????? dyld_stub_binder

??????? 所有的延遲綁定符號(hào)都存儲(chǔ)在_TEXT段的stubs節(jié)區(qū)(樁節(jié)區(qū)),編譯器在生成代碼時(shí)創(chuàng)建的符號(hào)調(diào)用就生成在此節(jié)區(qū)中睹欲,該節(jié)區(qū)被稱為“樁”節(jié)區(qū)供炼,樁只是一小段臨時(shí)使用的指令,在stubs中只是一條jmp跳轉(zhuǎn)指令窘疮,跳轉(zhuǎn)的地址位于__DATA段__la_symbol_ptr節(jié)區(qū)中袋哼,指向的是一段代碼,類(lèi)似于如下的語(yǔ)句:

push xxx

jmp yyy

??????? 其中xxx是符號(hào)在動(dòng)態(tài)鏈接信息中延遲綁定符號(hào)數(shù)據(jù)的偏移值闸衫,yyy則是跳轉(zhuǎn)到_TEXT段的stub_helper節(jié)區(qū)頭部涛贯,此處的代碼通常為:

lea??????? r11, qword [ds:zzz]

push?????? r11

jmp??????? qword [ds:imp___nl_symbol_ptr_dyld_stub_binder]

??????? jmp跳轉(zhuǎn)的地址是__DATA段中__nl_symbol_ptr節(jié)區(qū),指向的是符號(hào)dyld_stub_binder()蔚出,該函數(shù)由dyld導(dǎo)出弟翘,實(shí)現(xiàn)位于dyld源碼的“dyld_stub_binder.s”文件中,它調(diào)用dyld::fastBindLazySymbol()來(lái)綁定延遲加載的符號(hào)骄酗,后者是一個(gè)虛函數(shù)稀余,實(shí)際調(diào)用ImageLoaderMachOCompressed的doBindFastLazySymbol(),后者調(diào)用bindAt()解析并返回正確的符號(hào)地址趋翻,dyld_stub_binder()在最后跳轉(zhuǎn)到符號(hào)地址去執(zhí)行睛琳。這一步完成后,__DATA段__la_symbol_ptr節(jié)區(qū)中存儲(chǔ)的符號(hào)地址就是修正后的地址嘿歌,下一次調(diào)用該符號(hào)時(shí)掸掏,就直接跳轉(zhuǎn)到真正的符號(hào)地址去執(zhí)行,而不用dyld_stub_binder()來(lái)重新解析該符號(hào)了宙帝。

3 load函數(shù)

3.1 為什么要分析Load函數(shù)

??????? 通過(guò)一個(gè)簡(jiǎn)單的圖丧凤,可以看到Load函數(shù)的調(diào)用者都是哪些函數(shù)。

??????? 除了前面提到的loadInsertedDylib函數(shù)之外接觸的比較多的就是dlopen了步脓,所以愿待,可以看出load是dyld動(dòng)態(tài)加載一個(gè)Mach-O文件的重要接口。在動(dòng)態(tài)加載一個(gè)Mach-o文件的時(shí)候靴患,最終都調(diào)用了load這個(gè)API仍侥。

3.2 Load函數(shù)分析

????? 函數(shù)的實(shí)現(xiàn)為一系列的loadPhase*函數(shù),主要可以分為這幾個(gè)部分:

????1鸳君、處理環(huán)境變量农渊,生成各種搜索路徑。

????2或颊、如果該lib已經(jīng)加載過(guò)砸紊,則利用share_cache中已經(jīng)存在的imageloader實(shí)例传于。

????3、如果該lib沒(méi)有加載過(guò)醉顽,通過(guò)讀取文件沼溜,將mach-o文件映射到內(nèi)存中,生成imageloader的實(shí)例游添。

3.2.1 load

??????? 通過(guò)一幅圖可以簡(jiǎn)單的理解load函數(shù)的流程系草。load函數(shù)主要做的這幾件事情:

????1、處理suffix字段唆涝。

??? 2找都、通過(guò)loadPhase0函數(shù)從share_cache中加載image。

??? 3石抡、如果share_cache中不存在image檐嚣,則再使用不同的參數(shù)調(diào)用loadPhase0函數(shù)助泽,通過(guò)open函數(shù)讀取文件并加載image到內(nèi)存中啰扛。

??? 4、函數(shù)調(diào)用結(jié)束后的內(nèi)存管理嗡贺。

//根據(jù)所有的環(huán)境變量生成路徑隐解,去加載一個(gè)ImageLoader

ImageLoader*load(const char* path, const LoadContext& context)

{

??????????? CRSetCrashLogMessage2(path);

??????????? const char* orgPath = path;


??????????? //dyld::log("%s(%s)\n",

__func__ , path);

??????????? charrealPath[PATH_MAX];

??????????? // when DYLD_IMAGE_SUFFIX is in used,

do a realpath(), otherwise a load of "Foo.framework/Foo" will not

match

??????????? //當(dāng)設(shè)置了DYLD_IMAGE_SUFFIX字段,需要使用realpath來(lái)加載

??????????? if( context.useSearchPaths &&( gLinkContext.imageSuffix != NULL) ) {

??????????????????????? if( realpath(path, realPath) != NULL )

??????????????????????????????????? path =realPath;

??????????? }


??????????? // try all path permutations and

check against existing loaded images

??????????? //嘗試各種路徑組合去加載image

??????????? ImageLoader* image =loadPhase0(path, orgPath, context, NULL);

??????????? if( image != NULL ) {

??????????????????????? CRSetCrashLogMessage2(NULL);

??????????????????????? returnimage;

??????????? }


??????????? // try all path permutations and try

open() until first success

??????????? std::vector exceptions;

??????????? image = loadPhase0(path, orgPath,context, &exceptions);


? ????????? /*...*/


??? CRSetCrashLogMessage2(NULL);

??????????? if( image != NULL ) {

??????????????????????? /*加載成功內(nèi)存處理*/

??????????????????????? returnimage;

??????????? }

??????????? else if ( exceptions.size() == 0) {

??????????????????????? /*出錯(cuò)處理*/

??????????? }

??????????? else{

??????????????????????? /*出錯(cuò)處理*/

??????????? }

}

3.2.2 loadPhase0函數(shù)

??? 函數(shù)邏輯比較簡(jiǎn)單:

??? 1诫睬、遍歷DYLD_ROOT_PATH環(huán)境變量煞茫,生成加載路徑,調(diào)用loadPhase1摄凡。

??? 2续徽、如果不存在DYLD_ROOT_PATH環(huán)境變量,則使用原始的路徑_

// try root substitutions

//主要處理DYLD_ROOT_PATH環(huán)境變量的功能亲澡,修飾Loadimage時(shí)候的path

//運(yùn)行完結(jié)之后執(zhí)行l(wèi)oadPhase1

static ImageLoader* loadPhase0(const char* path, const char* orgPath, const LoadContext& context, std::vector* exceptions)

{

????? //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions);


????? // handle DYLD_ROOT_PATH which forces absolute paths to use a new root

????? if ( (gLinkContext.rootPaths != NULL) && (path[0] == '/') ) {

????????????????? for(const char* const* rootPath = gLinkContext.rootPaths ;*rootPath != NULL; ++rootPath) {

????????????????????????????? char newPath[strlen(*rootPath) + strlen(path)+2];

????????????????????????????? strcpy(newPath, *rootPath);

????????????????????????????? strcat(newPath, path);

????????????????????????????? ImageLoader*image = loadPhase1(newloadPhase1Path, orgPath, context, exceptions);

????????????????????????????? if( image != NULL )

????????????????????????????????????????? returnimage;

????????????????? }

????? }


????? // try raw path

????? return loadPhase1(path, orgPath, context, exceptions);

}

3.2.3 loadPhase1

??????? 主要處理內(nèi)容:

??? 1钦扭、通過(guò)LD_LIBRARY_PATH參數(shù)組成的所有路徑,通過(guò)loadPhase2嘗試加載image床绪。

??? 2客情、當(dāng)無(wú)法通過(guò)LD_LIBRARY_PATH獲取image時(shí),則通過(guò)DYLD_FRAMEWORK_PATH與DYLD_LIBRARY_PATH組成的路徑癞己,通過(guò)loadPhase2嘗試加載image膀斋。

??? 3、如果上面兩個(gè)流程都無(wú)法加載到image則通過(guò)原始路徑通過(guò)loadPhase3嘗試加載image痹雅。

??? 4仰担、如果依然無(wú)法加載到image則通過(guò)DYLD_FALLBACK_FRAMEWORK_PATH環(huán)境變量,組成路徑最后嘗試加載image绩社。

??????? 這里要注意一下摔蓝,因?yàn)椴煌姆种褂玫腜hase函數(shù)有可能是不同的技掏。同_時(shí)該函數(shù)也確定了環(huán)境變量在動(dòng)態(tài)加載時(shí)的優(yōu)先級(jí)。

static ImageLoader* loadPhase1(const char* path, const char* orgPath, const LoadContext& context, std::vector* exceptions)

{

? ? ? ? //dyld::log("%s(%s, %p)\n", __func__ , path, exceptions);

? ? ? ? ImageLoader* image = NULL;


? ? ? ? // handle LD_LIBRARY_PATH environment variables that force searching

? ? ? ? //如果存在LD_LIBRARY_PATH變量项鬼,優(yōu)先通過(guò)LD_LIBRARY_PATH中設(shè)置的路徑進(jìn)行搜索和加載

? ? ? ? if( context.useLdLibraryPath&& (sEnv.LD_LIBRARY_PATH != NULL) ) {

? ? ? ? ? ? image = loadPhase2(path, orgPath, context, NULL, sEnv.LD_LIBRARY_PATH, exceptions);

? ? ? ? ? ? if( image != NULL )

? ? ? ? ? ? ????return image;

? ? ? ? }


? ? ? ? // handle DYLD_ environment variables that force searching

? ? ? ? //如果使用了DYLD_FRAMEWORK_PATH或者sEnv.DYLD_LIBRARY_PATH哑梳,則使用這兩個(gè)環(huán)境變量去加載

? ? ? ? if(context.useSearchPaths &&((sEnv.DYLD_FRAMEWORK_PATH != NULL) || (sEnv.DYLD_LIBRARY_PATH != NULL)) ) {

????????????image = loadPhase2(path, orgPath, context, sEnv.DYLD_FRAMEWORK_PATH, sEnv.DYLD_LIBRARY_PATH, exceptions);

? ? ? ? ? ? if( image != NULL )

? ? ? ? ? ? ????return image;

? ? ? ? }


? ? ? ? // try raw path

? ? ? ? //如果上面的環(huán)境變量都沒(méi)有設(shè)置,就使用原始地址去加載

? ? ? ? //函數(shù)是loadphase3

? ? ? ? image = loadPhase3(path, orgPath, context, exceptions);

? ? ? ? if( image != NULL )

? ? ? ? ????return image;


? ? ? ? // try fallback paths during second time (will open file)

? ? ? ? const char* const* fallbackLibraryPaths =sEnv.DYLD_FALLBACK_LIBRARY_PATH;

? ? ? ? if( (fallbackLibraryPaths != NULL)&& !context.useFallbackPaths )

? ? ? ? ????fallbackLibraryPaths =NULL;

? ? ? ? if( !context.dontLoad? && (exceptions != NULL) &&((sEnv.DYLD_FALLBACK_FRAMEWORK_PATH != NULL) || (fallbackLibraryPaths != NULL))) {

? ? ? ? ????image= loadPhase2(path, orgPath, context, sEnv.DYLD_FALLBACK_FRAMEWORK_PATH, fallbackLibraryPaths, exceptions);

? ? ? ? ? ? if( image != NULL )

? ? ? ? ? ? ????return image;

? ? ? ? }


? ? ? ? return NULL;

}

3.2.4 loadPhase2, loadPhase3, loadPhase4

??????? 這兩個(gè)函數(shù)純粹的實(shí)現(xiàn)了路徑修改的邏輯绘盟,通過(guò)不同的方式去生成最終的加載路徑鸠真,邏輯與loadPhase0基本類(lèi)似,有興趣可以自行查看代碼龄毡。

LoadPhase2

LoadPhase3

LoadPhase4

3.2.5 loadPhase5

??????? loadPhase5根據(jù)參數(shù)exceptions的不同形成了兩個(gè)不同的分支吠卷。

??? 1、loadPhase5load:通過(guò)讀取文件沦零,加載文件到內(nèi)存中祭隔,實(shí)例化ImageLoader。

??? 2路操、loadPhase5check:通過(guò)遍歷已經(jīng)加載的ImageLoader的容器疾渴,獲取已經(jīng)加載的ImageLoader。

// open or check existing

//檢測(cè)是否有覆蓋的屯仗,修正path搞坝,最后調(diào)用loadPhase5Load或者check

static ImageLoader* loadPhase5(const char* path, const char* orgPath, const LoadContext& context, std::vector* exceptions)

{

????//dyld::log("%s(%s, %p)\n", __func__ , path, exceptions);


? ? // check for specific dylib overrides

? ? for (std::vector::iterator it = sDylibOverrides.begin(); it != sDylibOverrides.end(); ++it) {

? ? ????if ( strcmp(it->installName, path) == 0) {

? ? ? ? ????path= it->override;

? ? ? ? ? ? break;

? ? ? ? }

? ? }


? ? if( exceptions != NULL )

? ? ????return loadPhase5load(path, orgPath, context, exceptions);

? ? else

? ? ????return loadPhase5check(path, orgPath, context);

}

??????? loadPhase5check的邏輯非常簡(jiǎn)單,就是遍歷容器魁袜,取出相同名字的imageLoader對(duì)象桩撮。有興趣的可以自己查看loadPhase5check

3.2.6 loadPhase5load

??????? 這里是真正的加載邏輯:

??? 1峰弹、防止Image改名店量,在Image的容器里面遍歷,檢查是否已經(jīng)加載

??? 2鞠呈、在SharedCache尋找是否存在Image的緩存融师,如果存在的使用ImageLoaderMachO::instantiateFromCache來(lái)實(shí)例化ImageLoader。

??? 3粟按、如果上面兩個(gè)都沒(méi)有找到的話诬滩,就通過(guò)loadPhase5open打開(kāi)文件,并讀取到內(nèi)存灭将。

static ImageLoader* loadPhase5load(const char* path, const char* orgPath, const LoadContext& context, std::vector* exceptions)

{

????//dyld::log("%s(%s, %p)\n", __func__ , path, exceptions);

? ? ImageLoader* image = NULL;


? ? // just return NULL if file not found, but record any other errors

? ? structstat stat_buf;

? ? if ( my_stat(path, &stat_buf) == -1) {

? ? ????int err = errno;

? ? ? ? if( err != ENOENT ) {

? ? ? ? ????exceptions->push_back(dyld::mkstringf("%s:stat() failed with errno=%d", path, err));

? ? ? ? }

? ? ? ? return NULL;

????}


? ? // in case image was renamed or found via symlinks, check for inlode match

? ? image = findLoadedImage(stat_buf);

? ? if( image != NULL )

? ? ????return image;


? ? // do nothing if not already loaded and if RTLD_NOLOAD or NSADDIMAGE_OPTION_RETURN_ONLY_IF_LOADED

? ? //RTLD_NOLOAD或者NSADDIMAGE_OPTION_RETURN_ONLY_IF_LOADED字段設(shè)置了則不進(jìn)行加載

? ? if( context.dontLoad )

? ? ????return NULL;


#if DYLD_SHARED_CACHE_SUPPORT

? ? // see if this image is in shared cache

? ? const macho_header* mhInCache;

? ? const char* pathInCache;

? ? long slideInCache;

? ? //如果在sharedCacheImage中找到了疼鸟,則通過(guò)cache來(lái)加載

? ? if( findInSharedCacheImage(path, false, &stat_buf, &mhInCache, &pathInCache, &slideInCache) ) ????{

? ? ????image =ImageLoaderMachO::instantiateFromCache(mhInCache, pathInCache, slideInCache, stat_buf, gLinkContext);

? ? ????return checkandAddImage(image, context);

????}

#endif

? ? // file exists and is not in dyld shared cache, so open it

? ? // shared_cache中不存在image,則通過(guò)LoadPhase5open來(lái)加載image

? ? return loadPhase5open(path, context, stat_buf, exceptions);

}

3.2.7 loadPhase5open與loadPhase6

??????? loadPhase5open只是一個(gè)簡(jiǎn)單的封裝庙曙。

//根據(jù)路徑打開(kāi)文件

//調(diào)用loadPhase6

static ImageLoader* loadPhase5open(const char* path, const LoadContext& context, const struct stat& stat_buf, std::vector* exceptions)

{

????//dyld::log("%s(%s, %p)\n", __func__ , path, exceptions);


????// open file (automagically closed when this function exits)????

? ? FileOpener file(path);


? ? // just return NULL if file not found, but record any other errors

? ? if ( file.getFileDescriptor() == -1) {

? ? ????int err = errno;

? ? ? ? if( err != ENOENT ) {

? ? ? ? ????const char* newMsg = dyld::mkstringf("%s:open() failed with errno=%d", path, err);

? ? ? ? ? ? exceptions->push_back(newMsg);

? ? ? ? }

? ? ? ? ????return NULL;

? ? }


? ? try{

? ? ????return loadPhase6(file.getFileDescriptor(), stat_buf, path, context);

? ? }

? ? catch (const char* msg) {

? ? ????const char* newMsg = dyld::mkstringf("%s: %s",path, msg);

? ? ? ? exceptions->push_back(newMsg);

? ? ? ? free((void*)msg);

? ? ? ? return NULL;

????}

}


??????? 做了錯(cuò)誤提示之后空镜,調(diào)用了loadPhase6。

static ImageLoader* loadPhase6(int fd, const structstat& stat_buf, const char* path, const LoadContext& context)

{

????//dyld::log("%s(%s)\n", __func__ , path);

? ? uint64_t fileOffset = 0;

? ? uint64_t fileLength = stat_buf.st_size;


? ? // validate it is a file (not directory)

? ? if((stat_buf.st_mode & S_IFMT) !=S_IFREG )

? ? ????throw"not a file";


? ? ? ? uint8_t firstPage[4096];

? ? ? ? bool shortPage = false;


? ? ? ? // min mach-o file is 4K

????????if ( fileLength < 4096) {

? ? ? ? ????if ( pread(fd, firstPage, fileLength, 0) != (ssize_t)fileLength )

? ? ? ? ? ? ????throwf("preadof short file failed: %d", errno);

? ? ? ? ? ? shortPage = true;

? ? ? ? }

? ? ? ? else{

????????????????if ( pread(fd, firstPage, 4096,0) != 4096)

? ? ? ? ? ? ? ? ????throwf("preadof first 4K failed: %d", errno);

? ? ? ? }


? ? ? ? // if fat wrapper, find usable sub-file

? ? ? ? //如果是一個(gè)fat格式的文件,找到對(duì)應(yīng)的子文件

? ? ? ? //從fat文件中找到對(duì)應(yīng)子文件的代碼吴攒。

? ? ? ? const fat_header* fileStartAsFat = (fat_header*)firstPage;

? ? ? ? if( fileStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC)) {

? ? ? ? ????if( fatFindBest(fileStartAsFat,&fileOffset, &fileLength) ) {

? ? ? ? ? ? ????if ( (fileOffset+fileLength) > (uint64_t)(stat_buf.st_size) )

? ? ? ? ? ? ? ? ????throwf("truncated fat file.? file length=%llu, but needed slice goes to %llu", stat_buf.st_size, fileOffset+fileLength);

? ? ? ? ? ? ? ? ? ? if (pread(fd, firstPage, 4096, fileOffset) != 4096)

? ? ? ? ? ? ? ? ? ? ????throwf("preadof fat file failed: %d", errno);

????????????}

? ? ? ? ? ? else{

? ? ? ? ? ? ????throw"no matching architecture in universal wrapper";

? ? ? ? ? ? }

????????}


? ? ? ? // try mach-o loader

? ? ? ? if( shortPage )

? ? ? ? ????throw"file too short";

? ? ? ? //檢測(cè)運(yùn)行平臺(tái)是否正確

? ? ? ? if( isCompatibleMachO(firstPage, path)) {

? ? ? ? ????// only MH_BUNDLE, MH_DYLIB, and some MH_EXECUTE can be dynamically loaded

? ? ? ? ? ? //只有MH_EXECUTE张抄,MH_DYLIB,MH_BUNDLE三種文件才可被動(dòng)態(tài)加載

? ? ? ? ? ? switch( ((mach_header*)firstPage)->filetype) {

? ? ? ? ? ? ????case MH_EXECUTE:

? ? ? ? ? ? ? ? case MH_DYLIB:

? ? ? ? ? ? ? ? case MH_BUNDLE:

? ? ? ? ? ? ? ? ????break;

? ? ? ? ? ? ? ? default:

? ? ? ? ? ? ? ? ????throw"mach-o, but wrong filetype";

????????????}


#if TARGET_IPHONE_SIMULATOR?

? ? #if TARGET_OS_WATCH || TARGET_OS_TV

? ? ? ? ? ? // disable error during bring up of these simulators

????#else

? ? ? ? ? ? // dyld_sim should restrict loading os x binaries

? ? ? ? ? ? if( !isSimulatorBinary(firstPage, path) ) {

? ? ? ? ? ? ????throw"mach-o, but not built for iOS simulator";

? ? ? ? ? ? }

????#endif

#endif


????????????// instantiate an image

? ? ? ? ? ? ImageLoader* image = ImageLoaderMachO::instantiateFromFile(path, fd, firstPage, fileOffset, fileLength, stat_buf, gLinkContext);


? ? ? ? ? ? // validate

? ? ? ? ? ? return checkandAddImage(image, context);

????????}


? ? ? ? // try other file formats here...

? ? ? ? // throw error about what was found

? ? ? ? switch (*(uint32_t*)firstPage) {

? ? ? ? ????case MH_MAGIC:

? ? ? ? ? ? case MH_CIGAM:

? ? ? ? ? ? case MH_MAGIC_64:

? ? ? ? ? ? case MH_CIGAM_64:

? ? ? ? ? ? ????throw"mach-o, but wrong architecture";

? ? ? ? ? ? default:

? ? ? ? ? ? ????throwf("unknown file type, first eight bytes: 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X0x%02X", firstPage[0], firstPage[1], firstPage[2], firstPage[3], firstPage[4], firstPage[5], firstPage[6],firstPage[7]);

? ? ? ? }

}

????1洼怔、做了Fat格式的檢測(cè)署惯,子類(lèi)型文件提取。

????2镣隶、檢測(cè)Mach-O類(lèi)型极谊,只有MH_EXECUTE,MH_DYLIB安岂,MH_BUNDLE三種文件才可被動(dòng)態(tài)加載轻猖。

????3、通過(guò)ImageLoaderMachO::instantiateFromFile生成ImageLoader的實(shí)例域那。

3.3 小結(jié)

????????Load的函數(shù)調(diào)用流程就是一系列的loadPhase*函數(shù)的調(diào)用咙边,在load最后都會(huì)通過(guò)ImageLoader的構(gòu)造函數(shù),實(shí)例化ImageLoader次员,接下來(lái) 就需要分析ImageLoader的幾個(gè)不同的構(gòu)造函數(shù)败许。

4 參考鏈接

iOS程序啟動(dòng)->dyld加載->runtime初始化 過(guò)程

http://www.cocoachina.com/ios/20170811/20228.html


iOS中的dyld緩存是什么?

https://blog.csdn.net/gaoyuqiang30/article/details/52536168


dyld 加載 Mach-O

https://segmentfault.com/a/1190000007769327


dyld源碼分析-動(dòng)態(tài)加載main的流程和load函數(shù)執(zhí)行的流程

https://blog.csdn.net/fishmai/article/details/51419824


dyld源碼分析-動(dòng)態(tài)加載main的流程和load函數(shù)執(zhí)行的流程

https://blog.csdn.net/fishmai/article/details/51419824


【性能優(yōu)化】今日頭條iOS客戶端啟動(dòng)速度優(yōu)化

http://www.reibang.com/p/7096478ccbe7

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末翠肘,一起剝皮案震驚了整個(gè)濱河市檐束,隨后出現(xiàn)的幾起案子辫秧,更是在濱河造成了極大的恐慌束倍,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盟戏,死亡現(xiàn)場(chǎng)離奇詭異绪妹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)柿究,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)邮旷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蝇摸,你說(shuō)我怎么就攤上這事婶肩。” “怎么了貌夕?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵律歼,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我啡专,道長(zhǎng)险毁,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮畔况,結(jié)果婚禮上鲸鹦,老公的妹妹穿的比我還像新娘。我一直安慰自己跷跪,他們只是感情好馋嗜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著吵瞻,像睡著了一般嵌戈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上听皿,一...
    開(kāi)封第一講書(shū)人閱讀 51,624評(píng)論 1 305
  • 那天熟呛,我揣著相機(jī)與錄音,去河邊找鬼尉姨。 笑死庵朝,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的又厉。 我是一名探鬼主播九府,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼覆致!你這毒婦竟也來(lái)了侄旬?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤煌妈,失蹤者是張志新(化名)和其女友劉穎儡羔,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體璧诵,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡汰蜘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了之宿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片族操。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖比被,靈堂內(nèi)的尸體忽然破棺而出色难,到底是詐尸還是另有隱情,我是刑警寧澤等缀,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布枷莉,位于F島的核電站,受9級(jí)特大地震影響项滑,放射性物質(zhì)發(fā)生泄漏依沮。R本人自食惡果不足惜涯贞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望危喉。 院中可真熱鬧宋渔,春花似錦、人聲如沸辜限。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)薄嫡。三九已至氧急,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間毫深,已是汗流浹背吩坝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哑蔫,地道東北人钉寝。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像闸迷,于是被迫代替她去往敵國(guó)和親嵌纲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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