大侦探福老师——幽灵Crash谜踪案

闲鱼Flutter技术的基础设施已基本趋于稳定,就在我们准备松口气的时候,一个Crash却异军突起冲击着我们的稳定性防线!闲鱼技术火速成立侦探小组执行嫌犯侦查行动,经理重重磨难终于在一个隐蔽的角落将其绳之以法!

幽灵Crash

问题要从闲鱼Flutter基础设施上一次大规模升级说起。2018年我们对闲鱼的Flutter基建作了比较大的重构,目标在于提高基建的稳定性和可扩展性。这个过程当然是挑战重重,在上一次大规模的重构集成发版后,我们虽然没有发现非常明显的异常问题,但是Crash率却出现了一个比较明显的增长。虽然总体数值还在可控范围之内,但这一个Crash却占据了几乎一大半。这个问题引起了我们警觉,我们立刻成立专项小组重点进行排查。

一般Crash Log能够为我们定位Crash提供主要信息,我们一起看看这个Crash的Log:

Thread 0 Crashed:
0   libobjc.A.dylib                 0x00000001c1b42b00 objc_object::release() :16 (in libobjc.A.dylib)
1   libobjc.A.dylib                 0x00000001c1b4338c (anonymous namespace)::AutoreleasePoolPage::pop(void*) :676 (in libobjc.A.dylib)
2   CoreFoundation                  0x00000001c28e8804 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ :28 (in CoreFoundation)
3   CoreFoundation                  0x00000001c28e8534 __CFRunLoopDoTimer :864 (in CoreFoundation)
4   CoreFoundation                  0x00000001c28e7d68 __CFRunLoopDoTimers :248 (in CoreFoundation)
5   CoreFoundation                  0x00000001c28e2c44 __CFRunLoopRun :1880 (in CoreFoundation)
6   CoreFoundation                  0x00000001c28e21cc _CFRunLoopRunSpecific :436 (in CoreFoundation)
7   GraphicsServices                0x00000001c4b59584 _GSEventRunModal :100 (in GraphicsServices)
8   UIKitCore                       0x00000001efb59054 _UIApplicationMain :212 (in UIKitCore)
9   Runner                          0x0000000102df4eb4 main main.m:49 (in Runner)
10  libdyld.dylib                   0x00000001c23a2bb4 _start :4 (in libdyld.dylib)

这是一个很典型的野指针Crash Log,是其中一种俗称的Over released问题。但是具体是哪个对象和方法,很难直接从Log上面得知,况且ARC下面的野指针更令人费解。

一些推测

Crash理因由变更引入的,我们直觉地从最近发版引入的主要变更去推测。考虑到我们开始出现问题的版本有几个比较大的改造,我们让相关的同学重新review了一下自己的代码,主要关注内存方面的问题。虽然没有找到非常确切的问题,我们还是进行了一次可疑代码优化,进行技术灰度却没有任何效果。在庞大的代码库数不清的提交中去找寻毫无头绪的野指针问题看起来不是一件容易的事情,

机型 iOS版本 闲鱼版本

我们详细的分析了Crash的数据以及用户操作日志,然后得出结论这个Crash与机型,系统版本都没明显联系。但是我们可以发现用户基本上都是在Flutter容器的详情页容易崩溃。Flutter不可避免成为了被怀疑对象,包括我们自己实现的基础设施,以及Flutter底层的库。

但是Flutter已经在闲鱼应用比较长的一段时间,Flutter底层我们几乎确定是稳定的,不然早就出问题了。这个时候主要怀疑点转移到了我们自己实现的组件,主要包括混合栈组件以及一些监控埋点设施。但是我们随后将这些怀疑对象通过技术灰度手段一一排除了嫌疑。

版本走势

从版本的Crash率的走势看,我们还发现这个问题有一个缓慢增长放量的过程,这不免让我们开始怀疑App是否存在类似的慢慢放量的功能需求。然而事实证明,这个方向没有任何收获。

无法复现的问题

不断有用户向我们反馈容易遇到闪退,但是我们自己的设备经过大量尝试却没有复现这个问题。这是最为头疼的,从用户的操作路径来看并无特殊的地方。无论是测试还是开发同学都无法在自己设备上面复现出来,无法复现的野指针问题非常难以定位。

