【iOS内功】ARM汇编实战,解析iOS14 UICollectionView死循环问题 (转载)

image

【iOS内功】ARM汇编实战,解析iOS14 UICollectionView死循环问题

背景

9月初iOS14正式发布,线上版本新冒出许多Crash。有一个Crash,UICollectionView刷新逻辑死循环,卡死了主线程。

阳差阳错,中美两个程序员的“误会”造成了这个Crash。

App有一个页面,自定义了一个XXCollectionView。XXCollectionView嵌套在Cell里,写代码的人偷懒,把delegate设置成自己。Apple工程师也不讲武德,把协议(UICollectionViewDelegate)没声明的方法转发给delegate执行,一点契约精神都没有。

Apple框架大多不开源,内部有奇怪的逻辑,我们只能通过读汇编指令去分析。刚开始分析源码时,奇怪的逻辑让我怀疑人生,最后通过调试汇编指令,才理清楚具体的原因。

这个案例挺经典的,分析过程中,用到了oc、lldb调试、arm等常用知识,也用到了归纳法、逻辑推理、逆向思维、抽象思维等思维方法。

我详细总结了分析过程,和大家交流和探讨。

iOS内功系列文章

Crash分析的基础知识了解不多的,可以参考原来写的一些文章

【iOS内功】Crash分析模型

【iOS内功】深入解析Crash调用栈的内存布局

【iOS内功】ARM黑魔法—栈桢的入栈和出栈

【iOS内功】使用Hopper定位疑难问题

1.0、Crash Log特征分析

1.1、环境特征

crash都发生在iOS14,推断是iOS14系统库逻辑改动引发的Crash。

1.2、调用堆栈

堆栈里有UICollectionview和CPXXProxy,接下来可以找一下,哪些页面同时用到这两个类,

swipe点击事件
...
-[UICollectionView _diffableDataSourceImpl]
...
_CF_forwarding_prep_0
...
-[CPXXProxy forwardInvocation]
...
-[UICollectionView _diffableDataSourceImpl] (开始循环)
...
_CF_forwarding_prep_0
...
-[CPXXProxy forwardInvocation]
复制代码

image

1.3、场景复现

符合条件的页面很多,试了几个都复现不了。后面发现有一个页面A,abort日志里有很多swipe事件,页面也用了UICollectionview。点击页面A的各种区域,最后在点击空白处卡死了,并且复现了同样的堆栈。

2.0、源码调试分析

为了简洁,下面的术语用缩略词表示
diffxx代表_diffableDataSourceImpl
CPXXProxy代表_CPCollectionViewFlowLayoutProxy
复制代码

2.1、页面A和页面B有什么差别?

很多页面虽然也用了UICollectionView和CPXXProxy,但不会出现这个crash。于是我找了其中一个页面,下面称为页面B,从逻辑上推导,页面A和页面B逻辑上一定有一个差异,这个差异最终造成页面A的Crash。接下来,分析页面A和B的差异。

差异1:页面A里嵌套了UICollectionview

页面A和页面B有一个差异点,页面A的UICollectionView有多种cell,其中一个cell里又嵌套了UICollectionView。cell里的XXCollectionview继承自系统的UICollectionview。

差异2:页面B不会调用触发“-[CPXXProxy forwardInvocation]”

找到关键路径的3个方法,添加符号断点。模拟Crash的操作,分析页面A和页面B调用栈的差异.

-[UICollectionView _diffableDataSourceImpl]
_CF_forwarding_prep_0
-[CPXXProxy forwardInvocation]
复制代码

调试发现,页面A会调用到“-[CPXXProxy forwardInvocation]”,而页面B并不会。页面A走到forwardInvocation,说明系统给CPXXProxy发送了未实现的方法,这个方法是“_diffableDataSourceImpl”。

2.2、_diffableDataSourceImpl方法是哪里定义的?

iOS13引入了DiffableDataSource,帮助UITableView和UICollection更方便地实现局部刷新。对外开发的类是NSDiffableDataSourceSnapshot,并没有diffxx方法。

使用runtime的接口,导出UICollectionView所有的方法,发现里面包括diffxx,diffxx方法并没有对外开放,是一个私有方法。

2.3、页面A为什么会调用到"-[CPXXProxy _diffableDataSourceImpl]"

diffxx是UICollectionView自己的方法,为什么转发给delegate,这是挺奇怪的逻辑。

