目录
1、概述
2、问题初步排查
3、进一步分析
4、查看windbg中变量的内存,最终定位问题
5、总结
C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...) https://blog.csdn.net/chenlycly/article/details/125529931 最近帮兄弟项目组排查一个C++软件异常崩溃问题时,遇到了一个更为典型的通过查看windbg中变量内存值去定位软件异常的案例,今天在这里再给大家分享一下,以供参考。
自《C++软件异常排查从入门到精通系列教程》专栏发布以来,反响比较好,专栏中系统总结了引发C++软件异常的多种原因及排查方法,详细讲解了分析C++软件异常所需要掌握的理论知识和基础技能,通过实例来讲述一些常用分析工具的使用方法,最后给出了多个异常分析实例并详细讲述了这些异常实例的完整排查过程,很好了弥补了C++软件开发人员在软件调试及异常处理方面的缺失。
专栏中的文章讲解的比较细,充分讲述了涉及到的诸多细节,在同事及读者群体中反映较好。很多朋友开始对照着文章中讲述的内容去学习,去动手操练了。
最近在帮同事排查一个C++软件异常崩溃问题时,遇到了一个更典型的通过查看windbg中变量内存值去定位软件异常的案例,之前已经分享了一篇通过查看windbg中变量的值去定位C++软件异常问题的文章:
通过查看Windbg中的变量值去定位C++软件异常问题https://blog.csdn.net/chenlycly/article/details/125731044我还想再把此次遇到的更典型的案例分享出来,以供大家参考学习。本文也算是这篇文章的姊妹篇吧,感兴趣的朋友可以两篇文章一起看一下。
兄弟项目组的软件在测试过程中发生了崩溃,软件内置的异常捕获模块捕获到了异常并生成了dump文件。开发同事从测试同事那边取来了dump文件,决定自行分析一下,正好也能检验一下最近学习C++软件异常排查的效果。
不少同事这段时间也在学习《C++软件异常排查从入门到精通系列教程》专栏中的文章,在逐步熟悉Windbg、IDA等工具的使用。
用windbg打开dump文件,输入.ecxr切换到异常的上下文,查看发生异常的那条汇编指令:
这句汇编只是将ebp寄存器中的内容mov到esp寄存器中,并没有发现明显的异常。
然后,输入kn命令查看崩溃时的函数调用堆栈:
在函数调用堆栈中看到了directui.dll模块,于是使用lm命令查看该模块的时间戳,如下所示:
文件是2022年1月21日16点46分19秒生成的,于是按照这个时间点,到版本服务器的对应目录中找到了drectui.pdb文件:
将pdb路径设置到windbg中。然后重新输入.ecxr和kn命令,重新查看加载pdb文件后的函数调用堆栈:
看到了directui模块中的详细函数名,比如directui!DirectUICore::CNotifyUIImpl::MessageHandler和directui!DirectUICore::CNotifyUIImpl::Invoke,这些接口是directui开源库的处理消息路由机制的框架接口,难道问题出在directui开源库中?
函数调用堆栈中除了directui模块,还有exe主程序的模块,然后查看主程序模块的时间戳,取来了对应版本的pdb文件,将所在路径设置到windbg中,然后再次输入.excr和kn命令查看函数调用堆栈,但始终看不到exe主程序中具体的函数名,于是找到我,让我过去帮忙分析一下。
首先使用lm命令查看exe主程序文件的时间戳,确认取来的主程序的pdb是对的,有时在windbg中设置pdb的路径后windbg会自动加载失败,此时需要使用.reload命令去强制加载一下,即:
.reload /f xxxxxx.exe
执行命令后可以使用lm命令查看一下,如果pdb加载成功后会lm命令展示的信息中显示pdb的路径,如下所示:
exe主程序的pdb文件确实加载成功了!
于是再次输入.ecxr和kn命令查看函数调用堆栈,看到了exe主程序对应的函数:
问题肯定是出在exe主程序的CCsuiMainDlg::OnSavePlatformInfo函数中,一般directui框架代码是不会有问题的。
于是根据调用堆栈中最后一帧(最上方的第00号帧)中显示函数中的行号:
j:\hd6\xxxxxxxx\xxxxxxxxx\source\csuimaindlg.cpp @ 5013
居然指向函数结尾的大括号处:
不是某行具体的代码,即并没有崩溃某个具体的代码行上!
于是回到上面显示的出问题的那条汇编指令:
汇编指令是将ebp寄存器值mov到esp中,这条指令怎么会有异常呢?于是又看了一下打开dump文件时提示的异常提示信息:
stack buffer overrun,有栈buffer内存越界?到所在的函数CCsuiMainDlg::OnSavePlatformInfo中去检查局部变量是否有越界的代码?如果是因为内存中的数据异常导致对局部变量的越界,则看代码是很难看出来的。
于是想到了到windbg中查看函数调用堆栈中最后一帧(最上方的第00号帧)函数CCsuiMainDlg::OnSavePlatformInfo函数中局部变量内存中的值,看看能不能找到线索。
于是点击函数调用堆栈中最后一帧前面的数字序号00超链接,查看CCsuiMainDlg::OnSavePlatformInfo函数中的局部变量的内存,如下:
果然发现了问题,一个8字节的字符数组achPort,居然塞入了一个9字节长度的字符串"170850304",应该就是该局部变量的栈内存被越界了!
于是到C++源码中查看都对achPort变量执行了什么操作,该变量的值是如何设置进去的。查看代码得知:
其中SCREEN_PORT宏定义如下:
下面是对achPort设置值的代码:
上述代码调用itoa系统函数将TMtPlatformApiAddr_Api结构体对象tCfg中的整型端口字段dwPort转成整型数字字符串后设置到achPort buffer中的,难道是TMtPlatformApiAddr_Api结构体对象tCfg中的dwPort字段值有问题?
于是在windbg中点击tCfg结构体对象的超链接,查看结构体对象中的dwPort字段值,如下:
是个很大的值:0xa2ef800=170850304,显然这是个异常值,端口号最大值是65535,dwPort中的这个值远大于65536,所以肯定是底层传过来的TMtPlatformApiAddr_Api结构体对象的数据有问题。所以将这个异常大的值170850304,作为字符串赋值给了charachPort[8]了,导致了内存越界。
至此,这个崩溃问题算是定位出来了。至于底层传上来的TMtPlatformApiAddr_Api结构体对象的值为什么会有问题,就交给兄弟项目组的人自己去排查了。此处为了规避这个内存越界的问题,可以添加一个端口值是否在有效范围内的判断:
if( dwPort > 0 && dwPort <= 65535 )
{
// ...
}
本案例也是通过查看windbg中变量内存中的内容快速定位问题的,但本案例中的问题更加隐蔽,是在退出函数时产生的崩溃,不是崩溃某行具体的代码上。这个案例有一定的代表性,值得大家来研究学习一下,所以在此再给大家做个分享。