十七、启动流程(一)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++的函数回调方式。

相关推荐
Digitally1 小时前
如何将大型音频文件从 iPhone 发送到不同的设备
ios·iphone
吴Wu涛涛涛涛涛Tao6 小时前
Flutter 实现「可拖拽评论面板 + 回复输入框 + @高亮」的完整方案
android·flutter·ios
搜狐技术产品小编202312 小时前
CAEmitterLayer:iOS 中创建炫酷粒子效果的魔法工具
macos·ios·objective-c·cocoa
00后程序员张1 天前
iOS App 混淆与资源保护:iOS配置文件加密、ipa文件安全、代码与多媒体资源防护全流程指南
android·安全·ios·小程序·uni-app·cocoa·iphone
咕噜签名分发冰淇淋1 天前
内测分发是什么?
ios
2501_916007472 天前
Transporter App 使用全流程详解:iOS 应用 ipa 上传工具、 uni-app 应用发布指南
android·ios·小程序·https·uni-app·iphone·webview
白玉cfc2 天前
【OC】单例模式
开发语言·ios·单例模式·objective-c
Digitally2 天前
比较 iPhone:全面比较 iPhone 17 系列
android·ios·iphone
2501_915909062 天前
HTTPS 错误解析,常见 HTTPS 抓包失败、443 端口错误与 iOS 抓包调试全攻略
android·网络协议·ios·小程序·https·uni-app·iphone