OC底層探索(十二): dyld4應(yīng)用程序加載初探

所用版本:

  • 處理器: Intel Core i9
  • MacOS 12.3.1
  • Xcode 13.3.1
  • dyld-941.4
  • objc4-838

雖然蘋果官網(wǎng)發(fā)布的正式版才到dyld-852.2

dyld-852.2

不過(guò)github上可以下到最新非正式版本, 寫文章時(shí)候最新版本為dyld-941.4, 估計(jì)以后還會(huì)更新氨鹏。dyld3dyld4我認(rèn)為改動(dòng)還是比較大陨界。

dyld-941.4
dyld設(shè)計(jì)

dyld4的針對(duì)于的mach-o解析器 ( iOS上可執(zhí)行文件格式是Mach-O格式, 下方也有具體解釋) 方面跟dyld3相同,但是引入了 JustInTime 的加載器來(lái)優(yōu)化。

  • dyld3: 相比dyld2新增預(yù)構(gòu)建/閉包, 目的是將一些啟動(dòng)數(shù)據(jù)創(chuàng)建為閉包存到本地金吗,下次啟動(dòng)將不再重新解析數(shù)據(jù),而是直接讀取閉包內(nèi)容
  • dyld4: 采用pre-build + just-in-time 預(yù)構(gòu)建/閉包+實(shí)時(shí)解析的雙解析模式, 將根據(jù)緩存有效與否選擇合適的模式進(jìn)行解析, 同時(shí)也處理了閉包失效時(shí)候需要重建閉包的性能問(wèn)題聂喇。

初看下dyld新舊版本對(duì)比, 看一下dyld加載流程相較之前改變

dyld舊版本
dyld4新版本模擬器改動(dòng)
dyld4新版本真機(jī)改動(dòng)


我這里先帶入dyld以及dyld做了什么 , 先看個(gè)例子

普通的一個(gè)OC項(xiàng)目, ViewController中加一個(gè)+ (void)load方法, main中加一個(gè)函數(shù)SAFuc

ViewController
main

運(yùn)行一下, 看下它們走的順序, 結(jié)果如下

運(yùn)行

會(huì)發(fā)現(xiàn), 先走的load方法, 再走SAFuc, 最后走的main中的Hello world

這塊其實(shí)就會(huì)有疑問(wèn), 不應(yīng)該先走mainHello world么? 所以我們就要看下應(yīng)用程序加載流程

動(dòng)態(tài)庫(kù)/靜態(tài)庫(kù)/編譯過(guò)程

先看下編譯過(guò)程的流程圖

我們先了解動(dòng)態(tài)庫(kù), 靜態(tài)庫(kù), 代碼編譯過(guò)程這幾個(gè)概念, 方便后面探索

靜態(tài)庫(kù) / 動(dòng)態(tài)庫(kù)

通常程序都會(huì)依賴系統(tǒng)一些庫(kù), 庫(kù)是什么呢? 其實(shí)庫(kù)就是一些可執(zhí)行的二進(jìn)制文件, 能被操作系統(tǒng)加載到內(nèi)存里面中舟奠。庫(kù)分為兩種靜態(tài)庫(kù), 動(dòng)態(tài)庫(kù)

靜態(tài)庫(kù)

.a, .lib等。鏈接階段時(shí)靜態(tài)庫(kù)會(huì)被完整地復(fù)制, 一起打包在可執(zhí)行文件中鞍恢,被多次使用就有多份冗余拷貝傻粘。

靜態(tài)庫(kù)

  • 優(yōu)點(diǎn): 編譯完成之后, 鏈接到目標(biāo)程序中, 同時(shí)打包到可執(zhí)行文件里面, 不會(huì)有外部依賴。

  • 缺點(diǎn): 靜態(tài)庫(kù)會(huì)有兩份, 所以會(huì)導(dǎo)致目標(biāo)程序體積增大, 對(duì)內(nèi)存, 性能, 速度消耗很大帮掉。并且相同靜態(tài)庫(kù)每個(gè)app中都會(huì)拷貝一份弦悉。

動(dòng)態(tài)庫(kù)

.framework等。程序編譯時(shí)并不會(huì)鏈接到目標(biāo)程序中蟆炊,目標(biāo)程序只會(huì)存儲(chǔ)指向動(dòng)態(tài)庫(kù)的引用警绩,在程序運(yùn)行時(shí)才被載入。蘋果大部分都是動(dòng)態(tài)庫(kù)

