十七、启动流程(一)dyld3

本文由快学吧个人写作,以任何形式转载请表明原文出处

一、资料准备

  1. objc4-818.2

对应mac的版本是11.1。可根据自己的系统版本挑选可以进行调试的源码。

  1. dyld3-852

因为mac的版本我没有更新过,一直停留在11.1,所以我选择的dyld源码也是老版本的dyld3,最新版本的dyld4会在系统更新之后再单独写一篇关于dyld4的个人见解。

  1. 关于dyld和库,以及framework的一些简单的内容放在了上一章。另外,源码中有注释的地方一定是比较重要的地方,否则不会进行注释。所以自己探索的时候,有注释的地方可当作重点来看。

二、项目准备

新建一个正常的iOS的项目。

三、思路

  1. 一个常规的iOS项目的启动会从main.m中的main函数开始。而在开发中,曾发现+load方法会比main.m更先调用。
  2. 既然main函数不是最早的程序启动入口,那么+load是不是?或者说load之前是否还做过一些其他的操作?
  3. 库是怎么进入到了项目中的?dyld到底做了什么?

四、程序的main函数之前还做了什么

  1. 在准备的项目中的ViewController里面实现+load方法。在main.m处打上断点。运行程序,运行结果如下图。
  1. 可以看到load是在main函数之前就调用了。那么load是怎么调用的?在load处打上断点。运行程序,查看堆栈信息(lldb命令是bt,xcode自带查看堆栈信息的地方)。
  1. 调用信息是从下向上看,所以在load之前,最开始调用的是_dyld_start

五、如何查找dyld的执行流程

  1. 打开dyld852源码,全局搜索_dyld_start。只看arm64架构下的。汇编看不懂无所谓,后面有注释。
  1. 直接搜::start或者dyldbootstrap::start(是搜不到的,所以start不是一个c++的函数,那么作为一个oc的底层,最可能的就是c函数,c函数的书写规范是:void + 空格 + 函数名 + (,例如:void test()。所以搜空格 + start(
  1. dyld::main可以直接跳转到源码,直接进入dyld的main源码,这里就是dyld的主程序,也就是dyld做了什么。

六、dyld的执行流程

1. 怎么去看dyld的main函数

明确目的 : 为了找dyld怎么加载的库,怎么就调用到了ViewController的+load方法。+load方法可是objc库中的方法,也就是在libobjc动态库中,而不是dyld中的方法。

  1. 看参数。dyld的main函数都有哪些参数,既然有这些参数,那么基本都是要用的。
  2. 有备注的一般比较重要,可以从中挑选重要的内容。
  3. 只看arm64架构下的。

2. dyld的简要流程

1. 环境变量的处理

2. 加载共享缓存

检查共享缓存是否可用,iOS中是必须开启共享缓存的。映射共享缓存到共享区。像我们常用的UIKit、CoreFoundation框架,都是要映射加载到共享缓存区的。

3. 把dyld自己加到uuid列表中

4. 为主可执行程序实例化一个镜像加载器

说人话就是实例化一个主程序。

5. 加载所有的动态库

把所有的lib库都变成了image(镜像),在需要的时候,就可以直接map(映射)到上面。

6. 链接主可执行程序

7. 链接动态库

8. 执行弱绑定

9. 运行所有的初始化程序

10. 查找main()函数入口

11. 小结

从上面的流程可以看到,dyld是在第5步的时候,通过遍历环境变量中的DYLD_INSERT_LIBRARIES标识,把所有插入的动态库都加载到内存中。

那么dyld是怎么调用起的libobjc库中的+load方法?

上面调用了load方法,然后打印了调用方法的堆栈信息,看堆栈信息 :

3. initializeMainExecutable

  1. 通过堆栈信息可以看到,ViewController在调用load方法之前,是进入了dyld的main函数,然后一直到initializeMainExecutable这里,这就是上面的简要流程中的 : 9.运行所有的初始化程序。

  2. 跟着堆栈信息进源码 : initializeMainExecutable ---> runInitializers(需要全局搜索) ---> processInitializers(需要全局搜索) ---> recursiveInitialization(需要全局搜索) ---> notifySingle(需要全局搜索)

  1. sNotifyObjCInit需要存储的是一个函数的地址。在这里进行回调,完成初始化程序。那么就需要找sNotifyObjCInit在哪里被赋值,被给予函数地址。全局搜索sNotifyObjCInit
  1. 那么哪里调用了registerObjCNotifiers,对sNotifyObjCInit进行赋值?搜索registerObjCNotifiers
  1. 找到了_dyld_objc_notify_register。再看dyld里面谁给_dyld_objc_notify_register进行了赋值,就看不到了。

  2. 那么在上面的项目中打一个_dyld_objc_notify_register的符号断点,再运行程序看看哪里调用的_dyld_objc_notify_register

  1. 找到objc4-818.2的源码,查找_dyld_objc_notify_register
  1. 所以sNotifyObjCInit存储的是load_images函数的地址,也就是调用sNotifyObjCInit就等于调用load_images

七、总结

  1. dyld的入口是 : _dyld_start_dyld_start会调用dyldbootstrap::start函数。这不是一个c++函数,而是一个C函数。
  2. start函数会返回dyld::main,也就是dyld的主函数。也就是_dyld_start会调起dyld的main函数。
  3. dyld的main函数的主要流程 :

(1). 环境变量的检查和处理。

(2). 检查共享缓存是否可用(iOS环境下强制可用),加载共享缓存,并将共享缓存映射到共享区。

(3). dyld将自己加入到UUID列表中。

(4). 为主可执行程序初始化一个镜像加载器。就是实例化这个主程序。

(5). 加载所有的动态库。就是将所有的动态库都变成镜像,在需要的地方,直接可以映射。

(6). 链接主可执行程序。

(7). 链接所有的动态库。链接动态库一定要在链接主可执行程序之后执行,这是为了保证主可执行程序的动态库的优先性,其他插入的动态库必须在主可执行程序的动态库的后面。

(8). 对主可执行程序执行弱绑定。

(9). 执行所有初始化函数。

(10). 找main()函数的入口

  1. 在执行所有的初始化函数的时候,libSystem、libDispatch,libobjc,就会进行初始化。

  2. dyld和libobjc的联系是通过通知来完成的,实质上是通过c++的函数回调方式。

相关推荐
tangweiguo0305198714 小时前
SwiftUI布局完全指南:从入门到精通
ios·swift
T1an-118 小时前
最右IOS岗一面
ios
坏小虎21 小时前
Expo 快速创建 Android/iOS 应用开发指南
android·ios·rn·expo
光影少年1 天前
Android和iOS原生开发的基础知识对RN开发的重要性,RN打包发布时原生端需要做哪些配置?
android·前端·react native·react.js·ios
北京自在科技1 天前
Find My 修复定位 BUG,AirTag 安全再升级
ios·findmy·airtag
Digitally1 天前
如何不用 USB 线将 iPhone 照片传到电脑?
ios·电脑·iphone
Sim14801 天前
iPhone将内置本地大模型,手机端AI实现0 token成本时代来临?
人工智能·ios·智能手机·iphone
Digitally2 天前
如何将 iPad 上的照片传输到 U 盘(4 种解决方案)
ios·ipad
报错小能手2 天前
ios开发方向——swift并发进阶核心 @MainActor 与 DispatchQueue.main 解析
开发语言·ios·swift
LcGero2 天前
Cocos Creator 业务与原生通信详解
android·ios·cocos creator·游戏开发·jsb