线上监控技术

从变更和问题特征排除没有实质性的进展,我们开始尝试线上的一些监控方法来协助排查。希望可以拿到更加详细的相关信息。

GCD线程跟踪技术

从Crash Log我们可以到这应该是一个autorelease对象野指针导致的问题,本来应该autorelease进行释放的对象,在其被AutoReleasePool释放前就因为某种原因提前释放。我们怀疑是否存在多线程导致的问题,所以我们采用GCD线程跟踪技术进行监控。

这个技术的基本原理是hook住GCD的dispatch方法,将block的返回地址通过 __builtin_return_address函数拿到,然后编码写入到当前的线程名中,崩溃的时候,从线程名字中解码得出dispather的返回地址即可定位到是谁dispatch的这个block,然后随同Crash Log的扩展字段将其上传到后台。

GCD是一套C接口,所以我们采用fishhook去hook,此类底层hook对性能会有一定影响,所以我们只在专门的技术验证灰度中采用此项技术。fishhook的大致原理是重新绑定一些C的符号,因为很多共享的库的符号比如GCD在iOS中是动态绑定到App的可执行文件中的。而目前这部分符号表所在的内存没有签名,所以可以通过MachO提供的接口去进行重新绑定。感兴趣的同学可以参考Facebook fishhook项目。

我们准备了一个技术灰度版本来监控这个问题。可能由于样本比较小,我们收集到的返回地址数量非常有限。通过符号解析,得出来的都是一些NSFoundation对象,没有太多有价值的东西。之前怀疑这问题可能发生在GCD执行的block中,只是收集崩溃的时候GCD上一次调用的返回地址本身也缺乏针对性。

期望是美好的,现实是骨感觉,最终我们没有拿到有用的信息。

线上Zombie的野指针监控

在Debug模式下,Xcode有用强大的工具去帮助你定位野指针。最为通用的野指针监控工具莫过于NSZombie,如果我们能在线上开启Zombie应该能够很容易的抓到野指针对象。淘系基础设施里面有线上Zombie的实现。

线上的Zombie实现主要原理hook对象的dealloc方法在dealloc的时候通过runtime的动态性将其转变成一个Zombie类,当有其它消息发给Zombie对象的时候我们就可以根据存储下来的类型定位到Zombie的对象类型。详细可以参考Mike Ash的Let's build NSZombie。不过需要注意的是,这里面的实现是基于MRC,ARC实现上可能会有差异,基本原理是大致相同的。

我们在闲鱼App中根据基础提供的文档将线上Zombie打开进行灰度监控,所幸的是我们拿到了一些野指针对象。量也不是很多,只有个位数的类型。

可能是由于样本不够大,没有覆盖到典型的用户。或许是我们的监控组件无法抓到这个特定类型的Crash。最终在排查完所有收集到的野指针对象后,依然没有解决这个Crash。

线上监控似乎没能为我们打开突破口。

UI自动化

我们还是期望与能够将问题重现出来,这样可以迅速通过Xcode定位到问题。从概率上确实不算太高,基于前面手动复现困难的问题,我们尝试利用自动化工具去做自动复现尝试。

SwiftMonkey + 引擎DEBUG

SwiftMonkey是一个比较好的UI自动化工具,集成简单,而且可以在Debug模式下面进行自动UI测试。也就是说我们可以在保持Xcode各种强大工具有效的前提下进行自动化测试。

我们采用Local Debug Flutter引擎进行测试以便拿到相关的符号,经过一段时间的自动化测试我们在模拟器上面抓到了一摸一样的Crash Log!

这不得不说是一个令人振奋的消息,Xcode抓到的Zombie对象是一个NSMutableArray,这是一个通用对象,似乎也没有特别的地方。这个时候我们需要用到Xcode提供的malloc log和Address sanitizer去跟踪是谁创建的这个对象。

我们在模拟器上面打开malloc log以及Address sanitizer复现问题导出MemGraph然后使用

memory history 地址
malloc log MemGraph 地址

