理解 Mach-O 并提高程序啟動速度

我們?nèi)粘i_發(fā)的打包或者 SDK 的打包會生成一個ipa 或者 framework。在 framework 和 ipa 文件中其實都可以找到一個 exec 文件。這個文件就是一個 Mach-O 文件。這一次主要就是深入的去了解 Mach - O 文件在到底都用來做什么。

(一)了解 Mach - O 的結(jié)構(gòu)

如果我們想對 Mach -O 文件有所了解,可以將我們打包好的 ipa 文件后綴改成 .zip,然后解壓生成 Payload 文件,在其中就可以找到 exec 文件?;蛘哒乙粋€動態(tài)庫的 framework 在其中也可以找到 exec 文件。

然后用 MachOView 獲取文件內(nèi)容。MachOView 相關(guān)教程
文件格式大致如下。

Mach-O 1.0

1.Fat Header 文件

MachOView 中查看 Fat Header結(jié)構(gòu)大概如下圖
PS:上下兩個圖使用了不一樣的 exec 文件 因為我的 MachOView 一直閃退... 知道好的解決方案的小伙伴也煩請告知

Mach-O 1.1

Magic Number 主要是快速的獲取當前的二進制文件用于 32 位還是 64 位CPU

從中我們同時可知這個二進制文件支持的架構(gòu)個數(shù)。如果想知道 framework 是否存在隱患,不支持你需要支持機型的架構(gòu),你提前就可以這樣進行查看。

同時可知如果我們的 ipa 打包好后,下發(fā)給用戶,如圖Mach-O 1.0可知文件中包含多個所支持架構(gòu)生成的文件。也是說使用Fat Header讀取來獲取與當前 CPU 匹配的 Executable,然后在進行后續(xù)的操作。當然如果我們是制作 SDK , 此時就是生成一個 Library 。
接下來就來探究他們的結(jié)構(gòu)。

2.Executable 和 Library

打開后可以看到其架構(gòu)結(jié)構(gòu)大致如下

Mach-O 1.2

Mach Header 的結(jié)構(gòu)如下

Mach-O 1.3

其實這和上邊的 Fat Header 很相似,但是這里主要包含下文會介紹的加載過程中的信息(比如 SEGMENT 段中需要加載的 dyld 信息就是由 Mach Header 提供)

現(xiàn)在看看 Load Commands ,這里就是二進制文件加載進內(nèi)存要執(zhí)行的一些指令。
這里的指令主要在負責我們 APP 對應進程的創(chuàng)建和基本設(shè)置(分配虛擬內(nèi)存,創(chuàng)建主線程,處理代碼簽名/加密的工作),然后對動態(tài)鏈接庫(.dylib 系統(tǒng)庫和我們自己創(chuàng)建的動態(tài)庫)進行庫加載和符號解析的工作。

首先看下 Load Commands 的目錄結(jié)構(gòu)

Mach-O 1.4

從上圖可知 Load Commands 主要包含了有多個 Segment 段,每個中又包含了多個 Section 段。每一部分都是系統(tǒng)執(zhí)行指令。
其中 LC_SEGMENT 包含空指針陷阱
__TEXT 段主要包含程序代碼和只讀的常量,這個段的內(nèi)容如果是系統(tǒng)動態(tài)庫的內(nèi)容那么所有進程公用
__DATA 段主要包含全局變量和靜態(tài)變量,這個段的內(nèi)容每個進程單獨進行維護
__LINKEDIT 主要包含鏈接器使用的符號和其他的表(比如函數(shù)名稱、地址等) 這個段的內(nèi)容也是可以多進程公用的。

此外還需介紹下和 SEGMENT 并列的一些比較重要的指令。

LC_MAIN 是在所有的庫都加載完成后,有其中的指令啟動程序的主線程。我們的程序也是在這個函數(shù)之后才開始執(zhí)行 main() 函數(shù)的。

LC_CODE_SIGNATURE 我想每個 iOSer 都知道代碼簽名的機制,其實代碼簽名的校驗也是在這個指令下進行。實際上指令會把整個文件進行 hash 化處理并簽名,在運行時去驗證簽名的正確性。(想要詳細了解代碼簽名機制的小伙伴看這里)

(二)Mach - O 加載過程

我們在了解了 Mach-O 的結(jié)構(gòu)后再看加載過程應該更好理解一些。
Mach-O 的加載的過程大致如下

  • load dyld

PS:在 iOS 10 后 dyld 為 tbd,網(wǎng)上有說法 tbd 的出現(xiàn)是因為 iOS 10 后對系統(tǒng)文件進行壓縮后的文件就是現(xiàn)在的 tbd , 能起到減少包大小的作用。

