App啟動(dòng)優(yōu)化 - 理論部分

本文分為理論【1-4】和實(shí)踐【5-6】?jī)刹糠郑?/p>

  1. main()函數(shù)之前發(fā)生了什么
  2. Mach-O格式
  3. 虛擬內(nèi)存基礎(chǔ)知識(shí)
  4. 如何加載和準(zhǔn)備Mach-O二進(jìn)制文件
  5. 如何測(cè)量啟動(dòng)時(shí)間
  6. 優(yōu)化啟動(dòng)時(shí)間

一乏悄、Mach-O文件

Mach-O是運(yùn)行時(shí)可執(zhí)行文件的文件類型蝴悉。

(一)Mach-O的文件類型
  • 可執(zhí)行文件:它是應(yīng)用程序中最重要的二進(jìn)制文件谎亩,也是應(yīng)用擴(kuò)展文件的主二進(jìn)制文件义黎。
  • 動(dòng)態(tài)庫【Dylib】:它是一個(gè)動(dòng)態(tài)庫几苍,在其他平臺(tái)上又稱為DSO或DLL檬贰,
  • 捆綁包【Bundle】:它是一種特殊的動(dòng)態(tài)庫攒磨,無法進(jìn)行鏈接,只能在運(yùn)行時(shí)使用dlopen()函數(shù)打開它戈鲁。Mac OS的插件會(huì)用到。

圖像【Image】:它是指可執(zhí)行文件嘹叫,動(dòng)態(tài)庫或捆綁包的任意一種類型;
框架【Framework】:它是一種帶有資源和標(biāo)頭目錄的動(dòng)態(tài)庫诈乒,存儲(chǔ)該動(dòng)態(tài)庫需要的文件罩扇。

函數(shù)定義: 
  void * dlopen( const char * pathname, int mode ); 
  函數(shù)描述: 
  dlopen函數(shù)以指定模式打開指定的動(dòng)態(tài)連接庫文件,并返回一個(gè)句柄給調(diào)用進(jìn)程怕磨。使用dlclose()來卸載打開的庫喂饥。 
  mode:分為這兩種 
  RTLD_LAZY 暫緩決定,等有需要時(shí)再解出符號(hào) 
  RTLD_NOW 立即決定肠鲫,返回前解除所有未決定的符號(hào)员帮。 
  RTLD_LOCAL 
  RTLD_GLOBAL 允許導(dǎo)出符號(hào) 
  RTLD_GROUP 
  RTLD_WORLD 

  返回值: 
  打開錯(cuò)誤返回NULL 
  成功,返回庫引用 
  編譯時(shí)候要加入 -ldl (指定dl庫) 
(二)Mach-O圖像格式
  • Mach-O圖像被分成數(shù)段导饲;
  • 所有的段名都由大寫字母組成捞高;
  • 每一段都是頁面大小的倍數(shù),而頁面大小由硬件決定渣锦,arm64處理器的頁面大小是16KB硝岗,其他都是4KB;

下例中TEXT段大小是3頁袋毙,DATALINKEDIT段大小都是1頁型檀。

image_0.png

最常見的段名是TEXTDATALINKEDIT听盖。實(shí)際上幾乎每一個(gè)二進(jìn)制文件都包含這三段胀溺,你可以添加自定義段,但一般不會(huì)給它賦值皆看。

TEXT仓坞,DATALINKEDIT段的作用:

  • TEXT:它是文件的開頭,包含了Mach的頭文件悬蔽,任何機(jī)器指令以及任何只讀常量扯躺,比如C字符串。
  • DATA:它是重寫段蝎困,它包含了所有的全局變量录语。
  • LINKEDIT:它不包含全局變量的函數(shù),它包含變量函數(shù)信息禾乘,比如名稱和地址
  1. 分區(qū)
  • 分區(qū)是段的子范圍澎埠;
  • 分區(qū)不用遵循頁面的大小始藕;
  • 分區(qū)的名稱都用小寫字母表示蒲稳;
image_1.jpg
(三)Mach-O通用文件

假設(shè)我們生成一個(gè)64位的iOS應(yīng)用氮趋,現(xiàn)在我們有一個(gè)Mach-O文件。當(dāng)我們也想讓它在32位的設(shè)備上運(yùn)行江耀,Xcode中會(huì)發(fā)生什么變化呢剩胁?
當(dāng)我們重新生成時(shí),Xcode會(huì)生成另一個(gè)單獨(dú)的Mach-O文件祥国,這個(gè)是為32位生成的armv7昵观。然后這兩個(gè)文件合并成第三個(gè)文件,這個(gè)文件叫作Mach-O通用文件舌稀。它前端有一個(gè)頭文件啊犬,所有的頭文件都有一個(gè)所有體系結(jié)構(gòu)的列表,它們的偏移值也在文件里壁查。該頭文件也是一個(gè)頁面的大小觉至。

