iOS底层探索应用程序加载原理

应用程序会依赖很多的库,包括系统的,如UIKitCoreFoundation,还有第三方的。

什么是库?

  • 库是可执行的二进制文件,能够被操作系统加载到内存。
  • 库又分为静态库和动态库

编译过程

image.png

  1. 源文件经过预编译进行词法语法的分析
  2. 将预编译结果编译成汇编
  3. 链接库文件生成可执行文件

动态链接器dyld引出

在我们使用断点断住程序的时候,xCode左侧Thread1最下面会调用start函数这个函数来自libdyld.dylib

image.png

动态链接器dyld源码分析

1.下载dyld最新源码,目前最新是852dyld源码下载

2.全局搜索_dyld_start

image.png

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

image.png
image.png

4.在start函数中最后一步returndyld::_main,进入main函数。

image.png
image.png
能看到main函数占用了八百多行代码。这里不打算一开始就从上往下一行一行分析,而是使用反推法倒着分析,先了解主要流程。

dyld::_main函数源码分析主要流程

1.函数的末尾返回了result,查看result在函数体内有哪些赋值操作。

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.png
registerObjCNotifiers内部:

  • 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

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享