iOS-底層原理 15:dyld發展史

iOS 底層原理 文章匯總

dyld簡介

  • dyld全名 The dynamic link editor

  • 是蘋果的動態鏈接器

  • 是蘋果操作系統的一個重要組成部分;

  • 在應用被編譯打包成可執行文件之后(即Mach-O),將其交由dyld負責鏈接,加載程序

  • dyld貫穿了App啟動的過程,包含加載依賴庫、主程序,如果我們需要進行性能優化、啟動優化等,不可避免的需要和dyld打交道

  • 且dyld是開源的,我們可以在官網下載它的源碼來閱讀理解

dyld 1.0(1996-2004)

  • dyld 1包含在NeXTStep 3.3中,在此之前的NeXT使用靜態二進制數據。作用并不是很大,

  • dyld 1是在系統廣泛使用C++動態庫之前編寫的,由于C++有許多特性,例如其初始化器的工作,在靜態環境工作良好,但是在動態環境中可能會降低性能。因此大型的C++動態庫會導致dyld需要完成大量的工作,速度變慢

  • 在發布macOS 10.0Cheetah前,還增加了一個特性,即Prebinding預綁定。我們可以使用Prebinding技術為系統中的所有dylib和應用程序找到固定的地址。dyld將會加載這些地址的所有內容。如果加載成功,將會編輯所有dylib和程序的二進制數據,來獲得所有預計算。當下次需要將所有數據放入相同地址時就不需要進行額外操作了,將大大的提高速度。但是這也意味著每次啟動都需要編輯這些二進制數據,至少從安全性來說,這種方式并不友好。

dyld 2(2004-2017)

dyld 2從2004年發布至今,已經經過了多個版本迭代,我們現在常見的一些特性,例如ASLRCode Signshare cache等技術,都是在dyld 2中引入的

dyld 2.0(2004-2007)

  • 2004年在macOS Tiger中推出了dyld 2

  • dyld 2dyld 1完全重寫的版本,可以正確支持C++初始化器語義,同時擴展了mach-o格式并更新dyld。從而獲得了高效率C++庫的支持。

  • dyld 2具有完成的dlopendlsym(主要用于動態加載庫和調用函數)實現,且具有正確的語義,因此棄用了舊版的API

    • dlopen:打開一個庫,獲取句柄

    • dlsym:在打開的庫中查找符號的值

    • dlclose:關閉句柄。

    • dlerror:返回一個描述最后一次調用dlopen、dlsym,或 dlclose 的錯誤信息的字符串。

  • dyld設計目標提升啟動速度。因此僅進行有限的健全性檢查。主要是因為以前的惡意程序比較少

  • 同時dyld也有一些安全問題,因此對一些功能進行了改進,來提高dyld在平臺上的安全性

  • 由于啟動速度的大幅提升,因此我們可以減少Prebinding的工作量。與編輯程序數據的區別在于,在這里我們僅編輯系統庫,且可以僅在軟件更新時做這些事情。因此在軟件更新過程中,可能會看到“優化系統性能”類似的文字。這就是在更新時進行Prebinding。現在dyld用于所有優化,其用途就是優化。因此后面有了dyld 2

dyld 2.x(2007-2017)

  • 在2004-20017這幾年間進行了大量改進,dyld 2的性能顯著提高
  • 首先,增加了大量的基礎架構平臺
    • 自從dyld 2在PowerPC發布之后,增加了x86x86_64armarm64和許多的衍生平臺。
    • 還推出了iOStvOSwatchOS,這些都需要新的dyld功能
  • 通過多種方式增加安全性
    • 增加 codeSigning代碼簽名、
    • ASLR(Address space layout randomization)地址空間配置隨機加載:每次加載庫時,可能位于不同的地址
    • bound checking邊界檢查:mach-o文件中增加了Header的邊界檢查功能,從而避免惡意二進制數據的注入
  • 增強了性能
    • 可以消除Prebinding,用share cache共享代碼代替

ASLR

  • ASLR是一種防范內存損壞漏洞被利用的計算機安全技術,ASLR通過隨機放置進程關鍵數據區域的地址空間來防止攻擊者跳轉到內存特定位置來利用函數

  • Linux已在內核版本2.6.12中添加ASLR

  • Apple在Mac OS X Leopard 10.5(2007年十月發行)中某些庫導入了隨機地址偏移,但其實現并沒有提供ASLR所定義的完整保護能力。而Mac OS X Lion 10.7則對所有的應用程序均提供了ASLR支持。

  • Apple在iOS 4.3內導入了ASLR

