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

相关文章
相关标签/搜索