image_2.png

通過上面我們知道,Mach-O圖像的每段都是頁面大小的倍數(shù)睡腿,而且頭文件也需要一個(gè)頁面的大小语御,這樣會(huì)浪費(fèi)很多空間。那為何還要這樣做呢席怪?這就涉及到虛擬內(nèi)存沃暗。

二、虛擬內(nèi)存

在軟件工程里有句格言何恶,任何問題都可以通過添加一個(gè)間接層加以解決孽锥。而虛擬內(nèi)存所解決的問題就是,所有這些進(jìn)程存在時(shí)該如何管理所有的物理內(nèi)存细层。為了解決這個(gè)問題惜辑,添加了一個(gè)小的間接層,每個(gè)進(jìn)程都是一個(gè)邏輯地址空間疫赎,映射到RAM的某個(gè)物理頁面盛撑,這種映射不一定是一對(duì)一的。邏輯地址可以不對(duì)應(yīng)任何物理RAM捧搞,也可以多個(gè)邏輯地址對(duì)應(yīng)同一個(gè)物理RAM抵卫,這樣帶來很多中可能。那能利用虛擬內(nèi)存做什么呢胎撇?
首先如果有一個(gè)邏輯地址不映射任何物理RAM介粘,當(dāng)進(jìn)程要訪問該地址時(shí)就會(huì)產(chǎn)生頁面錯(cuò)誤,內(nèi)核將停止該線程晚树,并試圖找出解決方案姻采。
下一點(diǎn)是如果有兩個(gè)進(jìn)程,對(duì)應(yīng)兩個(gè)邏輯地址爵憎,這兩個(gè)邏輯地址映射同一個(gè)物理頁面慨亲,這兩個(gè)進(jìn)程共享相同的RAM位婚瓜,進(jìn)程之間開始共享。
另一個(gè)有趣的功能是基于文件的映射刑棵,不用把整個(gè)文件讀入RAM巴刻,而是可以調(diào)用mmap()函數(shù)告訴虛擬內(nèi)存系統(tǒng),我想把這部分文件映射到進(jìn)程里的這段地址蛉签。這么做的原因是冈涧,不用讀取整個(gè)文件,通過設(shè)置該映射正蛙,第一次訪問這些不同的地址時(shí),如同已經(jīng)在內(nèi)存里讀過营曼,每次訪問未訪問過的地址時(shí)乒验,都會(huì)導(dǎo)致頁面錯(cuò)誤,內(nèi)核會(huì)讀該錯(cuò)誤頁面蒂阱。這樣將會(huì)造成讀取文件遲緩锻全。
現(xiàn)在我們結(jié)合前面講的關(guān)于Mach-O的內(nèi)容,可以知道任何Dylib和圖像的TEXT段都可以映射到多個(gè)進(jìn)程录煤,這將會(huì)造成讀取遲緩鳄厌,而這些頁面可以在進(jìn)程間共享。那么DATA段呢妈踊?
DATA用來讀寫了嚎,有一個(gè)策略叫寫入時(shí)復(fù)制,這和Apple文件系統(tǒng)的克隆很相似廊营。寫入時(shí)復(fù)制所做的就是它積極地在所有進(jìn)程里共享DATA頁面歪泳。一個(gè)進(jìn)程會(huì)發(fā)生什么,只要它們只是從共享內(nèi)容的全局變量中讀取就可以了露筒。但是一旦有進(jìn)程想要寫入其他DATA頁面呐伞,就會(huì)發(fā)生寫入時(shí)復(fù)制。
寫入時(shí)復(fù)制使內(nèi)核把該頁面復(fù)制到另一個(gè)物理RAM中慎式,并將映射重定向到該頁面伶氢。所以該進(jìn)程有了該頁面的副本。這會(huì)給我們帶來臟頁面和凈頁面瘪吏,而副本被認(rèn)為是臟頁面癣防。臟頁面是指含有進(jìn)程的特定信息。凈頁面是指內(nèi)核可以按照需要重新建立的頁面掌眠,比如重新讀取磁盤劣砍。所以臟頁面比凈頁面要昂貴很多。
最后一點(diǎn)是頁面也有權(quán)限界限扇救,這指的是可以標(biāo)記一個(gè)頁面可讀刑枝、可寫或可執(zhí)行香嗓、或者它們的任意組合。

