dyld 源碼學習

背景

衆所周知一個 iOS App 的程序入口是main.m, 但系統是怎麼找到main.m的估計不少人就有疑問了,本文將詳細解釋這個問題.html

dyld

dyld(the dynamic link editor), 動態連接器,是專門用來加載動態庫以及主程序的庫. 當kernel作好程序的啓動準備工做以後,系統的執行由內核態轉換爲用戶態,由 dyld 首先開始工做,iOS 中用到的全部系統framework都是動態庫,好比最經常使用的UIKit.framework,Foundation.framework, 而這些動態庫是手機內全部App共享的,因此須要在咱們 App運行前加載進來. dyld 主要的工做有:git

  • 初始化 App 運行環境
  • 連接依賴的動態庫以及主程序
  • rebase / binding
  • 返回 main.m 的函數地址 接下來分析下dyld 的源碼.

源碼分析

在 demo 里加[NSObject init]的符號斷點來看下: github

能夠看到入口函數事在 dyid_start方法裏的 dyldbootstrap::start方法,接下來去源碼裏看看. 在 dyld 源碼裏找到 dyldStartup.s找到了 __dyld_start,這裏只截取了arm架構的部分.
經過註釋能夠看到有調用 dyldbootstrap::start,那順着調用再往下看. 在 dyldInitialization.cpp中找到了 start

  • 首先經過slideOfMainExecutable拿到隨機地址的偏移量
  • 調用rebaseDyld重定位
  • mach_init() mach消息初始化
  • __guard_setup() 棧溢出保護 接下來調用了dyld::_main,將返回值傳遞給__dyld_start的調用main.m函數.

dyld::_main是dyld中的關鍵方法,代碼也很是多,它的實現能夠分爲如下幾步: (關鍵部分有註釋)

  • 設置運行環境
  • 加載共享緩存
  • 加載主程序
  • 加載動態庫
  • 連接主程序
  • 連接動態庫
  • 初始化主程序
  • 返回入口地址

0x01 設置運行環境

0x02 加載共享緩存

checkSharedRegionDisable是檢查共享緩存是否禁用,裏面能夠看到一行註釋,iOS 必須開啓共享緩存才能運行.

static void checkSharedRegionDisable(const dyld3::MachOLoaded* mainExecutableMH, uintptr_t mainExecutableSlide) {
	// iOS cannot run without shared region
}
複製代碼

接下來調的mapSharedCache()就是加載共享緩存的邏輯,就不深刻了.bootstrap

0x03 加載主程序

這一步將主程序 Mach-O 加載進內存,並實例化了一個 ImageLoader.先看下 instantiateFromLoadedImage的調用棧:
其中 ImageLoader是一個抽象類,它的兩個子類 ImageLoaderMachOCompressedImageLoaderMachOClassic負責把 Mach-O 實例化爲 Image.但要用哪一個子類來進行實例化是經過 sniffLoadCommands來判斷Mach-O 文件的 LINKEDIT 是classic或者compressed.

0x04 加載動態庫

遍歷 DYLD_INSERT_LIBRARIES環境變量,而後調用 loadInsertedDylib加載.

0x05 連接主程序

調用 link連接主程序,內核調用的是 ImageLoader::link 函數,主要是作了加載動態庫、rebase、binding 等操做,代碼比較多,我就不貼了,在附件的源碼上有我寫的詳細註釋.

0x06 連接動態庫

這一步將前面調用 addImage()函數保存在sAllImages 中的動態庫列表循環調用 link進行連接,而後調registerInterposing註冊符號替換. 注意這裏的 i+1, 由於sAllImages中第一項是主程序,因此取 i+1項.

0x07 初始化主程序

這一步由initializeMainExecutable()完成。dyld會優先初始化動態庫,而後初始化主程序。該函數首先執行runInitializers(),內部再依次調用processInitializers()、recursiveInitialization(),在recursiveInitialization()函數裏找到了 notifySingle();

context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
複製代碼

再往下找到sNotifyObjCInit,再去找它的賦值找到registerObjCNotifiers,從函數註釋來看是用objc runtime來調的,這塊以後再看.在查閱一些資料以後得知,這裏的sNotifyObjCInit就是調用 objc 中的 load_images,它調用全部的 load 方法,在調用完 load 方法之後調用了緩存

bool hasInitializers = this->doInitialization(context);
複製代碼

doInitialization又調用了doModInitFunctions, 也就是constuctor方法,關於這個方法能夠參看連接.安全

0x08 返回入口地址

這裏調用主程序的 getEntryFromLC_MAIN,就是從``Load Command 中讀取LC_MAIN 入口,若是沒有,就讀取LC_UNIXTHREAD ,而後跳到入口處執行,就回到了咱們熟悉的main.m`.

說明

1.dyld 源碼: opensource.apple.com/tarballs/dy…
2.本文分析所用版本: dyld-635.2
3.帶註釋 dyld源碼地址: Github
bash

參考

1.iOS 應用逆向與安全
2.www.dllhook.com/post/238.ht…
3.blog.sunnyxx.com/2014/08/30/…
架構

相關文章
相關標籤/搜索