iOS 13中dyld 3的改进和优化 / 2019-08-28

在iOS 13系统中,iOS将全面采用新的dyld 3以替代之前版本的dyld 2。dyld 3带来了可观的性能提升,减少了APP的启动时间。

在iOS开发中,如需要进行性能优化,启动优化等工作时,不可避免的要和dyld打交道。网上很多资料上说dyld是用来加载dylib,实际上是不准确的。dyld贯穿了APP启动的过程,包括加载依赖库,主程序。

在iOS 13系统中,iOS将全面采用新的dyld 3以替代之前版本的dyld 2。 因为dyld 3完全兼容dyld 2,API接口是一样的,所以在大部分情况下,开发者不需要做额外的适配就能平滑过渡。

写这篇文章的起因是我们发现线上产品中有部分iOS 13 beta版本用户出现偶现crash,问题最终定位到iOS 13中dyld版本的变更导致一个底层接口调用时序不对引发了死锁(这种情况比较极端)。

这篇文章主要说明dyld 3带来的变化。在开始之前,先展示一张来自wwdc2017/413 session中的截图,直观的展示了dyld 2和dyld 3的功能区别。

dyld3变化

dyld 2

dyld2从2004年发布至今,已经经过了很多次版本迭代,我们现在常见的特性比如ASLR,Code Sign,shared cache等技术,都是在dyld 2中引入的。关于dyld 2的研究已经有很多,因为dyld是开源的(代码地址在:opensource-apple/dyld),所以dyld 2的整个流程是比较清晰的。需要注意的是,这份dyld的代码已经很久未更新了。dyld 2的新特性可能并未在源码中显示,因此在开发中遇到不一致的问题也不必感到奇怪。

根据上图以及dyld源码可知,dyld 2主要工作流程为:

  • dyld的初始化,主要代码在dyldbootstrap::start,接着执行dyld::_main,dyld::_main代码较多,是dyld加载的核心部分;
  • 检查并准备环境,比如获取二进制路径,检查环境变量,解析主二进制的image header等信息;
  • 实例化主二进制的image loader,校验主二进制和dyld的版本是否匹配;
  • 检查shared cache是否已经map,没有的话则先执行map shared cache操作;
  • 检查DYLD_INSERT_LIBRARIES,有的话则加载插入的动态库(实例化image loader);
  • 执行link操作。这个过程比较复杂,会先递归加载依赖的所有动态库(会对依赖库进行排序,被依赖的总是在前面),同时在这阶段将执行符号绑定,以及rebase,binding操作;
  • 执行初始化方法。OC的+load以及C的constructor方法都会在这个阶段执行;
  • 读取Mach-O的LC_MAIN段获取程序的入口地址,调用main方法。

dyld 3

dyld 3并不是WWDC19推出来的新技术,早在2017年就被引入至iOS 11,当时主要用来优化系统库。现在,在iOS 13中它也将用于启动第三方APP,将完全替代dyld 2。由于dyld 3的代码并未开源,目前仅能通过官方披露的资料来了解到底做了什么改进。

dyld 3最大的特点就是部分是进程外的且有缓存的,在打开APP时,实际上已经有不少工作都完成了。

dyld 3的主要部分

dyld 3包含三个组件:

  • 本进程外的Mach-O分析器/编译器;

在dyld 2的加载流程中,Parse mach-o headers和Find Dependencies存在安全风险(可以通过修改mach-o header及添加非法@rpath进行攻击),而Perform symbol lookups会耗费较多的CPU时间,因为一个库文件不变时,符号将始终位于库中相同的偏移位置,这两部分在dyld 3中将采用提前写入把结果数据缓存成文件的方式构成一个”lauch closure“(可以理解为缓存文件)。

  • 本进程内执行”lauch closure“的引擎;

验证”lauch closures“是否正确,映射dylib,执行main函数。此时,它不再需要分析mach-o header和执行符号查找,节省了不少时间。

  • ”lauch closure“的缓存:

系统程序的”lauch closure“直接内置在shared cache中,而对于第三方APP,将在APP安装或更新时生成,这样就能保证”lauch closure“总是在APP打开之前准备好。

总体来说,dyld 3把很多耗时的操作都提前处理好了,极大提升了启动速度。

dyld 3的符号缺失问题

dyld 2默认采取的是lazy symbol的符号加载方式,但在dyld 3中,在app启动之前,符号解析的结果已经在”lauch closure“内了,所以“lazy symbol”就不再需要。这时,如果有符号缺失的情况,APP的行为会有不同:在dyld 2中,首次调用缺失符号时APP会crash;而dyld 3中,缺失符号会导致APP一启动就会crash。

关于all_image_infos

all_image_infos结构是dyld 1的遗留内容,它只是一个内存中的image信息的结构,并没有一个公开的API获取,在后续将会被废弃。

其它文章

wwdc2017/413
wwdc2019/423
iOS 13 适配
Address Sanitizer的原理和使用
iOS 13 Scene Delegate and multiple windows
iOS Sign With Apple实践