bounds checking 邊界檢查

  • 對mach-o header中的許多內容添加了重要的邊界檢查功能,從而可以避免惡意二進制數據的注入

share cache 共享代碼

  • share cache最早實在iOS3.1macOS Snow Leopard中被引入,用于完全取代Prebinding

  • share cache是一個單文件,包含大多數系統dylib,由于這些dylib合并成了一個文件,所以可以進行優化。

    • 重新調整所有文本段(_TEXT)數據段(_DATA),并重寫整個符號表,以此來減小文件的大小,從而在每個進程中僅掛載少量的區域。允許我們打包二進制數據段,從而節省大量的RAM

    • 本質是一個dylib預鏈接器,它在RAM上的節約是顯著的,在普通的iOS程序中運行可以節約500-1g內存

    • 還可以預生成數據結構,用來供dyld和Ob-C在運行時使用。從而不必在程序啟動時做這些事情,這也會節約更多的RAM和時間

  • share cache在macOS上本地生成,運行dyld共享代碼,將大幅優化系統性能

dyld 2 工作流程

dyld 2是純粹的in-process,即在程序進程內執行的,也就意味著只有當應用程序被啟動時,dyld 2才能開始執行任務

以下是dyld 2的工作流程圖示


dyld 2的工作流程圖示
  • 1、dyld的初始化,主要代碼在dyldbootstrap::start,接著執行dyld::_maindyld::_main代碼較多,是dyld加載的核心部分;
  • 2、檢查并準備環境,例如獲取二進制路徑、檢查環境配置,解析主二進制的image header等信息
  • 3、實例化主二進制的image loader,校驗主二進制和dyld的版本是否匹配
  • 4、檢查share cache是否已經map,如果沒有則需要先執行map share cache操作
  • 5、檢查DYLD_INSERT_LIBRARIES,如果有則加載插入的動態庫(即實例化image loader)
  • 6、執行link操作,會先遞歸加載依賴的所有動態庫(會對依賴庫進行排序,被依賴的總是在前面),同時在這階段將執行符號綁定,以及rebasebinding操作;
  • 7、執行初始化方法,OC的+load和C的constructor方法都會在這個階段執行;
  • 8、讀取Mach-oLC_MAIN段獲取程序的入口地址,調用main函數

簡化版

  • ① 解析 mach-o 文件,找到其依賴的庫,并且遞歸的找到所有依賴的庫,形成一張動態庫的依賴圖。iOS 上的大部分 app 都依賴幾百個動態鏈接庫(大部分是系統的動態庫),所以這個步驟包含了較大的工作量。

  • ② 匹配 mach-o 文件到自身的地址空間

  • ③ 進行符號查找(perform symbol lookups)

  • rebasebinding:由于 app 需要讓地址空間配置隨機加載,所以所有的指針都需要加上一個基地址

  • ⑤ 運行初始化程序,之后運行 main() 函數

dyld 3(2017-至今)

  • dyld 3是2017年WWDC推出的全新的動態鏈接器,它完全改變了動態鏈接的概念,且將成為大多數macOS系統程序的默認設置。2017 Apple OS平臺上的所有系統程序都會默認使用dyld 3.

  • dyld 3最早是在2017年的iOS 11中引入,主要用來優化系統庫。

  • 而在iOS 13系統中,iOS全面采用新的dyld 3來替代之前的dyld 2,因為dyld 3完全兼容dyld 2,其API接口也是一樣的,所以,在大部分情況下,開發者并不需要做額外的適配就能平滑過渡。

為什么需要重新設計dyld 2,形成新的dyld 3 ?

重新設計dyld,主要從以下幾方面進行考慮

  • 性能:想要盡可能的提高啟動速度

  • 安全性:在dyld 2中增加了安全特性,但是很難跟隨現實情形,雖然做了很多工作,但是難以實現這個目標

  • 可靠性可測試性:為此Apple發布了很多不錯的測試框架,例如XCTest,但是這些測試框架依賴于動態鏈接器底層功能,然后將測試框架的庫插入進程中,所以不能用于測試現有的dyld代碼,且難以測試安全性和性能水平

如何將 dyld 2 改進和優化為 dyld 3?

改進和優化建議

