目錄
前言
main before
dyld簡(jiǎn)介
dyld加載流程
總結(jié)
前言
對(duì)于一個(gè)程序的加載隙姿,我們看到的入口函數(shù)都是main.m里面的main函數(shù)茵烈,這讓我們很容易的認(rèn)為程序是從這里開(kāi)始執(zhí)行的。其實(shí)不然,在這之前,故事已經(jīng)悄悄展開(kāi)了...
main before
在main函數(shù)之前,究竟做了什么师坎?我們新建一個(gè)工程,在main.m文件里給我們的main函數(shù)打上斷點(diǎn)堪滨,探探究竟胯陋。
此時(shí)我們可以看到,在main之前執(zhí)行了一個(gè)start函數(shù)袱箱,敲上bt指令查看遏乔,可以看到libdyld.dylib start,再敲上up指令发笔。
這時(shí)我們看到dylib的start并不是我們想要的盟萨,有點(diǎn)失望......重新整整思路,我們會(huì)發(fā)現(xiàn)筐咧,load函數(shù)在main函數(shù)之前執(zhí)行鸯旁,馬上為load函數(shù)打下斷點(diǎn)探一探噪矛。
在這斷點(diǎn)下量蕊,我們可以看到函數(shù)調(diào)用棧的調(diào)用順序,發(fā)現(xiàn)首先調(diào)用_dyld_start,馬上跟進(jìn)
查看_dyld_start艇挨,我們看到調(diào)用的是dyldbootstrap這個(gè)類的start函數(shù)残炮,此時(shí)想要繼續(xù)探究就必須查看蘋(píng)果的dyld源碼(開(kāi)源),這里的版本是635.2缩滨。
dyld簡(jiǎn)介
dyld全名為dynamic loader势就。在程序啟動(dòng)運(yùn)行時(shí)會(huì)依賴很多系統(tǒng)動(dòng)態(tài)庫(kù),系統(tǒng)動(dòng)態(tài)庫(kù)會(huì)通過(guò)dyld(動(dòng)態(tài)加載器)(默認(rèn)是/usr/lib/dyld)加載到內(nèi)存中脉漏,系統(tǒng)內(nèi)核讀取程序可執(zhí)行文件信息做一些準(zhǔn)備工作苞冯,接著會(huì)將工作交給dyld。由于很多程序需要使用系統(tǒng)動(dòng)態(tài)庫(kù)侧巨,不可能在每個(gè)程序加載時(shí)都去加載所有的系統(tǒng)動(dòng)態(tài)庫(kù)舅锄,為了優(yōu)化程序啟動(dòng)速度和利用動(dòng)態(tài)庫(kù)緩存,iOS系統(tǒng)采用了共享緩存技術(shù)司忱,將所有系統(tǒng)庫(kù)(私有與公有)編譯成一個(gè)大的緩存文件皇忿,這就是dyld_shared_cache,該緩存文件存在iOS系統(tǒng)下的 /System/Library/Caches/com.apple.dyld/目錄下。
dyld加載流程
從_dyld_start函數(shù)開(kāi)始設(shè)置相關(guān)信息坦仍,并在最后調(diào)用了_mian()函數(shù)鳍烁。
進(jìn)入_main()函數(shù),我們可以看到dyld加載的主要流程繁扎。
1.設(shè)置上下文信息幔荒,配置進(jìn)程是否受限
首先,調(diào)用setContext,設(shè)置上下文信息铺峭,包括后面需要調(diào)用的函數(shù)及傳入?yún)?shù)墓怀。然后,調(diào)用configureProcessRestrictions卫键,設(shè)置進(jìn)程是否受限傀履。
2.配置環(huán)境變量,獲取當(dāng)前運(yùn)行架構(gòu)
調(diào)用checkEnvironmentVariables莉炉,如果allowEnvVarsPath與allowEnvVarsPrint為空钓账,直接跳過(guò),否則調(diào)用processDyldEnvironmentVariable處理并設(shè)置環(huán)境變量絮宁。
3.檢查共享緩存是否映射到了共享區(qū)域
首先梆暮,調(diào)用 checkSharedRegionDisable 檢查是否開(kāi)啟共享緩存,在iOS中是必須開(kāi)啟的绍昂,接著調(diào)用 mapSharedCache函數(shù)啦粹,將共享緩存映射到共享區(qū)域。
4.加載可執(zhí)行文件窘游,生成一個(gè)ImageLoader 實(shí)例對(duì)象
調(diào)用 instantiateFromLoadedImage 函數(shù)實(shí)例化一個(gè) ImageLoader 對(duì)象唠椭。該函數(shù)先調(diào)用 isCompatibleMachO 來(lái)判斷文件的架構(gòu)是否和當(dāng)前的架構(gòu)兼容,然后調(diào)用 ImageLoderMachO::instantiateMainExecutable 來(lái)加載文件生成實(shí)例忍饰,并將 image 添加到全局 sAllImages 中贪嫂。
5.加載所有插入的庫(kù)
遍歷 DYLD_INSERT_LIBRARIES 環(huán)境變量,調(diào)用 loadInsertedDylib 加載艾蓝。
6.鏈接主程序
調(diào)用 link 鏈接主程序力崇。內(nèi)核調(diào)用的是ImageLoader::link 函數(shù)。
7.鏈接所有插入的庫(kù)赢织,執(zhí)行符號(hào)替換
對(duì) sAllimages (除了主程序的Image外)中的庫(kù)調(diào)用link進(jìn)行鏈接亮靴,然后調(diào)用 registerInterposing 注冊(cè)符號(hào)插入。
8.執(zhí)行初始化方法
initializeMainExecutable 執(zhí)行初始化方法于置,其中 +load 和 constructor 方法就是在這里執(zhí)行茧吊。 initializeMainExecutable 內(nèi)部先調(diào)用了動(dòng)態(tài)庫(kù)的初始化方法,后調(diào)用主程序的初始化方法俱两。
該函數(shù)依次調(diào)用了 runInitializers饱狂、processInitializers、recursiveInitialization宪彩、notifySingle休讳。也就是我們?cè)诤瘮?shù)調(diào)用棧里看到的順序
在notifySingle函數(shù)里我們找不到 load_images 的調(diào)用,但分析發(fā)現(xiàn)一個(gè)可疑的函數(shù)指針
此處調(diào)用了sNotifyObjCInit 尿孔,發(fā)現(xiàn) sNotifyObjCInit? 是在下面的位置賦值的俊柔。繼續(xù)尋找筹麸,可以找到調(diào)用該函數(shù)的位置。
當(dāng)我們繼續(xù)尋找誰(shuí)調(diào)用了_dyld_objc_notify_register()函數(shù)時(shí)雏婶,發(fā)現(xiàn)在dyld源碼里找不到物赶。從函數(shù)的定義來(lái)看,該接口是供 objc runtime 調(diào)用的留晚,我們可以在新工程里為 _dyld_objc_notify_register 下符號(hào)斷點(diǎn)查看酵紫。
這時(shí),打開(kāi)objc 源碼 查看_objc_init()函數(shù)错维。
看到_dyld_objc_notify_register()函數(shù)的第二個(gè)參數(shù)時(shí)奖地,我們找到了 load_images ,查看load_images()函數(shù)發(fā)現(xiàn)一個(gè)回調(diào) call_load_methods()赋焕,繼續(xù)查看call_load_methods()函數(shù)参歹,發(fā)現(xiàn)里面循環(huán)調(diào)用 call_class_loads(),這也就說(shuō)明為什么load函數(shù)比main函數(shù)先調(diào)用隆判。到這里犬庇,我們找到函數(shù)調(diào)用棧的所有函數(shù),接下來(lái)返回dyld侨嘀。
9.尋找主程序入口
調(diào)用 getEntryFromLC_MAIN臭挽,從 Load Command 讀取LC_MAIN入口,如果沒(méi)有LC_MAIN入口飒炎,就讀取LC_UNIXTHREAD埋哟,然后跳到入口處執(zhí)行笆豁,這樣就來(lái)到了我們熟悉的main函數(shù)處郎汪。
總結(jié)
上面對(duì)dyld加載大概走了一個(gè)流程,很多細(xì)節(jié)還沒(méi)探究闯狱。最后附上一張圖煞赢!