iOS程序啟動->dyld加載->runtime初始化(初識)

程序的開始main函數與Coding生涯的開始hello World!.png

iOS開發中,main函數是我們熟知的程序啟動入口,但實際上并非真正意義上的入口,因為在我們運行程序,再到main方法被調用之間,程序已經做了許許多多的事情,比如我們熟知的runtime的初始化就發生在main函數調用前,還有程序動態庫的加載鏈接也發生在這階段,本文主要對從程序啟動到main函數中發生的主要事情進行簡單介紹。

其實簡單總結起來就是:

系統先讀取App的可執行文件(Mach-O文件),從里面獲得dyld的路徑,然后加載dyld,dyld去初始化運行環境,開啟緩存策略,加載程序相關依賴庫(其中也包含我們的可執行文件),并對這些庫進行鏈接,最后調用每個依賴庫的初始化方法,在這一步,runtime被初始化。當所有依賴庫的初始化后,輪到最后一位(程序可執行文件)進行初始化,在這時runtime會對項目中所有類進行類結構初始化,然后調用所有的load方法。最后dyld返回main函數地址,main函數被調用,我們便來到了熟悉的程序入口。

下面我們將結合代碼對整個過程進行分析:

dyld加載

這里先說下Mach-O文件。

Mach-O文件格式是 OS X 與 iOS 系統上的可執行文件格式,像我們編譯過程產生的.O文件,以及程序的可執行文件,動態庫等都是Mach-O文件。它的結構如下:

mach-o文件.jpg

有如下幾個部分組成:

Header:保存了一些基本信息,包括了該文件運行的平臺、文件類型、LoadCommands的個數等等。

LoadCommands:可以理解為加載命令,在加載Mach-O文件時會使用這里的數據來確定內存的分布以及相關的加載命令。比如我們的main函數的加載地址,程序所需的dyld的文件路徑,以及相關依賴庫的文件路徑。

Data: 這里包含了具體的代碼、數據等等。

我們可以通過Mach-O文件查看器MachOView查看一個測試項目(這里放上地址)編譯后的可執行文件內容:

Mach-O文件內容.png

這里可以看到,程序需要的dyld的路徑在LC_LOAD_DYLINKER命令里,一般都是在/usr/lib/dyld 路徑下。這里的LC_MAIN指的是程序main函數加載地址,下面還有寫LC_LOAD_DYLIB指向的都是程序依賴庫加載信息,如果我們程序里使用到了AFNetworking,這里就會多一條名為LC_LOAD_DYLIB(AFNetworking)的命令,如下圖

三方庫.png

這里可以看到一些我們比較常用的三方庫:AFNetworking,IQKeyboard等。

系統加載程序可執行文件后,通過分析文件來獲得dyld所在路徑來加載dyld,然后就將后面的事情甩給dyld了。

從dyld開始

dyld: (the dynamic link editor)動態鏈接器,其源碼是開源的

ImageLoader: 用于輔助加載特定可執行文件格式的類,程序中對應實例可簡稱為image(如程序可執行文件,Framework庫,bundle文件)。

dyld接手后得做很多事情,主要負責初始化程序環境,將可執行文件以及相應的依賴庫與插入庫加載進內存生成對應的ImageLoader類的image(鏡像文件)對象,對這些image進行鏈接,調用各image的初始化方法等等(注:這里多數事情都是遞歸的,從底向上的方法調用),其中runtime也是在這個過程中被初始化,這些事情大多數在dyld:_mian方法中被發生,我們可以看段簡潔的代碼:

dyld::_main函數代碼.png

這里的_main函數是dyld的函數,并非我們程序里的main函數。

1.sMainExecutable = instantiateFromLoadedImage(....)與loadInsertedDylib(...)

這一步dyld將我們可執行文件以及插入的lib加載進內存,生成對應的image。

sMainExecutable對應著我們的可執行文件,里面包含了我們項目中所有新建的類。

InsertDylib一些插入的庫,他們配置在全局的環境變量sEnv中,我們可以在項目中設置環境變量DYLD_PRINT_ENV為1來打印該sEnv的值。

環境變量設置.png

運行程序Log如下:

打印出插入庫的log.png

可以看到插入的庫為:libBacktraceRecording.dyliblibViewDebuggerSupport.

有時我們會在三方App的Mach-O文件中通過修改DYLD_INSERT_LIBRARIES的值來加入我們自己的動態庫,從而注入代碼,hook別人的App(相關資料)。

2.link(sMainExecutable,...)和link(image,....)

