iOS-底層原理 15:dyld發(fā)展史

iOS 底層原理 文章匯總

dyld簡介

  • dyld全名 The dynamic link editor;

  • 是蘋果的動(dòng)態(tài)鏈接器

  • 是蘋果操作系統(tǒng)的一個(gè)重要組成部分;

  • 在應(yīng)用被編譯打包成可執(zhí)行文件之后(即Mach-O),將其交由dyld負(fù)責(zé)鏈接,加載程序。

  • dyld貫穿了App啟動(dòng)的過程,包含加載依賴庫、主程序,如果我們需要進(jìn)行性能優(yōu)化、啟動(dòng)優(yōu)化等,不可避免的需要和dyld打交道

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

dyld 1.0(1996-2004)

  • dyld 1包含在NeXTStep 3.3中,在此之前的NeXT使用靜態(tài)二進(jìn)制數(shù)據(jù)。作用并不是很大,

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

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

dyld 2(2004-2017)

dyld 2從2004年發(fā)布至今,已經(jīng)經(jīng)過了多個(gè)版本迭代,我們現(xiàn)在常見的一些特性,例如ASLR、Code Sign、share cache等技術(shù),都是在dyld 2中引入的

dyld 2.0(2004-2007)

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

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

  • dyld 2具有完成的dlopendlsym(主要用于動(dòng)態(tài)加載庫和調(diào)用函數(shù))實(shí)現(xiàn),且具有正確的語義,因此棄用了舊版的API

    • dlopen:打開一個(gè)庫,獲取句柄

    • dlsym:在打開的庫中查找符號(hào)的值

    • dlclose:關(guān)閉句柄。

    • dlerror:返回一個(gè)描述最后一次調(diào)用dlopen、dlsym,或 dlclose 的錯(cuò)誤信息的字符串。

  • dyld設(shè)計(jì)目標(biāo)提升啟動(dòng)速度。因此僅進(jìn)行有限的健全性檢查。主要是因?yàn)橐郧暗膼阂獬绦虮容^少

  • 同時(shí)dyld也有一些安全問題,因此對(duì)一些功能進(jìn)行了改進(jìn),來提高dyld在平臺(tái)上的安全性

  • 由于啟動(dòng)速度的大幅提升,因此我們可以減少Prebinding的工作量。與編輯程序數(shù)據(jù)的區(qū)別在于,在這里我們僅編輯系統(tǒng)庫,且可以僅在軟件更新時(shí)做這些事情。因此在軟件更新過程中,可能會(huì)看到“優(yōu)化系統(tǒng)性能”類似的文字。這就是在更新時(shí)進(jìn)行Prebinding。現(xiàn)在dyld用于所有優(yōu)化,其用途就是優(yōu)化。因此后面有了dyld 2

dyld 2.x(2007-2017)

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

ASLR

  • ASLR是一種防范內(nèi)存損壞漏洞被利用的計(jì)算機(jī)安全技術(shù),ASLR通過隨機(jī)放置進(jìn)程關(guān)鍵數(shù)據(jù)區(qū)域的地址空間來防止攻擊者跳轉(zhuǎn)到內(nèi)存特定位置來利用函數(shù)

  • Linux已在內(nèi)核版本2.6.12中添加ASLR

  • Apple在Mac OS X Leopard 10.5(2007年十月發(fā)行)中某些庫導(dǎo)入了隨機(jī)地址偏移,但其實(shí)現(xiàn)并沒有提供ASLR所定義的完整保護(hù)能力。而Mac OS X Lion 10.7則對(duì)所有的應(yīng)用程序均提供了ASLR支持。

  • Apple在iOS 4.3內(nèi)導(dǎo)入了ASLR。

bounds checking 邊界檢查

  • 對(duì)mach-o header中的許多內(nèi)容添加了重要的邊界檢查功能,從而可以避免惡意二進(jìn)制數(shù)據(jù)的注入