虛擬內(nèi)存的作用:
1. 虛擬內(nèi)存是間接層
2. 將每個(gè)進(jìn)程的地址映射到物理RAM(頁面粒度)

虛擬內(nèi)存的特征:
1. 頁面錯(cuò)誤
2. 相同的RAM頁面出現(xiàn)在多個(gè)進(jìn)程中
3. 文件支持的頁面
    3.1  mmap()
    3.2  懶讀取
4. 寫入時(shí)復(fù)制(COW)
5. 臟頁面與干凈頁面
6. 權(quán)限:rwx

以上就是Mach-O格式和虛擬內(nèi)存的內(nèi)容∽俺現(xiàn)在看看它們是如何一起工作的靠娱,在之前,我們先看看Dyld(全稱the dynamic link editor掠兄,即動(dòng)態(tài)鏈接器像云,其本質(zhì)Mach-O文件,專門用來加載動(dòng)態(tài)庫的庫)是如何操作的蚂夕,它在Mach-O和虛擬內(nèi)存之間是如何映射的迅诬。

三、Dyld的操作過程

現(xiàn)有一個(gè)Dylib文件婿牍,如下圖所示:

image_3.png

我們沒有把它讀到內(nèi)存中侈贷,而是把它映射到內(nèi)存,所以在內(nèi)存里該Dylib文件本應(yīng)該占用8個(gè)頁面等脂∏温可以看到,不同的是有這些“全零填充”上遥。
大部分全局變量的初始值都是零搏屑,所以靜態(tài)鏈接器進(jìn)行了優(yōu)化,把所有值為0的全局變量都移到了尾端粉楚,然后不占用任何磁盤空間辣恋。取而代之,我們利用虛擬內(nèi)存的特性模软,在該頁面第一次被訪問時(shí)展运,告訴虛擬內(nèi)存把它填滿0蛤售。所以它不需要讀取。Dyld必須要做的第一件事是在內(nèi)存中查看該進(jìn)程的Mach頭文件。它將查看內(nèi)存的頂盒友存,此時(shí)那里是空的设拟,沒有內(nèi)容映射到物理頁面上祭衩,所以產(chǎn)生頁面錯(cuò)誤棍郎。到那時(shí)內(nèi)核意識(shí)到它被映射到了一個(gè)文件,所以它將讀取文件的第一頁鲫剿,將其放入物理RAM鳄逾,設(shè)置其映射。

image_4.png

現(xiàn)在Dyld可以真正通過Mach頭文件開始讀取灵莲。它通過讀取Mach頭文件雕凹,Mach頭文件讓DyldLINKEDIT段上查看這條信息。再一次,Dyld跳下去查看進(jìn)程1的底盒枚抵。這又會(huì)產(chǎn)生頁面錯(cuò)誤线欲,內(nèi)核又讀入RAM的另一個(gè)LINKEDIT的物理頁面。

image_4.png

Dyld現(xiàn)在可以期望一個(gè)LINKEDIT汽摹。此刻在進(jìn)程中李丰,LINKEDIT將會(huì)告訴Dyld對(duì)DATA頁面做一些修正,讓Dylib可運(yùn)行逼泣。所以同樣的事情又發(fā)生了趴泌,Dyld現(xiàn)從DATA頁面讀取數(shù)據(jù),但是有一點(diǎn)不同拉庶,Dyld想要寫回一些內(nèi)容修改DATA頁面嗜憔,此刻寫入時(shí)復(fù)制出現(xiàn)了。這個(gè)頁面變成了臟頁面氏仗。所以臟RAM的8個(gè)頁面將會(huì)是什么吉捶?若我只用malloc()函數(shù)分配8頁內(nèi)存,然后讀了一些內(nèi)容進(jìn)去廓鞠,我將會(huì)有8個(gè)頁面的臟RAM。但現(xiàn)在我只有1頁的臟RAM和2個(gè)凈頁面谣旁。

image_5.png

如果第二個(gè)進(jìn)程加載同一個(gè)Dylib將會(huì)發(fā)生什么床佳?在第二個(gè)進(jìn)程里,Dyld會(huì)經(jīng)歷相同的步驟榄审,首先它查看Mach頭文件砌们,但內(nèi)核在RAM某處已經(jīng)有這頁了,所以內(nèi)核只是簡(jiǎn)單地把映射重定向搁进,重利用該頁面浪感,并沒有任何IO操作。LINKEDIT也是如此饼问,更加快速影兽。我們來看DATA頁面,此時(shí)內(nèi)核必須要看看在DATA頁面莱革,干凈的副本是否還存在RAM其他地方峻堰,如果還在,就可以重利用盅视;如果不在捐名,就必須要重新讀取。

