发布时间:2024-02-22 12:30
昨天兄弟项目组的一个C++软件在运行过程中出现了崩溃,邀请我过去帮忙分析一下。取来了dump文件,然后使用windbg分析,大概找出了出问题的点。这个问题有一定的代表性,在此给大家做个分享。
测试同事在测试时,点击某窗口中的导出文件按钮,将相关数据导出到文件中,会弹出系统的保存文件对话框,如下所示:
结果一会就出现了崩溃。
我们的软件添加了异常捕获模块,捕获到异常,生成了保存异常上下文的dump文件。于是,取来崩溃时捕获到的dump文件,使用windbg打开,输入.ecxr命令切换到异常时的上下文,然后输入kn命令查看函数调用堆栈,如下所示:
堆栈中没有显示详细的堆栈信息,看不到相关模块的具体的函数名称,也看不到代码的行号。所以我们需要取一下相关模块的pdb文件。
看堆栈中主要涉及到directui.dll模块,于是输入lm vm directui*命令查看directui.dll库的时间戳(directui.dll库的生成时间):
通过时间戳到文件服务器上找对应时间点的pdb文件,如下:
将pdb文件拷贝到桌面上,然后将pdb文件的路径设置到windbg中。点击菜单栏File->Symbol File Path...,打开设置pdb符号文件路径,勾选reload选项即可,如下:
设置pdb文件路径,勾选reload选项后,windbg会自动去加载pdb文件的。当然有时不会去自动加载,就需要我们使用.reload /f directui.dll去强制加载。如果pdb时间戳和二进制文件不一致,即使强制加载也加载不了的。
加载pdb文件后就能看到完整的函数调用堆栈信息了,比如能看到具体的函数名,能看到源代码中的行号,如下:
但查看到的函数调用堆栈很奇怪,并没有崩溃具体的业务代码上,是崩溃在系统库中,是框架代码触发的,这个排查起来就比较麻烦了。
于是查看系统层上面的那个函数调用:directui!DirectUICore::CAppWindow::HandleMessage,这是directui库中框架代码中的函数,是窗口的消息处理函数,于是点开这个函数的超链接,查看该函数中相关内存中的值,查看到当前处理的消息号为0x202,如下所示:
注意这个地方有个技巧,在查看函数调用堆栈时,可以尝试着去查看某个函数中相关变量的值,查看函数局部变量或者C++类的内存值,说不定通过变量值可以找到相关的线索。
当然有时只能看到部分变量的值,因为我们这个自动保存的dump不是全dump文件,文件比较小(大概几MB左右),不会保存所有的内存数据。全dump文件,会很大,会达到几百MB,甚至上GB,会保存所有变量的值。
全dump一般是两种场景下操作生成的。一种是我们将windbg挂载到目标进程上后,使用.dump命令手动导出的dump文件,完整命令比如:.dump /ma D:\\20220610.dmp。另一种是直接从任务管理器右键菜单中导出的。
对于软件中的异常捕获模块在捕获到异常时生成的dump文件,是要保存到用户磁盘上的,不不可能太大的,否则会大量消耗用户的磁盘空间。我们在调用系统API函数MiniDumpWriteDump生成dump文件时,我们会设置生成mini版dump文件的参数。
根据消息号整数值判断,消息号值很小,肯定是个系统窗口消息,不是用户自定义消息。于是在代码中随便输入一个系统消息WM_CREATE,然后go到定义的地方,搜索0x202,该值对应的消息号为WM_LBUTTONUP,如下:
结合测试同事反馈的现象截图:
是点击导出文件的按钮后弹出文件保存对话框模态框后,产生的崩溃。所以,这个这个WM_LBUTTONUP消息应该是和点击导出文件按钮有关的,鼠标点击按钮在左键弹起时执行进按钮的相应函数(一般左键按下不会执行响应函数,左键弹起才会执行响应函数)。
文件保存对话模态框窗口是在点击导出文件按钮的相应函数中弹出的,有没有可能模态框关闭后返回到按钮的相应函数中时,按钮控件所在的窗口已经关闭销毁了,结果还运行回按钮的响应函数中,还访问到窗口类的数据成员,触发了内存访问违例,产生了崩溃?
应该是上述描述的情况,directui框架中已经添加了相应的处理,可以将给按钮设置异步执行相应函数的属性,不再使用默认的同步执行,这样当模态框关闭返回后,不再因为按钮所在窗口被销毁了导致内存访问违例了。