App啟動之Dyld在做什麼

Ginhhor大帥發表於2019-03-17

前言

這裡主要剖析一下一個App從點選圖示,到展現首頁的整個過程。

App是如何啟動的

按順序劃分

  • 載入可執行檔案(讀取Mach-O)
  • 載入動態庫(Dylib)
  • Rebase & Bind
  • Objc
  • Initializers

--------- main() ---------

  • 執行AppDelegate的代理方法(如:didFinishLaunchingWithOptions)。
  • 根據業務註冊SDK,獲取資料庫資料等。
  • 初始化Windows,初始化ViewController。

載入可執行檔案(讀取Mach-O)

Apple作業系統使用dyld載入可執行檔案。

dyld全程為dynomic loader,作用是載入一個程式所需要的Image,在opensource-apple可以找到它的開原始碼。

載入動態庫(Dylib)

dyld讀取完Mach-O的Header和Load Commands後,就會找到可執行檔案的依賴動態庫。接著dyld會將所依賴的動態庫載入到記憶體中。這是一個遞迴的過程,依賴的動態庫可能還會依賴別的動態庫,所以dyld會遞迴每個動態庫,直至所有的依賴庫都被載入完畢。

載入後的動態庫會被快取到dyld shared cache中,提高讀取效率。

簡單的說下Mach-O,簡單的可以分為三個部分,Header,Load Commands,Segment Data。

Header中包含的是可執行檔案的CPU架構,Load Commands的數量和佔用空間。

Load Commands中包含的是Segment的Header與記憶體分佈,以及依賴動態庫的版本和Path等。

Segment Data就是Segment彙編程式碼的實現,每段Segment的記憶體佔用大小都是分頁頁數的整數倍。

iOS-dyld-2019-03-17-3

Rebase & Bind

這兩個過程合在一起說,是因為他們之間的工作是相互補充的。

Apple為了解決應用安全,用到了ASLR(Address space layout randomization 地址空間佈局隨機化)和Code Sign。

App被啟動後,會被對映到虛擬記憶體中,這樣App在這個空間中就有了一個起始地址,但這個起始地址是固定的。ASLR能使這個起始地址隨機化,這項技術可以防止攻擊者通過初始地址+偏移量的方法找到函式的記憶體地址。

Code Sign就是簽名,在進行加密的時候,會對每一個Page(這裡指的是Segment Data)都進行加密,當dyld進行載入的時候,會對每一個Page都進行獨立的驗證。

Mach-O中採用了PIC(Position Independent Code 地址無關程式碼),大當我們在呼叫函式時,會在__Data段中建立一個指向該函式的指標,通過這個指標來間接呼叫。

Mach-O中有很多符號,有些指向當前Mach-O的(我們為App編寫的程式碼),有些指向其他DyLib(依賴的動態庫)。

Rebase的作用是重新修正指向當前Mach-O指標的指向,因為上面提到的ASLR將地址隨機化,起始地址不在是固定的,重新修復後,App才能正常執行。

Bind的作用是重新修復外部指標的指向,這個過程會根據字串匹配的方式來查詢符號表,比起Rebase會略慢(這裡fishhook的實現基礎,它在dyld繫結C庫的時候進行了hook)。

iOS-dyld-2019-03-17-2

Objc

因為Objective C的動態特性,所以在Main函式執行之前,需要把類資訊註冊到一個全域性Table中。同時,Category的方法也會被註冊到對應類中,Category中的同名方法實現,會根據編譯順序,被最後一個編譯的Category實現所覆蓋。同時還會做Selector的唯一性檢測。

Initializers

這個階段是包含必要的初始化。

  • +load
  • C/C++靜態初始化物件和標記有__attribute__(constructor)的方法

這裡區分下+load方法與+Initialize方法,前者是在類載入時呼叫的,後者是在類第一次收到message之前呼叫的。

main()方法之後的事

這裡就不做展開了,都是我們親手寫的程式碼。

Dyld 3

以上我們介紹了dyld2的載入方式,在2017WWDC,Apple推出了Dyld3。

Dyld2是從程式開始時才開始執行的,而Dyld3則將Dyld2的一些過程進行了分解。

iOS-dyld-2019-03-17-1

Dyld3分為out-of-process,和in-process。

out-process會做:

  • 分析Mach-O Headers
  • 分析以來的動態庫
  • 查詢需要的Rebase和Bind的符號
  • 將上面的分析結果寫入快取。

in-process會做:

  • 讀取快取的分析結果
  • 驗證分析結果
  • 載入Mach-O檔案
  • Rebase&Bind
  • Initializers

使用了Dyld3後,App的啟動速度會進一步提高

啟動階段的優化建議

  • 減少動態庫的數量,推薦使用系統庫。
  • 減少類和方法的數量。
  • 減少初始化函式。
  • 儘量使用Swift。
    • Swift沒有初始化器。
    • Swift不允許特定型別的未對齊資料結構。
    • Swift程式碼更精簡。

想要了解Dyld的同學,可以看看這篇文章App 啟動流程以及優化 WWDC 2017

參考資料

深入理解iOS App的啟動過程

App 啟動流程以及優化 WWDC 2017

相關文章