总目录
1. WinDbg概述
2. WinDbg主要功能
3. WinDbg程序调试示例
4. CPU寄存器及指令系统
5. CPU保护模式概述
6. 汇编语言不等于CPU指令
7. 用WinDbg观察托管程序架构
8. Windows PE/COFF文件格式简述
9. 让WinDbg自动打开DotNet Runtime源程序
10. WinDbg综合实战
前言
本篇主要介绍WinDbg的下载、安装、主界面介绍、加载被调试程序、WinDbg命令类型、WinDbg命令列表、WinDbg帮助系统的使用。
下载与安装
WinDbg安装包并非普通exe或rar文件,而是扩展名为.appinstaller的微软应用商店安装包文件,文件名为windbg.appinstaller,下载请点击这里,如果链接失效,可以到这里查找。
备注:.appinstaller应用商店文件本质是xml文件,可以用任何文本阅读器打开。在Windows 10及以上环境双击该文件,Windows会自动连网,并到https://windbg.download.prss.microsoft.com/dbazure/prod/1-2402-24001-0/windbg.msixbundle下载真实的安装包文件并自动安装。
由于我的电脑已经安装好了,所以双击后会显示如下页面。图中左上角是原始安装文件,右上角是WinDbg应用程序Logo图像。
软件安装好以后,在开始菜单就可以找到WinDbg图标。在我的电脑上,该图标实际指向了这个文件:
C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2402.24001.0_x64__8wekyb3d8bbwe\DbgX.Shell.exe。
WinDbg界面
打开的WinDbg,初始界面如下图所示。其中虚线框称为命令(Command)窗口,是我们调试程序时最常用到的窗口。该窗口底部红色实线框是命令输入栏,由于图示状态下尚未加载任何被调试程序,此时还无法输入任何调试命令,而是显示:
Debuggee not connected (当前未与被调试应用建立连接)。
上图中点击红色虚线圆所示的关闭符号,会关闭命令窗口,可以使用以下三种方式将该窗口再找回:
- View -> Layouts -> Default(见下图右侧):此按钮将恢复系统默认窗口布局
- View -> Command(见下图左侧):此按钮会显示Command窗口,可能需要重新定位
- Alt + 1 快捷键
WinDbg还有其他窗口可用,如下图红色虚线框所示,读者可尝试使用。
加载被调试程序
WinDbg是用来调试程序或进程的,只有和被调试程序或进程进行了连接,才能输入调试命令。
在上图中点击左上角的文件按钮,就会打开如下图所示界面:
界面很简洁,可以打开exe文件,也可以让WinDbg附加到已经运行的进程上,或者使用调试器打开转储文件(俗称Dump文件),甚至可以调试内核等。本系列文章主要使用打开exe文件方式,也就是上图中的第二项(Launch executable)。本文中,我将使用一个自编的C#小程序(Core.exe)作为示例,但你可以调试任何其他应用Windows程序,比如Windows自带的记事本程序(notepad.exe)。
一旦选定了被调试对象,WinDbg就会启动程序加载,期间命令窗口会输出很多信息。启动完成后的界面如下图所示。此界面和前面界面之间的最主要差别是:命令行已经不是灰显了,可以输入调试命令了。除此以外,左上角还列出了被调试的程序、WinDbg版本,命令窗口还会输出更多信息,比如截图显示:当前使用的是64位版本的WinDbg(因为有AMD64)。
图中命令行左侧的**0:000>**是命令行提示符,冒号左侧的0代表的是进程号,冒号后面的000代表的是当前线程。既然调试一个应用程序实际上就是打开了一个操作系统进程,而一个进程可能会包含数个线程,所以输入命令时一定要关注当前处于哪个进程和哪个线程。
在命令行输入波浪号 ~ 并回车,可以显示当前所有线程。
如下图所示,我的调试器当前有两个线程,分别是0号和1号线程。切换线程的命令是 ~[线程ID]s,比如输入 ~1s就会将当前线程切换到1号线程,命令行提示符也会发生相应变化。您可以试试在不同的线程下输入k命令显示当前栈帧,会发现0:000>和0:001>下的栈帧结果是不同的,比如1号线程栈中的KERNEL32模块在0号线程中就不存在:
备注:如果WinDbg是以内核态调试,则命令行提示符会包含kd字样,意思是kernel debug,如果被调试电脑有多个CPU,则KD前还会显示CPU编号。如:
0: kd>
如果调试器暂时正忙于处理未执行完毕的上一条命令,则命令行会显示BUSY字样。系统BUSY时,虽然可以输入新命令,但系统必须等到之前命令执行完毕以后才能执行新命令。下图所示的BUSY是因为WinDbg正在从微软符号服务器下载符号。
如果被调试程序处于运行状态,则不仅有BUSY标记,还会显示Debuggee is running,此时无法输入命令。这种情况下,必须先使用Break按钮将被调试程序中断到调试器,然后才能输入命令,如下图所示:
WinDbg帮助
WinDbg功能实在太过强大了,真想将其写透,至少需要上千页的文章。
不过,我们并不需要学会所有功能以后才开始实际调试。WinDbg是图形界面,简单上手其实非常容易,只要稍加学习即可。当确实需要深入研究某项具体功能时,可以使用WinDbg自带的帮助系统,也就是点击上图中右上方那个Local Help按钮,即可弹出帮助窗口,如下图所示:
帮助窗口中有目录、索引、搜索和收藏夹四个选项卡,如上图红色框所示。如果需要系统学习,可以使用目录选项卡,此时整个帮助就如同一本教科书。建议初学者起码跟随图示Getting Started with WinDbg(User-Mode)操作一遍,算是初步入门。
如果需要深入了解某个具体命令,可以点击搜索选项卡,输入要查找的命令名回车,然后双击列表中合适的行,右侧就会出现该命令的详细解释。下图以查询bp命令为例说明:
上述示例中我们搜索bp,结果列出了bp, bu, bm三个命令在一行的一篇文章。
如果我们又想查找与bp命令相关的其他主题,比如我们想知道bp和bu有什么区别,那么我们可以使用索引选项卡,依旧输入bp,目录窗口会列出和bp相关的很多主题,比如在breakpoints主题下,我们发现有BP versus BU(BP与BU),双击即可直接显示这篇文章。
如果此时又想知道Unresolved Breakpoints(bu Breakpoints)在整个帮助目录中的位置,那么我们可以再切换到目录选项卡,此时左侧的目录结构清晰表明,这篇文章位于Debugging Technique -> Standard Debugging Techniques -> Using Breakpoints目录下,其上一篇文章是Breakpoint Syntax(断点命令语法)。
另外,在命令行窗口直接输入.hh加上欲查询命令可以快速打开WinDbg帮助窗口并直接完成搜索,比如使用下面的命令同样可以达到前文所述效果,但速度更快。
0:000> .hh bp
WinDbg命令
WinDbg之所以非常强大,主要基于两项重大支撑:
- 丰富的命令
- 脚本支持
WinDbg命令非常多,如能熟练驾驭,调试Windows程序几乎可以说所向披靡。
不过正因为它的命令太多,也让熟练掌握该技术充满挑战。
脚本支持是建立在命令基础上的,WinDbg可以通过编写脚本(比如使用JavaScript、lambda表达式等),将诸多命令链接起来,实现复杂的调试和丰富且个性化的显示。微软官网有一系列有关WinDbg使用的视频,感兴趣的可以点击这里学习。
WinDbg有三种命令,分别称为基本命令,元命令和扩展命令,三者各自有不同的语法。
基本命令
可以直接在命令行输入的命令称为基本命令。基本命令是WinDbg自带的命令。以下例子使用 lmnm 命令列出 Core 模块的基本信息(加载起始地址、加载结束地址、模块名、所属文件):
0:000> lmnm core
Browse full module list
start end module name
00000000`02330000 00000000`02338000 Core Core.dll
WinDbg的基本命令是调试过程中使用最多的命令,其命令一般比较短,数量也并不很多。
可以使用?命令可以列出所有基本命令!
0:000> ?
Open debugger.chm for complete debugger documentation
B[C|D|E][<bps>] - clear/disable/enable breakpoint(s)
BL - list breakpoints
BA <access> <size> <addr> - set processor breakpoint
BP <address> - set soft breakpoint
D[type][<range>] - dump memory
DT [-n|y] [[mod!]name] [[-n|y]fields]
[address] [-l list] [-a[]|c|i|o|r[#]|v] - dump using type information
DV [<name>] - dump local variables
DX [-r[#]] <expr> - display C++ expression using extension model (e.g.: NatVis)
E[type] <address> [<values>] - enter memory values
G[H|N] [=<address> [<address>...]] - go
K <count> - stacktrace
KP <count> - stacktrace with source arguments
LM[k|l|u|v] - list modules
LN <expr> - list nearest symbols
P [=<addr>] [<value>] - step over
Q - quit
R [[<reg> [= <expr>]]] - view or set registers
S[<opts>] <range> <values> - search memory
SX [{
e|d|i|n} [-c "Cmd1"] [-c2