做者:字节移动技术——陈奕前端
去年 9 月份开始,许多用户升级到 iOS 14 以后,线上出现不少 ImageIO 相关堆栈的 Crash 问题,并且公司内几乎全部的 APP 上都有出现,在部分 APP上甚至达到了 Top 3 Crash。算法
得益于 APM 平台精准数据采集机制和丰富的异常信息现场,咱们经过收集到详细的 Crash 日志信息进行分析解决。markdown
从堆栈信息看,是在 ImageIO 解析图片信息的时候 Crash ,而且最后调用的方法都是看起来都是和 INameSpacePrefixMap
相关,推测 Crash 应该是和这个方法 CGImageSourceCopyPropertiesAtIndex
的实现有关。多线程
机型集中在 iOS14 以上的版本,同时是在后台出现函数
从 CrashLog 作一个初步分析oop
从堆栈信息看,这段代码是图片库在子线程经过 CGImageSourceCopyPropertiesAtIndex
解析 imageSource
中的图片相关信息,而后发生了野指针的 Crash。性能
CGImageSourceCopyPropertiesAtIndex
的输入只有一个 imageSource
,imageSource
由图片的 data 生成,调用栈并无多线程操做,能够排除是多线程操做 imageSource
、data 致使的 Crash。测试
看堆栈是在解析 PNG 图片,经过将下发的图片格式换成 JPG 格式,发现量级并无下降。推测 Crash 不是某种特定图片格式引发的。spa
Navigate => Go To File Offset 2555072线程
0000000181b09cc0 ldr x8, [x8, #0x10]
,能够看到应该是访问 [x8, #0x10]
指向的内存出错了far: 0x000021a1ee2fa271
,并且 x8 寄存器已是一个错误的值 0x000021a1ee2fa261
0000000181b09cbc ldr x8, [x20]
x8 是存在 x20 指向的内存中(即 x8 = *x20
)
0000000181b09c98 ldr x20, [x21, #0x8]
x20 又存在 [x21, #0x8]
指向的内存中
0000000181b09c8c adrp x21, #0x1da0ed000
,0000000181b09c90 add x21, x21, #0xe10
x21 指向的是一个 data 段,推测 x21 应该是一个全局变量,因此,多是这个全局变量野了,或者是这个全局变量引用的某些内存(x20)野了
ImageIO`AdobeXMPCore_Int::ManageDefaultNameSpacePrefixMap(bool)::sDefaultNameSpacePrefixMap
复制代码
sDefaultNameSpacePrefixMap
只在AdobeXMPCore_Int::ManageDefaultNameSpacePrefixMap(bool)
这个函数中调用。可能会在多线程下调用这个函数,而致使这个全局变量的出现 data race 致使了野指针。
__ZZN16AdobeXMPCore_IntL31ManageDefaultNameSpacePrefixMapEbE26sDefaultNameSpacePrefixMap: // AdobeXMPCore_Int::ManageDefaultNameSpacePrefixMap(bool)::sDefaultNameSpacePrefixMap
00000001da0ede10 dq 0x0000000000000000 ; DATA XREF=__ZN16AdobeXMPCore_IntL31ManageDefaultNameSpacePrefixMapEb+44, __ZN16AdobeXMPCore_IntL31ManageDefaultNameSpacePrefixMapEb+120, __ZN16AdobeXMPCore_IntL31ManageDefaultNameSpacePrefixMapEb+392
复制代码
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()
sDefaultNameSpacePrefixMap
时 Crash,推测多是用户手动杀进程后,全局变量在主线程已经被析构,后台线程还会继续访问这个全局变量,从而出现野指针访问异常。发现 Crash 日志的主线程堆栈也出现 _exit 的调用,能够肯定是全局变量析构致使。在用户手动杀进程后,主线程将这个全局变量析构了,这时候子线程再访问这个全局变量就出现了野指针。
尝试在子线程不断调用 CFDictionaryRef CGImageSourceCopyPropertiesAtIndex(CGImageSourceRef isrc, size_t index, CFDictionaryRef options);
,而且手动杀掉进程触发这个 crash
能够证实上述的推理是正确的。
CFDictionaryRef CGImageSourceCopyPropertiesAtIndex(CGImageSourceRef isrc, size_t index, CFDictionaryRef options);
这个方法在解析部分图片的时候最终会访问全局变量
ImageIO`AdobeXMPCore_Int::ManageDefaultNameSpacePrefixMap(bool)::sDefaultNameSpacePrefixMap
复制代码
在用户手动杀进程后,这个sDefaultNameSpacePrefixMap
被析构,若是这时候在子线程再被访问就可能出现野指针的问题
由于sDefaultNameSpacePrefixMap
是在系统库内部的全局变量,没办法对其进行修改,只能避免在子线程调用 CGImageSourceCopyPropertiesAtIndex
方法
方法一: CGImageSourceCopyPropertiesAtIndex
是用来获取图片的宽高、imageOrientation
、动图帧等信息,选择用其余方法来替换,e.g. 宽高用 CGImageRef
来获取
方法二:将 CGImageSourceCopyPropertiesAtIndex
被调用的线程收敛起来,调用atexit函数来注册一个进程结束回调函数,进程结束的时候将终止线程
字节跳动移动平台团队(Client Infrastructure)是大前端基础技术行业领军者,负责整个字节跳动的中国区大前端基础设施建设,提高公司全产品线的性能、稳定性和工程效率,支持的产品包括但不限于抖音、今日头条、西瓜视频、火山小视频等,在移动端、Web、Desktop等各终端都有深刻研究。
就是如今!客户端/前端/服务端/端智能算法/测试开发 面向全球范围招聘!一块儿来用技术改变世界,感兴趣能够联系邮箱 chenxuwei.cxw@bytedance.com,邮件主题 简历-姓名-求职意向-电话。