image_5.png
image_6.png
image_7.png
image_8.png

在該進(jìn)程中闹击,Dyld會(huì)讓RAM變臟镶蹋。

image_8.png

最后一步是LINKEDIT,只在Dyld進(jìn)行操作時(shí)被需要。所以它可以提醒內(nèi)核,當(dāng)它完成時(shí)贺归,它不再需要這些LINKEDIT頁面淆两,當(dāng)別人需要RAM時(shí),可以回收它們∧恋現(xiàn)在有兩個(gè)進(jìn)程在共享這些Dylib琼腔,每個(gè)進(jìn)程都本應(yīng)該有8個(gè)頁面,也就是一共有16個(gè)臟頁面踱葛。但現(xiàn)在我們只有2個(gè)臟頁面和1個(gè)干凈的丹莲、共享頁面。

image_9.png

以上講了Dyld如何將Mach-O映射到虛擬內(nèi)存中尸诽,下面我們看看安全如何影響Dyld的甥材。

四、安全

有兩點(diǎn)安全問題會(huì)影響到Dyld

  1. ASLR地址空間布局隨機(jī)化
    這是20年前的舊技術(shù)性含,基本概念是把加載地址隨機(jī)化洲赵。
  2. 代碼簽名
    在Xcode中,代碼簽名是指對(duì)整個(gè)文件運(yùn)行一個(gè)加密哈希算法商蕴,然后在文件上簽名叠萍。為了在運(yùn)行時(shí)進(jìn)行驗(yàn)證,整個(gè)文件都必須要重新讀取绪商。所以在編譯階段苛谷,我們讓Mach-O文件的每一個(gè)頁面都進(jìn)行自己的加密哈希算法,所有哈希都存儲(chǔ)在LINKEDIT里格郁。這使得你的每個(gè)未被修改的頁面在被讀取的過程中都能得到及時(shí)驗(yàn)證腹殿。

現(xiàn)在我們來研究從exec()main()

五、exec()

exec()是一個(gè)系統(tǒng)調(diào)用函數(shù)例书,它用新程序替換當(dāng)前進(jìn)程中的程序锣尉。當(dāng)進(jìn)你入內(nèi)核,想把這個(gè)進(jìn)程換成這個(gè)新程序時(shí):
首先內(nèi)核會(huì)抹去整個(gè)地址决采,映射到你指定的可執(zhí)行程序自沧。ASLR把它映射到一個(gè)隨機(jī)地址。

image_10.png

下一步是從該隨機(jī)地址回溯到零地址树瞭,把整個(gè)區(qū)域標(biāo)記為不可訪問暂幼,意思是指不可讀、不可寫移迫、不可執(zhí)行旺嬉。該區(qū)域在32位處理器下至少4KB大小,64位處理器下至少4GB大小厨埋。這樣可以捕捉任何空指針引用邪媳,捕捉任何指針截?cái)唷?/p>

image_11.png
 


六、關(guān)于Dylibs

Unix誕生的前幾十年,一切都很簡(jiǎn)單雨效,我只需映射一個(gè)程序迅涮,把指針引用指向它,開始運(yùn)行它即可徽龟。然后共享庫被發(fā)明出來叮姑。那么誰來加載Dylibs呢?人們很快意識(shí)到情況太過復(fù)雜据悔,不想讓內(nèi)核做這件事传透。所以人們新建了幫助程序,在我們的平臺(tái)上叫作Dyld极颓,在其他Unix平臺(tái)又叫作LD.SO朱盐。
因此當(dāng)內(nèi)核完成進(jìn)程的映射時(shí),它現(xiàn)在將另一個(gè)名為DyldMach-O文件映射到另一個(gè)隨機(jī)地址的進(jìn)程中菠隆。把PC指向Dyld兵琳,讓Dyld完成進(jìn)程的啟動(dòng)。現(xiàn)在Dyld在運(yùn)行進(jìn)程骇径,它的工作是加載所有依賴的動(dòng)態(tài)庫躯肌,讓它們完全準(zhǔn)備好開始運(yùn)行。

image_12.png

七破衔、Dyld步驟

