duilib框架中一种常见的代码同步执行引发的崩溃问题分析

目录

1、问题描述

2、使用Windbg初步分析

3、估计是按钮的响应函数执行完返回到按钮类的成员函数中产生的崩溃

4、进一步分析发现,是点击分享直播按钮的响应函数返回后,回到duilib框架代码中产生的崩溃

5、分析出产生崩溃的场景

6、详细讲解duilib框架代码中异步发送按钮点击通知消息的实现机制(以前为了解决本案例这类崩溃问题添加的一套异步处理机制)

7、解决办法

8、最后


C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931Windows C++ 软件开发从入门到精通(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12695902.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_2276111.html       这个问题比较典型,是duilib原有的框架引起的,是代码同步执行导致的,我们之前已经针对这类问题,对duilib框架做了改进,本文通过对这个崩溃问题的分析,详细讲解代码同步执行引发问题的本质。

1、问题描述

       测试同事在测试直播列表功能时,程序发生了闪退崩溃。但测试同事不记得崩溃之前执行了什么操作,所以我们没法根据操作现象去跟踪分析了。好在我们的软件安装了异常捕获模块,软件发生崩溃闪退时异常捕获模块感知到了,自动生成了包含异常上下文的dump文件。目前只能通过dump文件去分析了。

2、使用Windbg初步分析

       取来dump文件,用Windbg打开dump文件,输入.excr命令切换到异常上下文,然后输入kn命令查看崩溃时的函数调用堆栈。根据调用堆栈中显示的模块信息,使用lm命令查看模块的时间戳,到我们的文件服务器上找到对应时间点的pdb文件,然后将pdb文件路径设置到Windbg中,并将项目源码的路径设置到Windbg中,然后重新使用.ecxr切换到异常上下文,然后使用kn查看详细的函数调用堆栈(加载pdb后,可以查看到具体的函数名、变量信息和代码的行号等信息),Windbg自动跳转到崩溃堆栈对应的源代码行处,如下所示:

从崩溃的代码处看,在directui!DirectUICore::CButtonUI::DoEvent函数退出时发生了异常(函数调用堆栈中的00帧都看不到信息了),但看不出为啥会崩溃。用!analyze –v详细分析了异常信息,也只是提示是0xC0000005内存访问违例,也得不到什么详细的信息!

       关于Windbg如何静态分析dump文件,此处就不展开了,我之前专门写了一篇使用Windbg分析dump文件的要点及一般步骤的文章,可以去查看一下:
使用Windbg分析dump文件的一般步骤及要点详解https://blog.csdn.net/chenlycly/article/details/130873143

除了静态分析dump文件,有时我们可能需要将Windbg附加到进程上进行动态调试,关于Windbg动态调试目标程序,我也写了相关的要点与一般步骤,可以去查看:
使用Windbg调试目标进程的一般步骤及要点详解https://blog.csdn.net/chenlycly/article/details/131029795

3、估计是按钮的响应函数执行完返回到按钮类的成员函数中产生的崩溃

       这时想到,是不是又是以前遇到的那类问题,在按钮的响应函数中弹出了模态框,等模态框返回时,代码又回到按钮相关的代码中继续向下执行,而因为一些原因按钮控件对象已经被销毁了(按钮对象的内存被释放了),因为当前代码返回到按钮的相关函数中继续执行(duilib框架中的代码是同步执行的),可能会访问到这个按钮控件的成员变量的值,但当前按钮对象的内存已经被释放了,不能再访问了,从而导致了内存访问违例,导致崩溃。到底是不是这个问题呢?下面需要逐步验证一下。

4、进一步分析发现,是点击分享直播按钮的响应函数返回后,回到duilib框架代码中产生的崩溃

       这到底是哪个控件哪条消息触发的directui!DirectUICore::CButtonUI::DoEvent函数调用呢?尝试着将调用函数堆栈中帧展开,看看能不能看到函数中的局部变量的值以及C++类对象this指针中的值。不过可能看不到,因为这个dump文件比较小,不是全dump,不可能包含所有的内存信息。先展开this指针,看看当前的是哪个控件触发的:

点击这个this指针链接,打开:

然后点击m_sName中存储的控件名称:

可以看到这是“分享直播”按钮触发的directui!DirectUICore::CButtonUI::DoEvent函数调用:

点击这个按钮确实会弹出一个分享直播链接的模态框:

估计当时的问题场景是这样的:点击某个直播节目的分享按钮,弹出分享窗口,该分享窗口一直没关闭,过一段时间将该分享窗口关闭,关闭后程序发生了闪退。

       然后再看directui!DirectUICore::CButtonUI::DoEvent函数中的局部变量event:

看看到底是哪个消息触发进入该函数的。点击局部变量event链接,看到:

我们看看这个tpye=14,到底对应哪个事件,找到事件定义列表:

Type=14,正好对应左键弹起事件UIEVENT_BUTTONUP,这就对了,一般我们是在左键弹起时才会添加消息响应函数的(而不是直接在左键按下时就设置响应函数的)。


       在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)

