记一次定位偶发崩溃问题

本文记录定位偶发崩溃问题的全过程,并给出相关建议,沉淀经验。

背景描述

测试反馈,通过右键添加股票,有一定概率闪退,没有dump以及更多的信息,崩溃频率随机。

问题复现

同测试进一步沟通,初步定位崩溃涉及的业务范围,在本地Debug下,重试了若干次,发现同样路径的操作,在多次操作后,有那么1、2次会触发崩溃,Release下直接闪退,Debug下弹出“参数错误”提示框,并且没有Dump信息。

定位问题

尝试1:在Debug环境下,尝试重现此崩溃,当触发崩溃时,会有如下弹框。

image.png

尝试通过弹框定位问题,发现此弹框是系统内置弹框,无法定位到弹框的构造函数,此路不通。

尝试2:猜想,这类系统错误弹框肯定是框架层面来弹出的,至于为什么弹出,可能是因为某些异常吧。尝试在 wincore.cpp,查找异常处理来定位,在此处下断点,当错误弹框再次出现时,断点在系统异常处理处被触发,具体如下图所示:

image.png

到这里,已经找到这个错误弹框是由谁弹出的,但此时的调用堆栈,对定位问题没有帮助,因为此时已进入异常处理流程,不是异常发生时的流程。此路不通。

尝试3:重新梳理业务流程,走查代码,没发现什么问题。只发现一处可优化点,当股票在当前分组已存在,后续就没必要添加,这个优化点和本次崩溃没关系,还要继续查找。

尝试4:继续思考,同样的操作流程,为什么有时候崩溃,有时候不崩溃呢?按理说,如果流程以及数据都正确,那么无论重复多少次,最终都应该是一样,但这里却有一定概率崩溃,很让人费解!继续查找代码,没发现什么大问题,想得头疼,于是下班,明天再看。

尝试5:新的一天来了,整理下思绪,继续前进。通过前面的分析,发现当崩溃出现时,往往是在点击正常弹框之后,于是乎,怀疑是正常弹框的后续流程有问题,但在该后续流程的单步调试中,每个变量的值又都是正确的,没发现问题,这就奇怪了。

尝试6:既然在尝试2中可以捕获到异常,那么可以从异常入手,在异常的构造函数下断点,看看是哪里触发了该异常。这个方法果然有效,当再次崩溃时,直接就停在了异常的构造函数中,如下图所示:

image.png

顺着调用堆栈往上找,最终问题定位在 CMenuHelper::OnMenuSelect ,在正常弹框后,有一个引用变量,它的主体先释放,而它在释放后被继续使用,造成崩溃,业界将这种内存问题称之为 use-after-free,简称为 UAF

尝试7:定位到问题后,想着既然前面的人写出了这个隐含的问题,在当前文件其他地方,有没有类似问题呢?小手一搜,果然发现了类似的问题,这说明,一个人,在同一个场景下犯下的错误,在不明确指出他错误时,他是很难知道自己做错了。因此,如果后续遇到类似,基于之前自认为是正确的做法,原样拷贝过来,那么在此会继续犯错,直到该问题被其他人发现

UAF问题复现

在弄清楚问题根因后,尝试在本地构建最小复现UAFDemo,关键代码如下:


	vector<CString> vTest(1, "test");
	CString& vTest1 = vTest[0];	
	CString strTag(vTest1);
	TRACE("1. strTag:%s \n", strTag);
	vTest.clear();			// 释放
	strTag = vTest1;		// 释放后继续使用
	TRACE("2. strTag:%s \n", strTag);

在本地VS2015DebugRelease环境中尝试了很多次,确实会触发崩溃,但不是每次都触发崩溃,而是有一定概率,具体为什么,暂未深究。如果释放点和后续使用点相距较远,甚至是传递到其他函数中,那么这类问题将更加难以查找和定位。

即使在Debug环境中触发,直接从崩溃堆栈上看,得不到定位问题的有价值信息,后续很难跟进。

从正确性上看,单步执行没问题,但当使用者多了后,这种隐藏的Bug就会时不时冒出来,复现路径难以捉摸,没有Dump,定位难度非常大。

小结

此问题是对已释放的引用变量进行操作,导致异常内存访问,从而崩溃。

因此,在后续工作中,针对引用类型变量,要非常注意它的生命周期,不要在它的本体释放后继续使用。

针对 use-after-free 这类内存偶发崩溃问题,查找定位难度很大,可有如下方法:

  1. 根据测试反馈的场景,尽可能缩小业务逻辑范围
  2. 按照测试提供的步骤,尝试各种方式和路径的操作,探索总结崩溃发生的大致规律和路径
  3. 如有异常提示,可从该异常入手,在异常基类的构造函数中下断点,再反复尝试,直到问题重现
  4. 当断点触发后,往上回溯堆栈,定位问题根因
  5. 查找同类型场景,是否存在相同问题

经验小结:

  • 拷贝粘贴的操作会复制那些你以为是对的,但实际上是错误的实现,这类错误会不断积累,直到被用户或测试发现。
  • 当一头扎进去一个难题很深很久,想了很久没有头绪时,不妨出去走走或者听听歌曲,让大脑放轻松,过段时间再看这个问题,说不定会有新的思路。
posted @ 2023-02-17 10:34  浩天之家  阅读(95)  评论(0编辑  收藏  举报