讓我們來瀏覽這些步驟清女,底部有很多步驟和一個(gè)時(shí)間線,我們?yōu)g覽這些的時(shí)候运敢,也會(huì)瀏覽時(shí)間線校仑。

  • Map all dependent dylibs, recurse Rebase all images
  • Bind all images
  • ObjC prepare images
  • Run initializers
image_13.png
(一) 加載動(dòng)態(tài)庫

首先Dyld是否需要映射所有依賴的動(dòng)態(tài)庫忠售?什么是依賴的動(dòng)態(tài)庫传惠?
要找到它們,首先要讀取內(nèi)核中已經(jīng)映射好的主可執(zhí)行文件的頭部稻扬,在該頭文件中是一個(gè)所有依賴庫的列表卦方。因此必須將其解析出來。所以必須要找到每一個(gè)動(dòng)態(tài)庫泰佳。一旦找到每個(gè)動(dòng)態(tài)庫盼砍,必須打開并運(yùn)行每個(gè)文件的開頭,需要確保是這是一個(gè)Mach-O文件逝她,對(duì)它進(jìn)行驗(yàn)證浇坐,找到它的編碼簽名,將這個(gè)編碼簽名注冊(cè)到內(nèi)核中黔宛。
然后它可以在這個(gè)動(dòng)態(tài)庫中的每一段調(diào)用mmap()函數(shù)

image_14.png

總結(jié):

- 解析依賴的動(dòng)態(tài)庫列表近刘;
- 找到必須的`Mach-O`文件;
- 打開并讀取文件的開頭;
- 驗(yàn)證`Mach-O`文件觉渴;
- 注冊(cè)代碼簽名介劫;
- 為每一段調(diào)用`mmap()`函數(shù);
(二) 遞歸加載

假如你的應(yīng)用依賴A.dylibB.dylib兩個(gè)動(dòng)態(tài)庫案淋,而A.dylibB.dylib自身也可能依賴其他dylib座韵。所以Dyld必須為每一個(gè)dylib再做一次同樣的事,而每個(gè)dylib可能依賴于已經(jīng)加載的東西或新的東西踢京,所以Dyld必須確定它是否已經(jīng)被加載誉碴,如果沒有被加載,Dyld需要加載它漱挚。所以如此繼續(xù)這種操作翔烁,最終所有依賴的都被加載了。
通常一個(gè)系統(tǒng)里的普通進(jìn)程旨涝,都會(huì)加載1至400個(gè)動(dòng)態(tài)庫蹬屹,這個(gè)加載數(shù)量很大。還好這些動(dòng)態(tài)庫大部分都是OS庫白华,OS系統(tǒng)在構(gòu)建時(shí)慨默,會(huì)預(yù)計(jì)算和預(yù)緩存那些Dyld加載內(nèi)容所要做的工作。所以O(shè)S庫加載很快弧腥。

image_15.png

現(xiàn)在所有的動(dòng)態(tài)庫都已經(jīng)加載完成厦取,但是它們都彼此獨(dú)立,我們必須要把它們捆綁在一起管搪,這就是所謂的修復(fù)(fix-ups)虾攻。

(三) 修復(fù)(fix-ups)

關(guān)于修復(fù),有一點(diǎn)我們已經(jīng)知道更鲁,由于代碼簽名的存在我們無法修改指令霎箍。那么如果不能修改它調(diào)用的指令,動(dòng)態(tài)庫如何調(diào)用另一個(gè)動(dòng)態(tài)庫呢澡为?這又用到了間接引用的技術(shù)漂坏。
所以我們的code-gen稱為動(dòng)態(tài)PIC,即地址無關(guān)代碼媒至。這意味著代碼可以動(dòng)態(tài)地加載到該地址顶别,也就是說地址間接地被分配。這所意味的是為了讓一個(gè)調(diào)用另一個(gè)拒啰,code-gen實(shí)際上在DATA段里新建一個(gè)指針驯绎,并且該指針指向了我們想調(diào)用的位置
。代碼加載該指針谋旦,并且跳向該指針剩失。所以所有的Dyld都在修復(fù)指針和數(shù)據(jù)骗随。

image_16.png

現(xiàn)在主要有兩種修復(fù),重設(shè)基址和綁定赴叹。它們的區(qū)別是什么呢鸿染?

  • 重設(shè)基址:是指如果有一個(gè)指針指向圖像范圍內(nèi),需要做出的所有的修改乞巧。
  • 綁定:是指如果指針指向圖像范圍外涨椒,他們必須進(jìn)行不同的修復(fù)。
image_17.png