動(dòng)態(tài)庫(kù)
  • 優(yōu)點(diǎn): 不需要拷貝到目標(biāo)程序, 減少App包的體積

    • 多個(gè)App可以使用同一個(gè)動(dòng)態(tài)庫(kù), 共享內(nèi)存, 節(jié)約資源

    • 由于運(yùn)行時(shí)才會(huì)去加載, 那么可以在App不使用時(shí)隨時(shí)對(duì)庫(kù)進(jìn)行替換或更新, 更新靈活

  • 缺點(diǎn): 動(dòng)態(tài)載入會(huì)帶來(lái)一部分性能損失, 同時(shí)動(dòng)態(tài)庫(kù)也會(huì)使得程序依賴于外部環(huán)境盅称。一旦動(dòng)態(tài)庫(kù)沒(méi)有或消失, 程序會(huì)出現(xiàn)問(wèn)題肩祥。

代碼編譯過(guò)程

編譯過(guò)程
  • 源文件: .h, .m, .cpp, .c等文件
  • 預(yù)編譯: 預(yù)先編譯文件(源文件), 詞法語(yǔ)法分析, 替換宏, 刪除注釋, 展開(kāi)頭文件, 產(chǎn)生.i文件
  • 編譯: 編譯文件, 將.i文件轉(zhuǎn)換為匯編語(yǔ)言, 產(chǎn)生.s文件(匯編文件)
  • 匯編: 將匯編文件轉(zhuǎn)換為機(jī)器代碼文件, 產(chǎn)生.o文件
  • 鏈接: 把之前所有操作的文件鏈接到程序里面來(lái), 對(duì).o文件中引用其他庫(kù)的地方進(jìn)行引用, 生成最后的 可執(zhí)行文件后室。動(dòng)態(tài)庫(kù)與靜態(tài)庫(kù)區(qū)別其實(shí)就是鏈接的區(qū)別。
可執(zhí)行文件

可執(zhí)行文件位置: 通常編譯后的 程序.cpp顯示包內(nèi)容, 可找到可執(zhí)行文件(黑黑的一個(gè)文件)

可執(zhí)行文件

其實(shí)可執(zhí)行文件就是能夠運(yùn)行起來(lái)的文件, 我們也可以把它拖到終端中回車, 可發(fā)現(xiàn)也能打印出信息混狠。(ios項(xiàng)目需要真機(jī)運(yùn)行, 直接拖入終端回車會(huì)報(bào)錯(cuò))

當(dāng)然我們?nèi)绻胍樵兿到y(tǒng)動(dòng)態(tài)庫(kù)可執(zhí)行文件, 以CoreFoundation為例

CoreFoundation
  • 斷點(diǎn)image listCoreFoundation按路徑搜索 可以找到CoreFoundation.frame
CoreFoundation`可執(zhí)行文件
  • CoreFoundation.frame右鍵選擇顯示包內(nèi)容即可看到CoreFoundation的可執(zhí)行文件


dyld

dyld(the dynamic link editor)是蘋果的動(dòng)態(tài)鏈接器岸霹,是蘋果操作系統(tǒng)的重要組成部分,在app被編譯打包成可執(zhí)行文件格式的Mach-O文件后将饺,交由dyld負(fù)責(zé)連接動(dòng)靜態(tài)庫(kù)贡避,加載程序

dyld流程
  • 這里的image不是圖片是鏡像文件, 庫(kù)加載進(jìn)去就是映射, 映射一份到內(nèi)存, 而這個(gè)東西就image。映射可以理解成, 例如 動(dòng)態(tài)庫(kù)都存在沙盒路徑磁盤里面, 當(dāng)我們用到相應(yīng)動(dòng)態(tài)庫(kù)時(shí)候, copy一份(找了一個(gè)替身)加載到我們用到程序的內(nèi)存里面予弧。

探索dyld之前我們要先了解入口, 在load方法處加一個(gè)斷點(diǎn)刮吧,bt查看下, 當(dāng)然也可以通過(guò)左側(cè)的堆棧信息查看。

舊版本
舊版本dyld入口
新版本
dyld4新版本模擬器改動(dòng)
dyld4新版本真機(jī)改動(dòng)

棧結(jié)構(gòu), 先進(jìn)后出, 所以要從后往前看掖蛤。

  • 舊版本dyld: _dyld_start開(kāi)始, 接下來(lái)走dyldbootstrap, 源碼入口需要在dyld_start開(kāi)始杀捻。
  • 新版本dyld: start開(kāi)始接下來(lái)走 dyld4prepare方法, 源碼入口需要在start中開(kāi)始探索。

當(dāng)然我們也可以走下匯編看下dyld`在哪里,

舊版本

可發(fā)現(xiàn)在libdyld.dylib這里面

舊版本在libdyld.dylib

既然在libdyld.dylib里面, 那我們可以去蘋果官方 Source Browser 可下到dyld源碼, 如下圖

新版本

而新版本......不得不說(shuō)官方很嚴(yán)謹(jǐn)蚓庭。(后面拿真機(jī)做例子)

新版本
新版本
舊版本

