引言
1.一般我們都知道app
的啟動(dòng)都是從main
函數(shù)開始的,但其實(shí)在main
函數(shù)之前系統(tǒng)做了一些其他的工作。實(shí)際上我們的應(yīng)用從磁盤加載到內(nèi)存是通過dyld
來加載卤橄。最后會(huì)返回主程序的main
函數(shù)地址
2.dyld
的概述:(the dynamic link editor
)是蘋果的動(dòng)態(tài)鏈接器,是蘋果操作系統(tǒng)一個(gè)重要組成部分,在系統(tǒng)內(nèi)核做好程序準(zhǔn)備工作之后评凝,交由dyld負(fù)責(zé)余下的工作。也就是內(nèi)核讀取完MachO
的Header
之后腺律,就交給DYLD
了
3.dyld源碼
4.objc源碼
Dyld加載流程圖
在正式開始分析之前奕短,我先放出一張總體的流程,這樣可以從全局了解整個(gè)流程
找到dyld入口
拿到源碼匀钧,大家可能不知道如何開始分析翎碑,首先我們運(yùn)行一個(gè)空項(xiàng)目模擬app啟動(dòng)過程,如下圖在load
方法打斷點(diǎn)之斯,可以看到函數(shù)調(diào)用棧日杈,最后調(diào)用_dyld_start
方法,
打開
dyld
源碼找到通過命名空間dyldbootstrap
找到了start
函數(shù)
_dyld_start
下面是start函數(shù)實(shí)現(xiàn)
在
start
主要作用:
-
rebaseDyld(dyldsMachHeader)
計(jì)算ASLR(隨機(jī)地址) - 棧溢出保護(hù)(為堆棧canary設(shè)置隨機(jī)值)
__guard_setup(apple)
- 調(diào)用
_dyld_main
_dyld_main 函數(shù)
進(jìn)入main函數(shù),如下圖佑刷,不到800行代碼main函數(shù)代碼還是很多的莉擒,主要可以細(xì)分為如下流程:
配置環(huán)境變量
加載共享緩存
dyld2 和 dyld3判斷
主程序?qū)嵗?/code>
插入動(dòng)態(tài)庫
link主程序、綁定符號(hào)
執(zhí)行初始化方法
return主程序main地址
配置環(huán)境變量
從環(huán)境中獲取主可執(zhí)行文件的cdHash
獲取主程序的版本瘫絮、架構(gòu)等信息
檢測設(shè)置
checkEnvironmentVariables(envp)
檢查設(shè)置環(huán)境變量defaultUninitializedFallbackPaths(envp)
設(shè)置環(huán)境遍歷默認(rèn)值DYLD_PRINT_OPTS
涨冀、 DYLD_PRINT_ENV
在xcode設(shè)置這兩個(gè)變量會(huì)打印相關(guān)參數(shù)、環(huán)境變量信息加載共享緩存
共享緩存
:實(shí)際上就是像我們UIKit
Foundation
麦萤,也就是系統(tǒng)級(jí)別的動(dòng)態(tài)庫鹿鳖,因?yàn)槊總€(gè)app都會(huì)用到扁眯,蘋果為了優(yōu)化app,就會(huì)把這些庫放到一些公共的區(qū)域了翅帜,我們稱之為共享緩存
姻檀,這樣的好處就是對(duì)于一些公共的庫,不用每個(gè)app都去加載涝滴。
checkSharedRegionDisable
檢查是否開啟共享緩存mapSharedCache
加載共享緩存庫施敢,如果是第一次加載,就去加載狭莱。一般如果其他應(yīng)用加載過僵娃,就不用加載,大大優(yōu)化了app啟動(dòng)時(shí)間
dyld2 腋妙、dyld3判斷
蘋果后面的版本對(duì)采用dyld3(閉包)方式加載默怨,但是加載的原理基本不變主程序初始化
通過
instantiateFromLoadedImage
方法初始化一個(gè)ImageLoader
對(duì)象,sMainExecutable
插入動(dòng)態(tài)庫
如上圖通過遍歷
DYLD_INSERT_LIBRARIES
環(huán)境變量骤素,調(diào)用loadInsertedDylib
插入動(dòng)態(tài)庫
鏈接主程序
設(shè)置
llinkingMainExecutable = true
調(diào)用link
方法開始鏈接主程序
鏈接動(dòng)態(tài)庫
遍歷
sAllImages
數(shù)組插入動(dòng)態(tài)庫匙睹,綁定注冊(cè)信息。由于數(shù)組的第一個(gè)是主程序所以i+1
符號(hào)綁定
通過
recursiveBindWithAccounting
遞歸綁定主程序遍歷綁定插入的動(dòng)態(tài)庫
recursiveBind
執(zhí)行初始化方法
initializeMainExecutable
主程序初始化济竹,其主要是會(huì)遍歷調(diào)用runInitializers
方法然后進(jìn)入
processInitializers
痕檬,為初始化做準(zhǔn)備調(diào)用
recursiveInitialization
遞歸初始化后進(jìn)入notifySingle
根據(jù)上面空項(xiàng)目的函數(shù)調(diào)用棧我們發(fā)現(xiàn),調(diào)用完
notifySingle
會(huì)調(diào)用load_image
方法送浊,但是我們發(fā)現(xiàn)并沒有找到梦谜,但是在notifySingle
方法里面我們看到了sNotifyObjCInit
通知oc方法的一個(gè)回調(diào)sNotifyObjCInit
回調(diào)是通過registerObjCNotifiers
傳入了搜索源碼
registerObjCNotifiers
發(fā)現(xiàn)一個(gè)非常關(guān)鍵的方法,也就是dyld
和objc
溝通的橋梁_dyld_objc_notify_register
然后我們打開
objc
源碼袭景,搜索_dyld_objc_notify_register
唁桩,發(fā)現(xiàn)在_objc_init
調(diào)用了,也就是注冊(cè)了通知讓后通知最后調(diào)用了
load_images
這個(gè)方法這個(gè)方法主要調(diào)用
call_load_methods
耸棒,然后循環(huán)遍歷調(diào)用類的load和分類的load方法上面主要是
dyld
調(diào)用objc
的流程荒澡,回到dyld
源碼的main
通過
LC_Main
拿到main
地址并返回所以從上面的流程可以看出,類的load
和分類的load
方法是比主程序的main
方法更早調(diào)調(diào)用的与殃。