前言
- 本文章主要介绍如何使用WinDbg进行动态调试。如果程序崩溃后,没有记录dump文件,或者程序启动时发生异常,比如常见的 应用程序无法正常启动(0xc000007b) 报错,都可以使用WinDbg动态调试功能来定位问题。文章最后,对WinDbg常用命令进行了总结。
- 对WinDbg不熟悉的可以参考我的另一篇WinDbg入门教程 WinDgb安装和使用
什么时候使用WinDbg进行动态调试
- 程序崩溃后没有生成dump文件
- 使用visual studio调试时发生异常,看不到完整的调用堆栈
- 程序启动时发生异常错误,比如比较常见的 应用程序无法正常启动(0xc000007b)
WinDbg动态调试步骤
- 测试程序
-
#include <windows.h> #include <stdio.h> void myfunc() { printf("join to myfunc...\n"); //这里程序会崩溃 char* p = NULL; memcpy(p, "hello word", strlen("hello word")); printf("end myfunc"); } void myMath(int a, int b) { int sum = 0; sum = a + b; printf("sum = %d\n", sum); myfunc(); int minus = 0; minus = a - b; printf("sum = %d\n", sum); } int main() { myMath(10, 20); system("pause"); return 0; }
-
- 使用WinDbg动态调试程序
-
先启动我们的程序,打开WinDbg,选择File->Attach to a Process,然后选择要调试的目标进程。这里会看到当前运行的所有进程,选择你要调试的进程即可。
-
如果要调试程序无法正常启动,或者说启动后会直接退出,那么也可以直接使用WinDbg打开程序进行调试,选择 File->Open Executable,选择要调试的程序打开即可。
-
运行后默认会终端,在命令行输入g命令,可以继续运行
-
通过上面测试程序可以知道,程序运行后会崩溃,从调试信息可以看到,发生Access violation,即内存访问违规。
-
使用kn命令,可以看到具体的堆栈信息,并且打印了的行号。
-
如果这里的堆栈没有函数和行号信息,是因为没有加载pdb文件,选择File->Symbol File Path将pdb所在目录添加进去即可。
-
使用lm命令可以查看pdb文件是否加载上了,使用lm vm 模块名可以看到更详细的信息。如果pdb文件的完整路径打印出来了,说明加载成功了。
-
如果pdb文件加载失败,第一个原因就是可执行程序和pdb文件不一致,也就是说可执行程序可能改过代码了,但pdb文件还是之前的。还有一个原因就是可执行程序和pdb文件时间戳不一致,也就是说代码是一致的,但可执行程序生成时间和pdb生成时间不一致。如果还有其他问题导致pdb文件加载失败,可以使用 .reload /f 可执行程序名 强制加载pdb文件。
-
可以通过 File->Source File Path将源代码路径设置进行,就可以在WinDbg中使用源代码调试了
-
那么相比于使用Visual Studio调试,WinDbg有什么好处呢。比如我们写完程序,放到客户电脑上,程序运行异常了,从日志和dump文件都分析不出问题,而且开发机还复现不了问题。这个时候就只能在客户电脑上调试了。那如果使用Visual Studio调试,我们不仅要去搭建环境,而且还要将代码拷贝到客户电脑上,实际场景肯定是不现实的。这个时候就可以使用WinDbg来进行调试了,只需要在客户电脑上安装一个WinDbg,并把对应程序的pdb文件拷贝过去,就可以现场进行调试了。
-
通过WinDbg导出dump文件
- 如果程序崩溃了,但我们程序又没有记录dump文件的功能,可以通过 .dump /ma dump文件名 命令将dump文件导出,事后进行分析。
应用程序无法正常启动(0xc000007b)分析
- 这种报错一般是由于可执行程序的依赖库位数不匹配导致的,比如我这个可执行程序依赖的vcruntime140d.dll是64位,我打包发布的时候,拷了一个32位的库进去,运行时就会报这个错误。
- 那实际项目中,可执行程序依赖的库是很多的,我们不知道哪一个依赖库出现了问题,就可以使用WinDbg来进行动态调试。
- 使用WinDbg调试时可以看到,加载vcruntime140d.dll时出现了错误,这样我们就能准确定位到是哪个依赖库出问题了。
WinDbg常用命令
- g
- 跳过当前中断,继续运行
- .ecxr
- 切换到发生异常的线程中。如果是动态调试,会直接切换到异常的线程中,因此不需要执行此命令。
- kn/kv/kp
- 查看当前线程的函数调用堆栈
- lm
- 查看二exe或者二进制文件的信息,lm vm fileDump 可以查看fileDump模块的详细信息。
- .reload
- 加载pdb文件,比如 .reload /f fileDump.exe , 强制加载 fileDump.exe的pdb文件
- ~
- 查看当前进程所有线程信息
- ~ns
- 切换到n号线程中
- !analyze
- 详细分析当前异常,完整命令为 !analyze -v
- .dump
- 导出dump文件,比如 .dump /ma F:\0001.dmp
- bp/bl/bc
- bp : 添加断点,比如 bp fileDump!main,给fileDump模块中的main函数设置断点
- bl : 列出当前所有断点
- bc : 清除断点
- r
- 查看当前线程所有寄存器的值
- .cls
- 清屏
- .effmach
- 切换当前进程的上下文