今天翻译了两篇 Raymond Chen 的技术文章:
翻译《The Old New Thing》- Stupid debugger tricks: Calling functions and methods-优快云博客https://blog.youkuaiyun.com/weixin_41863029/article/details/138948598翻译《The Old New Thing》- Using the “gu“ debugger command to find the infinite loop-优快云博客
https://blog.youkuaiyun.com/weixin_41863029/article/details/138959342
它们都涉及到了利用 WinDbg 调试 C++ 程序,虽然第一篇文章中, Raymond Chen 戏谑的称此方法为"Stupid debugger tricks",但其实这是一种很好的深入学习编程语言,尤其是 C++ 语言的方法。通过专业的调试工具,可以了解语言之下,更为底层的细节和实现逻辑,尤其面对 C++ 语言浩瀚的语法体系,调试之下,更有把握。
WinDbg 是什么?
WinDbg 是一个调试器,可用于分析故障转储、调试实时用户模式和内核模式代码,以及检查 CPU 寄存器和内存。
WinDbg 是面向驱动开发的调试工具,但它同时也提供了用户态的调试功能,而且非常的完备强大,熟练掌握,对深入研究语言、系统有莫大的帮助。
WinDbg 什么样?
首先,我们看看 WinDbg 源码级调试的时候的样子,有个直观的感受:

稍微设置了下,看起来像是简配版的 Visual Studio。
也许有的同学会问,如果有源码为什么我们不直接用 Visual Studio 打开项目开 Debug 模式调试?
首先 Visual Studio 更适合开发期间的调试,但如果是生产环境下出了问题,我们无需完整项目源码,只需要调试部分的代码以及 .pdb 符号文件即可。另外,WinDbg 的调试功能异常强大,更适合于针对特定问题深入细节的探究。当然它的学习门槛也有些高。
安装
WinDbg 调试工具包含在 Windows 软件开发工具包 (SDK) 中。
微软Windows SDK 官网下载对应版本的 SDK 包,选择系统对应的版本,如 Win11,Win10,Win8 等
下载后挂载镜像安装
其实 WinDbg 完全可以绿色运行,将我本地安装的程序打包上传,直接下载解压即可。
配置调试环境
配置符号路径
WinDbg 调试离不开符号文件,即 .PDB 文件。
符号文件包含大量的数据,这些数据在运行二进制文件时实际上并不需要,但在调试过程中很有用。
通常,符号文件可能包含:
全局变量
局部变量
函数名称及其入口点的地址
FPO) 记录 (框架指针省略
源行号
符号(*.pdb)文件哪里来?
Visual Studio 打开项目,配置切换为 Debug,编译后即可看到,默认和 .exe 文件在同一目录下。
在工程设置里,配置属性 \ 链接器 \ 调试 下的 前两个选项即是控制该文件是否成成和生成路径的。
因此,符号路径就是.pdb文件所在的路径,默认也就是.exe所以在的路径。
打开 Windbg ,点击菜单项 File / Symbol File Path ... 项,或 CTRL+S 快捷键打开符号路径设置窗口
以 srv*符号路径*http://msdl.microsoft.com/download/symbols;srv* 的形式输入,勾选 【Reload】复选框后,点击 OK 按钮确定返回,完成设置。
也可以在命令窗口输入以下命令进行设置
.sympath SRV*符号路径http://msdl.microsoft.com/download/symbols
.reload /f
每个模块(一般是 .exe、.dll)都有自己的符号文件,经过如上设置,怎样确定是否 Windbg 正确加载了符号呢?
通过 lm 命令,列出当前加载的所有模块信息,如果模块后面附带了 (pdb symbols)字样及 pdb 文件路径,则说明该模块的符号已经被正确加载。
或者通过 !chksym <模块名> 命令查看该模块具体的符号信息,如下图
注意,这里的模块名是不带后缀的,如这里的 test3.exe,模块名是 test3。
配置源码路径
打开 Windbg ,点击菜单项 File / Source File Path ... 项,或 CTRL+P 快捷键打开符号路径设置窗口
输入代码所在路径即可。
调试
经过如上两步设置,就可以开始调试了。
点击菜单项 File / Open Executable ... 项 或 CTRL + E,选择要调试的程序文件,WinDbg 加载后将自动停留在程序入口处,点击菜单项 File / Open Source File ... 项 或 CTRL + O 打开源码文件.cpp 。
我这里的测试代码很简单,源码如下:
#include <iostream>
int sum(int a, int b) {
return a + b;
}
int main()
{
std::cout << "sum(135,246) = " << sum(135, 246) << std::endl;
system("pause");
}
假设我们想调试 sum 函数,观察进入后的参数,那么首先在这里设置断点。
将光标定位到源文件窗口中需要断点的行,即 【return a + b;】这一行,按下F9,这一行将标记为红色,表示已下断点。
由于程序此时,停留在入口处,我们让它跑起来,命令行中输入 g ,或者 F5 ,程序将停在断点处,此时断点显示为粉色,表示命中断点:
此时,我们便可以观察 sum 参数值,输入 kn 命令,显示栈帧。
我们观察到最上面的为 sum 函数的栈帧信息,除了函数地址、参数信息外,还列出了对应的文件名和行号。
k 系列的命令用于栈回溯,简单解读下:
- 每一行代表栈上的一个栈帧(即函数)
- 最上面一行表示的是当前正在执行的函数,后续以此是上层父函数
- 第一列是栈帧的基地址EBP
- 第二列是函数的返回地址
- 第三列是函数名以及执行位置
但这里没有显示我们想要的参数信息,我们使用 kp 命令进一步展示函数原型及参数信息
至此,我们看到了函数完整的所有信息,包括参数类型和值,非常的直观!
如果输入 kP 呢?看起来会更舒服,请自行测试。
进一步,我们可以像在 IDE 中那样,通过快捷键进行各类调试操作,如F11 步入、F10单步、Shift+F11 跳出函数体等。
另外,由于我编译的程序时 32 位的,对应 x86 版的 WinDbg 才能成功进行源码级调试。如果是64位的,那么必然只能用 x64 版的WinDbg才可以调试。
总结
本篇作为 Windbg 调试 C++ 源码的抛砖引玉,希望能够给为各位对 C++ 细节有研究兴趣的同学一个不同的角度。Windows 调试和 Windbg 工具本身的使用又是另外两个浩瀚的领域,学无止尽,共勉!