我們都知道APP的入口函數(shù)是main(),而在main()函數(shù)調(diào)用之前奶段,APP的加載過(guò)程是怎樣的呢孵奶?接下來(lái)我們一起來(lái)分析APP的加載流程。
一. 準(zhǔn)備工作
由于load()
比main()
調(diào)用更早巧娱,因此我們創(chuàng)建一個(gè)工程,在控制器中寫一個(gè)load()
函數(shù)烘贴,并斷點(diǎn)運(yùn)行禁添,如下圖:
運(yùn)行起來(lái)之后,可以清晰的看到比較詳細(xì)的函數(shù)調(diào)用順序桨踪,從_dyld_start()
到dyld:notifySingle()
老翘,頻率出現(xiàn)最多的就是這個(gè)dyld,那么dyld是什么?它在做什么铺峭?
簡(jiǎn)單來(lái)說(shuō)dyld是一個(gè)動(dòng)態(tài)鏈接器墓怀,用來(lái)加載所有的庫(kù)和可執(zhí)行文件。接下來(lái)我們將通過(guò)對(duì)dyld源碼分析卫键,去追蹤dyld到底做了什么傀履?
二. dyld加載流程分析
1. 首先下載dyld源碼。
2. 打開dyld源碼工程莉炉,根據(jù)上圖dyldbootstrap::start
為關(guān)鍵字搜索dyldbootstrap
中調(diào)用的start()
钓账,如下圖:
3. 進(jìn)入dyld的start
函數(shù)
其中rebaseDyld()
分析如下:
4. 進(jìn)入dyld的main
函數(shù)
注:因?yàn)?code>dyld::main()函數(shù)代碼比較多,以下會(huì)分段介紹絮宁,也會(huì)介紹相對(duì)來(lái)說(shuō)比較重要的函數(shù)梆暮。
4.1 內(nèi)核檢測(cè)
4.2 獲取main執(zhí)行文件的cdHash緩存區(qū)
4.3 獲取CPU信息
// 獲取CPU信息
getHostInfo(mainExecutableMH, mainExecutableSlide);
4.4 設(shè)置MachHeader和內(nèi)存偏移量
// 設(shè)置MachHeader和內(nèi)存偏移量Slide
uintptr_t result = 0;
sMainExecutableMachHeader = mainExecutableMH;
sMainExecutableSlide = mainExecutableSlide;
4.5 設(shè)置上下文
// 設(shè)置上下文,保存信息
setContext(mainExecutableMH, argc, argv, envp, apple);
4.6 配置進(jìn)程限制
4.7 檢測(cè)環(huán)境變量
4.8 打印環(huán)境配置信息
// 打印環(huán)境配置信息
if ( sEnv.DYLD_PRINT_OPTS )
printOptions(argv);
if ( sEnv.DYLD_PRINT_ENV )
printEnvironmentVariables(envp);
此處可以自己定義環(huán)境變量配置绍昂,回到剛才創(chuàng)建的新工程中啦粹,在Edit Scheme
-> Run
-> Arguments
-> Environment Variables
添加兩個(gè)參數(shù) DYLD_PRINT_OPTS
和 DYLD_PRINT_ENV
,并設(shè)置測(cè)試value值窘游,如下:
運(yùn)行程序唠椭,可看到如下打印信息:
4.9 加載共享緩存(如果沒有共享緩存,iOS將無(wú)法運(yùn)行)
主要函數(shù)mapSharedCache()
如下:
4.10 dyld
配置
(1) dyld3
(閉包模式)
iOS11版本之后忍饰,引入dyld3閉包模式(ClosureMode):加載速度更快泪蔫,效率更高。
開始執(zhí)行閉包模式
判斷是否開啟了閉包模式
啟動(dòng)閉包模式加載
其中launchWithClosure
加載閉包喘批,會(huì)把后面說(shuō)到的dyld2
的大部分流程都封裝到launchWithClosure()
這個(gè)函數(shù)里面了撩荣,這里不再細(xì)說(shuō)launchWithClosure
,因?yàn)樵诮酉聛?lái)的dyld2
(非閉包)中會(huì)詳細(xì)解釋整個(gè)dyld加載的流程饶深,也就是launchWithClosure
實(shí)現(xiàn)過(guò)程餐曹。
(2) dyld2
(非閉包模式)
開始執(zhí)行閉包模式
把dyld加入到UUID列表
// 把dyld加入到UUID列表
addDyldImageToUUIDList();
配置緩存代理
4.11 創(chuàng)建主程序的Image
開始創(chuàng)建主程序的Image,通過(guò)instantiateFromLoadedImage()
敌厘,調(diào)用instantiateMainExecutable()
台猴,實(shí)例化具體的Image類,最后生成的對(duì)象俱两,設(shè)置到gLinkContext
中饱狂。
4.12 設(shè)置動(dòng)態(tài)庫(kù)的版本
// 加載完共享緩存,設(shè)置動(dòng)態(tài)庫(kù)的版本
checkVersionedPaths();
4.13 加載插入的動(dòng)態(tài)庫(kù)
// 加載插入的動(dòng)態(tài)庫(kù)
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
oadInsertedDylib(*lib);
}
4.14 鏈接主程序和動(dòng)態(tài)庫(kù)
- 其中的
link()
函數(shù)宪彩,函數(shù)體調(diào)用了image->link()
休讳,函數(shù)具體如下:
- 判斷是否需要重新加載所有的Image
4.15 綁定主程序和動(dòng)態(tài)庫(kù)
4.16 初始化主程序
根據(jù)Demo上的堆棧信息,如下:
終于看到熟悉的函數(shù)了尿孔,那么dyld加載流程也快結(jié)束了俊柔。
根據(jù)堆棧信息筹麸,獲取函數(shù)調(diào)用層級(jí)關(guān)系。
// 初始化主程序
initializeMainExecutable();
- 查找
runInitializers()
:dyld::initializeMainExecutable()
->ImageLoader::runInitializers()
- 查找
processInitializers()
:ImageLoader::runInitializers()
->ImageLoader::processInitializers()
- 查找
recursiveInitialization()
:ImageLoader::processInitializers()
->ImageLoader::recursiveInitialization()
- 查找
notifySingle()
:ImageLoader::recursiveInitialization()
->dyld::notifySingle()
- 查找
load_images()
:在dyld::notifySingle()
中并沒有找到load_images()
雏婶,但是找到了sNotifyObjCInit()
物赶,該字段是objc函數(shù)回調(diào)。在dyld::notifySingle()
中執(zhí)行了這個(gè)回調(diào)留晚,那就需要追溯到誰(shuí)去注冊(cè)的這個(gè)回調(diào)了酵紫。 - 全局查找
sNotifyObjCInit()
賦值的地方。在registerObjCNotifiers()
中賦值错维,如下:
- 全局查找
registerObjCNotifiers
憨闰,在_dyld_objc_notify_register()
中調(diào)用,且第二個(gè)參數(shù)是我們需要的需五。如下:
- 全局查找
_dyld_objc_notify_register()
,并沒有在dyld源碼庫(kù)里找到轧坎,此時(shí)需要在源工程中宏邮,打符號(hào)斷點(diǎn)_dyld_objc_notify_register
,重新編譯執(zhí)行缸血,可以看到是_objc_init()
調(diào)用了蜜氨。此時(shí)只能去查找objc源碼了。 -
objc
源碼分析捎泻,在objc-os.mm
文件中找到_objc_init()
函數(shù)飒炎,其中注冊(cè)了_dyld_objc_notify_register
回調(diào)。
其中第二個(gè)參數(shù)就是load_images()
笆豁,在load_images()
中也找到了call_load_methods()
郎汪。
此時(shí)初始化程序還未執(zhí)行完成,回到之前的 ImageLoader::recursiveInitialization()
方法中闯狱。
- 執(zhí)行
this->doInitialization()
函數(shù)
- 發(fā)送通知煞赢,初始化主程序完成。
4.17 進(jìn)入主程序
// 通知此進(jìn)程將要進(jìn)入程序main()
notifyMonitoringDyldMain();
到此哄孤,start()
-> main()
照筑,全部執(zhí)行完畢。
三. 總結(jié)
- dyld(動(dòng)態(tài)鏈接器):是蘋果操作系統(tǒng)一個(gè)重要組成部分瘦陈,加載所有的庫(kù)和可執(zhí)行文件凝危。
- dyld加載流程:
- 從
_dyld_start()
開始 ->dyldbootstrap::start()
- 進(jìn)入dyld的
main()
- 檢測(cè)內(nèi)核,配置重定向信息:
rebase_dyld()
- 加載共享緩存
-
dyld2
/dyld3
(閉包模式)- 實(shí)例化主程序
- 加載動(dòng)態(tài)鏈接庫(kù) (主程序和動(dòng)態(tài)庫(kù)的image都會(huì)加載allImage里面:
loadAllImage
晨逝,主程序在第0
位置) - 鏈接主程序蛾默、動(dòng)態(tài)庫(kù)、綁定符號(hào)(非懶加載捉貌、弱符號(hào))等
-
最關(guān)鍵
:初始化方法initializeMainExecutable()
ImageLoader::runInitializers()
ImageLoader::processInitializers()
ImageLoader::processInitializers()
ImageLoader::recursiveInitialization()
-
dyld::notifySingle()
- 此函數(shù)執(zhí)行一個(gè)回調(diào)
_dyld_objc_notify_register()
- 通過(guò)斷點(diǎn)調(diào)試:此回調(diào)是
_objc_init()
初始化時(shí)賦值的一個(gè)函數(shù)load_images()
趴生,里面執(zhí)行了call_load_methods()
函數(shù)阀趴,其作用是循環(huán)調(diào)用各個(gè)類的方法。
- 此函數(shù)執(zhí)行一個(gè)回調(diào)
-
doModInitFunctions()
函數(shù):內(nèi)部會(huì)調(diào)用全局C++對(duì)象的構(gòu)造函數(shù)__attribute__((constructor))
的C函數(shù)
- 返回主程序入口苍匆,執(zhí)行
main
函數(shù)
- 從