更多精彩内容,欢迎观看:
iOS Crash 治理:淘宝VisionKitCore 问题修复(上):https://developer.aliyun.com/article/1396545
对比下各版本操作系统
既然线上观察到 iOS 16.2 以上就不会出现 Crash了,那可能真的是系统 Bug ,并且偷偷摸摸解决了。于是寻找几台高版本的手机进行实验。
▐ iOS 16.2
长按 webview 后, __vk_cgImageRemoveBackgroundWithDownsizing_block_invoke函数传递过来的 x1 是 nil,而且针对 VKCRemoveBackgroundResult 所有符号打符号断点,发现长按webview时,不会命中任何逻辑。彻底和 iOS 16.1.1 的设备逻辑不一致了。
▐ iOS 17
到了iOS 17 后又不一样了,VisionKitCore-[VKCRemoveBackgroundResult _createCGImageFromBGRAPixelBuffer:cropRect:]:改成了直接调用 visionkit 里面的 vk_cgImageFromPixelBuffer 创建。
VisionKitCore`-[VKCRemoveBackgroundResult _createCGImageFromBGRAPixelBuffer:cropRect:]: -> 0x204396388 <+0>: cbz x0, 0x2043963f8 ; <+112> 0x20439638c <+4>: pacibsp 0x204396390 <+8>: stp d11, d10, [sp, #-0x40]! 0x204396394 <+12>: stp d9, d8, [sp, #0x10] 0x204396398 <+16>: stp x20, x19, [sp, #0x20] 0x20439639c <+20>: stp x29, x30, [sp, #0x30] 0x2043963a0 <+24>: add x29, sp, #0x30 0x2043963a4 <+28>: fmov d8, d3 0x2043963a8 <+32>: fmov d9, d2 0x2043963ac <+36>: fmov d10, d1 0x2043963b0 <+40>: fmov d11, d0 0x2043963b4 <+44>: mov x0, x1 0x2043963b8 <+48>: bl 0x20444d4e8 ; vk_cgImageFromPixelBuffer 0x2043963bc <+52>: mov x19, x0 0x2043963c0 <+56>: fmov d0, d11 0x2043963c4 <+60>: fmov d1, d10 0x2043963c8 <+64>: fmov d2, d9 0x2043963cc <+68>: fmov d3, d8 0x2043963d0 <+72>: bl 0x206acc070 0x2043963d4 <+76>: mov x20, x0 0x2043963d8 <+80>: mov x0, x19 0x2043963dc <+84>: bl 0x206acc110 0x2043963e0 <+88>: mov x0, x20 0x2043963e4 <+92>: ldp x29, x30, [sp, #0x30] 0x2043963e8 <+96>: ldp x20, x19, [sp, #0x20] 0x2043963ec <+100>: ldp d9, d8, [sp, #0x10] 0x2043963f0 <+104>: ldp d11, d10, [sp], #0x40 0x2043963f4 <+108>: retab 0x2043963f8 <+112>: ret
▐ iOS 16.1.1
blockInvoke 的时候也就是说 x1 一定是有值的,因此会走调用逻辑。看看这个图片到底有什么用?
看上去绘制了一个低分辨率的缩略图,不知道有啥用。
继续看 :
看起来是回调到了 webkit,那webkit 是开源的,继续看——
找到对应设备存在的Webkit版本号:
代码在 ImageAnalysisUtilities.mm(地址:https://github.com/WebKit/WebKit/blob/releases/Apple/Safari-16.1-iOS-16.1.1/Source/WebKit/Platform/cocoa/ImageAnalysisUtilities.mm)
看上去做图像识别的,但是还不确定,继续搜谁调用了它 ,Github目前能直接搜索符号
基本确认是做图像物体识别的,并且有额外判断逻辑,没有 image 就 return。
WebContextMenuProxyMax.mm(地址:https://github.com/WebKit/WebKit/blob/main/Source/WebKit/UIProcess/mac/WebContextMenuProxyMac.mm#L334)
解决方案
基于前面的原因得到一些初步的结论:这个功能是 iOS 16 新增的Feature,也就是图像识别,在iOS 16中,系统相册也可以长按抠图,同时 系统直接给 WKWebview 里面的所有图片都增加了这个功能。
- iOS 16.0..<16.2 期间的所有版本都是有隐含 Bug 的。并不是开发者造成的
- _memmove. platformmemory 是非常底层常用的 API,不可能是这的问题。
- 大概率是 WKWebview 使用方式导致的,或者是 VisionKit 抠图能力有 Bug。但是由于多次异步加 XPC 调度已经很难确认。
▐ 第一种解决方案
我突然想到,既然是默认的行为,那是不是去掉这个行为就好了,同时在前面的的调用栈发现,当 -[VKCRemoveBackgroundResult createCGImage]创建图片识别时,系统也有判空逻辑,不会出现 Crash 那我不让它返回就好了。
于是我写个 demo 测试下Hook 掉这个行为, 用了下之前去家里的小猫照片。
- (void)viewDidLoad { WKWebView *webview = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config]; [self.view addSubview:webview]; [webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.valiantcat.cn/dsn.html"]] ]; UIButton *button = [UIButton buttonWithType:(UIButtonTypeCustom)]; button.frame = CGRectMake(0, 0, 300, 200); [button setTitle:@"点击hook" forState:(UIControlStateNormal)]; [button setTitleColor:UIColor.redColor forState:UIControlStateNormal]; [self.view addSubview:button]; button.center = self.view.center; [button addTarget:self action:@selector(hook) forControlEvents:(UIControlEventTouchUpInside)]; } - (void)hook { Class class = objc_getClass("VKCRemoveBackgroundResult"); SEL selector = sel_registerName("createCGImage"); Method m = class_getInstanceMethod(class, selector); const char *type = method_getTypeEncoding(m); IMP newImp = imp_implementationWithBlock(^CGImageRef(id self, SEL cmd) { return NULL; }); IMP oldImp = class_replaceMethod(class, selector, newImp, type); NSLog(@"%p", oldImp); }
可以发现,在 hook 后,长按图片不再有抠图功能。
综上猜测,觉得这个方案可行,于是咨询了下详情和容器,他们并未对 WKWebView 的默认行为做额外处理,并不太会影响手机淘宝的业务。于是准备上线。
不过在上线前突然发现, 淘宝里扫一扫和拍立淘有 visionkit 的使用,觉得有风险,又陷入了困境。
▐ Diff 发现
突然想到既然代码是开源,并且只在 iOS 16.0..
▐ 第二种解决方案
继续尝试从 WKWebview 排查。长按触发堆栈查找有用信息。
通过阅读代码后发现这是 iOS 16 新增的功能,同时在源码中查找到了是如何添加的手势
突然发现原来在 iOS 16 以前 WKWebView 里面只有一个手势,当长按时,会触发保存图片菜单。
在 iOS 16 以后,WKWebview 添加了两个手势,竞争用户的长按动作。
- 超时逻辑验证
直接添加符号断点-[WKContentView imageAnalysisGestureDidBegin:]并添加 Command thread return 中断逻辑。发现果然会命中超时逻辑。结合代码可以看到超时的菜单中没有 copySubject 逻辑。
- 非超时逻辑
WKContentViewInteraction.mm(地址:https://github.com/WebKit/WebKit/blob/releases/Apple/Safari-16.1-iOS-16.1.1/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm)抠图识别成功后,具有 CopySubject 菜单。
因此新的方案为 Hook WKWebView 长按手势图片识别能力。
static void hook2(void) { Class class = objc_getClass("WKContentView"); SEL selector = sel_registerName("imageAnalysisGestureDidBegin:"); Method m = class_getInstanceMethod(class, selector); const char *type = method_getTypeEncoding(m); IMP newImp = imp_implementationWithBlock(^void(id self,UILongPressGestureRecognizer *ges) { // do nothing }); if (m == NULL || class == NULL) { return; } IMP oldImp = class_replaceMethod(class, selector, newImp, type); } void hookStart() { if (@available (iOS 16.0, *)) { if (@available (iOS 16.2, *)) { return; } else { hook2(); } } }
▐ 线上观察
由于 Hook 长按手势后会导致 WKWebview 自带的抠图功能和文字 OCR 功能失效,担心有舆情风险。我们选择在手机淘宝安全气垫 SDK 实现此 Hook,并且通过放量修复。我们在 10.28.11 中通过放量来进行观察,发现Crash 从 500+ 跌倒了 67 (冷起生效,有时效性问题),可以确认修复有效,并且没有舆情反馈。全量后,经过观察,带有 Hook 方案的手机淘宝 Crash 基本跌 0,至此此 Bug 彻底修复。 日降低 Crash 1200+,影响设备 1000+ 。
总结
稳定性治理是一个长期的事情,由于前期同事的努力使得用户Crash 基本解决,一些操作系统的 Bug 逐步浮出水面,冲上排行榜,起初我并没有信心解决系统的 Bug,但是在定位过程中利用自己学习到的知识抽丝剥茧逐步定位到问题,也让自己对系统 Crash 不在畏惧,同时感谢同事在排查Bug 期间的经验输出和指导。
同时在定位过程中如有疑问或错误,欢迎讨论、指正。
参考资料
1. iOS app crashed on iOS 16 https://developer.apple.com/forums/thread/718305
2. The-ABI-for-ARM-64-bit-Architecture https://developer.arm.com/documentation/den0024/a/The-ABI-for-ARM-64-bit-Architecture/Register-use-in-the-AArch64-Procedure-Call-Standard/Parameters-in-general-purpose-registers
3. WebKit https://github.com/WebKit/WebKit