對上面生成的Image進行進行鏈接。其主要做的事有對image進行load(加載),rebase(基地址復位),bind(外部符號綁定),我們可以查看源碼:

link方法.png

recursiveLoadLibraries(context, preflightOnly, loaderRPaths)

遞歸加載所有依賴庫進內存。

recursiveRebase(context)

遞歸對自己以及依賴庫進行復基位操作。在以前,程序每次加載其在內存中的堆棧基地址都是一樣的,這意味著你的方法,變量等地址每次都一樣的,這使得程序很不安全,后面就出現ASLR(Address space layout randomization,地址空間配置隨機加載),程序每次啟動后地址都會隨機變化,這樣程序里所有的代碼地址都是錯的,需要重新對代碼地址進行計算修復才能正常訪問。

recursiveBind(context, forceLazysBound, neverUnload)

對庫中所有nolazy的符號進行bind,一般的情況下多數符號都是lazybind的,他們在第一次使用的時候才進行bind。

3.initializeMainExecutable()

這一步主要是調用所有image的Initalizer方法進行初始化。這里的Initalizers方法并非名為Initalizers的方法,而是C++靜態對象初始化構造器,atribute((constructor))進行修飾的方法,在LmageLoader類中initializer函數指針所指向該初始化方法的地址。

initiallizer函數指針.jpg

我們可以在程序中設置環境變量DYLD_PRINT_INITIALIZERS為1來打印出程序的各種依賴庫的initializer方法:

可以打印出調用了Initalizers的image的.png

運行程序,系統Log打印如下:

lnitializer調用log.png

(由于打印的比較長,這樣就截取開頭的log)可以看到每個依賴庫對應著一個初始化方法,名稱各有不同。

這里最開始調用的libSystem.dylib的initializer function比較特殊,因為runtime初始化就在這一階段,而這個方法其實很簡單,我們可以在這里看到init.c源碼,主要方法如下:

libSystem_initializer方法.jpg

其中libdispatch_init里調用了到了runtime初始化方法_objc_init.我們可以、在程序中打個符號斷點來驗證:

_objc_init符號斷點.png

運行程序,然后斷點命中,我們來看下調用棧:

objc_init調用棧.png

這里可以看到_objc_init調用的順序,先libSystem_initializer調用libdispatch_init再到_objc_init初始化runtime。

runtime初始化后不會閑著,在_objc_init中注冊了幾個通知,從dyld這里接手了幾個活,其中包括負責初始化相應依賴庫里的類結構,調用依賴庫里所有的laod方法。

就拿sMainExcuatable來說,它的initializer方法是最后調用的,當initializer方法被調用前dyld會通知runtime進行類結構初始化,然后再通知調用load方法,這些目前還發生在main函數前,但由于lazy bind機制,依賴庫多數都是在使用時才進行bind,所以這些依賴庫的類結構初始化都是發生在程序里第一次使用到該依賴庫時才進行的。

main函數被調用

當所有的依賴庫庫的lnitializer都調用完后,dyld::main函數會返回程序的main函數地址,main函數被調用,從而代碼來到了我們熟悉的程序入口。

main函數入口.png

結語

這里只是簡單了概括了從程序啟動->dyld加載依賴庫->runtime初始化->main 的過程。但這階段還有很多事情未講,如果想深入了解還得結合源碼來學習,這里我已經將dyld和runtime源碼都放在這了,大家可直接下載,也可以從opensource-apple下載。

再嘮嗑會

dyld源碼前前后后讀個大概懂,花了我3個多禮拜的空閑時間,由于C和C++基礎并不是很好,所以特意跑回學校買了幾本書補了下基礎,不過讀源碼的這段時間還是挺累的。

為什么要去讀源碼,主要是看別人的文章時并不能很好解決我的某些疑問,而且只有真正去認識源碼,去親身體會才能加深對它的理解。

學習的旅途雖然頗累,但一路下來收獲頗多。加油!

前行路,路漫漫,一人一酒似逍遙。

一張圖.jpg

參考資料

1.Mach-O 可執行文件

2.dylib動態庫加載過程分析

3.iOS 程序 main 函數之前發生了什么

4.今日頭條iOS客戶端啟動速度優化

5.App 啟動時間:過去,現在和未來

6.優化 App 的啟動時間

7.dyld在hook方面的小東西

喜歡的話點個喜歡唄^_^

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,791評論 6 545
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,795評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,943評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,057評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,773評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,106評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,082評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,282評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,793評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,507評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,741評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,220評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,929評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,325評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,661評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,482評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,702評論 2 380

推薦閱讀更多精彩內容