目录
1、前言
2、案例1:程序退出时弹出报错提示框
2.1、问题说明
2.2、到系统应用程序日志中看系统有没有自动生成dump文件
2.3、将Windbg附加到目标程序上进行动态调试
3、案例2:程序在运行过程中弹出ASSERT断言提示框
3.1、问题说明
3.2、将Windbg附加到进程上调试
3.3、Windbg是如何找到pdb文件的?
4、使用Windbg静态分析dump文件以及动态调试目标进程的一般步骤
5、最后
VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html开源组件及数据库技术(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_2276111.html 有时我们需要将Windbg附加到目标程序进程上动态调试,去排查软件运行过程中出现的异常。今天以最近项目中遇到的两个问题实例,给大家详细讲解一下使用Windbg动态调试去分析问题的完整过程,以供大家借鉴或参考。
1、前言
Windbg是微软提供的一款强大的调试器工具,是分析C++软件异常问题的必备利器。使用Windbg分析软件异常主要有两种方式,一种是使用Windbg静态分析dump文件,一种是将Windbg附加到目标进程上进行动态调试。在日常工作中,主要使用静态分析dump文件的方式,但在少部分场景下可能没有生成dump文件或者生成的dump不足以排查出问题,这就需要使用Windbg进行动态调试了。
本文主要来讲述使用Windbg动态调试的相关主题内容。在以往排查的多个问题实例的基础上,对需要使用Windbg进行动态调试的场景进行了总结,主要有以下几种场景:(主要看没有生成dump文件的场景)
1)程序发生死循环或死锁时
程序并没有发生异常闪退或崩溃,仅仅是发生死循环或死锁,程序还在持续运行。因为没有产生异常,程序中安装的异常捕获模块是无法感知到的,所以无法生成dump文件。分析这类问题,需要将Windbg附加到出问题的进程上进行动态调试,去查看线程的函数调用堆栈等信息。
2)程序发生异常,但异常捕获模块没有捕获到
程序中虽然安装了异常捕获模块,但异常捕获模块只能捕获到大部分异常崩溃,还有一小部分异常是感知不到的,感知不到的情况下是没法生成dump文件的。比如程序运行过程中发生的一些闪退问题,异常捕获模块没有感知到,可以尝试使用Windbg去动态调试,去尝试复现问题。Windbg在动态调试时,一旦程序发生异常,Windbg会第一时间感知到并中断下来,就可以去查看函数调用堆栈等信息去分析了。
3)异常捕获模块感知到了异常,但导出dump文件时产生了二次崩溃,dump文件生成失败
异常捕获模块感知到了异常,但在导出dump文件时异常捕获模块产生了崩溃(即产生了二次崩溃),dump文件生成失败。4)程序运行过程中检测到不正常,直接调用abort函数强制结束进程,导致程序闪退
程序闪退,主要有两种原因,一种是运行过程遇到了异常直接崩溃退出,一种是程序中监测到不正场直接调用abort直接将进程终止。代码中根据变量的值或者函数的返回值检测到不正常,可能会因为程序相关的业务没法正常执行认为程序没有继续存活下去的必要了,直接调用abort函数强行将当前进程终止掉,程序直接闪退。这种情况下的闪退,并不是因为执行汇编代码产生了异常崩溃,而是直接主动调用abort函数导致的,所以异常捕获模块感知不到,也就无法生成dump文件。
对于这种调用abort导致程序闪退的情况,可以将Windbg附加到进程上动态调试,abort函数内部会发出一个特殊的异常信号让调试器Windbg中断下来,这样Windbg就能感知到,通过查看此时的函数调用堆栈进行分析了。
5)用IDE调试程序时产生异常,但看不到有效的函数调用堆栈,可以尝试使用Windbg进行动态调试
有时我们在用IDE调试代码遇到异常崩溃时,可能看不到有效的函数调用堆栈(比如只能看到一两行函数调用记录,或者只能看到底层模块的模块名,看不到具体的函数),可以尝试使用Windbg动态调试去查看到问题发生时的完整函数调用堆栈。
6)程序启动崩溃或失败时
程序启动崩溃或启动失败时,可以尝试用Windbg去启动程序,程序发生异常时Windbg就会中断下来,就可以进行分析了。
7)程序弹出报错提示框时
程序弹出报错提示框,此时进程还在的,可以将Windbg附加到进程上进行分析。当然也可以直接打开Windows任务管理器直接导出dump文件供事后分析。
关于使用Windbg动态调试场景的详细说明及相关的问题案例,在此就不再赘述了,感兴趣的,可以查看我之前写的长篇文章:
使用 Windbg 分析软件异常时的诸多细节与技巧总结https://blog.csdn.net/chenlycly/article/details/135517926 最近项目中遇到了两个使用Windbg动态调试分析问题的实例,在这里给大家大概地分享一下。
在这里,给大家重点推荐一下我的几个热门畅销专栏:
专栏1:(该专栏订阅量接近400个,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!)
C++软件调试与异常排查从入门到精通系列文章汇总https://blog.csdn.net/chenlycly/article/details/125529931
本专栏根据近几年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的实战问题分析实例,带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!
专栏中的文章均是通过项目实战总结出来的(通过项目实战积累了大量的异常排查素材和案例),有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!
专栏2:
C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html
以多年的开发实战为基础,总结并讲解一些的C/C++基础与进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域的多个方面的内容,同时给出C/C++及网络方面的常见笔试面试题,并详细讲述Visual Studio常用调试手段与技巧!
专栏3:
开源组件及数据库技术https://blog.csdn.net/chenlycly/category_12458859.html
以多年的开发实战为基础,分享一些开源组件及数据库技术!
2、案例1:程序退出时弹出报错提示框
2.1、问题说明
测试人员反馈,在其Win7的电脑上关闭我们的软件(退出软件)时,每次都会弹出应用程序已经停止运行的提示,如下所示:
并且这个问题在他的这台Win7电脑上是必现的。对比了一下,在Win10的电脑上运行程序,并没有这个问题,即不同的系统中表现是有差异的。但这个问题在Win7的系统中是必现的,会严重影响用户的体验,必须要解决掉。
看到这个提示框,初步怀疑可能是程序退出时发生了异常(崩溃),所以系统弹出这个异常提示框。
2.2、到系统应用程序日志中看系统有没有自动生成dump文件
既然怀疑是程序退出时产生了崩溃,所以尝试到系统应用程序日志中看看有没有程序的异常记录以及自动生成dump文件。具体的操作步骤是,右键点击“此电脑”,在弹出的右键菜单中点击“管理”菜单项,如下所示:
在打开的计算机管理窗口中,在路径 系统工具->事件查看器->Windows 日志->应用程序中点击“应用程序”节点:
右侧就会显示系统记录的应用程序事件。确实看到了程序发生崩溃的记录,但并没有生成dump文件的记录。如果系统自动生成了dump文件,则会在记录文字中给出dump文件的完整路径等信息。
之前在客户那边遇到的一个程序崩溃的问题,没有生成dump文件,将Windbg直接挂载到目标程序进程上调试运行,也没感知到异常。后来在操作系统的应用程序日志中找到了程序的异常记录,并查看到了系统自动生成的dump文件路径,后来正是用这个dump文件定位了问题。之前我在文章中对这个问题进行了记录与总结,可以查看对应的文章:
使用Windbg分析从系统应用程序日志中找到的系统自动生成的dump文件去排查问题https://blog.csdn.net/chenlycly/article/details/132024253
2.3、将Windbg附加到目标程序上进行动态调试
这个问题在测试同事的Win7电脑上是必现的,所以很适合使用Windbg动态调试去分析排查。
于是重新启动程序,然后将Windbg附加到程序进程上(附加到进程上时会自动中断下来,输入g命令,让程序继续运行),然后退出程序复现问题。程序退出时果然发生了异常,Windbg感知到了并中断了下来,于是输入kn命令查看此时的函数调用堆栈:
因为没有加载pdb符号文件,所以堆栈中看不到具体的函数名和行号,只能看到相关的模块名。于是使用lm命令查看堆栈中的模块的时间戳,找到对应时间点的pdb文件,然后将pdb文件的路径设置到Windbg中,然后再使用kn命令查看函数调用堆栈,如下所示:
堆栈中看到了具体的函数名和代码行号。堆栈中出问题的模块是协议开发组维护的,于是将函数调用堆栈等信息发给协议组的同事,让他们对照着C++源码去排查。后来他们排查出了原因,是对一块堆内存释放了两次,在第二次释放时产生了崩溃。
3、案例2:程序在运行过程中弹出ASSERT断言提示框
3.1、问题说明
为了排查某个问题,直接运行Debug版本的exe程序(不是使用Visual Studio调试运行程序),在执行某个操作时,弹出了如下的ASSERT断言提示框:
即代码中出现了断言错误,可能是代码运行过程中遇到了异常的变量值。
这种断言其实排查起来很简单,如果直接使用Visual Studio调试运行的话,直接点击重试按钮,就会中断在发生异常的地方,然后查看函数调用堆栈就能很快定位问题。当前是直接运行Debug版本的exe程序,不是使用Visual Studio调试的,此时进程和问题都还在的,可以将Windbg附加到进程上,然后的点击ASSERT断言对话框中的重试按钮,然后Windbg就会中断下来。
3.2、将Windbg附加到进程上调试
将Windbg附加到弹出ASSERT断言的进程上,然后点击ASSERT断言提示框中的重试按钮,然后Windbg直接跳转到如下图的STL vector列表的数组下标操作符重载函数处:
vector subscript out of range,vector下标超过范围了。于是输入kn命令查看此时的函数调用堆栈:
堆栈中的第一行位于STL源码中,第二条位于我们的业务库中,所以要看第二条记录中的函数。
细心的朋友可能会有疑惑,我们并没有找pdb文件,为啥输出的函数调用堆栈信息中能看到具体的函数名和行号呢?这个问题接下来会详细讲!
根据第二条记录中显示的函数名和行号,找到源码中的位置,如下:
代码中使用数组下标的方式访问了vector列表vtUserDomainlist中的第一个元素,按照上面的指示,下标超过范围了。
读第一个元素时就超过范围了,说明列表中没有元素,第一个位置的元素是不存在的。在访问列表元素时没有判断这个列表是否为空,就使用下标直接访问第1个元素了!
3.3、Windbg是如何找到pdb文件的?
上面我们提到了,有人可能会有疑问,我们并没有去找pdb文件,为啥Windbg显示的函数调用堆栈中能显示出具体的函数名和行号呢?那是因为编译代码时生成的二进制文件中会自动写入默认的pdb文件路径(就是编译时生成的pdb文件路径)。以一个测试程序TestDlg.exe为例,我们直接使用Notepad++、记事本或者UltraEdit打开exe文件(可以先启动这些工具,然后将exe文件拖入到这些工具中即可以查看),然后以pdb关键字搜索,就能找到编译时写入的pdb文件的绝对路径,如下:
pdb文件的绝对路径为:
D:\VSProjects\TestDlg\Debug\TestDlg.pdb
而在本问题中,运行的Debug版本程序就是我机器上编译的,程序也是在我机器上运行的,通过写入的pdb文件的绝对路径,在我机器上能找到这个pdb文件,所以Windbg就自动加载了,所以堆栈中能看到详细的函数名及代码行号了。
如果将我机器上编译的exe主程序拷贝到其他机器上运行,在弹出上述的ASSERT断言提示框时将Windbg附加上去,在运行机器上就找不到pdb文件了,也就看不到详细的堆栈信息了。
4、使用Windbg静态分析dump文件以及动态调试目标进程的一般步骤
为了让大家能够顺利地将Windbg用起来,有所参考,我之前详细总结了使用Windbg静态分析dump文件的一般步骤以及将Windbg附加到目标进程上进行动态调试的一般步骤,分别详细介绍了分析问题的详细步骤,并以具体的问题实例进行详细地演示,很有指导意义。
使用Windbg静态分析dump文件的一般步骤,可以参考我之前写的文章:
使用Windbg静态分析dump文件的一般步骤及要点详解https://blog.csdn.net/chenlycly/article/details/130873143 将Windbg附加到目标进程上进行动态调试的一般步骤,可以参考我之前写的文章:
使用Windbg动态调试目标进程的一般步骤及要点详解https://blog.csdn.net/chenlycly/article/details/131029795
5、最后
本文给出了两个将Windbg附加到程序进程上动态调试分析的实例,虽然不复杂,但都有一定的代表性,可以给大家提供一定的借鉴或参考。