专栏1:(该精品技术专栏的订阅量已达到480多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!欢迎订阅!)

C++软件调试与异常排查从入门到精通系列文章汇总icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931

本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的项目问题实战分析实例(很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!

考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!

专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!

专栏2:  

C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795

常用的C++软件辅助分析工具有PE工具、Dependency Walker、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!

专栏3: 

C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html

以多年的开发实战为基础,总结并讲解一些的C/C++基础与进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域的多个方面的内容,同时给出C/C++及网络方面的常见笔试面试题,并详细讲述Visual Studio常用调试手段与技巧!

专栏4:   

VC++常用功能开发汇总(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585

将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。

专栏5: 

Windows C++ 软件开发从入门到精通(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12695902.html

根据多年C++软件开发实践,详细地总结了Windows C++ 应用软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。


5、分析出产生崩溃的场景

       那何时才会出现,分享直播链接的模态框还处于显示状态时,触发这个模态框弹出的按钮控件被销毁了呢?后来想了一下,确实会出现这种场景的。

       我们创建直播后,会设置一个定时器去自动刷新直播列表以将刚创建出来的直播刷新出来,而之前使用秒表测试出从发起直播创建,到直播列表中能刷新出这个新创建的直播要消耗10多秒中,这个时间间隔可能会随着网络的快慢和服务器响应速度而产生变动。

       所以后来想到一个办法,我们在发起直播创建后,我们会每隔5秒钟去刷新一次,一共刷新5次,达到5次后则停止自动刷新了。所以从发起创建起的第15秒就能将刚创建的直播项刷出来了,在list列表中将这个直播项添加进去显示,我们看到后点击分享按钮,弹出分享直播的模态框。而时间到达20秒,我们又会去刷新直播列表,会将之前的列表中的直播项都销毁掉,而分享直播连接的按钮就是这个直播项中的,直播项(布局容器)被销毁了,这个直播项按钮控件肯定也会被一起销毁了,所以当之前打开的模态框关闭后,因为按钮的响应函数是同步执行(非异步的),代码就会回到directui!DirectUICore::CButtonUI::DoEvent函数中继续执行,CButtonUI::DoEvent的后续代码可能会访问到当前的这个按钮控件的其他数据成员变量,但是这个按钮控件对象已经被销毁了,这样就会产生内存访问违例,从而产生了崩溃。

6、详细讲解duilib框架代码中异步发送按钮点击通知消息的实现机制(以前为了解决本案例这类崩溃问题添加的一套异步处理机制)

      本例中崩溃,正是按钮左键点击的响应函数执行完,返回到duilib框架代码中的CButtonUI::DoEvent中产生的崩溃,因为按钮左键点击的通知消息是同步发送的,响应函数执行完返回后有回到了发送左键点击通知消息的CButtonUI::DoEvent函数中了。这是代码同步执行导致的崩溃,之前我们就发现过类似的问题,对duilib框架中的CButtonUI类代码进行了改进,支持发送异步通知消息,将这种同步执行代码的问题给解决掉。

       下面讲讲给CButtonUI类添加的异步发送按钮点击通知消息的相关实现机制。

       首先,在CButtonUI::SetAttribute中给按钮控件新增一个名为“async”的异步属性,该属性就是给按钮的部分按键点击通知消息设置异步发送,如下:

然后将该异步属性保存到CButtonUI的成员变量m_bAsync中:

       我们再到CButtonUI发送按键点击通知消息发送的接口CButtonUI::Activate中,如下:

从截图中可以看出,按键按下通知消息UIEVENT_KEYDOWN、左键按下点击消息UIEVENT_BUTTONDOWN、左键点击弹起消息UIEVENT_BUTTONUP、左键双击消息UIEVENT_DBLCLICK均使用了异步标记变量m_bAsync,均支持设置异步发送通知消息。特别是点击按钮的通知消息UIEVENT_BUTTONUP(注意按钮点击是以左键弹起LBUTTONUP为准的,不是以左键按下LBUTTONDOWN为准的)。

       接下来我们来到发送通知消息的CPaintManagerUI::SendNotify接口中:

接口中调用了另一个重载的接口CPaintManagerUI::SendNotify,最终在该重载的接口中处理了异步发送:

我们先看同步发送通知消息的分支,static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg);这句代码就是找到按钮点击的响应函数并执行。按钮点击通知消息的响应函数是通过下面的宏绑定的:

这样当响应函数执行完后,还会回到CPaintManagerUI::SendNotify接口,继续向上返回,回到CButtonUI::Activate函数中,再向上回到CButtonUI::DoEvent函数中,就是当前问题出现崩溃的这个函数中。

       我们再来看异步发送通知消息的分支,没有调用CNotifyUIImpl::Notify去寻找按钮点击通知消息的响应函数,而是将当前的按钮通知消息放到内存列表m_aAsyncNotify中,然后给当前窗口发送WM_APP + 2窗口消息。此处调用的PostMessage发送WM_APP + 2窗口消息的,该WM_APP + 2消息被投递到当前UI应用程序的消息队列中,PostMessage立即返回。然后UI主线程从消息队列中取出WM_APP + 2消息,分发给对应的窗口去处理,我们去CPaintManagerUI::MessageHandler消息处理函数中查看处理该消息的代码:

取出缓存列表m_aAsyncNotify中待异步发送的按钮点击通知消息,然后调用CNotifyUIImpl::Notify去寻找按钮点击通知消息的响应函数,去执行这些响应函数,待响应函数执行完了后,返回到当前的CPaintManagerUI::MessageHandler函数中,而不是同步执行的CButtonUI::DoEvent函数,就不会再去访问已经销毁的CButtonUI对象的数据成员内存了,就不会有内存访问违例了。

       再继续向上返回,就回到框架的Handlelessage函数中:

紧接着就返回到线程的函数调用顶部了,不存在再去访问已经释放的类对象内存的问题了,这样异步发送按钮通知消息完美地解决了同步发送按钮点击通知消息在某些场景下引发崩溃的问题了。

7、解决办法

       这个问题是按钮控件的响应函数同步执行引发的崩溃,解决办法很简单,在xm界面布局文件中给按钮设置一个异步通知的属性即可:(注意,此处的xml截图不是本案例问题中的,只是引用另一个xml文件说明一下)

8、最后

       这个bug在duilib开源框架中时有发生,比较典型,有必要详细讲述一下,希望能给duilib使用者们提供一个借鉴或参考。 

       此外,duilib开源库还是有不少掩藏的缺陷,我们需要在搞清楚duilib内部的逻辑控制与运行机制之后,结合开发过程中遇到的实际问题,对duilib进行改进,必要时根据产品业务的需求对duilib进行扩展。 

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dvlinker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值