share cache 共享代碼

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

  • share cache是一個(gè)單文件,包含大多數(shù)系統(tǒng)dylib,由于這些dylib合并成了一個(gè)文件,所以可以進(jìn)行優(yōu)化。

    • 重新調(diào)整所有文本段(_TEXT)數(shù)據(jù)段(_DATA),并重寫整個(gè)符號(hào)表,以此來減小文件的大小,從而在每個(gè)進(jìn)程中僅掛載少量的區(qū)域。允許我們打包二進(jìn)制數(shù)據(jù)段,從而節(jié)省大量的RAM

    • 本質(zhì)是一個(gè)dylib預(yù)鏈接器,它在RAM上的節(jié)約是顯著的,在普通的iOS程序中運(yùn)行可以節(jié)約500-1g內(nèi)存

    • 還可以預(yù)生成數(shù)據(jù)結(jié)構(gòu),用來供dyld和Ob-C在運(yùn)行時(shí)使用。從而不必在程序啟動(dòng)時(shí)做這些事情,這也會(huì)節(jié)約更多的RAM和時(shí)間

  • share cache在macOS上本地生成,運(yùn)行dyld共享代碼,將大幅優(yōu)化系統(tǒng)性能

dyld 2 工作流程

dyld 2是純粹的in-process,即在程序進(jìn)程內(nèi)執(zhí)行的,也就意味著只有當(dāng)應(yīng)用程序被啟動(dòng)時(shí),dyld 2才能開始執(zhí)行任務(wù)

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


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

簡化版

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

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

  • ③ 進(jìn)行符號(hào)查找(perform symbol lookups)

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

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

dyld 3(2017-至今)

  • dyld 3是2017年WWDC推出的全新的動(dòng)態(tài)鏈接器,它完全改變了動(dòng)態(tài)鏈接的概念,且將成為大多數(shù)macOS系統(tǒng)程序的默認(rèn)設(shè)置。2017 Apple OS平臺(tái)上的所有系統(tǒng)程序都會(huì)默認(rèn)使用dyld 3.

  • dyld 3最早是在2017年的iOS 11中引入,主要用來優(yōu)化系統(tǒng)庫。

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

為什么需要重新設(shè)計(jì)dyld 2,形成新的dyld 3 ?

重新設(shè)計(jì)dyld,主要從以下幾方面進(jìn)行考慮

  • 性能:想要盡可能的提高啟動(dòng)速度

  • 安全性:在dyld 2中增加了安全特性,但是很難跟隨現(xiàn)實(shí)情形,雖然做了很多工作,但是難以實(shí)現(xiàn)這個(gè)目標(biāo)

  • 可靠性可測(cè)試性:為此Apple發(fā)布了很多不錯(cuò)的測(cè)試框架,例如XCTest,但是這些測(cè)試框架依賴于動(dòng)態(tài)鏈接器底層功能,然后將測(cè)試框架的庫插入進(jìn)程中,所以不能用于測(cè)試現(xiàn)有的dyld代碼,且難以測(cè)試安全性和性能水平

如何將 dyld 2 改進(jìn)和優(yōu)化為 dyld 3?

改進(jìn)和優(yōu)化建議

從上面的dyld 2的工作流程中,我們了解了dyld 2的執(zhí)行流程,可以從以下兩個(gè)方面來改進(jìn)和優(yōu)化:

  • 確定安全敏感的部分

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

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

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

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

    • Perform symbol lookups符號(hào)查找就是其中一個(gè),因?yàn)樵谝粋€(gè)特定的庫中,除非進(jìn)行軟件更新或者在磁盤上更改庫,不然符號(hào)將始終位于庫中的相同的偏移位置(即符號(hào)偏移量固定);

dyld 2 改進(jìn)和優(yōu)化

以下是dyld 2dyld 3 的一些改變,主要是將安全敏感的部分 和 占用大量資源的部分移動(dòng)到上層,然后將一個(gè)closure寫入磁盤進(jìn)行緩存,然后我們?cè)诔绦蜻M(jìn)程中使用closure。以下是圖示