下面我們一起看看其步驟:
我們可以在任何二進(jìn)制文件上運(yùn)行dyldinfo指令绽媒,就可以看到dyld必須為該二進(jìn)制文件做的所有修復(fù)工作蚕冬。

[~]> xcrun dyldinfo -rebase -bind -lazy_bind DongDong.app/DongDong
 for arch armv7:
 rebase information (from compressed dyld info):
 segment section          address     type         value
 __DATA  __nl_symbol_ptr  0x002F800C  pointer  0x002FC9E0
 __DATA  __nl_symbol_ptr  0x002F8010  pointer  0x002FC458
 __DATA  __nl_symbol_ptr  0x002F8014  pointer  0x002FEFE8
 __DATA  __nl_symbol_ptr  0x002F8018  pointer  0x002EDB00
 __DATA  __nl_symbol_ptr  0x002F8050  pointer  0x00322A6C
 __DATA  __nl_symbol_ptr  0x002F8054  pointer  0x002FC878
 ......
 
 bind information:
 segment section          address        type    addend dylib            symbol
 __DATA  __nl_symbol_ptr  0x002F833C    pointer      0 Alamofire        _$s9Alamofire12JSONEncodingVAA17ParameterEncodingAAWP
 __DATA  __nl_symbol_ptr  0x002F8340    pointer      0 Alamofire        _$s9Alamofire12JSONEncodingVN
 __DATA  __objc_classrefs 0x0031C4C8    pointer      0 CFNetwork        _OBJC_CLASS_$_NSHTTPURLResponse
 __DATA  __objc_classrefs 0x0031C4B4    pointer      0 CFNetwork        _OBJC_CLASS_$_NSMutableURLRequest
 __DATA  __objc_classrefs 0x0031C57C    pointer      0 CFNetwork        _OBJC_CLASS_$_NSURLConnection
 __DATA  __objc_classrefs 0x0031C4B0    pointer      0 CFNetwork        _OBJC_CLASS_$_NSURLSession (weak import)
 ......
 
 lazy binding information (from lazy_bind part of dyld info):
 segment section          address    index  dylib            symbol
 __DATA  __la_symbol_ptr  0x002F8574 0x0000 libswiftFoundation _$s10Foundation10URLRequestV19_bridgeToObjectiveCSo12NSURLRequestCyF
 __DATA  __la_symbol_ptr  0x002F8578 0x004D libswiftFoundation _$s10Foundation10URLRequestV3url11cachePolicy15timeoutIntervalAcA3URLV_So017NSURLRequestCacheE0VSdtcfC
 __DATA  __la_symbol_ptr  0x002F857C 0x00BC libswiftFoundation _$s10Foundation10URLRequestVMa
 __DATA  __la_symbol_ptr  0x002F8580 0x00E3 libswiftFoundation _$s10Foundation12CharacterSetV11whitespacesACvgZ
 __DATA  __la_symbol_ptr  0x002F8584 0x011C libswiftFoundation _$s10Foundation12CharacterSetVMa
 __DATA  __la_symbol_ptr  0x002F8588 0x0145 libswiftFoundation _$s10Foundation17NSLocalizedString_9tableName6bundle5value7commentS2S_SSSgSo8NSBundleCS2StF
 ......

(四) 重設(shè)基址

在過去你可以為每一個(gè)dylib指定首選加載地址,該首選加載地址是一個(gè)靜態(tài)鏈接器是辕,和Dyld一起工作囤热。這樣,若把它加載到該首選加載地址获三,則所有本應(yīng)該在內(nèi)部編碼的指針和數(shù)據(jù)都是正確的旁蔼,那么Dyld就不用做任何修復(fù)。但是現(xiàn)在疙教,因?yàn)橛辛?code>ASLR棺聊,dylib被加載到隨機(jī)地址上。
它被滑動(dòng)到其他地址贞谓,也就是說所有那些指針和數(shù)據(jù)都還依然指向舊地址限佩。所以為了修復(fù)它們,我們需要計(jì)算滑動(dòng)值裸弦,也就是移動(dòng)距離祟同,并且將該滑動(dòng)值添加到每一個(gè)內(nèi)部指針上。
因此重設(shè)基址是指遍歷所有內(nèi)部數(shù)據(jù)指針理疙,然后為它們添加一個(gè)滑動(dòng)值晕城。所以這個(gè)概念很簡(jiǎn)單,讀沪斟、添加广辰、寫暇矫,讀主之、添加、寫李根。但是這些數(shù)據(jù)指針在哪里呢槽奕?這些指針在段中的位置都編碼在LINKEDIT段里。此時(shí)房轿,所有映射都已經(jīng)結(jié)束粤攒,當(dāng)我們開始重設(shè)基址時(shí)所森,實(shí)際上在所有DATA頁面都產(chǎn)生了頁面錯(cuò)誤。然后對(duì)頁面進(jìn)行修改時(shí)夯接,產(chǎn)生寫入時(shí)復(fù)制焕济。
由于所有的這些IO操作,重設(shè)基址有時(shí)會(huì)非常昂貴盔几。但是有一個(gè)技巧晴弃,就是按順序操作,從內(nèi)核的角度來看逊拍,它認(rèn)為數(shù)據(jù)錯(cuò)誤是按順序產(chǎn)生的上鞠。當(dāng)它如此認(rèn)為時(shí),內(nèi)核會(huì)進(jìn)行預(yù)讀芯丧,這樣I/O成本會(huì)降低很多芍阎。

