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

我們日常開發的打包或者 SDK 的打包會生成一個ipa 或者 framework。在 framework 和 ipa 文件中其實都可以找到一個 exec 文件。這個文件就是一個 Mach-O 文件。這一次主要就是深入的去了解 Mach - O 文件在到底都用來做什么。

(一)了解 Mach - O 的結構

如果我們想對 Mach -O 文件有所了解,可以將我們打包好的 ipa 文件后綴改成 .zip,然后解壓生成 Payload 文件,在其中就可以找到 exec 文件。或者找一個動態庫的 framework 在其中也可以找到 exec 文件。

然后用 MachOView 獲取文件內容。MachOView 相關教程
文件格式大致如下。

Mach-O 1.0

1.Fat Header 文件

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

Mach-O 1.1

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

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

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

2.Executable 和 Library

打開后可以看到其架構結構大致如下

Mach-O 1.2

Mach Header 的結構如下

Mach-O 1.3

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

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

首先看下 Load Commands 的目錄結構

Mach-O 1.4

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

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

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

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

(二)Mach - O 加載過程

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

  • load dyld

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

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

  • Rebasing

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

  • Binding

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

  • Objc Setup

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

  • Initializers

這里主要對于 OC 對象回調用每個類的 +load 方法。
對于類對象的調用順序是 根據之前 dylib 加載行程了一張巨大的網,現在從子節點一直向上加載到根節點。 這樣確保 dyld 加載前依賴的 dyld 已經加載。

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

(三)改善啟動時間

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

測試啟動時間可以如下設置

Mach-O 3.1

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


Mach-O 3.2

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

  • load dyld images

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

  • rebase & bind

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

  • ObjC Setup

這一部分可優化空間。這里出現的問題,其實和 rebase & bind 中的問題類似,其實還是需要減少 Class 、Category、Selector 的數量。

  • Initializer

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

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容