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.静态库、动态库

通常程序都会依赖系统一些库, 库是什么呢? 其实库就是一些可执行的二进制文件, 能被操作系统加载到内存里面中。库分为两种:静态库 / 动态库

  • 静态:是一堆.o文件的集合。格式.a, .lib等。链接阶段时静态库会被完整地复制, 一起打包在可执行文件中,被多次使用就有多份冗余拷贝。

    • 优点: 编译完成之后, 链接到目标程序中, 同时打包到可执行文件里面, 不会有外部依赖。
    • 缺点: 静态库会有两份, 所以会导致目标程序体积增大, 对内存, 性能, 速度消耗很大。并且相同静态库每个app中都会拷贝一份。
  • 动态库:是一个已经完全链接后的镜像。格式.framework等。程序编译时并不会链接到目标程序中,目标程序只会存储指向动态库的引用,在程序运行时才被载入。苹果大部分都是动态库。

    • 优点: 不需要拷贝到目标程序, 减少App包的体积;多个App可以使用同一个动态库, 共享内存, 节约资源;由于运行时才会去加载, 那么可以在App不使用时随时对库进行替换或更新, 更新灵活。
    • 缺点: 动态载入会带来一部分性能损失, 同时动态库也会使得程序依赖于外部环境。一旦动态库没有或消失, 程序会出现问题
1.Xcode编译command + B产生可执行文件的过程:
  • a.编译
    处理源代码文件中以#开头的预编译的命令(#import/#include等等);把包含的文件插入到指定的位置;替换代码中的宏定义;删除代码里的注释等等。产生.i文件。
  • b.编译
    词法分析;语法分析;语义分析;生成相应的汇编代码文件。 产生.s文件(汇编文件)
  • c.汇编
    将汇编代码文件翻译成机器指令,机器指令会生成.o文件。
  • d.链接
    把之前所有操作的文件链接到程序里面来, 对.o文件中引用其他库的地方进行引用, 生成最后的 可执行文件。动态库与静态库区别其实就是链接的区别。

    • 静态链接: 链接器对.o文件进行符号收集、解析和重定位;把所有的.o粘合在一起从而形成可执行文件。(在静态链接之后就不会再有静态库了)
    • 动态链接: dyld动态链接(下面有介绍)
2.点击App启动的过程:
  • a.系统调用 exec() 分配内存、开辟进程
  • b.app对应的可执行文件加载到内存
  • c.dyld动态链接器加载到内存:load dyld
  • d.dyld动态链接器进行动态链接:rebase->bing->Objc->initalizers
  • e.调起main()函数

启动时间的划分可以把main()函数作为关键点分割成两块

main()函数启动分块.png

t1阶段main()之前的处理所需时间,称为pre-main

t1阶段.png

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 CommandsectionOther 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.5dyld4是iOS15发布的。

  • dyld3: 相比dyld2新增预构建/闭包, 目的是将一些启动数据创建为闭包存到本地,下次启动将不再重新解析数据,而是直接读取闭包内容
  • dyld4: 采用pre-build + just-in-time 预构建/闭包+实时解析的双解析模式, 将根据缓存有效与否选择合适的模式进行解析, 同时也处理了闭包失效时候需要重建闭包的性能问题

三、dyld工作流程

dyld流程

新建一个工程,在ViewController.m里重写+(void)load方法,在load方法上打个断点调试,运行app后在断点处输出其函数调用的堆栈:

dyld4模拟器 入口
dyld4真机 入口

版本dyld: start开始接下来走 dyld4中prepare方法, 源码入口需要在start中开始探索。
(旧版本dyld: _dyld_start开始, 接下来走dyldbootstrap, 源码入口需要在dyld_start开始。)

dyld start汇编
1.start函数是dyld的开始

%ignore_pre_1%

  • 走所有的 fixups chainsrebase dyld
  • mach消息初始化:mach_init()
  • 栈溢出保护:__guard_setup(kernArgs->findApple());
  • 构建进程配置:config = ProcessConfig(kernArgs, sSyscallDelegate);

%ignore_pre_2%

  • 创建Allocator和APIs/RuntimeState对象: APIs& state = APIs::bootstrap(config, sLocks);

APIs继承自RuntimeState

%ignore_pre_3%
%ignore_pre_4%

  • 加载程序的所有依赖项并将state绑定在一起:prepare(state, dyldMA);
  • 调用main(): appMain
2.prepare:加载程序的所有依赖项
  • gProcessInfo 是存储dyld所有镜像信息的结构体:,保存mach_header, dyld_uuid_info, dyldVersion等等信息

gProcessInfo的声明:
struct dyld_all_image_infos* gProcessInfo = &dyld_all_image_infos;

%ignore_pre_5%

  • 进行pre-build, 创建 mainLoader镜像装载器
    ps : 如果熟悉dyld3的小伙伴知道, 旧版本是创建一个ImageLoader镜像装载器
mainLoader
  • 创建just-in-time
    dyld4一个新特性, dyld4在保留了dyld3mach-o 解析器基础上,同时也引入了 just-in-time 的加载器来优化。

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的实现:

%ignore_pre_6%

接下来的工作是一些其他通知和写入操作。

  • 运行初始化方法

%ignore_pre_7%

3.runAllInitializersForMain:运行初始化方法

%ignore_pre_8%

  • 运行libSystem的初始化器
  • 告诉objclibSystem的子dylibs上运行所有的+load方法
    (可执行文件、动态库等等上面的load)

notifyObjCInit 这个工作在第4部分介绍

  • link 动态库和主程序: runInitializersBottomUpPlusUpwardLinks

runInitializersBottomUpPlusUpwardLinks 这个工作在第5部分介绍

4.notifyObjCInit

libSystem的初始化时,在其dylibs上需要通知objc运行所有的+load方法

notifyObjCInit的定义是在RuntimeState类的声明里:

_dyld_objc_notify_initsetObjCNotifiers被赋值的:

setObjCNotifiers是在_dyld_objc_notify_register被调用的

_dyld_objc_notify_register是在objc4源码里的 objc_init()里被调用的

最后objc_init()在库初始化时间之前由libSystem调用。

ps: load_images内容在下章节,知道它是用来进行加载和调用+load方法就够了。

5.link 动态库和主程序: runInitializersBottomUpPlusUpwardLinks

%ignore_pre_9%

runInitializersBottomUp

%ignore_pre_10%

6.调起主程序入口

prepare 函数调用最后返回的就是主程序入口 MainFunc

prepare最后返回主程序入口

start函数里调用了主程序入口

最后给个总结图

dyld4应用程序加载流程图

dyld4应用程序加载流程图

dyld3应用程序加载流程图:


Warning: Illegal string offset 'footer' in /www/wwwroot/dy.11456.top/wp-content/themes/Snape/single.php on line 49