WinDbg用户模式调试基础
在前面的文章中,介绍了如何使用WinDbg分析蓝屏原因https://www.cnblogs.com/zhaotianff/p/15150244.html
不过那会都是在网上找的资料,东拼西凑出来,并没有系统的去学习WinDbg。
最近在学习内核开发这一块的内容,刚好要用到WinDbg,所以这里找资料进行系统的入门一下,做个总结。
本文内容是基于用户模式下的调试,后面的文章中,会介绍内核模式下的调试。
Windows调试工具(Debugging Tools for Windows)
Debugging Tools for Windows由调试器、工具以及软件包中调试器的相关文档组成。这个工具包可以作为Windows SDK或者WDK的一部分安装。
工具包中包括四个调试器:Cdb.exe、Ntsd.exe、Kd.exe和WinDbg.exe。
本文只详细介绍WinDbg,其它工具只做简单介绍。
Cdb.exe和Ntsd.exe
Cdb和Ntsd是用户模式的、基于控制台的调试器。它们能够被附加到进程上,就像别的用户模式调试器一样。这两者都有控制台用户界面一键入一条命令,得到一个回应,如此这般地重复。这两者之间唯一的区别在于,如果从控制台窗口启动,Cdb会直接用原来那个窗口,而Ntsd会打开一个新的窗口。其他方面它们都一样。
Kd.exe
Kd是具有控制台用户界面的内核调试器,能够被附加到本地内核或者另一台机器上。
WinDbg.exe
WinDbg是唯一一个具有图形用户界面的调试器。根据从菜单所做的选择,或者启动时指定的命令行参数,它能够进行用户模式和内核模式调试。
本文是基于用户模式的调试。
WinDbg简介
像平常在Visual Studio中调试时,大部分的功能是借助菜单或按钮实现,WinDbg建立在命令之上。
用户输入一个命令,调试器用文本描述命令执行的结果给出响应。
在GUI模式下,一些结果用专门的窗口进行显示,比如局部变量、栈、线程等。
正是因为这种调试模式,所以WinDbg学习的门槛就要相对高一些,得记这些命令。
WinDbg支持三类命令:
1、内部命令
这些命令内建于调试器之中,操作被调试的目标
2、元命令
这些命令以(.)开头,操作调试进程自身,不直接操作被调试的目标。
3、扩展命令
这些命令以(!)开头,为调试器提供了很多功能。所有扩展命令均在外部DLL中实现。 默认情况下,调试器加载一组预定义的扩展DLL,但是通过使用.load命令可以从Debugger目录或其他目录中加载更多DLL。
说明:WinDbg是大小写不敏感的,所以在输入命令时,大小写都可以。
WinDbg基础调试(用户模式)
有两种方法可以初始化用户模式的调试
1、先打开WinDbg,再通过在“ 文件 ”菜单上,选择“ 启动可执行文件”。程序运行后,会自动附加到WinDbg
2、先运行程序,再打开WinDbg,然后通过“文件”菜单上,选择“附加到进程”,通过选择进程列表里的进程进行附加。
这里我们以notepad(Windows记事本)为例进行演示
1、打开WinDbg
2、启动notepad
在“ 文件 ”菜单上,选择“ 启动可执行文件”。 在“启动可执行文件”对话框中,输入C:\Windows\System32\notepad.exe。 (notepad.exe 文件通常位于 C:\Windows\System32.) 对于 “文件名”,请输入 notepad.exe。 选择“打开” 。进程创建后,会自动中断,WinDbg会自动添加一个断点。
说明:命令窗口是我们主要关注的窗口,它应该始终保持打开状态。这个窗口会显示各种命令的响应结果。
如果不小心关闭了,可以通过工具栏按钮再次打开
3、输入 .symfix命令,自动将符号路径设置为指向 Microsoft 符号存储
关于调试符号,可以参考https://www.cnblogs.com/zhaotianff/p/16931797.html
4、输入 x notepad!* 命令查看notepad模块的符号
1 0:000> x notepad!*2 00007ff6`40267460 notepad!<lambda_13f119b44549d7aec2177494846f39c3>::<lambda_invoker_cdecl> (void)3 00007ff6`4026eb70 notepad!wistd::__function::__func<<lambda_0186fc037f7c95b0bbb1a993b253897d>,long __cdecl(unsigned short *,unsigned __int64,unsigned __int64 *)>::operator() (void)4 00007ff6`402662d0 notepad!<lambda_694cf3250e255c878c64dabf1ae2e40c>::<lambda_invoker_cdecl> (void)5 00007ff6`40267bac notepad!ShowOpenSaveDialog (void)6 00007ff6`4027d75c notepad!StringLengthWorkerW (void)7 00007ff6`4027f5a0 notepad!`WaitForCompletion<Windows::Foundation::IAsyncOperationCompletedHandler<Windows::Security::EnterpriseData::FileProtectionInfo *>,Windows::Foundation::IAsyncOperation<Windows::Security::EnterpriseData::FileProtectionInfo *> >'::`2'::FTMEventDelegate::Invoke (void)8 00007ff6`40261380 notepad!wil::details::`dynamic initializer for 'g_header_init_InitializeStagingSRUMFeatureReporting'' (void)9 00007ff6`40267570 notepad!wistd::__function::__func<<lambda_0186fc037f7c95b0bbb1a993b253897d>,long __cdecl(unsigned short *,unsigned __int64,unsigned __int64 *)>::destroy (void)
这里的!后面的*通配符,代表显示全部符号。
如果我们想查找 notepad的Main函数,可以输入 x notepad!*main*
1 0:012> x notepad!*main* 2 00007ff6`47a10118 notepad!__mainCRTStartup (void) 3 00007ff6`47a11213 notepad!__mainCRTStartup$filt$0 (void) 4 00007ff6`47a13000 notepad!_imp___getmainargs = <no type information> 5 00007ff6`479fa140 notepad!WinMain (WinMain) 6 00007ff6`47a10100 notepad!WinMainCRTStartup (WinMainCRTStartup)
5、使用 ~ 命令,显示被调试进程内部的所有线程信息
1 0:000> ~ 2 . 0 Id: 5ba4.950 Suspend: 1 Teb: 00000034`cb6a0000 Unfrozen 3 1 Id: 5ba4.234c Suspend: 1 Teb: 00000034`cb6a2000 Unfrozen 4 2 Id: 5ba4.6834 Suspend: 1 Teb: 00000034`cb6a4000 Unfrozen 5 3 Id: 5ba4.52dc Suspend: 1 Teb: 00000034`cb6a6000 Unfrozen
线程基本信息如下所示(这里以列表里的第一项进行说明)
0 | Id: 5ba4.950 | Suspend: 1 | Teb: 00000034`cb6a0000 | Unfrozen |
调试器线程索引 | 进程ID.线程ID | 挂起计数(通常是1) | 线程环境块(TEB) | 是否冻结(从调试器的角度看,通常未冻结) |
说明:WinDbg默认以十六进制显示,可以使用 ? 命令将数值转换为十进制,
如前面进程Id为5ba4,输入 ? 5ba4,可以查看十进制 进程Id号
1 0:002> ? 5ba4 2 Evaluate expression: 23460 = 00000000`00005ba4
可以用0n前缀代表十进制数字,使用这个命令可以反查十六进制数字
1 0:002> ? 0n23460 2 Evaluate expression: 23460 = 00000000`00005ba4
6、输入 lm 命令,查看已经加载的模块
1 0:000> lm2 start end module name3 00007ff6`40260000 00007ff6`40292000 notepad (pdb symbols) C:\ProgramData\dbg\sym\notepad.pdb\48F76637AE64DAE8764C8F9F4B27AEA51\notepad.pdb4 00007ffb`e0710000 00007ffb`e0995000 COMCTL32 (deferred) 5 00007ffb`f29d0000 00007ffb`f29f1000 win32u (deferred) 6 00007ffb`f2b40000 00007ffb`f2bc0000 bcryptPrimitives (deferred) 7 00007ffb`f2bc0000 00007ffb`f2cba000 ucrtbase (deferred) 8 00007ffb`f3440000 00007ffb`f34de000 msvcp_win (deferred) 9 00007ffb`f3550000 00007ffb`f37f3000 KERNELBASE (deferred) 10 00007ffb`f3950000 00007ffb`f3ae4000 gdi32full (deferred) 11 00007ffb`f3af0000 00007ffb`f3b99000 shcore (deferred) 12 00007ffb`f3ba0000 00007ffb`f3d33000 USER32 (deferred) 13 00007ffb`f3d40000 00007ffb`f3de3000 advapi32 (deferred) 14 00007ffb`f3f90000 00007ffb`f4042000 KERNEL32 (deferred) 15 00007ffb`f49a0000 00007ffb`f4a3e000 msvcrt (deferred) 16 00007ffb`f4a50000 00007ffb`f4ae7000 sechost (deferred) 17 00007ffb`f4d70000 00007ffb`f4e90000 RPCRT4 (deferred) 18 00007ffb`f4f00000 00007ffb`f4f26000 GDI32 (deferred) 19 00007ffb`f55f0000 00007ffb`f5926000 combase (deferred) 20 00007ffb`f5a40000 00007ffb`f5c30000 ntdll (pdb symbols) C:\ProgramData\dbg\sym\ntdll.pdb\CFD10E5F223FEE5F26227CB82510FEDC1\ntdll.pdb
模块列表显示了当前被调试进程(notepad.exe)的所有模块(DLL和EXE)。你能够从中看到已加载的模块的起始和终止的虚拟地址。
在模块名称后面可以看到这个模块的符号的状态(在括号里)。可能的值有这些:
deferred(推迟)
在当前调试会话中还没用到,因此现在还没有被加载。这些符号会在载入时被载入。
pdb symbols(pdb符号)
正确的公开符号已经被载入(来自微软公开的符号服务器),后面会显示PDF文件路径
export symbol(输出符号)
这个DLL只有输出符号可用,这一般意味着该模块没有符号,或者没有找到相应的符号
no symbol(没有符号)
试图去找本模块的符号,但是什么都没发现,连输出符号都没有(这种模块没有输出符号,比如可执行文件和驱动程序文件)。
可以通过.reload /f modulename.dll强制加载模块的符号
如这里我执行 .reload /f gdi32.dll
1 0:000> .reload /f gdi32.dll2 0:000> lm3 start end module name4 00007ff6`40260000 00007ff6`40292000 notepad (pdb symbols) c:\symbols\notepad.pdb\48F76637AE64DAE8764C8F9F4B27AEA51\notepad.pdb5 00007ffb`e0710000 00007ffb`e0995000 COMCTL32 (deferred) 6 00007ffb`f29d0000 00007ffb`f29f1000 win32u (deferred) 7 00007ffb`f2b40000 00007ffb`f2bc0000 bcryptPrimitives (deferred) 8 00007ffb`f2bc0000 00007ffb`f2cba000 ucrtbase (deferred) 9 00007ffb`f3440000 00007ffb`f34de000 msvcp_win (deferred) 10 00007ffb`f3550000 00007ffb`f37f3000 KERNELBASE (deferred) 11 00007ffb`f3950000 00007ffb`f3ae4000 gdi32full (deferred) 12 00007ffb`f3af0000 00007ffb`f3b99000 shcore (deferred) 13 00007ffb`f3ba0000 00007ffb`f3d33000 USER32 (deferred) 14 00007ffb`f3d40000 00007ffb`f3de3000 advapi32 (deferred) 15 00007ffb`f3f90000 00007ffb`f4042000 KERNEL32 (deferred) 16 00007ffb`f49a0000 00007ffb`f4a3e000 msvcrt (deferred) 17 00007ffb`f4a50000 00007ffb`f4ae7000 sechost (deferred) 18 00007ffb`f4d70000 00007ffb`f4e90000 RPCRT4 (deferred) 19 00007ffb`f4f00000 00007ffb`f4f26000 GDI32 (pdb symbols) c:\symbols\gdi32.pdb\209AD405837D061EF9D34CBDC009D7711\gdi32.pdb 20 00007ffb`f55f0000 00007ffb`f5926000 combase (deferred) 21 00007ffb`f5a40000 00007ffb`f5c30000 ntdll (pdb symbols) c:\symbols\ntdll.pdb\CFD10E5F223FEE5F26227CB82510FEDC1\ntdll.pdb
7 当前线程指示
在前面输入 ~ 查看线程时,有一个线程数据前面带有一个点,这个线程就是当前线程。
1 0:005> ~ 2 0 Id: 8824.5a80 Suspend: 1 Teb: 00000096`de0af000 Unfrozen 3 1 Id: 8824.48bc Suspend: 1 Teb: 00000096`de0bd000 Unfrozen 4 2 Id: 8824.521c Suspend: 1 Teb: 00000096`de0bf000 Unfrozen 5 3 Id: 8824.460 Suspend: 1 Teb: 00000096`de0c3000 Unfrozen 6 4 Id: 8824.5a30 Suspend: 1 Teb: 00000096`de107000 Unfrozen 7 . 5 Id: 8824.9a8 Suspend: 1 Teb: 00000096`de109000 Unfrozen
如果没有指定哪个线程的话,任何线程命令都会作用到这个线程上。
8 输入命令 k ,显示当前线程的调用堆栈
1 0:005> k 2 # Child-SP RetAddr Call Site 3 00 00000096`ddfafc78 00007ffb`f5b0d4db ntdll!DbgBreakPoint 4 01 00000096`ddfafc80 00007ffb`f3fa7bd4 ntdll!DbgUiRemoteBreakin+0x4b 5 02 00000096`ddfafcb0 00007ffb`f5aace71 KERNEL32!BaseThreadInitThunk+0x14 6 03 00000096`ddfafce0 00000000`00000000 ntdll!RtlUserThreadStart+0x21
可以看到这个线程的调用列表(当然,只有用户模式的)。上面输出中的栈顶部是函数DbgBreakPoint,它位于模块ntdll.dll中。
通用的带有符号的地址格式是: modulename !functionname + offset。如果正好位于函数的开头,那么offset是可选的,也可能为零。
另外要注意模块名称里不带扩展名。在上面的输出中, DbgBreakPoint被DbgUiRemoteBreakin调用,而后者又被BaseThreadInitThunk调用,依此类推。
另外说明一下:该线程是被WinDbg注入的,而非进程的实际线程,以便强行进入目标进程。
9 输入命令 ~ns 切换线程 , n是线程的索引值。
如切换到线程0就是 ~0s
再执行 k,显示调用堆栈,输出如下:
1 0:005> ~0s2 win32u!NtUserGetMessage+0x14:3 00007ffb`f29d1164 c3 ret4 0:000> k5 # Child-SP RetAddr Call Site6 00 00000096`ddeafd28 00007ffb`f3bc477d win32u!NtUserGetMessage+0x147 01 00000096`ddeafd30 00007ff6`4026a3d3 USER32!GetMessageW+0x2d8 02 00000096`ddeafd90 00007ff6`402802b7 Notepad!WinMain+0x2939 03 00000096`ddeafe60 00007ffb`f3fa7bd4 Notepad!__mainCRTStartup+0x19f 10 04 00000096`ddeaff20 00007ffb`f5aace71 KERNEL32!BaseThreadInitThunk+0x14 11 05 00000096`ddeaff50 00000000`00000000 ntdll!RtlUserThreadStart+0x21
这是Notepad的主线程(第一个线程)。栈的顶部显示了线程正在等待用户界面消息。
10 输入 ~nk 在不切换线程的情况下显示指定线程的调用堆栈
如输入 ~0k,可以显示线程0的调用堆栈
1 0:002> ~0k 2 # Child-SP RetAddr Call Site 3 00 00000096`ddeafd28 00007ffb`f3bc477d win32u!NtUserGetMessage+0x14 4 01 00000096`ddeafd30 00007ff6`4026a3d3 USER32!GetMessageW+0x2d 5 02 00000096`ddeafd90 00007ff6`402802b7 Notepad!WinMain+0x293 6 03 00000096`ddeafe60 00007ffb`f3fa7bd4 Notepad!__mainCRTStartup+0x19f 7 04 00000096`ddeaff20 00007ffb`f5aace71 KERNEL32!BaseThreadInitThunk+0x14 8 05 00000096`ddeaff50 00000000`00000000 ntdll!RtlUserThreadStart+0x21
此时我们再调用 ~ 查看线程列表,发现 那个点 已经移到线程2。在线程5上还显示了一个#。
1 0:002> ~ 2 0 Id: 8824.5a80 Suspend: 1 Teb: 00000096`de0af000 Unfrozen 3 1 Id: 8824.48bc Suspend: 1 Teb: 00000096`de0bd000 Unfrozen 4 . 2 Id: 8824.521c Suspend: 1 Teb: 00000096`de0bf000 Unfrozen 5 3 Id: 8824.460 Suspend: 1 Teb: 00000096`de0c3000 Unfrozen 6 4 Id: 8824.5a30 Suspend: 1 Teb: 00000096`de107000 Unfrozen 7 # 5 Id: 8824.9a8 Suspend: 1 Teb: 00000096`de109000 Unfrozen
带有#标识的线程是引起最后一个断点的线程(在当前演示中是因为我们初始附加调试器的操作)
11 输入 !teb 命令可以查看当前线程的TEB
1 0:002> !teb2 TEB at 00000096de0bf0003 ExceptionList: 00000000000000004 StackBase: 00000096de5000005 StackLimit: 00000096de4ef0006 SubSystemTib: 00000000000000007 FiberData: 0000000000001e008 ArbitraryUserPointer: 00000000000000009 Self: 00000096de0bf000 10 EnvironmentPointer: 0000000000000000 11 ClientId: 0000000000008824 . 000000000000521c 12 RpcHandle: 0000000000000000 13 Tls Storage: 0000026f1b184d80 14 PEB Address: 00000096de0ae000 15 LastErrorValue: 0 16 LastStatusValue: c000000d 17 Count Owned Locks: 0 18 HardErrorMode: 0
!teb 后面带其它线程TEB的地址,可以输出其它线程的TEB
1 0:002> !teb 00000096`de0c30002 TEB at 00000096de0c30003 ExceptionList: 00000000000000004 StackBase: 00000096de6000005 StackLimit: 00000096de5ef0006 SubSystemTib: 00000000000000007 FiberData: 0000000000001e008 ArbitraryUserPointer: 00000000000000009 Self: 00000096de0c3000 10 EnvironmentPointer: 0000000000000000 11 ClientId: 0000000000008824 . 0000000000000460 12 RpcHandle: 0000000000000000 13 Tls Storage: 0000026f1b184ba0 14 PEB Address: 00000096de0ae000 15 LastErrorValue: 0 16 LastStatusValue: c000000d 17 Count Owned Locks: 0 18 HardErrorMode: 0
!teb命令显示的数据含义
StackBase和StackLimit:当前线程的用户模式栈基址和限制。
ClientId:进程和线程ID。
LastErrorValue :上一个Win32错误代码( GetLastError )。
Tls Storage:此线程的线程局部存储(TLS )数组。这里不做详细介绍,可以参考以下链接:使用线程本地存储 - Win32 apps | Microsoft Learn
PEB Address:进程环境块(PEB)的地址,可以通过!peb来查看PEB的内容
_teb命令显示的是其背后真正的结构中的部分内容,这里的结构是_TEB,它在ntdll中定义。
可以使用dt(display type) _teb命令查看真正的结构:
如果知道结构在哪里定义的,可以在结构名称前带上模块名称,如这里带上ntdll
1 dt ntdll!_teb
在前面的命令中加上一个地址,就可以得到这个结构数据成员的实际值
0:002> dt _teb 00000096de0bf000 ntdll!_TEB+0x000 NtTib : _NT_TIB+0x038 EnvironmentPointer : (null) +0x040 ClientId : _CLIENT_ID+0x050 ActiveRpcHandle : (null) +0x058 ThreadLocalStoragePointer : 0x0000026f`1b184d80 Void+0x060 ProcessEnvironmentBlock : 0x00000096`de0ae000 _PEB+0x068 LastErrorValue : 0+0x06c CountOfOwnedCriticalSections : 0+0x070 CsrClientThread : (null) +0x078 Win32ThreadInfo : 0x00000000`0000521c Void+0x080 User32Reserved : [26] 0+0x0e8 UserReserved : [5] 0+0x100 WOW32Reserved : (null) +0x108 CurrentLocale : 0x804+0x10c FpSoftwareStatusRegister : 0+0x110 ReservedForDebuggerInstrumentation : [16] (null) +0x190 SystemReserved1 : [30] (null) +0x280 PlaceholderCompatibilityMode : 0 ''+0x281 PlaceholderHydrationAlwaysExplicit : 0 ''+0x282 PlaceholderReserved : [10] ""+0x28c ProxiedProcessId : 0+0x290 _ActivationStack : _ACTIVATION_CONTEXT_STACK+0x2b8 WorkingOnBehalfTicket : [8] ""+0x2c0 ExceptionCode : 0n0+0x2c4 Padding0 : [4] ""+0x2c8 ActivationContextStackPointer : 0x00000096`de0bf290 _ACTIVATION_CONTEXT_STACK+0x2d0 InstrumentationCallbackSp : 0
每个成员都会显示出相对于结构起始处的偏移量、成员的名称和值。简单的值直接显示,而结构体的值(例如上面的ClientID )通常会显示成一个超链接。
单击这个超链接会显示出该结构的详情。调试器会使用一个新的dx命令来查看数据
1 0:002> dx -r1 (*((ntdll!_CLIENT_ID *)0x96de0bf040)) 2 (*((ntdll!_CLIENT_ID *)0x96de0bf040)) [Type: _CLIENT_ID] 3 [+0x000] UniqueProcess : 0x8824 [Type: void *] 4 [+0x008] UniqueThread : 0x521c [Type: void *]
说明:TEB全程Thread Environment Block(线程环境块),它包含了线程的上下文信息(The Thread Environment Block holds context information for a thread.)
TEB在线程数据结构中的位置如下:
关于TEB,如果想了解更多信息,可以查看《Windows Internals Seventh Edition Part 1》的第4章Thread
12 使用 bp / bu 命令添加断点
如我们要在CreateFile函数处设置一个断点,可以输入 bp kernel32!CreateFileW 命令
1 0:012> bp kernel32!CreateFileW 2 0:012> bl 3 0 e Disable Clear 00007ffb`f3fb2090 0001 (0001) 0:**** KERNEL32!CreateFileW
如我们要在 notepad.exe的Main函数
放置断点,可以输入 bp notepad!WinMain 命令
1 0:012> bp notepad!WinMain 2 0:012> bl 3 0 e Disable Clear 00007ff6`479fa140 0001 (0001) 0:**** notepad!WinMain
13、使用 bl 命令查看断点列表
1 0:012> bl 2 0 e Disable Clear 00007ffb`f3fb2090 0001 (0001) 0:**** KERNEL32!CreateFileW
可以看到断点的索引值(0)是被允许了还是被禁止了( e=被允许,d=被禁止)
并且得到用来禁止( bd命令)/ 启用(be命令)和删除( bc命令)该断点的超链接。单击链接可以禁止和删除断点
bd/be 后面带*,可以禁用或启用全部断点
bd/be 后面带数字,可以禁用或启用对应的断点
14、输入 g 命令、按下工具栏上的Go按钮或者按F5键,会继续执行进程。
调试器会显示正在忙碌,这也就意味着直到下次中断才能输入命令。
此时我们回到记事本,用文件菜单打开一个文件,打开文件时会调用CreateFileW函数,调试器会触发断点,然后中断。
此时我们输入 k 查看调用堆栈(如果调试器需要从微软的符号服务器下载符号的话,这里的加载时间会长一点,请耐心等待)
1 0:004> ~2 0 Id: 9b34.26b0 Suspend: 1 Teb: 00000005`79b86000 Unfrozen3 1 Id: 9b34.59a8 Suspend: 1 Teb: 00000005`79b88000 Unfrozen4 2 Id: 9b34.3be8 Suspend: 1 Teb: 00000005`79b8a000 Unfrozen5 3 Id: 9b34.2364 Suspend: 1 Teb: 00000005`79b8c000 Unfrozen6 . 4 Id: 9b34.40ac Suspend: 1 Teb: 00000005`79b8e000 Unfrozen7 5 Id: 9b34.57f4 Suspend: 1 Teb: 00000005`79ba4000 Unfrozen8 6 Id: 9b34.5b6c Suspend: 1 Teb: 00000005`79b92000 Unfrozen9 7 Id: 9b34.2f90 Suspend: 1 Teb: 00000005`79b94000 Unfrozen 10 8 Id: 9b34.4844 Suspend: 1 Teb: 00000005`79b96000 Unfrozen 11 9 Id: 9b34.8ab8 Suspend: 1 Teb: 00000005`79baa000 Unfrozen 12 10 Id: 9b34.5e2c Suspend: 1 Teb: 00000005`79b9a000 Unfrozen 13 11 Id: 9b34.9288 Suspend: 1 Teb: 00000005`79ba8000 Unfrozen 14 12 Id: 9b34.7bfc Suspend: 1 Teb: 00000005`79ba0000 Unfrozen 15 13 Id: 9b34.7188 Suspend: 1 Teb: 00000005`79bac000 Unfrozen 16 14 Id: 9b34.1a2c Suspend: 1 Teb: 00000005`79bae000 Unfrozen 17 15 Id: 9b34.44e4 Suspend: 1 Teb: 00000005`79bb0000 Unfrozen 18 16 Id: 9b34.5418 Suspend: 1 Teb: 00000005`79bb2000 Unfrozen 19 0:004> k 20 # Child-SP RetAddr Call Site 21 00 00000005`79d7e9f8 00007ffb`c01785d6 KERNEL32!CreateFileW 22 01 00000005`79d7ea00 00007ffb`c01786d2 TortoiseSVN+0x385d6 23 02 00000005`79d7ea60 00007ffb`c0178913 TortoiseSVN+0x386d2 24 03 00000005`79d7eaa0 00007ffb`c01778cc TortoiseSVN+0x38913 25 04 00000005`79d7ed50 00000000`60bb1706 TortoiseSVN+0x378cc 26 05 00000005`79d7f030 00007ffb`f4334625 TortoiseOverlays+0x1706 27 06 00000005`79d7f060 00007ffb`f43344f6 SHELL32!CFSIconOverlayManager::_GetFileOverlayInfo+0x111 28 07 00000005`79d7f140 00007ffb`f2df1331 SHELL32!CFSIconOverlayManager::GetFileOverlayInfo+0x46 29 08 00000005`79d7f180 00007ffb`f2e5936b windows_storage!CFSFolder::_GetOverlayInfo+0x179 30 09 00000005`79d7f240 00007ffb`f2e59267 windows_storage!CRegFolder::_GetOverlayInfo+0xbf 31 0a 00000005`79d7f310 00007ffb`f2d53de6 windows_storage!CRegFolder::GetOverlayIndex+0x47 32 0b 00000005`79d7f340 00007ffb`f2e5936b windows_storage!CAutoDestItemsFolder::GetOverlayIndex+0xb6 33 0c 00000005`79d7f3c0 00007ffb`f2e59267 windows_storage!CRegFolder::_GetOverlayInfo+0xbf 34 0d 00000005`79d7f490 00007ffb`caa1b191 windows_storage!CRegFolder::GetOverlayIndex+0x47 35 0e 00000005`79d7f4c0 00007ffb`caa42a95 explorerframe!CNscOverlayTask::_Extract+0x51 36 0f 00000005`79d7f510 00007ffb`caa16362 explorerframe!CNscOverlayTask::InternalResumeRT+0x45 37 10 00000005`79d7f540 00007ffb`f2e39be4 explorerframe!CRunnableTask::Run+0xb2 38 11 00000005`79d7f580 00007ffb`f2e39825 windows_storage!CShellTask::TT_Run+0x3c 39 12 00000005`79d7f5b0 00007ffb`f2e39705 windows_storage!CShellTaskThread::ThreadProc+0xdd 40 13 00000005`79d7f660 00007ffb`f3b226f6 windows_storage!CShellTaskThread::s_ThreadProc+0x35 41 14 00000005`79d7f690 00007ffb`f5a6f665 shcore!ExecuteWorkItemThreadProc+0x16 42 15 00000005`79d7f6c0 00007ffb`f5a745c4 ntdll!RtlpTpWorkCallback+0x165 43 16 00000005`79d7f7a0 00007ffb`f3fa7bd4 ntdll!TppWorkerThread+0x8d4 44 17 00000005`79d7fb60 00007ffb`f5aace71 KERNEL32!BaseThreadInitThunk+0x14 45 18 00000005`79d7fb90 00000000`00000000 ntdll!RtlUserThreadStart+0x21
15、 查看内存数据
当调试器在CreateFIleW函数中断时,我们能做些什么?
可能想知道现在正在打开什么文件,我们能够根据CreateFilew函数的调用惯例来得到这个信息。由于这是一个64位进程(并且处理器是Intel ),调用惯例中提到了第一个整数或者指针参数通过RCX、RDX、R8和R9寄存器进行传递。因为文件名是CreateFilew的第一个参数,所以相应的寄存器是RCX。
用 r 命令 显示 RCX 寄存器的值
1 0:004> r rcx 2 rcx=00000163401b4bc8
用 db 命令以字节方式显示内存,右边是相应的ASCII字符。
db 后面是内存的地址,也就是上面rcx=后面的值。
1 0:004> db 00000163401b4bc8 2 00000163`401b4bc8 5c 00 5c 00 2e 00 5c 00-70 00 69 00 70 00 65 00 \.\...\.p.i.p.e. 3 00000163`401b4bd8 5c 00 54 00 53 00 56 00-4e 00 43 00 61 00 63 00 \.T.S.V.N.C.a.c. 4 00000163`401b4be8 68 00 65 00 2d 00 30 00-30 00 30 00 30 00 30 00 h.e.-.0.0.0.0.0. 5 00000163`401b4bf8 30 00 30 00 30 00 34 00-31 00 31 00 30 00 35 00 0.0.0.4.1.1.0.5. 6 00000163`401b4c08 61 00 35 00 35 00 00 00-00 00 00 00 00 00 00 00 a.5.5........... 7 00000163`401b4c18 01 ba 67 bb 45 02 00 80-43 00 3a 00 5c 00 50 00 ..g.E...C.:.\.P. 8 00000163`401b4c28 72 00 6f 00 67 00 72 00-61 00 6d 00 20 00 46 00 r.o.g.r.a.m. .F. 9 00000163`401b4c38 69 00 6c 00 65 00 73 00-5c 00 54 00 6f 00 72 00 i.l.e.s.\.T.o.r.
由于这个字符串是Unicode的,所以使用db命令看起来不是非常方便。
使用 du 命令可以更加方便地查看Unicode字符串
1 0:004> du 00000163401b4bc8 2 00000163`401b4bc8 "\\.\pipe\TSVNCache-0000000041105" 3 00000163`401b4c08 "a55"
可以通过给寄存器名字前加@前缀来直接使用寄存器的值
1 0:004> du @rcx 2 00000163`401b4bc8 "\\.\pipe\TSVNCache-0000000041105" 3 00000163`401b4c08 "a55"
16、 使用 u 命令查看反汇编指令
我们增加一个新的断点,断点在NtCreateFile函数,这个API会被CreateFileW调用
1 0:014> bp ntdll!ntcreatefile 2 0:014> bl 3 0 e Disable Clear 00007ffb`f3fb2090 0001 (0001) 0:**** KERNEL32!CreateFileW 4 1 e Disable Clear 00007ffb`f5adcaf0 0001 (0001) 0:**** ntdll!NtCreateFile
用g 命令继续执行,调试器应该会中断
1 0:014> g 2 Breakpoint 1 hit 3 ntdll!NtCreateFile: 4 00007ffb`f5adcaf0 4c8bd1 mov r10,rcx
用 u 命令列出接下来要执行的8条指令
1 0:014> u2 ntdll!NtCreateFile:3 00007ffb`f5adcaf0 4c8bd1 mov r10,rcx4 00007ffb`f5adcaf3 b855000000 mov eax,55h5 00007ffb`f5adcaf8 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],16 00007ffb`f5adcb00 7503 jne ntdll!NtCreateFile+0x15 (00007ffb`f5adcb05)7 00007ffb`f5adcb02 0f05 syscall8 00007ffb`f5adcb04 c3 ret9 00007ffb`f5adcb05 cd2e int 2Eh 10 00007ffb`f5adcb07 c3 ret
值0x55被复制到了EAX寄存器。这是NtCreateFile的系统服务号。列表中显示的syscall指令用来转换到内核模式,然后执行NtCreateFile系统服务。
说明:CreateFileW函数在kernel32.dll中实现,这里kernel32.dll是Windows子系统的一个DLL。CreateFileW函数在用户模式运行,因此无法直接打开文件。在进行了一些错误检查之后,它调用了NtCreateFile。这是一个在NTDLL.dll中实现的函数,而NTDLL.dll是—个基础的DLL,它实现了“原生API(Native API)“,并且它实际上是位于用户模式的底层代码。NtCreateFile是一个执行到内核模式的转换API。在进行实际的转换之前,它先把一个叫作系统服务号的数字(NtCreateFile是0x55)放到CPU的寄存器里(Intel/AMD体系结构上是EAX)。然后它会执行一个特殊的CPU指令(在x64系统里是syscall,在x86系统里是sysenter)来实际转换到内核模式,并跳转到一个预定义的被称为系统服务分发器( system service dispatcher )的例程。
系统服务分发器继而使用EAX寄存器中的值作为系统服务分发表( System Service Dispatch Table,SSDT)的入口索引,代码跳转至相应的系统服务中。对上述的记事本例子来说,SSDT中相应的入口会指向IO管理器(I/O Manager )的NtCreateFile函数。请注意,这个函数与NTDLL.dll里的函数有相同的名称,而且还有一样的参数。当系统服务执行完毕后,线程会返回到用户模式,执行紧接着sysenter/syscall的指令。这些事件的顺序如下所示。
17、使用 p 命令能够以跳过函数的方式(逐过程)单步执行下一条指令(按F10键也可以)
说明: t 命令以进入函数的方式(逐语句)
1 0:014> p2 ntdll!NtCreateFile+0x3:3 00007ffb`f5adcaf3 b855000000 mov eax,55h4 0:014> p5 ntdll!NtCreateFile+0x8:6 00007ffb`f5adcaf8 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1 ds:00000000`7ffe0308=007 0:014> p8 ntdll!NtCreateFile+0x10:9 00007ffb`f5adcb00 7503 jne ntdll!NtCreateFile+0x15 (00007ffb`f5adcb05) [br=0] 10 0:014> p 11 ntdll!NtCreateFile+0x12: 12 00007ffb`f5adcb02 0f05 syscall 13 0:014> p 14 ntdll!NtCreateFile+0x14: 15 00007ffb`f5adcb04 c3 ret
由于当前是用户模式下的调试,所以单步跟踪进入syscall指令是不可能的。不管是逐过程还是逐语句,都会执行完该指令并返回结果。
在x64调用惯例下,函数的返回值保存在EAX或者RAX里。对系统调用来说,它是一个NTSTATUS值,因此EAX中包含返回状态:
1 0:014> r eax 2 eax=0
18、单击工具栏上的 ”Break“按钮 或按Ctrl + Break键可以强制中断
禁用所有断点,并让notepad继续运行
1 0:014> bd * 2 0:014> g
现在没有断点了,可以点击 Break
按钮,再次中断。
19、输入 qd 命令结束调试会话,并使任何用户模式目标应用程序保持运行状态。
这个命令实际上是 .detach命令(结束调试会话,但使任何用户模式目标应用程序保持运行状态)和q命令(结束调试会话)的结合
通过上面的命令,目前已经对WinDbg用户模式下的调试有了初步的认识,后续 我还会补充一些文章,对用户模式下的调试做深入介绍。