背景
近期辅助业务方定位了一个问题,看崩溃堆栈,没有任何直接有用的信息。在网上可以看到一些相关的讨论,但是都没有最终的结论。
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 的信息,理论上效果是一致。
结论
这个崩溃我只是辅助定位到了问题图片找到了具体的业务方,图片被下掉之后崩溃解决,崩溃的根本原因后续并没有继续跟进,这个定位方式相对简单,有些人认为是小菜一碟,然而之前跟进这个崩溃的同学在近一个月内都没有找到有效的线索(毕竟术业有专攻,这个同学是做直播相关的,希望不被他看到)。总之,这种方式还是非常有效的,希望能给遇到同样的问题同学提供一些思路(我又水了一篇,狗头保命)。