dyld源碼之后全局搜索dyld_start(找入口), 入口是匯編寫的, 看arm環(huán)境的就行致讥。 能找到接下去走dyldbootstrap, 這也跟之前bt打印內(nèi)容一致

dyld_start

dyldStartup__dyld_start(入口函數(shù))查找時(shí)發(fā)現(xiàn),是由dyld匯編實(shí)現(xiàn)(.s匯編文件)器赞,通過(guò)注釋發(fā)現(xiàn), 下面會(huì)調(diào)用call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)垢袱,是一個(gè)C++方法, 那么我們根據(jù)名字dyldbootstrap, 去尋找他的start方法。

新版本

新版本我們直接搜索dyldbootstrap, 肯定是無(wú)了

錯(cuò)誤示范

我們先搜索start, 在同樣的dyldStartup

匯編`start`

其實(shí)注釋已經(jīng)告訴我們, 此匯編代碼對(duì)齊堆棧并跳入C代碼:dyld:: start(const KernelArgs* kernArgs) 那么搜索 start(const KernelArgs* kernArgs)方法看一下, 有

void start(const KernelArgs* kernArgs)

這里的start方法是dyld的入口點(diǎn)港柜。那么我們接著這里進(jìn)行探索请契。 往下看有一個(gè)"prepare"準(zhǔn)備 方法MainFunc appMain = prepare(state, dyldMA);,

prepare

可看到這個(gè)方法是處理相關(guān)依賴綁定的方法, 那么進(jìn)入看下其源碼, 看看到底準(zhǔn)備些什么內(nèi)容

prepare底層

[ 配置環(huán)境/平臺(tái)/路徑/版本等信息 ]

看下gProcessInfostruct dyld_all_image_infos* gProcessInfo = &dyld_all_image_infos;是一個(gè)存儲(chǔ)dyld所有鏡像信息的一個(gè)結(jié)構(gòu)體

gProcessInfo底層

dyld_all_image_infos

可看出dyld_all_image_infos包含信息比較多, mach_header, dyld_uuid_info, dyldVersion等等。

其中mach_headerMach-O的頭部夏醉,而dyld加載的文件就是Mach-O類型的姚糊,即Mach-O類型是可執(zhí)行文件類型,由四部分組成:Mach-O頭部授舟、Load Command救恨、sectionOther Data释树,可以通過(guò)MachOView可查看可執(zhí)行文件信息

MachOView可查看可執(zhí)行文件信息

回到prepare方法, 接著往下看

[進(jìn)行pre-build, 創(chuàng)建mainLoader]

預(yù)構(gòu)建

接下來(lái)會(huì)創(chuàng)建一個(gè)mainLoader 主裝載器, 如果熟悉dyld3的小伙伴知道, 舊版本是創(chuàng)建一個(gè)ImageLoader鏡像裝載器

舊版本鏡像裝載器
mainLoader 主裝載器

mainLoader主裝載器, 可以理解成一個(gè)容器, 這里面陸續(xù)添加 可執(zhí)行文件, 動(dòng)態(tài)庫(kù)等等, 都裝載完成之后經(jīng)由后續(xù)一些處理, 就是我們打開(kāi)的App肠槽。

[ 創(chuàng)建just-in-time ]

just-in-time

這是dyld4一個(gè)新特性, dyld4在保留了dyld3mach-o 解析器基礎(chǔ)上,同時(shí)也引入了 just-in-time的加載器來(lái)優(yōu)化, 這里稍微細(xì)說(shuō)一下奢啥。

首先dyld3 出于對(duì)啟動(dòng)速度的優(yōu)化的目的, 增加了預(yù)構(gòu)建(閉包)秸仙。App第一次啟動(dòng)或者App發(fā)生變化時(shí)會(huì)將部分啟動(dòng)數(shù)據(jù)創(chuàng)建為閉包存到本地,那么App下次啟動(dòng)將不再重新解析數(shù)據(jù)桩盲,而是直接讀取閉包內(nèi)容寂纪。當(dāng)然前提是應(yīng)用程序和系統(tǒng)應(yīng)很少發(fā)生變化,但如果這兩者經(jīng)常變化等, 就會(huì)導(dǎo)閉包丟失或失效。所以dyld4 采用了 pre-build + just-in-time 的雙解析模式捞蛋,預(yù)構(gòu)建 pre-build 對(duì)應(yīng)的就是 dyld3 中的閉包孝冒,just-in-time 可以理解為實(shí)時(shí)解析。當(dāng)然just-in-time 也是可以利用 pre-build 的緩存的拟杉,所以性能可控庄涡。有了just-in-time, 目前應(yīng)用首次啟動(dòng)、系統(tǒng)版本更新搬设、普通啟動(dòng)穴店,dyld4 則可以根據(jù)緩存是否有效選擇合適的模式進(jìn)行解析。

[ 裝載內(nèi)容 ]

裝載