dyld2 -> dyld 3的改進(jìn)和優(yōu)化

dyld 3 組成部分/工作流程

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


dyld 3的工作流程圖示

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

進(jìn)程外的mach-o分析器和編譯器,是普通的后臺(tái)程序,用于提高測(cè)試基礎(chǔ)架構(gòu)的性能。

第一部分主要在App進(jìn)程之外做以下工作:

  • 解析所有搜索路徑@rpath、環(huán)境變量,因?yàn)樗鼈儠?huì)影響啟動(dòng)速度

  • 分析 mach-o二進(jìn)制數(shù)據(jù)

  • 執(zhí)行符號(hào)查找

  • 利用這些結(jié)果創(chuàng)建launch clourse

第二部分:in-process :engine

進(jìn)程內(nèi)的引擎,這部分常駐在內(nèi)存中,且在dyld 3不再需要分析mach-o文件頭或者執(zhí)行符號(hào)查找就可以啟動(dòng)應(yīng)用,因?yàn)榉治鰉ach-o和執(zhí)行符號(hào)查找都是耗時(shí)操作,所以極大的提高了程序啟動(dòng)速度。

第二部分主要在App進(jìn)程中做以下工作:

  • 檢查launch closure是否正確

  • 映射到dylib中,再跳轉(zhuǎn)main函數(shù)

第三部分:launch closure :cache

啟動(dòng)閉包launch closure緩存服務(wù)。其中大多數(shù)程序啟動(dòng)都會(huì)使用緩存,而不需要調(diào)用進(jìn)程外 mach-o分析器和編譯器。且launch closuremach-o更簡單,因?yàn)?code>launch closure是內(nèi)存映射文件,不需要用復(fù)雜的方法進(jìn)行分析,我們可以進(jìn)行簡單的校驗(yàn),目的是為了提高速度

  • 系統(tǒng)應(yīng)用的launch closure直接加入到共享緩存 share cache

  • 對(duì)于第三方應(yīng)用,我們將在應(yīng)用安裝或者更新期間構(gòu)建launch closure,因?yàn)榇藭r(shí) system library已發(fā)生更改

  • 默認(rèn)情況下,在iOStvOSwatchOS上,這些操作都將在運(yùn)行之前為您預(yù)先構(gòu)建。

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

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

啟動(dòng)閉包(launch closure)

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

dyld 3符號(hào)缺失問題

  • dyld 2中默認(rèn)采取的是lazy symbol的符號(hào)加載方式

  • dyld 3中,在app啟動(dòng)之前,符號(hào)解析的結(jié)果已經(jīng)在launch closure內(nèi)了,所以lazy symbol就不再需要了。

  • 如果此時(shí),如果有符號(hào)缺失的情況,dyld 2 和 dyld 3的表現(xiàn)是不同的

    • dyld 2中,首次調(diào)用缺失符號(hào)時(shí)App會(huì)crash

    • dyld 3中,缺失符號(hào)會(huì)導(dǎo)致App一啟動(dòng)就會(huì)crash

總結(jié)

  • dyld 2工作流程

    • 解析mach-o頭部

    • 查找依賴庫

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

    • 執(zhí)行符號(hào)查找

    • 使用ASLR進(jìn)行rebasebind綁定

    • 運(yùn)行所有初始化器

    • 執(zhí)行main函數(shù)

  • dyld 3工作流程

    • 進(jìn)程外:將dyld 2中的mach-o頭部解析、符號(hào)查找移到了進(jìn)程外執(zhí)行,且將其執(zhí)行結(jié)果放入啟動(dòng)閉包,存儲(chǔ)到磁盤中

    • 進(jìn)程內(nèi):驗(yàn)證啟動(dòng)閉包正確性,并映射dylib,執(zhí)行main函數(shù)

    • 啟動(dòng)閉包緩存服務(wù)

參考鏈接

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

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

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