如何定位 CA::Render 的崩溃问题

背景

近期辅助业务方定位了一个问题,看崩溃堆栈,没有任何直接有用的信息。在网上可以看到一些相关的讨论,但是都没有最终的结论。

developer.apple.com/forums/thre...

stackoverflow.com/questions/4...

崩溃堆栈如下所示:

arduino 复制代码
0	CoreGraphics	_ERROR_CGDataProvider_BufferIsNotReadable()
1	CoreGraphics	_CGDataProviderRetainBytePtr()
2	QuartzCore	CA::Render::(anonymous namespace)::create_image_from_image_data(CGImage*, CGColorSpace*, unsigned int, unsigned int, double)()
3	QuartzCore	CA::Render::create_image(CGImage*, CGColorSpace*, unsigned int, double)()
4	QuartzCore	CA::Render::copy_image(CGImage*, CGColorSpace*, unsigned int, double, double)()
5	QuartzCore	CA::Render::prepare_image(CGImage*, CGColorSpace*, unsigned int, double)()
6	QuartzCore	CA::Layer::prepare_commit(CA::Transaction*)()
7	QuartzCore	CA::Context::commit_transaction(CA::Transaction*)()
8	QuartzCore	CA::Transaction::commit()()
9	QuartzCore	CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*)()
10	CoreFoundation	___CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__()
11	CoreFoundation	___CFRunLoopDoObservers()
12	CoreFoundation	___CFRunLoopRun()
13	CoreFoundation	_CFRunLoopRunSpecific()
14	GraphicsServices	_GSEventRunModal()
15	UIKit	_UIApplicationMain()
17	libdyld.dylib	_start()

崩溃排查

思路1

崩溃发生在 CA::Render::prepare_image(CGImage*, CGColorSpace*, unsigned int, double)() 尝试将 CGImage 渲染出来,根据图片定位业务方。

step1 获取 CGImage 对象

scss 复制代码
(lldb) po $x20

<CGImage 0x1d03dbb70>

   <<CGColorSpace 0x1d00b6e60> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1)>

       width = 1056, height = 414, bpc = 8, bpp = 32, row bytes = 4224

       kCGImageAlphaPremultipliedFirst | kCGImageByteOrder32Little

       is mask? No, has mask? No, has matte? No, should interpolate? Yes

step2 将 CGImage 转换为 UIImage

+ (UIImage *)imageWithCGImage:(CGImageRef)cgImage;

step3 将 UIImage 转换为 NSData

NSData * __nullable UIImagePNGRepresentation(UIImage * __nonnull image)

lldb 抛出 error,尝试了几次都无法成功。社区有人在遇到相同问题时也曾尝试将 CGImage 渲染出来,都以失败告终,这个思路被废弃。

思路2

最近在死磕子线程更新 UI 的问题,子线程更新 UI 后在线程退出时会执行 CA::Transaction::commit,这个方法会调用CA::Layer::layer_being_drawn(CA::Transaction*, unsigned int) 来获当前正在渲染的 layer(这个描述不一定合适),layer_being_drawn 的地址可以通过断点或者 image lookup 获取。在这个崩溃栈帧的寄存器里面,可以获取到 CA::Layer 和 CA::Transaction 两个参数,在 lldb 动态调用 layer_being_drawn 尝试获取当前的 layer 对象。

php 复制代码
CA::Layer: 0x000000014b201560
CA::Transaction: 0x000000014711f000
(lldb) po void *(*$layer_being_drawn)(void *, void *,int) = (void *(*)(void *,void *, int))0x1b56493d8
(lldb) po $layer_being_drawn((void*)0x000000014b201560, (void *)0x000000014711f000, 0)

输出结果如下,此时仍然没有获取到业务相关的信息。

objectivec 复制代码
<UIWindowLayer:0x1d503c020; position = CGPoint (187.5 406); bounds = CGRect (0 0; 375 812); sublayers = (<UIWindowLayer: 0x1d503c040>); allowsGroupOpacity = YES; rasterizationScale = 3>

尝试遍历 layer 的图层树

objectivec 复制代码
NSString* layerTreeDescription(CALayer *layer, int depth, int max_depth) {
    NSMutableString *debugDescription = [[NSMutableString alloc] initWithString:[layer debugDescription]];
    depth++;
    if (depth <= max_depth && layer.sublayers.count > 0) {
        for (CALayer *sublayer in layer.sublayers) {
            [debugDescription appendFormat:@"\n%d - %@",
             depth,
             layerTreeDescription(sublayer, depth, max_depth)];
        }
    }
    return [debugDescription copy];
}

在输出里面搜索 CGImage,发现崩溃时正在渲染的 layer 是 RLAsyncLayer 的 contents 属性值。

objectivec 复制代码
19 - <RLAsyncLayer:0x163e3c900; position = CGPoint (176 69); bounds = CGRect (0 0; 352 138); delegate = <RichLabel: 0x1630f21a0; frame = (0 0; 352 138); text = ''; opaque = NO; layer = <RLAsyncLayer: 0x163e3c900>>; contents = <CGImage 0x1d03dbb70>

向上查找 RLAsyncLayer 的 superLayer。

objectivec 复制代码
18 - <CALayer:0x1d7c21c80; position = CGPoint (176.5 69); bounds = CGRect (0 0; 353 138); delegate = <UIView: 0x164490700; frame = (0 0; 353 138); gestureRecognizers = <NSArray: 0x1689b17f0>; layer = <CALayer: 0x1d7c21c80>>; sublayers = (<CALayer: 0x1d7a3b180>, <RLAsyncLayer: 0x163e3c900>); allowsGroupOpacity = YES; backgroundColor = <CGColor 0x1d0485ff0>>

继续查找, 此时定位到了业务相关的视图 XXXCommentCell。

objectivec 复制代码
17 - <CALayer:0x1d7838ea0; position = CGPoint (176.5 69); bounds = CGRect (0 0; 353 138); delegate = <XXXCommentCell: 0x145ef4480; baseClass = UICollectionViewCell; frame = (0 0; 353 138); layer = <CALayer: 0x1d7838ea0>>; sublayers = (<CALayer: 0x1d7c21c80>); allowsGroupOpacity = YES; backgroundColor = <CGColor 0x1d0485ff0>>

这之后的父视图,可以获取详细的视图信息,这些信息可以精准定位到业务信息。

思路3

定位到问题图片之后,再回顾问题定位流程发现 layer_being_drawn 可以用替换成更简单的方式,直接获取最顶层 window 的 layer,然后遍历 layer 子视图, 检索 CGImage 的信息,理论上效果是一致。

结论

这个崩溃我只是辅助定位到了问题图片找到了具体的业务方,图片被下掉之后崩溃解决,崩溃的根本原因后续并没有继续跟进,这个定位方式相对简单,有些人认为是小菜一碟,然而之前跟进这个崩溃的同学在近一个月内都没有找到有效的线索(毕竟术业有专攻,这个同学是做直播相关的,希望不被他看到)。总之,这种方式还是非常有效的,希望能给遇到同样的问题同学提供一些思路(我又水了一篇,狗头保命)。

相关推荐
zqx_727 分钟前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己44 分钟前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称1 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色1 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2342 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河2 小时前
CSS总结
前端·css
BigYe程普2 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H2 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍2 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai3 小时前
网站开发的发展(后端路由/前后端分离/前端路由)
前端