8

iOS 13中dyld 3的改进和优化

 2 years ago
source link: https://easeapi.com/blog/blog/83-ios13-dyld3.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

iOS 13中dyld 3的改进和优化

2019-08-28 21:10:34 / easeapi / 错误反馈

在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实践


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK