WinDbg全称Debugging Tools for Windows,是windows平台下的调试工具。
获取Windbg的三种途径:
(1)在Visual Studio安装时安装Windows Driver Kit(WDK)。WDK中包含WinDbg。
(2)安装Windows Software Development Kit (SDK)。SDK中包含WinDbg。下载地址
(3)如果只下载单独的WinDbg,先下载SDK,在安装过程中选择“Debugging Tools for Windows”,同时勾掉其他选择框。
WinDbg有两种类型:内核模式调试器和用户模式调试器。内核模式用于调试Windows内核以及驱动程序。用户模式用于调试用户程序,本文主要介绍用户模式的调试器,内核模式调试器的使用方法可在微软官网查找。
(1)打开WinDbg.exe
(2)在File菜单中选择Open Executable, 在 Open Executable 对话框中选择notepad.exe(C:\Windows\System32),点击Open。
(3)在底部窗口中,输入命令:
.sympath srv*
窗口将输出以下内容:
Symbol search path is: srv*
Expanded Symbol search path is: cache*;SRV*https://msdl.microsoft.com/download/symbols
符号搜索路径指示WinDbg查找符号文件(PDB)的地方,通过符号文件调试器可以获取代码模块中的函数名称、变量名等信息。输入如下命令将通知WinDbg进行初始化搜索和加载符号文件动作:
.reload
(4)输入以下命令可查看Notepad.exe模块的符号
x notepad!*
如果看不到任何输出,在此输入.reload命令。
输入以下命令可查看Notepad.exe模块中包含main名称的符号
x notepad!*main*
窗口将输出以下内容:
00a31409 notepad!WinMain (
00a331c9 notepad!WinMainCRTStartup (
(5)输入以下命令可在notepad!WinMain上打断点:
bu notepad!WinMain
输入以下命令验证断点是否已经设置:
bl
窗口将输出以下内容:
0 e Disable Clear 00a31409 0001 (0001) 0:**** notepad!WinMain
(6)输入以下命令运行Notepad:
g
Nopad将会运行到WinMain函数,并中断。输入以下命令可查看Notepad进程已加载模块列表:
lm
调试器窗口将输出以下内容:
start end module name
00a30000 00a60000 notepad (pdb symbols) C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\sym\notepad.pdb\BAF2EA337470437B9DF28CCDFD3EDEB22\notepad.pdb
6dfa0000 6dff1000 WINSPOOL (deferred)
6e1b0000 6e34e000 COMCTL32 (deferred)
73730000 73739000 VERSION (deferred)
74f10000 74f1c000 CRYPTBASE (deferred)
74f20000 74f80000 SspiCli (deferred)
74f80000 74ffb000 COMDLG32 (deferred)
75100000 75191000 OLEAUT32 (deferred)
75300000 75f4b000 SHELL32 (deferred)
76130000 76220000 RPCRT4 (deferred)
76220000 762c1000 ADVAPI32 (deferred)
76510000 76610000 USER32 (deferred)
76640000 76687000 KERNELBASE (deferred)
76730000 76787000 SHLWAPI (deferred)
76930000 76949000 sechost (deferred)
76d30000 76dfc000 MSCTF (deferred)
76e00000 76e90000 GDI32 (deferred)
76f30000 76f90000 IMM32 (deferred)
770c0000 7721c000 ole32 (deferred)
77250000 772fc000 msvcrt (deferred)
77350000 773ed000 USP10 (deferred)
773f0000 773fa000 LPK (deferred)
77400000 77510000 kernel32 (deferred)
77a50000 77bd0000 ntdll (pdb symbols) C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\sym\wntdll.pdb\EA251A0A79C44D5AAF977F6875A518662\wntdll.pdb
输入以下命令可查看调用堆栈信息:
k
调试器窗口将输出以下内容:
# ChildEBP RetAddr
00 0020fbac 00a3190e notepad!WinMain
01 0020fc3c 7741336a notepad!_initterm_e+0x1a1
02 0020fc48 77a89902 kernel32!BaseThreadInitThunk+0xe
03 0020fc88 77a898d5 ntdll!__RtlUserThreadStart+0x70
04 0020fca0 00000000 ntdll!_RtlUserThreadStart+0x1b
(7)输入以下命令再次运行Notepad:
g
(8)如果想中断Notepad的运行,可在Debug菜单中选择Break。
(9)输入以下命令可在ZwWriteFile处设置并且验证断点:
bu ntdll!ZwWriteFile
bl
(10)输入g再次运行Notepad。在Notepad窗口中输入一些文字然后在文件菜单中选择保存。运行代码将会在ZwCreateFile处中断。输入k可查看调用堆栈。
在调试器窗口左下角,命令行的左侧,显示了处理器和线程的数字。在上图中处理器数字为0,线程数字为0。下图中处理器数字为0,线程数字为19。所以,在下图中我们看到是线程19(在处理器0上运行)的堆栈。
(11)输入以下命令可查看现场0的堆栈:
~0s
k
(12)输入以下命令退出调试,并且从Notepad进程中分离:
qd
假设写好了以下简单的控制台程序并且已经编译完成。
...
void MyFunction(long p1, long p2, long p3)
{
long x = p1 + p2 + p3;
long y = 0;
y = x / p2;
}
void main ()
{
long a = 2;
long b = 0;
MyFunction(a, b, 5);
}
在这个例子中,我们假设编译好了程序(MyApp.exe),符号文件(MyApp.pdb)在C:\MyApp\x64\Debug这个路径中,程序代码在C:\MyApp\MyApp。
(1)打开WinDbg
(2)在File菜单中选择Open Executable,在Open Executable对话框中选择文件路径C:\MyApp\x64\Debug,选择MyApp.exe,点击Open。
(3)输入命令:
.sympath srv*
.sympath+ C:\MyApp\x64\Debug
.srcpath C:\MyApp\MyApp
现在WinDbg知道从何处查找程序的符号和代码。
(4)输入命令:
.reload
bu MyApp!main
g
程序将启动并发生中断,中断位置在main函数上。WinDbg显示出了代码和命令窗口。
(5)在Debug菜单中选择Step Into(或者按下F11)。继续单步调试知道进入MyFunction函数。当单步调试到代码行“y = x / p2”时,程序将会发生崩溃并且在调试器中发生中断。调试器窗口将输出以下内容:
(1450.1424): Integer divide-by-zero - code c0000094 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
MyApp!MyFunction+0x44:
00007ff6`3be11064 f77c2428 idiv eax,dword ptr [rsp+28h] ss:00000063`2036f808=00000000
(6)输入命令:
!analyze -v
WinDbg将显示出问题(除0异常)的分析结果
FAULTING_IP:
MyApp!MyFunction+44 [c:\myapp\myapp\myapp.cpp @ 7]
00007ff6`3be11064 f77c2428 idiv eax,dword ptr [rsp+28h]
EXCEPTION_RECORD: ffffffffffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 00007ff63be11064 (MyApp!MyFunction+0x0000000000000044)
ExceptionCode: c0000094 (Integer divide-by-zero)
ExceptionFlags: 00000000
NumberParameters: 0
...
STACK_TEXT:
00000063`2036f7e0 00007ff6`3be110b8 : ... : MyApp!MyFunction+0x44
00000063`2036f800 00007ff6`3be1141d : ... : MyApp!main+0x38
00000063`2036f840 00007ff6`3be1154e : ... : MyApp!__tmainCRTStartup+0x19d
00000063`2036f8b0 00007ffc`b1cf16ad : ... : MyApp!mainCRTStartup+0xe
00000063`2036f8e0 00007ffc`b1fc4629 : ... : KERNEL32!BaseThreadInitThunk+0xd
00000063`2036f910 00000000`00000000 : ... : ntdll!RtlUserThreadStart+0x1d
STACK_COMMAND: dt ntdll!LdrpLastDllInitializer BaseDllName ;dt ntdll!LdrpFailureData ;.cxr 0x0 ;kb
FOLLOWUP_IP:
MyApp!MyFunction+44 [c:\myapp\myapp\myapp.cpp @ 7]
00007ff6`3be11064 f77c2428 idiv eax,dword ptr [rsp+28h]
FAULTING_SOURCE_LINE: c:\myapp\myapp\myapp.cpp
FAULTING_SOURCE_FILE: c:\myapp\myapp\myapp.cpp
FAULTING_SOURCE_LINE_NUMBER: 7
FAULTING_SOURCE_CODE:
3: void MyFunction(long p1, long p2, long p3)
4: {
5: long x = p1 + p2 + p3;
6: long y = 0;
> 7: y = x / p2;
8: }
9:
10: void main ()
11: {
12: long a = 2;
...
(1)在File菜单中选择Attach to a Process,或者按F6
(2)在Attach to Process对话框中选择需要调试的进程,然后点击OK
(3)调试方法和以上两个类似
当应用程序、库、驱动或者操作系统进行链接时,链接器将会创建.exe,.dll以及一些额外的符号文件。符号文件中包含了大量程序运行时不需要用到的数据,但是在调试时这些数据会很有用。通常情况下,符号文件包含以下几个类别:
单个符号文件可能包含很多个符号,包括全局变量、函数名称以及很多局部变量。通常情况下,软件公司会发布两种版本的符号文件:全符号文件(包含公有符号和私有符号)和裁剪符号文件(只包含公有符号)
调试时,需要确保调试器可以访问调试对象相关联的符号文件。无论实时调试或者是调试dump文件都需要符号。调试代码时,需要加载正确的符号进入调试器。
Windows下的符号文件后缀名是.pdb。编译器和链接器控制符号的格式,Visual C++连接器将所有的符号都放到了.pdb文件中。
Windows操作系统提供了两种发布版本。小的二进制文件的免费版(或零售版)以及大的二进制文件并且包含很多调试符号的检测版(或者调试版)。每种发布版本都有其自己的符号文件。调试时,需要匹配正确的Windows版本的符号文件。
下表列出了一些列的包含标准的Windows符号树的目录:
目录 |
包含特定类型的符号文件 |
ACM |
Microsoft Audio Compression Manager files |
COM |
Executable files (.com) |
CPL |
Control Panel programs |
DLL |
Dynamic-link library files (.dll) |
DRV |
Driver files (.drv) |
EXE |
Executable files (.exe) |
SCR |
Screen-saver files |
SYS |
Driver files (.sys) |
在调试Windows内核、驱动或者应用程序时,需要访问正确的符号文件。
如果调试器在运行的时候已经连上了网络,可以直接使用微软的符号服务器。连接符号服务器的方法是使用命令.symfix(设置符号存储路径)。调试器会缓存连接的服务器上的符号文件,下次调试会自动使用本地的缓存文件。
Dump文件可分为内核Dump文件和用户模式Dump文件。本文主要讲述用户模式下Dump文件。用户模式Dump文件又可分为:完整用户模式Dump文件和Minidump文件。
(1)完整用户模式Dump文件
完整用户模式Dump文件是最基本的用户模式的dump文件。文件中包含进程的全部内存空间,程序自身镜像,句柄表,以及其他对调试器非常有用的信息。完整用户模式dump文件可以剪切成minidump文件,先将dump文件加载到调试器中,然后使用.dump(Create Dump File)命令保存一个新的minidump格式的dump文件。
注意尽管其名称有mini,但最大的minidump文件实际上包含的信息比完整的用户模式转储还多。 例如,.dump /mf或.dump /ma将创建比.dump /f更大,更完整的文件。
在用户模式下,.dump /m [MiniOptions]是最佳选择。用此开关创建的转储文件的大小可以从很小到很大。通过指定适当的MiniOptions,可以精确控制所包含的信息。
(2)Minidumps
用户模式转储文件仅包含与进程关联的内存的选定部分,称为最小转储。
小型转储文件的大小和内容取决于要转储的程序和执行转储的应用程序。有时,小型转储文件很大,并且包含完整的内存和句柄表。在其他时候,它要小得多-例如,它可能仅包含有关单个线程的信息,或仅包含有关堆栈中实际引用的模块的信息。
名称“minidump”具有误导性,因为最大的minidump文件实际上比“全”用户模式转储包含更多的信息。例如,.dump /mf或.dump /ma将创建比.dump /f更大,更完整的文件。 因此,建议在所有用户模式转储文件创建中使用.dump /m [MiniOptions],而不是.dump /f。
如果要使用调试器创建小型转储文件,则可以准确选择要包含的信息。 一个简单的.dump /m命令将包含有关组成目标进程的已加载模块的基本信息,线程信息和堆栈信息。可以使用以下任一选项进行修改:
选项 |
描述 |
/ma |
使用所有选项创建转储文件。/ma选项等同于/mfFhut。它将完整的内存数据,句柄数据,卸载的模块信息,基本内存信息和线程时间信息添加到小型转储文件中。 |
/mf |
将完整的内存数据添加到小型转储。将包含目标应用程序拥有的所有可访问的提交页面。 |
/mF |
将所有基本内存信息添加到小型转储。这会将流添加到小型转储,其中包含所有基本内存信息,而不仅仅是有关有效内存的信息。这使得调试器可以在调试小型转储时重建进程的完整虚拟内存布局。 |
/mh |
将有关与目标应用程序关联的句柄的数据添加到小型转储。 |
/mu |
将卸载的模块信息添加到小型转储。仅在Windows Server 2003和更高版本的Windows中可用。 |
/mt |
将其他线程信息添加到minidump。这包括线程时间,调试minidump时可以使用.ttime(显示线程时间)显示该时间。 |
/mi |
将辅助内存添加到小型转储。 辅助存储器是堆栈或后备存储器上的指针引用的任何存储器,外加围绕该地址的小区域。 |
/mp |
将进程环境块(PEB)和线程环境块(TEB)数据添加到小型转储。 如果您需要访问有关应用程序进程和线程的Windows系统信息,这将很有用。 |
/mw |
将所有已提交的可读写私有页面添加到小型转储中。 |
/md |
将可执行镜像中的所有读写数据段添加到小型转储中。 |
/mc |
在镜像中添加代码段。 |
/mr |
从小型转储中删除堆栈的那些部分,并存储对重新创建堆栈跟踪无用的内存。局部变量和其他数据类型值也将被删除。此选项不会使minidump变小(因为这些内存部分仅被清零了),但是如果希望保护其他应用程序的隐私,则该选项很有用。 |
/mR |
从小型转储中删除完整的模块路径。 仅包含模块名称。如果您希望保护用户目录结构的私密性,这是一个有用的选项。 |
/mk "FileName" |
(仅Windows Vista)除了用户模式小型转储之外,还创建内核模式小型转储。内核模式小型转储将限于用户模式小型转储中存储的相同线程。FileName必须用引号引起来。 |
这些选项可以组合。例如,命令.dump /mfiu可用于创建相当大的小型转储,或者命令.dump /mrR可用于创建可保留用户隐私的小型转储。 有关完整语法的详细信息,请参见.dump(创建转储文件)。
WinDbg可以分析用户模式的内存转储文件。在其上创建转储文件的处理器或Windows版本不需要与运行WinDbg的平台匹配。
注意:发生崩溃的程序必须和pdb文件的版本一致(同一时间编译),否则将会发生符号加载失败的情况。
分析步骤:
(1)在菜单File中选择Open Crash Dump菜单或者按下CTRL+D快捷键。当出现“Open Crash Dump”对话框时,在“File name”文本框中输入故障转储文件的完整路径和名称,或使用对话框选择正确的路径和文件名。 选择正确的文件后,单击“Open”。
(2)调试方法和1.2章节类似
A.输入 !analyze -v 命令进行自动分析
B.使用 .excr 命令定位到崩溃位置
C.使用 kp 命令查看崩溃时的堆栈信息