image_18.png

下面我們來看另一種修復(fù)---綁定

(五) 綁定

綁定是針對(duì)那些指向動(dòng)態(tài)庫范圍外的指針而言的。這些指針通過名稱進(jìn)行綁定缨恒,實(shí)際上都是字符串谴咸。本例中,LINKEDIT段里的malloc骗露,也就是說該數(shù)據(jù)指針需要指向malloc寿冕。所以運(yùn)行時(shí),dylib需要找到實(shí)現(xiàn)該符號(hào)的位置椒袍,這需要很多的計(jì)算驼唱,遍歷查找符號(hào)表。一旦找到驹暑,就把值存儲(chǔ)到該數(shù)據(jù)指針中玫恳。所以這種方式的計(jì)算復(fù)雜度要比重設(shè)基址高很多。但是I/O很少优俘,因?yàn)橹卦O(shè)基址已經(jīng)完成大部分的I/O京办。

image_19.png
(六) 通知ObjC運(yùn)行時(shí)

ObjC有很多DATA結(jié)構(gòu),DATA結(jié)構(gòu)類也就是指向其方法的指針帆焕,以及super gloss的指針等等惭婿。幾乎所有這些都通過重設(shè)基址或綁定被修復(fù)。但在ObjC運(yùn)行時(shí)還需要一些額外的操作叶雹。首先ObjC是一門動(dòng)態(tài)語言财饥,可以把一個(gè)類用名稱實(shí)例化。即ObjC在運(yùn)行時(shí)折晦,必須要維護(hù)一張表格钥星,這張表中包含了其映射類的所有名稱。每次加載的名稱都將定義一個(gè)類满着,并將該類的名稱注冊(cè)到一個(gè)全局的表中谦炒。接下來贯莺,在C++中,你們可能聽說過關(guān)于脆弱的ivar問題宁改。
ObjC中不存在脆弱的基類問題缕探,因?yàn)槲覀冏龅钠渲幸环N修復(fù)就是,在加載時(shí)動(dòng)態(tài)地改變所有ivar的偏移值还蹲。在ObjC里撕蔼,我們可以定義改變另一個(gè)類中方法的分類。有時(shí)這些分類在一些類中秽誊,而這些類不在另一個(gè)動(dòng)態(tài)庫的圖像中鲸沮,此刻應(yīng)用那些方法修復(fù)。最后锅论,ObjC基于選擇器是唯一的讼溺,所以我們需要唯一的選擇器。

image_20.png
(七) 初始化器

現(xiàn)在我們完成了所有的DATA修復(fù)最易,現(xiàn)在我們可以進(jìn)行所有可以靜態(tài)描述的DATA修復(fù)∨鳎現(xiàn)在是進(jìn)行動(dòng)態(tài)DATA修復(fù)的時(shí)機(jī)。
在C++里藻懒,有一個(gè)初始化器剔猿,可以指定等于任何你想要的表達(dá)式。那個(gè)任意的表達(dá)式此時(shí)需要運(yùn)行嬉荆,現(xiàn)在就運(yùn)行了归敬。因此,C++編譯器生成初始化器來完成那些任意DATA的初始化鄙早。
ObjC有一種方法汪茧,叫+load方法,現(xiàn)在+load方法已經(jīng)被否決限番,不建議使用舱污。建議使用+initialize方法。若有+load方法弥虐,此時(shí)它開始運(yùn)行扩灯。
看下面的這張圖,頂端是主可執(zhí)行文件霜瘪,所有的動(dòng)態(tài)庫都依照這張圖珠插,必須要運(yùn)行初始化器。按什么順序運(yùn)行呢粥庄?我們選擇從下往上丧失,原因在于當(dāng)初始化器運(yùn)行時(shí)豺妓,可能會(huì)調(diào)用一些動(dòng)態(tài)庫惜互,你需要確保那些動(dòng)態(tài)庫已經(jīng)準(zhǔn)備好被調(diào)用布讹。所以從下開始運(yùn)行初始化器,一直向上到應(yīng)用類训堆,可以很安全地調(diào)用依賴的內(nèi)容描验。所以一旦所有初始化器完成時(shí),現(xiàn)在我們終于可以調(diào)用主Dyld程序了坑鱼。