CPXXProxy被设置为UICollectionView的delegate,它会接收到UICollectionViewDelegate协议声明的方法,但UICollectionViewDelegate里并没有diffxx方法,理论上不应该触发这个方法的调用。

"-[CPXXProxy _diffableDataSourceImpl]"是UIKit内部逻辑触发的,而UIkit的源码没有开源,所以接下来只能调试Arm汇编继续分析。

3.0、汇编调试分析

我们应该从哪个方法入手?梳理一下思路。

“页面A为什么会出现异常”?触发异常逻辑肯定有一个源点,在这个源点之前页面A的逻辑也应该是正常的。

页面B作为正常的参照物,我们要找到它和页面A出现逻辑分叉的地方。对比页面A和页面B的调用栈,它们最后一个相同的方法是“-[UICollectionView _diffableDataSourceImpl]”,逻辑分叉就在这个方法里,因此我们就从这个方法入手分析。

3.1、“-[UICollectionView _diffableDataSourceImpl]”哪行指令出现逻辑分叉?

页面A指令

image

w0寄存器的值是1,没有命中tbz指令的跳转,按顺序继续执行下一行指令。一直执行到bl 0x196d04850指令,跳转到0x196d04850。

注1:tbz指令
复制代码

image

bl 0x196d04820里面经过几次跳转,最后执行objc_msgSend方法。根据寄存器的值,objc_msgSend里的target是“CPXXProxy”,selector就是是"diffxx"

image

CPXXProxy里并没有实现diffxx函数,进行消息转发_CF_forwarding_prep_0

页面B指令

image

w0寄存器的值是0,命中tbz指令的跳转,跳转到0x1991c12ac继续执行指令,后续也没有调用到CPXXProxy的方法。

结论

页面A和页面B的分叉点在tbz指令。tbz是一个条件跳转,页面A里tbz的测试值w0为1,页面B的测试值为0,最后走到了不同的逻辑。

3.2、w0的差异,是哪里造成的?

我们要找的关键指令就是“tbz w0 #0x0”,下面分析哪里将w0的值改为1。

image

执行"bl 0x197157750"指令前x0寄存器还是一个对象,执行后x0寄存器就成了1,说明这个方法调用的返回值就是1,也就是true。

image

进入"bl 0x197157750"调试,发现最终调用的方法是“CPXXXProxy respondsToSelector”,这个方法的返回值是true。也就是说,调用“-[CPXXXProxy respondsToSelector]”方法时,页面A和页面B结果不一样。

3.3、为什么“-[CPXXXProxy respondsToSelector]”的返回值不一样

CPXXXProxy有源码,直接分析源码的逻辑。

image

CPXXXProxy 简介

CPXXXProxy对象里有一个target属性,它是Cell里嵌套的XXCollectionView。

XXCollectionView的delegate和datasource设置为CPXXXProxy,collectionVie的回调方法先发给CPXXXProxy,CPXXXProxy接管了部分方法,自己不接管的回调方法,CPXXXProxy会再转发给target自行处理。
复制代码

根据截图显示,调用了“-[_target respondsToSelector:aSelector]”,运行结果是true。说明页面A的_target实现了“_diffableDataSourceImpl”方法,而页面B的_target并没有实现。

在页面A,XXCollectionView嵌套在cell里,CPXXXProxy的target设置为XXCollectionView。而在页面B,XXCollectionView没有嵌套,CPXXXProxy的target设置为页面B的页面控制器,XXViewController。

“_diffableDataSourceImpl”本身就是UICollectionView的方法,而XXCollectionView继承于UICollectionView,结果当然是true。

4.0、总结

分析到这里,已经豁然开朗,下图是死循环的调用链路。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VQ277AIT-1608018236100)(https://upload-images.jianshu.io/upload_images/25330108-61d5d4def89fc539.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]

  • =>"-[XXCollectionView _diffableDataSourceImpl"]"
  • =>"-[CPXXXProxy respondsToSelector:@"_diffableDataSourceImpl"]"
    • => “-[XXCollectionView respondsToSelector:@”_diffableDataSourceImpl"]" 结果是True
  • =>"-[CPXXXProxy _diffableDataSourceImpl"]"
  • =>"-[CPXXXProxy forwardInvocation"]"
  • =>"-[XXCollectionView _diffableDataSourceImpl"]"
  • =>…死循环

参考

注1:tbz指令

测试位为0发生跳转,imm指定目的寄存器的某一个位,『b5:b40』组成,0-63或者0-31,有b5决定。哪个目的寄存器由Rt指定,label是偏移地址。