最终定位到问题出现在Flutter引擎内部文件 accessibility_bridge.mm 533行:

    NSMutableArray* newChildren =[[[NSMutableArray alloc] initWithCapacity:newChildCount] autorelease];for (NSUInteger i = 0; i < newChildCount; ++i) {SemanticsObject* child = GetOrCreateObject(node.childrenInTraversalOrder[i], nodes);child.parent = object;[newChildren addObject:child];}object.children = newChildren;

这个问题把我们带到了Flutter的Accessibility(通用->辅助功能)支持模块,我们跟用户经过了交流,并没有发现用户有打开相关的辅助功能。

虽然Log是一摸一样的,我们有点不相信我们追寻的Crash是由于这个原因导致的。这的确是Flutter在Accessibility的一个坑,但是跟我们用户交流的情形不一致。而且模拟器上面容易出现,我们将测试包装到手机上却无法在复现这问题。很显然,用户都是真机,模拟器或许不能说明问题。此时我们还没有信心确认这个问题,开辅助功能的人应该是不多的。

这感觉好像在黑暗中看到光亮,一瞬间又被黑暗淹没了,我们似乎又来到了一个死胡同。到底是哪里出问题了?

用户面对面

线上交流

在问题排查的过程中我们一直跟用户保持良好的交流。工程师们主动联系用户,很多用户也热心响应我们的访问,给我们录制了不少崩溃现场的视频。我们可以看到那些反馈问题的用户很容易出现,但是不出现的用户基本上没有这个问题。我们开始怀疑跟账号的关系,可能有一些ABTest的参数所有影响。线上的交流虽然给了我们不少有用的信息,但是依然没有实质性突破。

线下面对面

我们开始寻找愿意协助我们现场排查问题用户,我们重点找了几个非常容易出现问题的杭州用户打算上门现场Debug。在和用户进行了深入交流以后,其中一个用户愿意已访问园区方式来现场协助工程师排查问题。

我们选了用户有时间的一个周末然后拿到用户的手机进行了调试,果然在用户的手机上非常容易复现。而且就是我们前面提到的accessibility_bridge.mm处的崩溃,为什么之前再模拟器上那么容易出现呢?

原来在引擎的代码中如果是模拟器的话是默认打开Accessibility的,而真机是取决于系统的设置。

#if TARGET_OS_SIMULATOR// There doesn't appear to be any way to determine whether the accessibility// inspector is enabled on the simulator. We conservatively always turn on the// accessibility bridge in the simulator, but never assistive technology.platformView->SetSemanticsEnabled(true);platformView->SetAccessibilityFeatures(flags);
#elsebool enabled = UIAccessibilityIsVoiceOverRunning() || UIAccessibilityIsSwitchControlRunning();if (enabled)flags |= static_cast<int32_t>(blink::AccessibilityFeatureFlag::kAccessibleNavigation);platformView->SetSemanticsEnabled(enabled || UIAccessibilityIsSpeakScreenEnabled());platformView->SetAccessibilityFeatures(flags);
#endif

原来这名用户打开了iOS的阅读屏幕功能: UIAccessibilityIsSpeakScreenEnabled, 这导致Flutter辅助支持模块被打开。我们马上联系其它用户确认,基本上用户都打开了“阅读屏幕”功能。至此,我们基本确认就是这个问题所致。我们随后进行了一个小范围禁用Accessibility的灰度实验确认就是这问题导致的Crash。

在经过止血修复以后,我们继续寻找野指针的源头。问题出在这个autorelease的NSMutableArray对象,这个代码看起来也没什么明显问题。FLutter引擎的iOS使用MRC进行内存管理。我们继续review相关的代码, 终于在SemanticsObject类发现了一段奇怪的代码:

- (void)dealloc {for (SemanticsObject* child in _children) {child.parent = nil;}[_children removeAllObjects];[_children dealloc];_parent = nil;[_container release];_container = nil;[super dealloc];
}

注意其中的[_children dealloc];,这里不应该直接调用dealloc,而只需要release,这或许就是MRC难以避免的误写吧。问题定位到,修复也就是分分钟钟的事情。

后来我们发现其实这个问题最近已经在Flutter官方master分支上修复了,只是我们自己维护的引擎尚未同步对应的代码。

至此,问题得到圆满解决,Crash率恢复到正常水平。

总结

