iOS底层探索-dyld(动态链接器)

510 阅读4分钟

1.1、相关概念

1.1.1、Mach-O

  • 是一种文件格式,以下常见的这些都是 Mach-O 格式的:
  • 目标文件 .o
  • 库文件
    • .a
    • framework
    • .dylib:动态库
  • bundle:资源文件包
  • dyld:动态链接器
  • 可执行文件
  • .dsym:符号表

1.1.2、静态库 / 动态库

  • 静态库:本质是一堆.o文件,被多次引用会重复导入,每个APP单独包含静态库内容

  • 动态库:一个已经链接完全的镜像image,多个APP可使用同一个动态库

2、项目启动流程

2.1、预编译

  • 主要处理源代码中以#号开头的
  • 会将包含的 #import文件插入指定位置宏定义展开与替换删除注释

2.2、编译

  • 生成汇编代码文件:.s文件
  • 词法分析、语法分析、语义分析

2.3、汇编

  • 汇编代码文件 --> 机器指令 --> .o文件

2.4、链接

2.4.1、静态链接
  • 每对.h与.m是单独编译的,并不知道其他文件中的地址,但是很多情况需要调用其他文件的方法、函数等,所以需要 链接器将所有的.o文件进行符号收集、解析等最后捏合成一个可执行文件

  • 静态链接后,静态库消失,静态库的.o文件与程序的.o文件被整合成可执行文件

2.4.2、动态链接
  • 项目断点时,lldb中 image list指令可以查看项目中的image

3、点击APP启动过程

3.1、流程概况

  1. app启动 --> exec() --> 分配内存、创建进程
  2. app对应的 可执行文件 加载到内存
  3. dyld动态链接器加载到内存:load dyld
  4. dyld动态链接器进行动态链接:rebase --> binding --> Objc --> initalizers
  5. 调起main()函数

3.2、dyld工作流程

dyld 在iOS15后来到了dyld4版本,内部执行内容与dyld3相比做出了一些调整,,但整体思路变化不大,执行的方法流程图大致如下:

3.2.1、dyld3

dyld流程分析图.png

重要的是从 initializeMainExecutable 开始往后的 初始化可执行文件和动态库 内容

  1. initializeMainExecutableExecutable :可执行文件) image.png

    • runInitializers:images初始化 image.png
    • sMainExecutable->runInitializers:主程序初始化
  2. processInitializersprocess:程序) image.png

  3. recursiveInitializationrecursive:递归)

  • 递归加载初始化 可执行文件所有依赖的动态库到内存(动态库可能还依赖别的动态库) image.png
  1. .notifySingle --> registerObjCNotifiers
  • 准备好objc、GCD,供加载其他动态库使用
  • 通知对 map_imagesload_imagesunmap_image 进行赋值 image.png
  1. _dyld_objc_notify_register(&map_images, load_images, unmap_image)
  • 通过objc_init初始化libobjc 时调用,三个参数是在 recursiveInitialization 中的 .notifySingle 赋值的
3.2.2、dyld4
  • 逻辑流程 1487527-ffbd14bbb0c7006d.png
  • 方法流程 84abf7ac67e84ab8930df57acdd973bf~tplv-k3u1fbpfcp-zoom-in-crop-mark-1304-0-0-0.image.png
  1. prepare

  2. runAllInitializersForMain b3362393ca684e2780db2090fcf270c8~tplv-k3u1fbpfcp-zoom-in-crop-mark-1304-0-0-0.image.png

  3. objc_init

    /***********************************************************************
    * _objc_init
    * Bootstrap initialization. Registers our image notifier with dyld.
    * Called by libSystem BEFORE library initialization time
    **********************************************************************/
    void _objc_init(void)
    {
        static bool initialized = false;
        if (initialized) return;
        initialized = true;
    
        // fixme defer initialization until an objc-using image is found?
        //环境配置
        environ_init();
        //绑定线程析构函数
        tls_init();
        //静态构造函数
        static_init();
        //runtime准备,创建2张表
        runtime_init();
        //异常初始化
        exception_init();
    #if __OBJC2__
        //缓存
        cache_t::init();
    #endif
        //macos专有
        _imp_implementationWithBlock_init();
    
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    
    #if __OBJC2__
        didCallDyldNotifyRegister = true;
    #endif
    }
    
  4. _dyld_objc_notify_register(&map_images, load_images, unmap_image) 25a3983e51d74d01a488dcfa589e59c0~tplv-k3u1fbpfcp-zoom-in-crop-mark-1304-0-0-0.image.png

  5. setObjcNotifiers

  • 不同于dyld3中.notifySingle --> registerObjCNotifiers方法,dyld4在 setObjcNotifiers 中对三个参数进行赋值 5bab0794e5e74f05bee5c17a15fc28c5~tplv-k3u1fbpfcp-zoom-in-crop-mark-1304-0-0-0.image.png

总结流程

  1. 加载动态库到内存

    • dyld 是⽤来加载可执⾏⽂件所依赖的动态库的,然后会对可执⾏⽂件和可执⾏⽂件所依赖的动态库进⾏初始化的操作

    • 在进⾏初始化的操作的时候⾸先初始化libsystem,否则就会报错,因为 libsystem初始化时,会初始化libdispatch(GCD库)libdispatch(GCD库)初始化时,会调用objc_init初始化libobjc可执行文件与其他动态库都需要objc,也可能需要依赖runtime、多线程

    • libobjc初始化时,会调⽤_dyld_objc_notify_register函数,这个函数会给dyld传递三个回调函数

      1. map_images: dyld将image镜像⽂件加载进内存时,会触发该函数
      2. load_images: dyld初始化image会触发该函数,也是load函数在main函数之前执行的原因
      3. unmap_image: dyld将image移除时会触发该函数
    • 然后,dyld会调⽤ map_images 和 load_images 来对image进⾏初始化的操作

  2. rebasebind

    • mach-o 文件中的符号地址都是虚拟地址,在程序启动时,系统会生成一个随机数ALSR虚拟地址 + ALSR = 物理地址(真正调用的地址)
    • rebase:从虚拟地址换算成物理地址的过程称之为 Rebase;修复的是 指向当前镜像内部 的资源指针
    • bind:在编译过程,可执行文件对动态库的方法调用是只声明了符号,在调用 dyld 把这些动态库加载到内存后,需要去相应的动态库中链接对应的方法,找到其指针,然后对可执行文件中的指针执行修复,这个过程称之为 Bind;指向的是 镜像外部 的资源指针
  3. 调起main函数

参考链接:juejin.cn/post/709711…