dyld 加載階段主要是加載動態(tài)鏈接庫的過程,所要加載的 dyld 在上文中的 Mach Header 中有記錄,這樣就知道了文件的讀取位置,然后進行代碼簽名并注冊進內(nèi)核。但是當前加載的 dyld 可能會包含其他 dyld ,所以這是就需要遞歸的進行加載。MAC OS 和 iOS 中都有共享緩存庫的概念,一般都把 dyld 進行預先鏈接,然后將鏈接保存在一個磁盤上,這樣對于這一部分的加載速度會很快。一般應用加載的 dyld 在 100 ~ 400 個左右。

  • Rebasing

因為當前系統(tǒng)內(nèi)存空間地址布局的隨機化,所有現(xiàn)在讀取 dyld 之后加載到的地址的都是隨機化的,這就和代碼以及數(shù)據(jù)指向的舊地址有偏差, 在這個過程中主要做的就是修復這個隨機化的地址。

  • Binding

簡單的解釋,就是我們在調(diào)用 dyld 的過程中可能會插入自己的代碼,在上一步中我們修復了 dyld 的指針地址,但是在 __LINKEDIT 中對于我們自己寫的代碼是用符號(symbol)進行綁定的。這個時候就需要找到指針指向的符號以及符號的具體的實現(xiàn),然后進行 bind 的過程,這時候就去符號表中進行查找,找到后存儲到 __DATA 段中的那個指針中,保證程序運行時可以正確的 jump 到正確的指令處 。

  • Objc Setup

這個過程如下:
1.類注冊的過程,然后維護一張映射類名和類的全局表。
2.對 Category 中的定義的方法,協(xié)議等插入對應的方法,協(xié)議等列表。
3.確定類方法的唯一性。

  • Initializers

這里主要對于 OC 對象回調(diào)用每個類的 +load 方法。
對于類對象的調(diào)用順序是 根據(jù)之前 dylib 加載行程了一張巨大的網(wǎng),現(xiàn)在從子節(jié)點一直向上加載到根節(jié)點。 這樣確保 dyld 加載前依賴的 dyld 已經(jīng)加載。

上邊一些列步驟執(zhí)行結(jié)束之后會執(zhí)行我們程序中的 main() 函數(shù),然后執(zhí)行 APPDelegate中的函數(shù)。

(三)改善啟動時間

在了解了 Mach-O 文件的原理之后,那么我們能做些什么呢?其實我們已經(jīng)知道了 main() 函數(shù)調(diào)用前都做了什么,那么我們就可以優(yōu)化這一部分的執(zhí)行時間。

測試啟動時間可以如下設(shè)置

Mach-O 3.1

用我們的項目測了下啟動時間,大致如下。


Mach-O 3.2

從上圖可知項目的啟動時間,就從上邊各個階段的原理上去找尋優(yōu)化方案。

  • load dyld images

上文已說蘋果對于這部分的優(yōu)化已經(jīng)做了共享緩存庫,如果有部分內(nèi)嵌(embedded)庫,這一部分的加載時間可能會較慢,現(xiàn)有方案就是將這一部分的庫進行組合或者使用靜態(tài)鏈接庫進行解決。記得去年聽 devLink 的時候小虎哥說過一些場景下用靜態(tài)庫會出現(xiàn)問題,他們最后的解決方案可以參考這篇文章

  • rebase & bind

對于 OC 而言,這一部分主要就是減少地址隨機化的修正的過程和符號尋址的過程,實際應用中減少 Class ,Selector 和 Category 的數(shù)量。

  • ObjC Setup

這一部分可優(yōu)化空間。這里出現(xiàn)的問題,其實和 rebase & bind 中的問題類似,其實還是需要減少 Class 、Category、Selector 的數(shù)量。

  • Initializer

因為 + load 方法在這個過程中調(diào)用,所以調(diào)用 +load 的方法最好改成 +initialize

現(xiàn)在我們對 Mach - O 就有了一定的理解。此時對于 Mach-O 文件的生成過程比較感興趣,接下來的文章可能會關(guān)于編譯過程文章閱讀后的總結(jié)和理解。

本文在書寫過程中參考了國內(nèi)大牛們的優(yōu)秀文章。
參考文章如下:
楊蕭玉的文章
今日頭條技術(shù)博客
蘋果去年的WWDC
南梔傾寒的簡書
深入解析 MAC OS X & iOS 操作系統(tǒng)一書。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容