往下看可看到, mainLoader進(jìn)行裝載, 裝載可執(zhí)行文件, 動(dòng)態(tài)庫(kù)等等

裝載

記錄插入信息, 遍歷所有dylibs, 一些記錄檢查操作繼續(xù)往下走拿穴。

[插入緩存]

插入緩存

這里是對(duì)dyld緩存的一個(gè)處理, 其中stateprepare傳入進(jìn)來(lái)的參數(shù), 其定義APIs& state = APIs::bootstrap(config, sLocks);是APIs方法里面的bootstrap引導(dǎo)程序方法泣洞。

Loader類
applyInterposingToDyldCache

image.png

接下來(lái)是一些其他通知和寫入操作, 簡(jiǎn)單看一下, 之后是下一個(gè)重點(diǎn)內(nèi)容runAllInitializersForMain

[運(yùn)行初始化方法]

runAllInitializersForMain

前面稍微提過(guò)state定義APIs& state = APIs::bootstrap(config, sLocks); , 源自DyldAPIs, 那么我們進(jìn)入看一下

runAllInitializersForMain

notifyObjCInit 函數(shù)

在執(zhí)行完初始化之后會(huì)執(zhí)行notifyObjCInit, 告訴objc 去運(yùn)行所有 +load 方法, 而此時(shí)系統(tǒng)main還沒(méi)有執(zhí)行, 這也就是為什么+ load方法執(zhí)行在main前面的原因。我們看一下notifyObjCInit內(nèi)部

notifyObjCInit

其中 _notifyObjCInit我們看一下默色。首先可以看到_notifyObjCInit定義是_dyld_objc_notify_init _notifyObjCInit = nullptr;

_notifyObjCInit

因?yàn)榕袛嗍且?code>_notifyObjCInit 非null才繼續(xù)后面, 所以我們要搜索下_notifyObjCInit什么地方賦值

setObjCNotifiers

_notifyObjCInitsetObjCNotifiers方法中的第二參數(shù)_dyld_objc_notify_init init
繼續(xù)找setObjCNotifiers_dyld_objc_notify_register球凰。

_dyld_objc_notify_register

這個(gè)方法其實(shí)在objc4源碼_objc_init方法中見(jiàn)過(guò)

objc_init

我們?cè)?code>objc_init內(nèi)部調(diào)用了dyld_objc_notify_register方法, 并為其傳入?yún)?shù)load_images (第二個(gè)參數(shù) init)骤公。

load_images
call_load_methods
call_class_loads

load_imagescall_load_methodscall_class_loads內(nèi)部也可以看出會(huì) 循環(huán)調(diào)用所有+load 方法平项,直到不再有荠察。

[link動(dòng)態(tài)庫(kù)和主程序]

runInitializersBottomUpPlusUpwardLinks

回到runAllInitializersForMain繼續(xù)看, runInitializersBottomUpPlusUpwardLinks循環(huán)link動(dòng)態(tài)庫(kù), 再link可執(zhí)行文件

runInitializersBottomUpPlusUpwardLinks
link動(dòng)態(tài)庫(kù)

[加載主程序入口]

runAllInitializersForMain準(zhǔn)備工作完成之后, 尋找App中main函數(shù), App正常運(yùn)行

找main

綜上也驗(yàn)證了dyld 打印信息

dyld4打印信息
dyld4應(yīng)用程序加載流程圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市酗失,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌昧绣,老刑警劉巖规肴,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異夜畴,居然都是意外死亡拖刃,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門贪绘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)兑牡,“玉大人,你說(shuō)我怎么就攤上這事税灌【” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵菱涤,是天一觀的道長(zhǎng)苞也。 經(jīng)常有香客問(wèn)我,道長(zhǎng)粘秆,這世上最難降的妖魔是什么如迟? 我笑而不...
    開(kāi)封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上殷勘,老公的妹妹穿的比我還像新娘此再。我一直安慰自己,他們只是感情好劳吠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布引润。 她就那樣靜靜地躺著,像睡著了一般痒玩。 火紅的嫁衣襯著肌膚如雪淳附。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天蠢古,我揣著相機(jī)與錄音奴曙,去河邊找鬼。 笑死草讶,一個(gè)胖子當(dāng)著我的面吹牛洽糟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播堕战,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼坤溃,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了嘱丢?” 一聲冷哼從身側(cè)響起薪介,我...
    開(kāi)封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎越驻,沒(méi)想到半個(gè)月后汁政,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缀旁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年记劈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片并巍。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡目木,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出懊渡,到底是詐尸還是另有隱情嘶窄,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布距贷,位于F島的核電站柄冲,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏忠蝗。R本人自食惡果不足惜现横,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧戒祠,春花似錦骇两、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至馏颂,卻和暖如春示血,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背救拉。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工难审, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人亿絮。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓告喊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親派昧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子黔姜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容