從上面的dyld 2的工作流程中,我們了解了dyld 2的執行流程,可以從以下兩個方面來改進和優化:

  • 確定安全敏感的部分

    • Parse mach-o headers解析mach-o 和 Find dependencies找到依賴庫,是安全敏感部分,即最大的安全隱患之一;

    • 惡意撰改mach-o頭部,可以進行某些攻擊;

    • 如果App使用了 @rpaths搜索路徑,可以通過惡意撰改路徑或者將一些庫插入到特定的位置,來達到破壞程序的目的;

  • 確定大量占用資源的部分(即可緩存部分)

    • Perform symbol lookups符號查找就是其中一個,因為在一個特定的庫中,除非進行軟件更新或者在磁盤上更改庫,不然符號將始終位于庫中的相同的偏移位置(即符號偏移量固定);

dyld 2 改進和優化

以下是dyld 2dyld 3 的一些改變,主要是將安全敏感的部分 和 占用大量資源的部分移動到上層,然后將一個closure寫入磁盤進行緩存,然后我們在程序進程中使用closure。以下是圖示

dyld2 -> dyld 3的改進和優化

dyld 3 組成部分/工作流程

dyld 3的工作流程主要分為3部分,如下所示


dyld 3的工作流程圖示

第一部分:out-of-process :mach-o parser

進程外的mach-o分析器和編譯器,是普通的后臺程序,用于提高測試基礎架構的性能。

第一部分主要在App進程之外做以下工作:

  • 解析所有搜索路徑@rpath、環境變量,因為它們會影響啟動速度

  • 分析 mach-o二進制數據

  • 執行符號查找

  • 利用這些結果創建launch clourse

第二部分:in-process :engine

進程內的引擎,這部分常駐在內存中,且在dyld 3不再需要分析mach-o文件頭或者執行符號查找就可以啟動應用,因為分析mach-o和執行符號查找都是耗時操作,所以極大的提高了程序啟動速度。

第二部分主要在App進程中做以下工作:

  • 檢查launch closure是否正確

  • 映射到dylib中,再跳轉main函數

第三部分:launch closure :cache

啟動閉包launch closure緩存服務。其中大多數程序啟動都會使用緩存,而不需要調用進程外 mach-o分析器和編譯器。且launch closuremach-o更簡單,因為launch closure內存映射文件,不需要用復雜的方法進行分析,我們可以進行簡單的校驗,目的是為了提高速度

  • 系統應用的launch closure直接加入到共享緩存 share cache

  • 對于第三方應用,我們將在應用安裝或者更新期間構建launch closure,因為此時 system library已發生更改

  • 默認情況下,在iOStvOSwatchOS上,這些操作都將在運行之前為您預先構建

  • macOS上,由于可以側向加載應用程序(這里應該是指非App Store安裝的應用),因此如果需要,in-process engine可以在首次啟動時RPC(Remote Procedure Call)到out to the daemon,然后,它就可以使用緩存的closure了。

所以綜上所述,dyld 3 把很多耗時的查找、計算和 I/O 操作都預先處理好了,使得啟動速度有了很大的提升。即dyld 3把很多耗時的操作都提前處理好了,極大提升了啟動速度。

啟動閉包(launch closure)

這是一個新引入的概念,指的是 app 在啟動期間所需要的所有信息。比如這個 app 使用了哪些動態鏈接庫,其中各個符號的偏移量,代碼簽名在哪里等等。

dyld 3符號缺失問題

  • dyld 2中默認采取的是lazy symbol的符號加載方式

  • dyld 3中,在app啟動之前,符號解析的結果已經在launch closure內了,所以lazy symbol就不再需要了。

  • 如果此時,如果有符號缺失的情況,dyld 2 和 dyld 3的表現是不同的

    • dyld 2中,首次調用缺失符號時App會crash

    • dyld 3中,缺失符號會導致App一啟動就會crash

總結

  • dyld 2工作流程

    • 解析mach-o頭部

    • 查找依賴庫

    • 映射mach-o文件,放入地址空間中

    • 執行符號查找

    • 使用ASLR進行rebasebind綁定

    • 運行所有初始化器

    • 執行main函數

  • dyld 3工作流程

    • 進程外:將dyld 2中的mach-o頭部解析、符號查找移到了進程外執行,且將其執行結果放入啟動閉包,存儲到磁盤中

    • 進程內:驗證啟動閉包正確性,并映射dylib,執行main函數

    • 啟動閉包緩存服務

參考鏈接

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容