iOS底層探索應用程序加載原理

應用程序會依賴不少的庫,包括系統的,如UIKitCoreFoundation,還有第三方的。c++

什麼是庫?bootstrap

  • 庫是可執行的二進制文件,可以被操做系統加載到內存。
  • 庫又分爲靜態庫和動態庫

編譯過程

image.png

  1. 源文件通過預編譯進行詞法語法的分析
  2. 將預編譯結果編譯成彙編
  3. 連接庫文件生成可執行文件

動態連接器dyld引出

在咱們使用斷點斷住程序的時候,xCode左側Thread1最下面會調用start函數這個函數來自libdyld.dylib緩存

image.png

動態連接器dyld源碼分析

1.下載dyld最新源碼,目前最新是852dyld源碼下載markdown

2.全局搜索_dyld_startapp

image.png

3.全局搜索c++函數的命名空間dyldbootstrap,在命名空間所在文件搜索start函數函數

image.png image.png

4.在start函數中最後一步returndyld::_main,進入main函數。oop

image.png image.png 能看到main函數佔用了八百多行代碼。這裏不打算一開始就從上往下一行一行分析,而是使用反推法倒着分析,先了解主要流程。源碼分析

dyld::_main函數源碼分析主要流程

1.函數的末尾返回了result,查看result在函數體內有哪些賦值操做。post

image.png

2.下面兩處都是sMainExecutable在調用函數的返回值測試

image.png image.png

3.查看sMainExecutable _main函數中的出處

image.png

查看初始化函數instantiateFromLoadedImage

image.png 返回machO讀取對象。

4.連接sMainExecutable

image.png

5.弱引用綁定主程序

image.png

6.運行全部已經初始化的東西

image.png

7.通知主程序進入的main()函數

image.png 主線流程已經分析完,下面跟隨主線流程看看細節

dyld::_main函數源碼分析流程細節補充

1.首先_main函數前兩百多行代碼是條件準備,包括環境、平臺、版本、路徑、主機等信息的處理 加載插入的動態庫

2.讀取共享緩存

image.png

3.在連接主程序前面,讀取插入的動態庫

image.png

4.在連接主程序後面,連接插入的動態庫 image.png

initializeMainExecutable分析

image.png 插入的動態庫、主程序都調用了runInitializers函數

image.png

image.png

image.png

由於gLinkContext.notifySingle = &notifySingle;

image.png

image.png

image.png

image.png

全局搜索registerObjCNotifiers的調用

image.png

發現是_dyld_objc_notify_register調用的,並且這個函數是在objc源碼是函數_objc_init調用的

image.png 接下來在objc源碼中_objc_init打上斷點,打印調用棧

image.png 再次使用反推法:

libdispatch源碼下載

libSystem源碼下載

由函數調用棧能看到

  • libobjc.A.dylib調用了_objc_init
  • libdispatch.dylib調用了_os_object_init
    • 分析_os_object_init實現:

image.png _os_object_init函數內部調用了_objc_init

  • libdispatch.dylib調用了libdispatch_init
    • 分析libdispatch_init實現:

image.png libdispatch_init調用了_os_object_init

  • libSystem.B.dylib調用了libSystem_initializer
    • 分析libSystem_initializer實現:

image.png

libSystem_initializer調用了libdispatch_init

  • 調用了dyld ImageLoaderMachO::doModInitFunctions函數
    • 分析doModInitFunctions實現:

image.png image.png 也就是說doModInitFunctions確保libSystem初始化

  • dyld ImageLoaderMachO::doInitialization
    • 分析doInitialization實現:

image.png doInitialization調用了doModInitFunctions

  • dyld ImageLoader::recursiveInitialization
    • 分析recursiveInitialization實現:

image.png 這一塊initializeMainExecutable分析流程也提到了

_dyld_objc_notify_register

_dyld_objc_notify_register(&map_images, load_images, unmap_image);

_objc_init_dyld_objc_notify_register有三個參數&map_imagesload_imagesunmap_image_dyld_objc_notify_register把參數原封不動的傳給了registerObjCNotifiers

image.pngregisterObjCNotifiers內部:

  • sNotifyObjCMapped = mapped
  • sNotifyObjCInit = init
  • sNotifyObjCUnmapped = unmapped
  • 因此map_images調用就是sNotifyObjCMapped的調用

全局搜索sNotifyObjCMapped在哪裏調用?

image.png

全局搜索notifyBatchPartial在哪裏調用?

image.png

也就是map_images(sNotifyObjCMapped)registerObjCNotifiers->notifyBatchPartial函數內調用

  • 同理load_images調用就是sNotifyObjCInit的調用

全局搜索sNotifyObjCInit在哪裏調用?

image.png

全局搜索notifySingle在哪裏調用?

image.png

load、c++函數、main函數調用順序分析

測試代碼準備:

int main(int argc, char * argv[]) {
    printf("main函數調用 %s \n",__func__);
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
__attribute__((constructor)) void cppFunc(){
    printf("C++函數調用 : %s \n",__func__);
}

複製代碼
@implementation ViewController
+ (void)load{
    NSLog(@"\n %s 函數調用",__func__);
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}
@end
複製代碼

執行結果以下: image.png 調用順序依次爲loadc++函數、main函數

load方法調用時機分析

_objc_init調用_dyld_objc_notify_register_dyld_objc_notify_register第二個參數load_images定義以下:

  • 查找load的方法:

image.png image.png image.png image.png

  • 調用全部load方法

image.png

image.png 因此load的方法在_objc_init即將結束時就調用了。

c++函數調用時機分析

c++函數內打上斷點,查看函數調用棧

image.png 在調用doModInitFunctions後調用了cppFunc,doModInitFunctions是讀取machO的,因此這個c++函數是寫在machO中的

main函數調用時機分析

dyld源碼中搜索_dyld_start的彙編 image.png 在調用完dyldbootstrap::start後會調到main函數

實操驗證:

image.png

  • 斷點過掉dyldbootstrap::start
  • register read讀取寄存器
  • rax就是main函數

dyld流程圖

dyld.png

相關文章
相關標籤/搜索