dyld加載應用啓動原理詳解

咱們都知道APP的入口函數是main(),而在main()函數調用以前,APP的加載過程是怎樣的呢?接下來咱們一塊兒來分析APP的加載流程。

一. 準備工做

因爲load()main()調用更早,所以咱們建立一個工程,在控制器中寫一個load()函數,並斷點運行,以下圖:bootstrap

運行起來以後,能夠清晰的看到比較詳細的函數調用順序,從_dyld_start()dyld:notifySingle(),頻率出現最多的就是這個dyld,那麼dyld是什麼?它在作什麼?緩存

簡單來講dyld是一個動態連接器,用來加載全部的庫和可執行文件。接下來咱們將經過對dyld源碼分析,去追蹤dyld到底作了什麼?markdown

_dyld_start

二. dyld加載流程分析

1. 首先下載dyld源碼

2. 打開dyld源碼工程,根據上圖dyldbootstrap::start爲關鍵字搜索dyldbootstrap中調用的start(),以下圖:

dyldbootstrap::start

3. 進入dyld的start函數

其中rebaseDyld()分析以下:閉包

4. 進入dyld的main函數

注:由於dyld::main()函數代碼比較多,如下會分段介紹,也會介紹相對來講比較重要的函數。app

4.1 內核檢測

4.2 獲取main執行文件的cdHash緩存區

4.3 獲取CPU信息

// 獲取CPU信息
    getHostInfo(mainExecutableMH, mainExecutableSlide);
複製代碼

4.4 設置MachHeader和內存偏移量

// 設置MachHeader和內存偏移量Slide
    uintptr_t result = 0;
    sMainExecutableMachHeader = mainExecutableMH;
    sMainExecutableSlide = mainExecutableSlide;
複製代碼

4.5 設置上下文

// 設置上下文,保存信息
    setContext(mainExecutableMH, argc, argv, envp, apple);
複製代碼

4.6 配置進程限制

4.7 檢測環境變量

4.8 打印環境配置信息

// 打印環境配置信息
    if ( sEnv.DYLD_PRINT_OPTS )
        printOptions(argv);
    if ( sEnv.DYLD_PRINT_ENV ) 
        printEnvironmentVariables(envp);
複製代碼

此處能夠本身定義環境變量配置,回到剛纔建立的新工程中,在Edit Scheme -> Run -> Arguments -> Environment Variables 添加兩個參數 DYLD_PRINT_OPTSDYLD_PRINT_ENV,並設置測試value值,以下:ide

運行程序,可看到以下打印信息:函數

4.9 加載共享緩存(若是沒有共享緩存,iOS將沒法運行)

主要函數mapSharedCache()以下:oop

4.10 dyld配置

(1) dyld3(閉包模式)源碼分析

iOS11版本以後,引入dyld3閉包模式(ClosureMode):加載速度更快,效率更高。測試

開始執行閉包模式

判斷是否開啓了閉包模式

啓動閉包模式加載

其中launchWithClosure加載閉包,會把後面說到的dyld2的大部分流程都封裝到launchWithClosure()這個函數裏面了,這裏再也不細說launchWithClosure,由於在接下來的dyld2(非閉包)中會詳細解釋整個dyld加載的流程,也就是launchWithClosure實現過程。

(2) dyld2(非閉包模式)

開始執行閉包模式

把dyld加入到UUID列表

// 把dyld加入到UUID列表
    addDyldImageToUUIDList();
複製代碼

配置緩存代理

4.11 建立主程序的Image

開始建立主程序的Image,經過instantiateFromLoadedImage(),調用instantiateMainExecutable(),實例化具體的Image類,最後生成的對象,設置到gLinkContext中。

4.12 設置動態庫的版本

// 加載完共享緩存,設置動態庫的版本
    checkVersionedPaths();
複製代碼

4.13 加載插入的動態庫

// 加載插入的動態庫
    if	( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
        for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
            loadInsertedDylib(*lib);
    }
複製代碼

4.14 連接主程序和動態庫

  • 其中的link()函數,函數體調用了image->link(),函數具體以下:

  • 判斷是否須要從新加載全部的Image

4.15 綁定主程序和動態庫

4.16 初始化主程序

根據Demo上的堆棧信息,以下:

終於看到熟悉的函數了,那麼dyld加載流程也快結束了。 根據堆棧信息,獲取函數調用層級關係。

// 初始化主程序
    initializeMainExecutable(); 
複製代碼
  • 查找runInitializers()dyld::initializeMainExecutable() -> ImageLoader::runInitializers()

加載主程序

  • 查找processInitializers()ImageLoader::runInitializers() -> ImageLoader::processInitializers()

runInitializers

  • 查找recursiveInitialization()ImageLoader::processInitializers() -> ImageLoader::recursiveInitialization()

processInitializers

  • 查找notifySingle()ImageLoader::recursiveInitialization() -> dyld::notifySingle()

recursiveInitialization

  • 查找load_images():在 dyld::notifySingle()中並無找到load_images(),可是找到了sNotifyObjCInit(),該字段是objc函數回調。在 dyld::notifySingle()中執行了這個回調,那就須要追溯到誰去註冊的這個回調了。
  • 全局查找sNotifyObjCInit()賦值的地方。在registerObjCNotifiers()中賦值,以下:

  • 全局查找registerObjCNotifiers,在_dyld_objc_notify_register()中調用,且第二個參數是咱們須要的。以下:

  • 全局查找_dyld_objc_notify_register(),並無在dyld源碼庫裏找到,此時須要在源工程中,打符號斷點_dyld_objc_notify_register,從新編譯執行,能夠看到是_objc_init()調用了。此時只能去查找objc源碼了。
  • objc源碼分析,在objc-os.mm文件中找到_objc_init()函數,其中註冊了_dyld_objc_notify_register回調。

objc-os.mm

其中第二個參數就是load_images(),在load_images()中也找到了call_load_methods()

此時初始化程序還未執行完成,回到以前的 ImageLoader::recursiveInitialization()方法中。

  • 執行this->doInitialization()函數

  • 發送通知,初始化主程序完成。

4.17 進入主程序

// 通知此進程將要進入程序main()
    notifyMonitoringDyldMain();
複製代碼

到此,start() -> main(),所有執行完畢。

三. 總結

  • dyld(動態連接器):是蘋果操做系統一個重要組成部分,加載全部的庫和可執行文件。
  • dyld加載流程:
    • _dyld_start()開始 -> dyldbootstrap::start()
    • 進入dyld的main()
    • 檢測內核,配置重定向信息:rebase_dyld()
    • 加載共享緩存
    • dyld2 / dyld3(閉包模式)
      • 實例化主程序
      • 加載動態連接庫 (主程序和動態庫的image都會加載allImage裏面:loadAllImage,主程序在第0位置)
      • 連接主程序、動態庫、綁定符號(非懶加載、弱符號)等
      • 最關鍵:初始化方法initializeMainExecutable()
        • ImageLoader::runInitializers()
        • ImageLoader::processInitializers()
        • ImageLoader::processInitializers()
        • ImageLoader::recursiveInitialization()
        • dyld::notifySingle()
          • 此函數執行一個回調_dyld_objc_notify_register()
          • 經過斷點調試:此回調是_objc_init()初始化時賦值的一個函數load_images(),裏面執行了call_load_methods()函數,其做用是循環調用各個類的方法。
        • doModInitFunctions()函數:內部會調用全局C++對象的構造函數 __attribute__((constructor))的C函數
      • 返回主程序入口,執行main函數
相關文章
相關標籤/搜索