为了排查这个问题,我们从多个方向同时进行了不同的尝试。具体来说从代码变更跟踪,线上监控技术,UI自动化以及深入阅读相关源码等方式同时去推进问题的解决。需要特别强调的是,跟用户的紧密交流也是解决问题的关键,俗话说知彼知己方能百战不殆,只有充分理解需要解决的问题才能更有效的将其解决。

问题的复现与否通常对于解决方案至关重要,一个能够复现的问题基本能够在现代的IDE提供的强大工具的帮助下方便定位到。一开始我们也是苦于没能找到复现的路径,原来这个Crash却被掩盖在一个并不常见的系统设置下面,同时深藏于Flutter复杂的引擎深部。好在有热心用户愿意协助我们排查问题为我们提供精确的问题现场,才得以最终成功将其确认并解决。


原文链接
本文为云栖社区原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/519007.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

html中心对齐,html – 对齐内联块中心

中心对齐内联块元素的最简单方法是什么&#xff1f;理想情况下,我不想为元素设置宽度.这种方式取决于在元素内输入的文本,内联块元素将扩展到新的宽度,而不必改变CSS内的宽度.内联块元素应该彼此重心(不是并排),以及元素内的文本.请参阅下面的代码或参见jsFiddle.当前的HTML&am…

linux shell脚本关闭指定端口号的进程

关闭指定进程中关键词的进程&#xff0c;最好找一个唯一标识 例如&#xff1a;项目名称 等等 文章目录一、管道方式1. 关闭指定程序进程号2. 关闭指定端口号的进程(推荐使用)3. 关闭指定进程关键词的进程(推荐使用)4. 操作记录5. 知识补充二、jps方式2.1. 使用场景说明2.2. 不同…

6 个步骤,教你在Ubuntu虚拟机环境下,用Docker自带的DNS配置Hadoop | 附代码

作者 | tianyouououou责编 | Carol来源 | CSDN 博客封图 | CSDN付费下载于视觉中国最近&#xff0c;作者整理了一套Hadoop搭建方案。最后的镜像大小1.4G多&#xff0c;使用docker子网&#xff0c;容器重新启动不需要重新配置/etc/hosts文件。配置过程中参考了如下博客&#xff…

开发函数计算的正确姿势——支持 ES6 语法和 webpack 压缩

首先介绍下在本文出现的几个比较重要的概念&#xff1a; 函数计算&#xff08;Function Compute&#xff09;: 函数计算是一个事件驱动的服务&#xff0c;通过函数计算&#xff0c;用户无需管理服务器等运行情况&#xff0c;只需编写代码并上传。函数计算准备计算资源&#xff…

quarz 定时任务 cron表达式

文章目录1. 表达式符号2. 通配符说明:3. 常用表达式例子:1. 表达式符号 说明允许填写的值允许的通配符秒0-59, - * /分0-59, - * /时0-23, - * /日1-31, - * /月1-12 / JAN-DEC, - * ? / L W周1-7 or SUN-SAT, - * ? / L #年1970-2099, - * / 2. 通配符说明: * 表示所有值。…

邮件格式转换html,HTML邮件模板 - lenglingx的个人页面 - OSCHINA - 中文开源技术交流社区...

邮件要求兼容 outlook 等邮箱软件&#xff0c;发现很多样式都不生效。找到的模板如下&#xff1a;尊敬的开发者&#xff1a;                         “xxx”在此次的‘网络友好度测试’评级&#xff1a;4颗星(最高5颗星)。注意点不支持头部style、外…

Apache Cassandra 数据存储模型

我们在《Apache Cassandra 简介》文章中介绍了 Cassandra 的数据模型类似于 Google 的 Bigtable&#xff0c;对应的开源实现为 Apache HBase&#xff0c;而且我们在 《HBase基本知识介绍及典型案例分析》 文章中简单介绍了 Apache HBase 的数据模型。按照这个思路&#xff0c;A…

解决Navicat 出错:1130-host . is not allowed to connect to this MySql server,MySQL

use mysql; select host,user from user; update user set host% where userroot; flush privileges;

Knative Eventing 中 Channel 如何注入默认 Provisioner