TBNZ介绍 www.cnblogs.com/rongmouzhan…

关注下面的标签,发现更多相似文章

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《Swift解析-新式iOS实战开发》是一本电子书,主要针对Swift语言和iOS开发进行详细的解析和讲解。该书致力于帮助读者掌握Swift语言的特性和iOS开发的基础知识,以及如何应用它们进行实际项目的开发。 该书的内容分为多个章节,从Swift的基础语法开始,逐步介绍了Swift的各种高级特性和常用框架。读者可以学习如何使用Swift语言来编写iOS应用程序,并了解如何使用Core Data、UIKit和其他重要的框架来创建用户界面、处理数据、进行网络通信等等。 该书的特点是详细而全面,通过大量的示例代码和实际项目案例,帮助读者更好地理解和应用所学知识。同时,书中还包括了一些实用的技巧和最佳实践,帮助读者提高代码质量和开发效率。 此外,该书还提供了一些额外的学习资源和链接,以帮助读者深入了解和学习更多的iOS开发知识。读者可以通过这些资源进一步扩展和巩固所学的内容。 总而言之,《Swift解析-新式iOS实战开发》是一本适合有一定编程基础,想要学习Swift语言和iOS开发的人士阅读的电子书。通过系统而全面的内容,读者可以获得丰富的知识和实践经验,为自己的iOS开发之路打下坚实的基础。 ### 回答2: 《Swift解析:新式iOS实战开发》是一本电子书,旨在帮助读者全面了解Swift编程语言以及在iOS开发中的应用。这本书采用了全新的教学方法和实战项目,为读者提供了一种系统学习Swift和实践iOS开发的途径。 首先,本书对Swift编程语言进行了全面详尽的介绍。无论是从基础语法到高级特性,还是从常用API到面向对象设计模式,本书都进行了深入的讲解和示范。读者通过系统地学习Swift语言,可以快速上手iOS开发,并且能够灵活运用各种语言特性提高开发效率。 其次,本书以实战项目为主线,通过搭建一个完整的iOS应用来帮助读者加深对Swift开发的理解。每个实战项目都紧跟着一系列的教学步骤和示例代码,读者可以跟随着书本进行实践,逐步掌握iOS开发的过程和技巧。这种以实践为基础的学习方式,能够帮助读者更好地理解和运用所学内容。 此外,本书还介绍了一些实用的开发工具和技巧,比如使用Xcode进行调试和测试、集成第三方框架和库、发布应用到App Store等。这些内容能够帮助读者更好地提升开发效率和应用质量,从而更好地应对实际的开发需求。 总的来说,《Swift解析:新式iOS实战开发》是一本详尽、实用、有趣的电子书,适合那些想要系统学习Swift语言和iOS开发的读者。通过阅读本书,读者能够从零开始学习,掌握iOS开发的核心知识和实践技巧,为日后的应用开发打下坚实的基础。 ### 回答3: 《Swift解析新式iOS实战开发》是一本关于Swift语言和iOS实战开发的电子书。这本书通过详细地解析Swift语言的各种特性和语法,并结合实战项目的开发经验,向读者介绍了如何使用Swift语言进行iOS开发。 这本电子书的内容非常丰富,包括Swift语言的基础知识、面向对象编程、函数式编程、UI设计、网络请求、数据存储等多个方面。每一章节都以实战项目为背景,结合代码示例和详细的解释,帮助读者理解和掌握相关知识。 通过阅读这本书,读者可以系统地学习和掌握Swift语言的特性和用法,了解iOS开发的各个方面,并学会如何将Swift语言应用于实际项目开发中。无论是初学者还是有一定开发经验的人员,都可以从书中获取到丰富的知识和实战经验。 此外,这本电子书还具有很强的实践性和针对性。书中提供了大量实战项目的案例,通过跟随案例的步骤进行开发,读者不仅可以学习相关的知识,还可以通过实际动手实现项目,加深对知识的理解和运用能力。 总结来说,《Swift解析新式iOS实战开发》是一本对于想要学习iOS开发并使用Swift语言的读者来说非常有价值的电子书。它全面介绍了Swift语言和iOS开发的各个方面,并提供了丰富的实战项目案例,帮助读者快速入门并掌握相关技能。对于想要系统学习和应用Swift语言的读者来说,这本电子书绝对是一本不可缺少的参考资料。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值