手把手教你 Debug — iOS 14 ImageIO Crash 分析

背景

去年 9 月份开始,许多用户升级到 iOS 14 之后,线上出现很多 ImageIO 相关堆栈的 Crash 问题,而且公司内几乎所有的 APP 上都有出现,在部分 APP上甚至达到了 Top 3  Crash。

得益于 APM 平台精准数据采集机制和丰富的异常信息现场,我们通过收集到详细的 Crash 日志信息进行分析解决。

问题定位

堆栈信息

从堆栈信息看,是在 ImageIO 解析图片信息的时候 Crash ,并且最后调用的方法都是看起来都是和 INameSpacePrefixMap 相关,推测 Crash 应该是和这个方法 CGImageSourceCopyPropertiesAtIndex 的实现有关。


4d32328ad918c0e61e1fe8e9cdaa38d7.png4892122c441af6901fd799d766d02e45.png88e57a6d8712e07c5184c68ce9b6f941.png


  • 从堆栈信息看,这段代码是图片库在子线程通过 CGImageSourceCopyPropertiesAtIndex 解析 imageSource 中的图片相关信息,然后发生了野指针的 Crash。

  • CGImageSourceCopyPropertiesAtIndex 的输入只有一个 imageSourceimageSource 由图片的 data 生成,调用栈并没有多线程操作,可以排除是多线程操作 imageSource、data 导致的 Crash。

  • 看堆栈是在解析 PNG 图片,通过将下发的图片格式换成 JPG 格式,发现量级并没有降低。推测 Crash 不是某种特定图片格式引起的。

反汇编分析

反汇编准备

  • iOS 14.3 的 iPhone 8
  • ImageIO 系统库:~/Library/Developer/Xcode/iOS DeviceSupport目录下找到对应 iOS 14.3 的 ImageIO
  • 一份 iOS 14.3、iPhone 8 上发生的  CrashLog
  • Hopper

反汇编

1、从 CrashLog 上找到 Crash 对应的指令偏移地址 2555072

ab6ba1b6d6bc4089d0375584019d0287.png

2、通过 Hopper 打开 ImageIO,跳转到指令偏移地址 2555072

Navigate => Go To File Offset 2555072

e16533668731c40dc95bdc0c5e7adb02.png

3、Crash 对应的指令应该是0000000181b09cc0 ldr x8, [x8, #0x10],可以看到应该是访问 [x8, #0x10]指向的内存出错

d38caf691a92e3d7f138eede9518c14a.png

5、向上回溯查看 x8 的来源

  • 0000000181b09cbc ldr x8, [x20] x8 是存在 x20 指向的内存中(即 x8 = *x20

  • 0000000181b09c98 ldr x20, [x21, #0x8] x20 又存在[x21, #0x8] 指向的内存中

  • 0000000181b09c8c adrp x21, #0x1da0ed0000000000181b09c90 add x21, x21, #0xe10 x21 指向的是一个 data 段,推测 x21 应该是一个全局变量,所以,可能是这个全局变量野了,或者是这个全局变量引用的某些内存(x20)野了

6、运行时 debug 查看 x8、x20、x21 对应寄存器的值是什么

  • x21 从内存地址的名字看,应该是一个全局的 Map

f388d19742ffa15ed6d087ebc99f08cf.pngc6ea9488a3d067e6f53c219158ac9165.png

8、经过在运行时反复调试,这个

AdobeXMPCore_Int::ManageDefaultNameSpacePrefixMap(bool) 会在多个方法中调用(并且调用时都加了锁,不太可能会出现 data race):

  • AdobeXMPCore_Int::INameSpacePrefixMap_I::CreateDefaultNameSpacePrefixMap()

  • AdobeXMPCore_Int::INameSpacePrefixMap_I::InsertInDefaultNameSpacePrefixMap(char const*, unsigned long long, char const*, unsigned long long)

  • AdobeXMPCore_Int::INameSpacePrefixMap_I::DestroyDefaultNameSapcePrefixMap()

581f3f3cfaf1c37b785fdafa52a9c9bc.jpeg


9、在后台线程访问访问全局变量 sDefaultNameSpacePrefixMap 时 Crash,推测可能是用户手动杀进程后,全局变量在主线程已经被析构,后台线程还会继续访问这个全局变量,从而出现野指针访问异常。发现 Crash 日志的主线程堆栈也出现 _exit 的调用,可以确定是全局变量析构导致。

38ddd907f393105f86b4060c82f259b2.png

Crash 发生的原因:

在用户手动杀进程后,主线程将这个全局变量析构了,这时候子线程再访问这个全局变量就出现了野指针。

868e0b70946c58896e861262232dd231.png

复现问题

尝试在子线程不断调用 CFDictionaryRef CGImageSourceCopyPropertiesAtIndex(CGImageSourceRef isrc, size_t index, CFDictionaryRef options);,并且手动杀掉进程触发这个 crash


cf488a79628c19ca1462aa60fe02db8c.pnge747fb3c1ef01de4d37622f671766457.png

可以证明上述的推理是正确的。

总结

  • CFDictionaryRef CGImageSourceCopyPropertiesAtIndex(CGImageSourceRef isrc, size_t index, CFDictionaryRef options); 这个方法在解析部分图片的时候最终会访问全局变量

0 个评论

要回复文章请先登录注册