➠更多技術乾貨請戳:聽雲博客 前言 最近看 ObjC的runtime 是怎麼實現 +load 鉤子函數的實現。進而引申分析了 dyld 處理 Mach-O 的這部分機制。 1.簡單分析 Mach-O 在dyld 中是如何被載入到記憶體中的; 2.分析了 +load 的 特殊載入時機; + load 上 ...
前言
最近看 ObjC的runtime 是怎麼實現 +load 鉤子函數的實現。進而引申分析了 dyld 處理 Mach-O 的這部分機制。
1.簡單分析 Mach-O 在dyld 中是如何被載入到記憶體中的;
2.分析了 +load 的 特殊載入時機;
+ load
上圖的調用棧告訴我們哪些函數被調用了。
dyld 是Apple 的動態鏈接器;在 xnu 內核為程式啟動做好準備後,就會將 PC 控制權交給 dyld 負責剩下的工作 (dyld 是運行在 用戶態的, 這裡由 內核態 切到了用戶態)。
每當有新的鏡像載入之後,都會執行 load-images 方法進行回調,這裡的回調是在整個ObjC runtime 初始化時 -objc-init 註冊的 :
有新的鏡像被 map 到 runtime 時,調用 load-images 方法,並傳入最新鏡像的信息列表 infoList:
這裡的鏡像就是 一些 System framework 的二進位。
進入 下圖函數 load-images-nolock 查找 load 函數
調用 prepare-load-methods 對 load 方法的調用進行準備(將需要調用 load 方法的類添加到一個列表中)
調用 -getObjc2NonlazyClassList 獲取所有的類的列表之後,會通過 remapClass 獲取類對應的指針,然後調用 schedule-class-load 遞歸地 將當前類和沒有調用 + load 父類進入列表。
在執行 add-class-to-loadable-list(cls) 將當前類加入載入列表之前,會先把父類加入待載入的列表,保證父類在子類前調用 load 方法。
在將鏡像載入到運行時、對 load 方法的準備就緒,執行 call-load-methods,開始調用 load 方法:
其中 call-class-loads 會從一個待載入的類列表 loadable-classes 中尋找對應的類,然後找到 @selector(load) 的實現並執行。
分析到這裡,已經能得知 load 函數是如何被調用的。
接下來分析 dyld 這部分怎麼載入鏡像的
1.1 數據結構
mach-o 文件頭 操作。
1.2 ImageLoader
每一個載入的 Mach-O 文件都會存在這樣一個ImageLoader 的 實例,上圖可以看出 這裡ImageLoader是一個抽象類,每一種具體的Mach-O 文件都會繼承 ImageLoader類, 繼承關係 如下圖:
在載入時會根據Mach-O的格式不同選擇生成不用的實例。
1.3 -main
在調用-main 函數之後,做了一下幾件事情:
-
選擇運行環境(iOS 模擬器)
-
初始化數據、設置全局變數、上下文信息
-
檢查文件是否Restricted
走完這些流程,就會調用 instantiateFromLoadedImage 函數,開始載入Mach-O 並且實例化 為 ImageLoader。
1.4 instantiateFromLoadedImage
這個函數做了三件事情:
-
檢查Mach-O 文件是否合法
-
初始化 ImageLoader 實例
-
調用addImage 函數添加 初始化後的實例到管理模塊中
1.5 isCompatibleMachO
Mach-O 文件的合法性檢查:
-
mach-header 中的 cputype與當前運行的CPU 版本是否支持
-
mach-header 中的 subtype 在該CPU 架構下的所有版本都可以支持
cputype 就是CPU 平臺, x86,ARM ,POWERPC 等, 而subtype 就是同一個平臺下的不同版本, 例如:arm7,arm7.
1.6 ImageLoaderMachO: : instantiateMainExecutable
該函數主要通過 sniffLoadCommands 函數來判斷 Mach-O 文件是否是壓縮過的,然後分別 選擇不同的 子類實例化。
1.7 sniffLoadCommands
這個函數主要做兩件事情
-
判斷Mach-O文件是classic的還是compressed的。
-
獲取mach-O文件的segment的數量。
1.8 ImageLoaderMachOClassic: :instantiateMainExecutable
classic 與 compressed 的初始化大同小異,先分析Classic 的實現
可以看到載入的核心代碼 還在 instantiateStart 函數中
1.9 instantiateStart
這裡仍然沒有出現載入的核心代碼,只是根據之前獲得的數據申請分配了記憶體,並計算 segments的 指針。 ImageLoaderMachOClassic 的構造函數才是載入 的核心邏輯。
2.0 ImageLoaderMachOClassic
根據Mach-O 文件 segments 將數據載入到 記憶體中, 任何返回 調用 addImage 函數。
2.1 addImage
這個函數只是做了數據更新
-
將image 添加到管理容器中
-
更新了記憶體分佈的信息
end
整個載入過程基本分為三個步驟:
-
合法加測
-
解析Mach-O文件頭信息,將segments 的具體信息 構建到image 的實例中
-
添加image 到管理容器
根據 dyld的源代碼的粗略分析, 更多信息需要分析 xnu 內核代碼。
參考
ObjC runtime 源代碼
dyld 源代碼
《Mac OSX and iOS Internals》
原文鏈接:http://blog.tingyun.com/web/article/detail/1346