Warning: Illegal string offset 'top' in /www/wwwroot/dy.11456.top/wp-content/themes/Snape/single.php on line 45
dyld源码
苹果官方资源opensource
objc4-838可编译联调源码
一、了解相关概念
1.静态库、动态库
通常程序都会依赖系统一些库, 库是什么呢? 其实库就是一些可执行的二进制文件, 能被操作系统加载到内存里面中。库分为两种:静态库 / 动态库
1.Xcode编译command + B
产生可执行文件的过程:
- a.
预编译
处理源代码文件中以#
开头的预编译的命令(#import
/#include
等等);把包含的文件插入到指定的位置;替换代码中的宏定义;删除代码里的注释等等。产生.i
文件。 - b.
编译
词法分析;语法分析;语义分析;生成相应的汇编代码文件。 产生.s
文件(汇编文件) - c.
汇编
将汇编代码文件翻译成机器指令,机器指令会生成.o
文件。 - d.
链接
把之前所有操作的文件链接到程序里面来, 对.o
文件中引用其他库的地方进行引用, 生成最后的可执行文件
。动态库与静态库区别其实就是链接的区别。
2.点击App启动的过程:
- a.系统调用
exec()
分配内存、开辟进程 - b.app对应的
可执行文件
加载到内存 - c.
dyld动态链接器
加载到内存:load dyld
- d.
dyld动态链接器
进行动态链接:rebase
->bing
->Objc
->initalizers
- e.调起
main()
函数
启动时间的划分可以把main()
函数作为关键点分割成两块
t1阶段
,main()
之前的处理所需时间,称为pre-main
t2阶段
,main()
及main()
之后处理所需时间
t2阶段耗时的主要是业务代码
3.Mach-O是什么?
Mach-O
: Mach Object
文件格式的缩写,它是一种用于可执行文件,目标文件.o
,动态库,内核转储的文件格式
。作为a.out
格式的替代,Mach-O
提供了更强的扩展性,并提升了符号表中信息的访问速度。
三种Mach-O
格式的文件:可执行文件
、dylib动态库
、Bundle资源文件包
Mach-O
由四部分组成:Mach-O头部
、Load Command
、section
、Other Data
,可以通过MachOView
可查看可执行文件信息。
二、什么是dyld
本文主要介绍dyld
源码执行流程。
在app
启动加载过程中类和分类加载都不可避免的触及dyld
,所以了解dyld
源码可以让我们更好的理解app
的工作原理。
什么是dyld?dyld(the dynamic link editor 动态链接器)
:是苹果操作系统的一个重要的组成部分。在iOS/Mac OSX系统中,仅有很少量的进程只需要内核就能完成加载,基本上所有的进程都是动态链接的,所以Mach-O镜像文件中会有很多对外部的库和符号的引用,但是这些引用并不能直接用,在启动时还必须要通过这些引用进行内容的填补,这个填补工作就是由动态链接器dyld来完成的,也就是符号绑定。
在app被编译打包成可执行文件格式的Mach-O文件后,在程序启动时交由dyld负责链接加载程序。
dyld
是开源的,可以通过苹果官网下载它的dyld源码,本文的对应的版本是941.5
。dyld4是iOS15发布的。
dyld3
: 相比dyld2新增预构建/闭包, 目的是将一些启动数据创建为闭包存到本地,下次启动将不再重新解析数据,而是直接读取闭包内容dyld4
: 采用pre-build + just-in-time
预构建/闭包+实时解析的双解析模式, 将根据缓存有效与否选择合适的模式进行解析, 同时也处理了闭包失效时候需要重建闭包的性能问题。
三、dyld工作流程
新建一个工程,在ViewController.m
里重写+(void)load
方法,在load方法上打个断点调试,运行app后在断点处输出其函数调用的堆栈:
新版本dyld: start
开始接下来走 dyld4中prepare
方法, 源码入口需要在start
中开始探索。
(旧版本dyld: _dyld_start
开始, 接下来走dyldbootstrap
, 源码入口需要在dyld_start
开始。)
1.start函数是dyld的开始
- mach消息初始化:
mach_init()
- 栈溢出保护:
__guard_setup(kernArgs->findApple());
- 构建进程配置:
config = ProcessConfig(kernArgs, sSyscallDelegate);
APIs继承自RuntimeState
2.prepare:加载程序的所有依赖项
gProcessInfo的声明:struct dyld_all_image_infos* gProcessInfo = &dyld_all_image_infos;
dyld3
出于对启动速度的优化的目的, 增加了预构建(闭包)
。App第一次启动或者App发生变化时会将部分启动数据创建为闭包存到本地,那么App下次启动将不再重新解析数据,而是直接读取闭包内容。当然前提是应用程序和系统应很少发生变化,但如果这两者经常变化等, 就会导闭包丢失或失效。
dyld4
采用了 pre-build + just-in-time
的双解析模式,预构建 pre-build 对应的就是 dyld3 中的闭包
,just-in-time
可以理解为实时解析。当然just-in-time
也是可以利用 pre-build
的缓存的,所以性能可控。有了just-in-time
, 目前应用首次启动、系统版本更新、普通启动,dyld4
则可以根据缓存是否有效去选择合适的模式进行解析。
- 装载uuid、动态库、可执行文件
记录插入信息, 遍历所有dylibs, 一些记录检查操作继续往下走
- 插入缓存
Loader::applyInterposingToDyldCache
的实现:
接下来的工作是一些其他通知和写入操作。
- 运行初始化方法
3.runAllInitializersForMain:运行初始化方法
- 运行
libSystem
的初始化器
- 告诉
objc
在libSystem
的子dylibs上运行所有的+load
方法
(可执行文件、动态库等等上面的load)
notifyObjCInit
这个工作在第4部分介绍
runInitializersBottomUpPlusUpwardLinks
这个工作在第5部分介绍
4.notifyObjCInit
在libSystem
的初始化时,在其子dylibs
上需要通知objc
运行所有的+load
方法
notifyObjCInit
的定义是在RuntimeState
类的声明里:
_dyld_objc_notify_init
在setObjCNotifiers
被赋值的:
那setObjCNotifiers
是在_dyld_objc_notify_register
被调用的
而_dyld_objc_notify_register
是在objc4源码里的 objc_init()
里被调用的
最后objc_init()
在库初始化时间之前由libSystem
调用。
ps: load_images
的内容在下章节,知道它是用来进行加载和调用+load
方法就够了。
5.link
动态库和主程序: runInitializersBottomUpPlusUpwardLinks
runInitializersBottomUp
6.调起主程序入口
在 prepare
函数调用最后返回的就是主程序入口 MainFunc
在start
函数里调用了主程序入口
最后给个总结图
dyld4应用程序加载流程图
dyld3应用程序加载流程图:
Warning: Illegal string offset 'footer' in /www/wwwroot/dy.11456.top/wp-content/themes/Snape/single.php on line 49