如何定位 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 的信息,理论上效果是一致。

结论

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

相关推荐
桂月二二4 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062065 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb5 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角5 小时前
CSS 颜色
前端·css
浪浪山小白兔6 小时前
HTML5 新表单属性详解
前端·html·html5
lee5767 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579657 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
limit for me7 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者7 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架
qq_392794487 小时前
前端缓存策略:强缓存与协商缓存深度剖析
前端·缓存