Windbg是微软提供的Windows平台上强大且高效的用户态和内核态调试工具,其调试功能甚至比微软的Visual Studio集成开发环境还要强大。Windbg是我们日常工作中频繁使用的分析工具,我们使用了多年,这一点我们是深有体会的。

windbg常用调试命令(使用Windbg分析软件异常时的相关经验分享)(1)

Windbg可以分析和定位多种软件异常问题,比如软件崩溃、死循环、内存泄露、多线程死锁等,其分析与定位问题的效率远比直接分析代码要高的多!下面就几个关于windbg使用上的一些细节,简单的说明一下。

1、Visual Studio调试时看不到有效的函数调用堆栈,windbg很多时候都能捕获到

有时候我们在使用Visaul Studio(后面简称VS)调试程序时,程序发生了崩溃,但是VS中看不到崩溃时的完整的函数调用堆栈,当然这个情况下往往是崩溃在底层的dll模块的,不是我们负责的代码模块。

这种情况我们遇到过很多次,比如:

windbg常用调试命令(使用Windbg分析软件异常时的相关经验分享)(2)

从VS给出的信息中,我们只是知道崩溃在系统运行时库ucrtbase.dll中,肯定是调用此库的上层代码引起的,这个ucrtbase.dll本身肯定是没问题的。但是看不到完整的函数调用堆栈,没法确定是哪个dll模块触发的,更搞不清楚是哪个函数调用引起的!

好在这个问题我们比较有经验,我们可以直接启动Debug版本的exe程序,然后启动windbg,将windbg附加到这个exe进程上,然后按照崩溃复现的操作步骤,复现后windbg即能抓到有效的崩溃信息,就能查看到详细的函数调用堆栈,就能知道问题出在哪个函数中了。

具体的操作是这样子的,windbg捕获到异常就会中断下来,在windbg中输入kn、kv或kp命令就能查看到崩溃时的函数堆栈调用信息了,如下所示:

windbg常用调试命令(使用Windbg分析软件异常时的相关经验分享)(3)

调用堆栈从下往上看,可以得知是崩溃在mtrtcmpdll.dll库中了。上图中我们已经加载了pdb符号库文件(包含完整的函数和变量的符号信息),所以能看到崩溃在哪个函数中,并且能看出崩溃在函数的哪一行代码上。

一般我们查看到函数调用堆栈以后,确定崩溃在哪个模块,然后使用“lm vm 模块名*”命令,查看这个模块文件的时间戳:

windbg常用调试命令(使用Windbg分析软件异常时的相关经验分享)(4)

确定目标模块是哪一天编译的,是几点几分几秒编译出来的,然后到版本编译服务器上找到对应的目录,取出对应的pdb文件,然后将pdb文件的路径添加到windbg中:(在windbg菜单栏中,File --> Symbol File Path)

windbg常用调试命令(使用Windbg分析软件异常时的相关经验分享)(5)

注意在上面的界面中勾选reload选项,勾选后windbg会自动去加载pdb文件。

有时候可能加载不成功,此时就需要使用“.realod /f 带文件后缀的完整的文件名”命令来强制加载pdb文件了。这个地方要注意一下,加载pdb文件是有严格的时间戳限制的,pdb文件和对应的库文件必须是一个时间点生成的,否则会加载失败!即便是一模一样的代码(没有改动的),两个时间点编译的,pdb都不能混用的!

2、没有pdb符号文件时,看到的函数调用堆栈可能是无效的,没法分析的

这个问题我们也遇到过,比如下面的场景:

windbg常用调试命令(使用Windbg分析软件异常时的相关经验分享)(6)

上面打印出来的函数调用堆栈全部是汇编指令的地址,完全看不到模块名,根本没法直接分析的。当然精通汇编代码的人,照样是有办法进行深入分析的,但比较费时费力!

根据崩溃的最后一条汇编指令所在的模块路径,得知是崩溃在哪个模块中,然后找到pdb文件添加到windbg中来,然后使用kn命令再来查看函数调用堆栈,就能看到详细的信息了:

windbg常用调试命令(使用Windbg分析软件异常时的相关经验分享)(7)

这样就能看到崩溃在哪个函数中了,崩溃在函数的哪一行代码上了。

3、导出的是64位的dump文件,查看调用堆栈之前需要转换成32位的查看

一般情况下,我们的软件都是32位程序,可能运行在64位Windows系统上,可能导出的dump文件是64位的,比如从Windows资源管理器中导出的dump文件:

windbg常用调试命令(使用Windbg分析软件异常时的相关经验分享)(8)

取来dump文件用windbg打开,打开后看到的函数调用堆栈如下:

windbg常用调试命令(使用Windbg分析软件异常时的相关经验分享)(9)

这样的堆栈信息完全不知所云,和我们的软件无法对应起来,很是奇怪!其实很简单,只要在windbg中输入“.effmach X86”命令,在windbg自带的帮助文档中可以看到该命令的详细说明:

windbg常用调试命令(使用Windbg分析软件异常时的相关经验分享)(10)

该命令可以将64位的函数调用堆栈上下文,转换成32位的,这样就能看到有效的函数调用堆栈了,如下:

windbg常用调试命令(使用Windbg分析软件异常时的相关经验分享)(11)

4、pdb文件非常重要,如何正确地将pdb文件加载到windbg中

上面我说到过,pdb文件和目标文件的时间戳要严格一致的,否则windbg会加载失败的。pdb加载成功与否,可以使用“lm vm 模块名*”命令查看的,如果加载成功,会显示出来的,如下所示:

windbg常用调试命令(使用Windbg分析软件异常时的相关经验分享)(12)

有一次我们在帮兄弟部门排查软件崩溃时,已经找到对应时间点的pdb文件,但是就是加载不起来,使用.reload命令强制加载也加载不起来!

后来在windbg的错误提示信息中看到:

windbg常用调试命令(使用Windbg分析软件异常时的相关经验分享)(13)

可以使用“!sym noisy”命令打开pdb加载详情开关,看看能不能找到加载失败的原因,于是就输入了该命令,然后用.reload命令再去加载目标库的pdb文件,看到了提示信息:

windbg常用调试命令(使用Windbg分析软件异常时的相关经验分享)(14)

我们的程序崩溃在kdcodec.dll库上,但是从上面的信息看出,是去加载带“_hp”后缀的kdcodec_hp.pdb文件,但是我们找到的是kdcodec.pdb文件,难道是kdcodec库的源代码工程是以带“_hp”后缀来命名的,经过个同事确认,确实是这样的!

这样就能确定原因了,pdb文件还有一个规则,就是pdb文件的名称必须要和源代码工程的名称完全一致,否则会加载失败!估计是拷贝文件的脚本,在拷贝pdb文件到版本服务器上时,将pdb文件重命名了,将“_hp”后缀去掉了,所以出现了上述加载失败的问题了。只要手动去加上“_hp”后缀就好了。

5、写在最后

上面是我多年来在使用windbg上的一些细节问题,在这里简单的给大家分享一下,希望能对大家有一定的帮助!

,