image_21.png

八膘流、main()函數(shù)之前發(fā)生了什么

通過上面的知識(shí),我們了解了進(jìn)程是如何啟動(dòng)的鲁沥,知道了Dyld是一個(gè)幫助程序呼股。

  • 加載所有的依賴庫;
  • 修復(fù)DATA頁面的所有指針画恰;
  • 運(yùn)行所有的初始化器彭谁;
  • 跳到主函數(shù);

理論部分到此結(jié)束允扇,那么如何把這些理論應(yīng)用到實(shí)際中呢缠局?
請(qǐng)閱讀下一章的實(shí)戰(zhàn)內(nèi)容:App啟動(dòng)優(yōu)化 --- 實(shí)踐部分

九、參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末考润,一起剝皮案震驚了整個(gè)濱河市狭园,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌糊治,老刑警劉巖唱矛,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異井辜,居然都是意外死亡揖赴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門抑胎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來燥滑,“玉大人,你說我怎么就攤上這事阿逃∶。” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵恃锉,是天一觀的道長(zhǎng)搀菩。 經(jīng)常有香客問我,道長(zhǎng)破托,這世上最難降的妖魔是什么肪跋? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮土砂,結(jié)果婚禮上州既,老公的妹妹穿的比我還像新娘谜洽。我一直安慰自己,他們只是感情好吴叶,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布阐虚。 她就那樣靜靜地躺著,像睡著了一般蚌卤。 火紅的嫁衣襯著肌膚如雪实束。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天逊彭,我揣著相機(jī)與錄音咸灿,去河邊找鬼。 笑死侮叮,一個(gè)胖子當(dāng)著我的面吹牛析显,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播签赃,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼谷异,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了锦聊?” 一聲冷哼從身側(cè)響起歹嘹,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎孔庭,沒想到半個(gè)月后尺上,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡圆到,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年怎抛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芽淡。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡马绝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出挣菲,到底是詐尸還是另有隱情富稻,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布白胀,位于F島的核電站椭赋,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏或杠。R本人自食惡果不足惜哪怔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧认境,春花似錦胚委、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兄猩。三九已至茉盏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間枢冤,已是汗流浹背鸠姨。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留淹真,地道東北人讶迁。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像核蘸,于是被迫代替她去往敵國(guó)和親巍糯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

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

  • 背景 一個(gè)項(xiàng)目做的時(shí)間長(zhǎng)了客扎,啟動(dòng)流程往往容易雜亂祟峦,庫也用的越來越多,APP的啟動(dòng)時(shí)間也會(huì)慢慢變長(zhǎng)徙鱼。本次將針對(duì)iOS...
    醬油瓶2閱讀 3,503評(píng)論 0 12
  • 這是一篇 WWDC 2016 Session 406 的學(xué)習(xí)筆記宅楞,從原理到實(shí)踐講述了如何優(yōu)化 App 的啟動(dòng)時(shí)間。...
    茗涙閱讀 1,858評(píng)論 0 3
  • App 運(yùn)行理論 理論速成Mach-O 術(shù)語Mach-O 是針對(duì)不同運(yùn)行時(shí)可執(zhí)行文件的文件類型袱吆。文件類型:Exec...
    未明一二閱讀 542評(píng)論 1 3
  • 這是一篇 WWDC 2016 Session 406 的學(xué)習(xí)筆記厌衙,從原理到實(shí)踐講述了如何優(yōu)化 App 的啟動(dòng)時(shí)間。...
    MTDeveloper閱讀 747評(píng)論 0 1
  • 身處繁華大都市的我們绞绒,每天坐在空調(diào)房婶希,吃著反季節(jié)的大棚蔬菜、水果蓬衡,放眼望去盡是鋼筋饲趋、水泥復(fù)制的高樓大廈,我們?cè)诿β?..
    謙謙心語閱讀 294評(píng)論 0 0