场景 通常的在创建Broker时&#xff0c;我们需要通过 spec.ChannelTemplate 指定使用某个具体的 Channel Provisioner。例如这样的Broker: apiVersion: eventing.knative.dev/v1alpha1 kind: Broker metadata:name: pubsub-channel spec:channelTemplate:provisioner:apiVers…

删库跑路事件发生,SaaS云服务如何守护数据安全

作者 | 蒋敏峰责编 | Carol封图 | CSDN付费下载于视觉中国近日&#xff0c;某SaaS服务商/微盟遭遇员工删库跑路&#xff0c;服务器出现大面积故障&#xff0c;一时间让平台上的几百万家商户生意基本停摆。这一事件发生后&#xff0c;不管是厂商还是平台上的用户&#xff0c;都在…

express模板引擎 html,Express使用html模板的代码分析

&#xfeff;express默认使用jade模板&#xff0c;可以配置让其支持使用ejs或html模板。1.安装ejs在项目根目录安装ejs.npminstallejs2、引入ejsvarejsrequire(ejs);//我是新引入的ejs插件3、设置html引擎app.engine(html,ejs.__express);设置视图引擎app.set(viewengine,html)…

记一次吐血的ping: unknown host

背景&#xff1a; 某客户的ECS&#xff0c;ping域名提示unknown host&#xff0c;ping ip则可以通&#xff0c;ping的时候抓包没有解析的包出去&#xff0c;是解析的问题吗&#xff1f;1&#xff0c;测试ping域名以及抓包发现没有dns的解析包出去 # ping www.baidu.com -c 1 p…

Navicat连接mysql8.0.1版本出现1251--Client

bash ALTER USER root% IDENTIFIED WITH mysql_native_password BY 123456;

Nacos Committer 张龙:Nacos Sync 的设计原理和规划

与你同行&#xff0c;抬头便是星空。 本文整理自Nacos Committer 张龙的现场分享&#xff0c;阿里巴巴中间件受权发布。 随着 Nacos 1.0.0 稳定版的发布&#xff0c;越来越多的企业开始在测试/预演/生产环境中逐步部署 Nacos。目前&#xff0c;除了部分企业已处于转型分布式架…

Linux 会成为主流桌面操作系统吗?

整理 | 屠敏出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;2020 年 1 月 14 日&#xff0c;微软正式停止了 Windows 7 系统的扩展支持&#xff0c;这意味着服役十年的 Windows 7&#xff0c;属于它的时代真的终结了&#xff0c;说不出的再见&#xff0c;只能怀恋。…

阿里搜索推荐系统又双叒叕升级了?!

搜索导购产品作为搜索的流量入口&#xff0c;承载了为用户导购推荐、搜索流量分流的重要功能。主要产品包括&#xff1a;首页底纹、下拉推荐、搜索发现、导航、历史搜索等。经过几年的探索和积累&#xff0c;各个产品越发地成熟&#xff0c;机器学习算法广泛地应用于导购产品中…

怎么把html转换成jpg6,html转为图片(六):xhtmlrenderer

引入依赖包org.xhtmlrenderercore-rendererR8 不支持css,js案例&#xff1a;public class Xhtmlrenderer { private String inputFilename "G:/index.html"; private String outputFilename "G:/html.png"; private int widthImage 1000; private int h…

oracle 查看当前登录用户和所有用户

oracle 查看当前用户名 show user select user from dualoracle 查看所有用户名 select * from all_users

处理网络超时问题的最佳实践

对于云上的用户来说&#xff0c;业务日志里面报超时问题处理起来往往比价棘手&#xff0c;因为1) 问题点可能在云基础设施层&#xff0c;也有可能在业务软件层&#xff0c;需要排查的范围非常广&#xff1b;2) 这类问题往往是不可复现问题&#xff0c;抓到现场比较难。在本文里…

BZip2Codec压缩、Map端压缩控制、Reduce端压缩控制……都在这份Hadoop整合压缩知识点里了!...

作者 | Tai_Park责编 | Carol来源 | CSDN 博客封图 | CSDN付费下载于东方 IC今天来聊聊 Hadoop 的压缩。压缩&#xff1a;原始数据通过压缩手段产生目标数据&#xff0c;要求输入和输出的内容是一样的&#xff08;大部分&#xff09;&#xff0c;但体积是不一样的。对于单机用户…