- Debugging Tools for Windows (WinDbg, KD, CDB, NTSD)
- Debugging Techniques
- Standard Debugging Techniques
- Using Breakpoints
- Reading and Writing Memory
- Using the !analyze Extension
- Noninvasive Debugging (User Mode)
- Debugging in Assembly Mode
- Changing Contexts
- Controlling Processes and Threads
- Controlling Exceptions and Events
- Debugging a Stack Overflow
- Manually Walking a Stack
- Debugging an Application Failure
- Crashing and Rebooting the Target Computer
- Finding a Memory Leak
- Debugging a Time Out
- Debugging a Stalled System
- Displaying a Critical Section
- Specialized Debugging Techniques
- Standard Debugging Techniques
Debugging Tools for Windows (WinDbg, KD, CDB, NTSD)
1、3种方式获取调试工具:WDK、SDK、standalone tool set
Getting Started with Windows Debugging
调试基本流程:
1、确定主机和目标机
2、确定调试内核还是应用层
3、按需选择调试工具
4、选择主机和目标机连接方式:网络、串口、USB、虚拟机
5、选择32位还是64位调试工具
6、配置符号
7、配置源码
8、熟悉调试工具操作方法
9、熟悉调试技术
10、使用调试指令
11、使用调试扩展
Getting Started with WinDbg (User-Mode)
为什么不注入到目标进程,都可以进行调试?
答:因为操作系统内核体系支持,只要应用层调用DebugActiveProcess启动调试,操作系统会触发int 3中断(从调用栈里看是这玩意儿:ntdll!DbgUiRemoteBreakin、ntdll!DbgBreakPoint),这个中断向量表里填充的响应函数是kiTrap,这个东西首先分发给调试系统,然后才会分发给中断异常系统。所以本质是利用中断和系统本身的内核级调试支持。剩下的,就是各种调试事件的使用。
如何调试一个应用层程序?步骤如下:
1、.sympath srv*xxxx xxxx表示符号路径,可以是网络路径,微软的符号服务器路径:http://msdl.microsoft.com/download/symbols,当然,这里可以直接使用界面设置
2、.reload 加载模块pdb,将从刚设置的位置搜索
3、x notepad!* 检查符号加载
4、g
5、菜单Break——>Debug
6、lm
7、k
8、bu notepad!WinMain
9、bl 设置完断点,列一下确认下
10、~ 罗列所有的线程id
11、~3s 切换到线程id为3的线程栈,然后k
12、!analyze -v 中断后,可以使用命令分析,得出基本结论和中断位置
13、qd Quit and Detach
Getting Started with WinDbg (Kernel-Mode)
调试驱动准备环境:
1、双机调试
2、双机连接方式:
(1)Win8及以上,可以使用网络连接
(2)Win8以下只能用usb、serial、1394
3、启动连接会话
(1)手动方法:https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/setting-up-kernel-mode-debugging-in-windbg--cdb--or-ntsd
(2)自动方法:使用Virtual-KD工具,将目录里面的target拷贝到目标机器,执行vminstall.exe;然后主机运行vmmon.exe,启动windbg即可
如何调试一个驱动?步骤如下:
1、建立双机连接
2、设置符号表
3、lm
4、x nt!*CreateProcess* 检查符号表
5、dt nt!_FILE_OBJECT 常用结构查看
6、bu nt!MmCreateProcessAddressSpace 设置断点
7、bl
8、g
9、k
10、bc * 清除所有断点
11、!process 0 0 输出所有当前进程的基本信息
12、!process ffffe00000d5290 2 输出某个进程的线程信息(ffffe00000d5290是进程句柄,标志进程地址)
13、!thread ffffe00000e6d080 某个线程的相关信息,特别是包括堆栈详细信息
14、!devnode 0 1 查看所有即插即用设备树信息
15、!devnode 0 9 查看设备信息及硬件资源
16、!devnode 0 1 disk 查看服务名为disk的设备信息
17、!devstack 0xffffe00001159610 查看pdo为0xffffe00001159610的设备栈
18、!drvobj disk 2 查看disk的驱动的基本信息,以及IRP派发函数
19、bu CLASSPNP!ClassGlobalDispatch 针对某个IRP派发函数,断点调试它的行为,或者查看它的调用栈
20、bl
21、k
22、qd
Debugging Techniques
通用调试技术和特殊调试技术
Standard Debugging Techniques
Using Breakpoints
命令:
bl(Breakpoint list)
.bpcmds(Display Breakpoint Commands)
bp(Set Breakpoint):下即时断点
bu(Set Unresolved Breakpoint):模块符号还未加载前,设置这个断点
bm(Set Symbol Breakpoint):类似bu,如果加了/d,类似bp
ba(Break on Access):数据断点,某个位置的发生读、写、执行、IO时,会触发断点
bc(Breakpoint Clear):清除一个或多个断点
bd(Breakpoint Disable):临时禁用断点
be(Breakpoint Enable)
br(Breakpoint Renumber):?
bs(Update Breakpoint Command):改变一个与断点关联的命令
bsc(Update Conditional Breakpoint):更新条件断点的触发条件
在某处中断,然后写dump,然后继续执行
bu MyFunction+0x47 ".dump c:\mydump.dmp; g"
数据断点:
ba r4 MyValue 这是在MyValue符号开始后的4字节,读的时候,触发断点
条件断点:
格式:
0:000> bp Address "j (Condition) 'OptionalCommands'; 'gc' "
0:000> bp Address ".if (Condition) {OptionalCommands} .else {gc}"
例子:
0:000> bp `mysource.cpp:143` "j (poi(MyVar)>0n20) ''; 'gc' "
0:000> bp `mysource.cpp:143` ".if (poi(MyVar)>0n20) {} .else {gc}"
其中,poi表示解引用,针对某个变量MyVar,使用一个poi表示它的地址;如果是一个指针MyPoint,需要两个poi(poi(MyPoint))表示它的值
使用命令
0:000> bp `:143` "j (poi(MyVar)>5) '.echo MyVar Too Big'; '.echo MyVar Acceptable; gc' "
0:000> bp `:143` ".if (poi(MyVar)>5) {.echo MyVar Too Big} .else {.echo MyVar Acceptable; gc} "
条件基于字符串的匹配的条件断点的使用:
首先下一个断点,并制定一个脚本文件的命令集:bp kernel32!CreateEventW "$$<c:\\commands.txt"
然后,commands.txt中的内容如下:
.if (@r9 != 0) { as /mu ${/v:EventName} @r9 } .else { ad /q ${/v:EventName} }
.if ($spat(@"${EventName}", "Global*") == 0) { gc } .else { .echo EventName }
这里要注意:
(1)x64处理器下,release版本的程序,函数的前4个整型或指针参数分别放在了寄存器rcx, rdx, r8, r9;浮点参数存储在xmm0-xmm3寄存器;x86还是放在栈里,需要使用栈偏移
(2)CreateEvent的原型为:HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPTSTR lpName);第四个参数就是name,所以是@r9
(3)as /mu ${/v:EventName} @r9,表达的意思是:给寄存器r9所在地址,取一个别名(as),是一个unicode字符串(/mu),名为EventName(${EventName}),且无论是否有该表达式的关键字在(${/v:}),都这么命名。
(4)ad /q ${/v:EventName},表达的意思是:从别名列表里面,删除别名为{/v:EventName}的别名,使用安静模式(/q)
(5)$spat(@"${EventName}", "Global*"),表达的意思是:比较两个字符串($spat("string","pattern")),第一个字符串是"${EventName}"所在地址(@"${EventName}"),第二个字符串可以是一个模糊匹配(Global*)
(6).echo EventName,表达的意思是:在界面上输出EventName的字符串,至于为啥不使用寄存器,不晓得
参考链接:
https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/x64-architecture
https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-------alias-interpreter-
https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/as--as--set-alias-
https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/ad--delete-alias-
https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/masm-numbers-and-operators
条件基于简单的寄存器值的条件断点的使用:
如果寄存器eax中的值等于0xa3,就会断下来,否则继续执行
0:000> bp mydriver!myFunction "j @eax = 0xa3 '';'gc'"
0:000> bp mydriver!myFunction ".if @eax = 0xa3 {} .else {gc}"
以下无论如何都不会断下来。原因是:如果在内核模式下,对于超过8位的情况,地址前默认都会加上0xffffffff,也就变成了0xffffffffc0004321
0:000> bp mydriver!myFunction "j @eax = 0xc0004321 '';'gc'"
0:000> bp mydriver!myFunction ".if @eax = 0xc0004321 {} .else {gc}"
正确用法如下:强制指定了高位为0(0x0`c0004321)
0:000> bp mydriver!myFunction "j (@eax & 0x0`ffffffff) = 0x0`c0004321 '';'gc'"
0:000> bp mydriver!myFunction ".if (@eax & 0x0`ffffffff) = 0x0`c0004321 {} .else {gc}"
常用条件断点使用范例:
0:000> bu mymod!myFunc+0x3A "j(myVar<7) '.echo "Breakpoint hit, condition myVar<7"'; 'gc'"
0:000> bu mymod!myFunc+0x3A ".if(myVar<7) {.echo "Breakpoint hit, condition myVar<7"} .else {gc}"
只给当前线程下断点:
只在当前下断点的线程执行nt!ntopenfile的时候,才会中断
kd> bp /t @$thread nt!ntopenfile
指定脚本执行:
.if (@eax == 1234) { .echo 1234 } .else { t "$<eaxstep" }
其中,t "string",表示trace(单步)执行一个命令
$<表示运行一个脚本文件,脚本文件为eaxstep
其他一些说明:
内核模式下,最多使用32个断点;用户模式,无限个
软件断点:bp、bu、bm
处理器断点:ba
main入口函数不能设置处理器断点
当使用一个调试器启动一个目标程序时,等所有的dll、exe都加载完,还未执行dll的初始化之前,调试器会创建一个初始断点
当调试器是attach上去,会立刻触发初始断点
用户模式使用断点,是设置在用户空间
内核模式使用断点,需要针对某个进程,首先!process 0 0,然后找到对应进程,.process /i xxxxx(这个是进程的地址EPROCESS),切换后,再下断点
查看用户模式地址空间范围和内核模式地址空间范围
0: kd> dp nt!mmhighestuseraddress L1
Reading and Writing Memory
通过虚拟地址访问内存
可以读写任意格式的内存,包括十六进制、单字、双字、四字、整型(长、短、无符号、8字节)、浮点数、字符串
d*(Display Memory):展示指定内存或内存范围的内容
e*(Enter Values):往一个内存地址写值
可以操作特殊数据结构
dt(Display Type):显示各种数据结构
ds,dS(Display String):显示STRING,ANSI_STRING,UNICODE_STRING数据结构
d*s(Display Words and Symbols):显示双字或四字的符号信息
!address:显示特殊地址的内存属性
操作内存范围:
m(Move Memory):移动一段内存到另一段
f(Fill Memory):写一个pattern到一个内存范围,重复直到范围写满
c(Compare Memory):比较两个内存范围
s(Search Memory):搜索一个特殊的pattern内存,或者搜索任意ASCII或UNICODE字符
.holdmem(Hold and Compare Memory):比较内存范围
其他:
n(Set Number Base):切换数值进制
?(Evaluate Expression):数值格式转换
.formats(Show Number Formats):数值格式转换显示
通过物理地址访问内存
读物理内存地址:!db, !dc, !dd, !dp, !du, !dw
写物理内存地址:!eb, !ed
填充物理内存:fp
搜索物理内存数据片:!search
可视化:Memory Window,只能在内核调试中使用,不能用于用户模式调试
访问全局变量
直接使用全局变量的符号名,以上所有内存操作的命令,皆可以使用符号名,调试器内部会转换成地址来操作
使用?可以展示任何符号的虚拟内存地址
0:000> ? MyCounter
Evaluate expression: 1244892 = 0012fedc
0:000> dd 0x0012fedc L1
0012fedc 00000052
改变符号内容
0:000> ed MyCounter 83
访问局部变量
所有全局变量的使用方法,均可以用于局部变量。为了告知编译器某个符号是局部变量,在前面加上$!,例如$!local_Var
dv(Display Local Variables):显示所有的局部变量的符号和名字
!for_each_local ["CommandString"]:针对当前栈帧的每一个局部变量执行某个操作
!for_each_frame:针对所有栈帧执行操作
打印每个栈帧的每个局部变量:
!for_each_frame !for_each_local dt @#Local
虚拟内存地址和物理内存地址互转
虚拟地址到物理地址的转换 —— 使用!vtop
(1)确保使用十六进制工作,如果不是,使用N 16切换一下
(2)确定一个地址的字节索引,例如地址0x0012F980,它的字节索引是0x980(低12位)
(3)确定进程基地址,使用!process 0 0,例如获取到的DirBase为0x098fd000
(4)确定进程基地址的分页编号(page frame number),这个是0x098fd(去掉低12位)
(5)使用!vtop 分页编号 虚拟地址
kd> !vtop 98fd 12f980
Pdi 0 Pti 12f
0012f980 09de9000 pfn(09de9)
这里,第二个数09de9000就是物理地址页的开始地址
(6)字节索引+物理地址开始页地址,就是最终的物理地址
0x09DE9000 + 0x980 = 0x09DE9980
(7)鉴定是否正确
kd> !dc 9de9980
# 9de9980 6d206e49 726f6d65 00120079 0012f9f4 In memory.......
# 9de9990 0012f9f8 77e57119 77e8e618 ffffffff .....q.w...w....
# 9de99a0 77e727e0 77f6f13e 77f747e0 ffffffff .'.w>..w.G.w....
# 9de99b0 .....
kd> dc 12f980
0012f980 6d206e49 726f6d65 00120079 0012f9f4 In memory.......
0012f990 0012f9f8 77e57119 77e8e618 ffffffff .....q.w...w....
0012f9a0 77e727e0 77f6f13e 77f747e0 ffffffff .'.w>..w.G.w....
0012f9b0 .....
-----------------------------------------------------------------
虚拟地址到物理地址的转换 —— 使用!pte
(1)确保使用十六进制工作,如果不是,使用N 16切换一下
(2)确定一个地址的字节索引,例如地址0x0012F980,它的字节索引是0x980(低12位)
(3)设置进程上下文为指定的进程
kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
....
PROCESS ff779190 SessionId: 0 Cid: 04fc Peb: 7ffdf000 ParentCid: 0394
DirBase: 098fd000 ObjectTable: e1646b30 TableSize: 8.
Image: MyApp.exe
kd> .process /p ff779190
Implicit process is now ff779190
.cache forcedecodeuser done
(4)使用!pte 虚拟地址
kd> !pte 12f980
VA 0012f980
PDE at C0300000 PTE at C00004BC
contains 0BA58067 contains 09DE9067
pfn ba58 ---DA--UWV pfn 9de9 ---DA--UWV
(5)pfn 9de9,表示的是PTE表的页编号索引,左移动12位,即是分页的物理地址头0x09de9000
(6)字节索引+物理地址开始页地址,就是最终的物理地址
0x09DE9000 + 0x980 = 0x09DE9980
Using the !analyze Extension
使用!analyze -v,分析一般的crash
使用!analyze -hang,分析线程挂起引起的死锁阻塞问题
内核调试下,使用!deadlock,分析内核资源死锁问题
使用!analyze -f,强制使用!analyze -v,及时没有crash发生
Noninvasive Debugging (User Mode)
非侵入式调试,特别适用于在目标程序已经无响应的情况
Debugging in Assembly Mode
反汇编指令:
u(Unassemble):反汇编显示一部分汇编代码
uf(Unassemble Function):反汇编显示一个函数的汇编代码
up(Unassemble from Physical Memory):反汇编显示存储在物理内存中的汇编代码
ur(Unassemble Real Mode BIOS):反汇编显示16位实模式汇编代码
ux(Unassemble x86 BIOS):反汇编基于X86指令集的汇编代码
Changing Contexts
有5种上下文概念
session context:多用户登录时,默认是当前登录用户。使用命令!session
process context:内核调试时,虚拟地址如何翻译问题。使用命令.process
user-mode address context:从来不会直接设置,切换进程上下文自动会切换该上下文
register context:也称为thread context,控制栈回溯。
用户模式下使用如下命令切换线程上下文:
.cxr(Display Context Record)
.ecxr(Display Exception Context Record)
内核模式下使用如下命令切换线程上下文:
.thread(Set Register Context)
.cxr(Display Context Record)
.trap(Display Trap Frame)
local context:翻译局部变量的时候用,区间内的。
使用命令:.frame(Set Local Context)切换栈帧,可以针对不同栈帧使用dv -v导出局部变量
Controlling Processes and Threads
调试器可以切换进程或线程,也可以控制线程freeze或unfreeze
显示进程和线程
|(Process Status)
~(Thread Status)
设置当前进程和线程
|s(Set Current Process)
~s(Set Current Process)
冻结和挂起线程
~n(Suspend Thread)
~m(Resume Thread)
~f(Freeze Thread)
~u(Unfreeze Thread)
Controlling Exceptions and Events
首先,异常是有一个链表结构,链表的尾部是调试器,链表的前面如果有任意一个服务已经处理了异常,就轮不到调试器了。只有未处理异常,才会中断到调试器中。
调试器可以恢复继续执行,使用命令:gh(Go with Exception Handled)或者gn(Go with Exception Not Handled)。
内核未处理异常,会直接引发bug check蓝屏
Debugging a Stack Overflow
造成栈溢出的主要三个原因:
1、无限递归时,消耗完整个栈空间
2、没有多余的committed page可供使用
3、page usage空间不足
所有的局部变量都存储在线程的调用栈中,当然编译器可能也会进行优化。
调试栈溢出的步骤:
第一步,是确认栈异常
.lastevent:查看什么异常,C00000FD表示STATUS_STACK_OVERFLOW
k:确认是栈溢出,_chkstk表示栈异常
第二步,需要查看下栈的使用情况,确认下是否是递归调用导致栈消耗完了,还是page页不足
TEB结构
TEB结构
```C++ typedef struct _TEB // from Reactos, Native API; checked and corrected for 2003 and nt 4.0 // should also work on XP and 2000 // the reactos version was probably from NT 3.51 SP3 { NT_TIB Tib; /* 00h */ PVOID EnvironmentPointer; /* 1Ch */ CLIENT_ID Cid; /* 20h */ HANDLE RpcHandle; /* 28h */ PVOID *ThreadLocalStorage; /* 2Ch */ PPEB Peb; /* 30h */ ULONG LastErrorValue; /* 34h */ ULONG CountOfOwnedCriticalSections; /* 38h */ PVOID CsrClientThread; /* 3Ch */ struct _W32THREAD* Win32ThreadInfo; /* 40h */ ULONG User32Reserved[26]; /* 44h */ ULONG UserReserved[5]; /* ACh */ PVOID WOW32Reserved; /* C0h */ LCID CurrentLocale; /* C4h */ ULONG FpSoftwareStatusRegister; /* C8h */ PVOID SystemReserved1[0x36]; /* CCh */ #if (VER_PRODUCTBUILD <= 1381) PVOID Spare1; /* 1A4h */ #endif LONG ExceptionCode; /* 1A4h */ #if (VER_PRODUCTBUILD >= 2600) ACTIVATION_CONTEXT_STACK ActivationContextStack; /* 1A8h */ UCHAR SpareBytes1[24]; /* 1BCh */ #elif (VER_PRODUCTBUILD >= 2195) UCHAR SpareBytes1[0x2c]; /* 1A8h */ #else /* nt 4.0 */ ULONG SpareBytes1[0x14]; /* 1ACh */ #endif GDI_TEB_BATCH GdiTebBatch; /* 1D4h */ /* 1FC for nt 4.0 */ ULONG gdiRgn; /* 6A8h */ /* 6DCh for nt 4.0 */ ULONG gdiPen; /* 6ACh */ ULONG gdiBrush; /* 6B0h */ CLIENT_ID RealClientId; /* 6B4h */ /* 6E8h for nt 4.0 */ PVOID GdiCachedProcessHandle; /* 6BCh */ ULONG GdiClientPID; /* 6C0h */ ULONG GdiClientTID; /* 6C4h */ PVOID GdiThreadLocaleInfo; /* 6C8h */ #if (VER_PRODUCTBUILD == 1381) PVOID Win32ClientInfo[5]; /* 700h */ PVOID glDispatchTable[0x118]; /* 714h */ ULONG glReserved1[0x1a]; /* B74h */ #else PVOID Win32ClientInfo[0x3e]; /* 6CCh */ PVOID glDispatchTable[0xe9]; /* 7C4h */ ULONG glReserved1[0x1d]; /* B68h */ #endif PVOID glReserved2; /* BDCh */ PVOID glSectionInfo; /* BE0h */ PVOID glSection; /* BE4h */ PVOID glTable; /* BE8h */ PVOID glCurrentRC; /* BECh */ PVOID glContext; /* BF0h */ NTSTATUS LastStatusValue; /* BF4h */ UNICODE_STRING StaticUnicodeString; /* BF8h */ WCHAR StaticUnicodeBuffer[0x105]; /* C00h */ PVOID DeallocationStack; /* E0Ch */ PVOID TlsSlots[0x40]; /* E10h */ LIST_ENTRY TlsLinks; /* F10h */ PVOID Vdm; /* F18h */ PVOID ReservedForNtRpc; /* F1Ch */ PVOID DbgSsReserved[0x2]; /* F20h */ ULONG HardErrorDisabled; /* F28h */ PVOID Instrumentation[0x10]; /* F2Ch */ PVOID WinSockData; /* F6Ch */ ULONG GdiBatchCount; /* F70h */ BOOLEAN InDbgPrint; /* F74h */ BOOLEAN FreeStackOnTermination; /* F75h */ BOOLEAN HasFiberData; /* F76h */ UCHAR IdealProcessor; /* F77h */ ULONG Spare3; /* F78h */ ULONG ReservedForPerf; /* F7Ch */ PVOID ReservedForOle; /* F80h */ ULONG WaitingOnLoaderLock; /* F84h */ #if (VER_PRODUCTBUILD >= 2195) Wx86ThreadState Wx86Thread; /* F88h */ PVOID* TlsExpansionSlots; /* F94h */ ULONG ImpersonationLocale; /* F98h */ ULONG IsImpersonating; /* F9Ch */ PVOID NlsCache; /* FA0h */ PVOID pShimData; /* FA4h */ ULONG HeapVirtualAffinity; /* FA8h */ PVOID CurrentTransactionHandle; /* FACh */ PTEB_ACTIVE_FRAME ActiveFrame; /* FB0h*/ PVOID FlsSlots; /* FB4h */ #endif } TEB, *PTEB; typedef struct _NT_TIB { struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList; PVOID StackBase; PVOID StackLimit; PVOID SubSystemTib; #if defined(_MSC_EXTENSIONS) union { PVOID FiberData; DWORD Version; }; #else PVOID FiberData; #endif PVOID ArbitraryUserPointer; struct _NT_TIB *Self; } NT_TIB; ```~*k:查看所有的线程,找到栈溢出的那个线程的TEB
!teb 7ffdc000:直接查看teb信息,找到StackBase和StackLimit查看栈的上下限,以及DeallocationStack表示栈的最大空间地址(一般情况下,都不支持!teb命令,因为没有符号)
动态查找TEB中栈信息:
0:002> dd 7ffdc000 L4
7ffdc000 009fdef0 00a00000 009fc000 00000000
栈消耗情况:StackBase - StackLimit
0:002> ? a00000-9fc000
Evaluate expression: 16384 = 00004000
也就是说,栈消耗为16k
查看最大栈空间,DeallocationStack在E0C偏移处,最大空间等于StackBase - DeallocationStack
0:002> dd 7ffdc000+e0c L1
7ffdce0c 009c0000
0:002> ? a00000-9c0000
Evaluate expression: 262144 = 00040000
也就是说,最大栈空间是256k,远大于消耗。因此,不应该是递归调用导致的栈溢出。那就只可能是分页达到极限,这就需要查看分页内存使用情况,需要进入内核模式查看
0:002> .breakin
Break instruction exception - code 80000003 (first chance)
ntoskrnl!_DbgBreakPointWithStatus+4:
80148f9c cc int 3
kd> !vm
*** Virtual Memory Usage ***
Physical Memory: 16268 ( 65072 Kb)
Page File: \??\C:\pagefile.sys
Current: 147456Kb Free Space: 65988Kb
Minimum: 98304Kb Maximum: 196608Kb
Available Pages: 2299 ( 9196 Kb)
ResAvail Pages: 4579 ( 18316 Kb)
Locked IO Pages: 93 ( 372 Kb)
Free System PTEs: 42754 ( 171016 Kb)
Free NP PTEs: 5402 ( 21608 Kb)
Free Special NP: 348 ( 1392 Kb)
Modified Pages: 757 ( 3028 Kb)
NonPagedPool Usage: 811 ( 3244 Kb)
NonPagedPool Max: 6252 ( 25008 Kb)
PagedPool 0 Usage: 1337 ( 5348 Kb)
PagedPool 1 Usage: 893 ( 3572 Kb)
PagedPool 2 Usage: 362 ( 1448 Kb)
PagedPool Usage: 2592 ( 10368 Kb)
PagedPool Maximum: 13312 ( 53248 Kb)
Shared Commit: 3928 ( 15712 Kb)
Special Pool: 1040 ( 4160 Kb)
Shared Process: 3641 ( 14564 Kb)
PagedPool Commit: 2592 ( 10368 Kb)
Driver Commit: 887 ( 3548 Kb)
Committed pages: 45882 ( 183528 Kb)
Commit limit: 50570 ( 202280 Kb)
Total Private: 33309 ( 133236 Kb)
.....
首先,看到NonPagedPool Usage(已使用3244 Kb,最大25008 Kb)和PagePool Usage(已使用10368 Kb,最大53248 Kb)的情况,都没有达到极限或者接近极限。再看看Commited Pages(已使用183528 Kb,最大 202280 Kb),几乎达到最大极限,说明是这个原因。(至于为啥不是等于极限大小,因为多线程跑的原因,此刻可能内存已经释放了,只要接近,那极有可能在某个时刻已经达到最大)
第三步,具体看看,到底是哪一步消耗的栈比较多,看反汇编中sub esp
0:002> k
ChildEBP RetAddr
009fdd0c 71a32520 COMCTL32!_chkstk+0x25
009fde78 77cf8290 COMCTL32!ListView_WndProc+0x4c4
009fde98 77cfd634 USER32!_InternalCallWinProc+0x18
009fdf00 77cd55e9 USER32!UserCallWinProcCheckWow+0x17f
009fdf3c 77cd63b2 USER32!SendMessageWorker+0x4a3
009fdf5c 71a45b30 USER32!SendMessageW+0x44
009fdfec 71a45bb0 COMCTL32!CCSendNotify+0xc0e
009fdffc 71a1d688 COMCTL32!CICustomDrawNotify+0x2a
009fe074 71a1db30 COMCTL32!Header_Draw+0x63
009fe0d0 71a1f196 COMCTL32!Header_OnPaint+0x3f
009fe128 77cf8290 COMCTL32!Header_WndProc+0x4e2
0:002> u COMCTL32!Header_Draw
COMCTL32!Header_Draw :
71a1d625 55 push ebp
71a1d626 8bec mov ebp,esp
71a1d628 83ec58 sub esp,0x58
71a1d62b 53 push ebx
71a1d62c 8b5d08 mov ebx,[ebp+0x8]
71a1d62f 56 push esi
71a1d630 57 push edi
71a1d631 33f6 xor esi,esi
Manually Walking a Stack
无效地址访问可能导致栈坏掉了,所以栈回溯可能失效,需要手工回溯栈
第一步,找到所有加载的模块在哪儿,顺便查看符号
kd> x *!
start end module name
77f70000 77fb8000 ntdll (C:\debug\ntdll.dll, \\ntstress\symbols\dll\ntdll.DBG)
80010000 80012320 Aha154x (load from Aha154x.sys deferred)
80013000 8001aa60 SCSIPORT (load from SCSIPORT.SYS deferred)
8001b000 8001fba0 Scsidisk (load from Scsidisk.sys deferred)
80100000 801b7b40 NT (ntoskrnl.exe, \\ntstress\symbols\exe\ntoskrnl.DBG)
802f0000 8033c000 Ntfs (load from Ntfs.sys deferred)
80400000 8040c000 hal (load from hal.dll deferred)
fe4c0000 fe4c38c0 vga (load from vga.sys deferred)
fe4d0000 fe4d3e60 VIDEOPRT (load from VIDEOPRT.SYS deferred)
fe4e0000 fe4f0e40 ati (load from ati.SYS deferred)
fe500000 fe5057a0 Msfs (load from Msfs.SYS deferred)
fe510000 fe519560 Npfs (load from Npfs.SYS deferred)
fe520000 fe521f60 ndistapi (load from ndistapi.sys deferred)
fe530000 fe54ed20 Fastfat (load from Fastfat.SYS deferred)
fe5603e0 fe575360 NDIS (NDIS.SYS, \\ntstress\symbols\SYS\NDIS.DBG)
fe580000 fe585920 elnkii (elnkii.sys, \\ntstress\symbols\sys\elnkii.DBG)
fe590000 fe59b8a0 ndiswan (load from ndiswan.sys deferred)
fe5a0000 fe5b7c40 nbf (load from nbf.sys deferred)
fe5c0000 fe5c1b40 TDI (load from TDI.SYS deferred)
fe5d0000 fe5dd580 nwlnkipx (load from nwlnkipx.sys deferred)
fe5e0000 fe5ee220 nwlnknb (load from nwlnknb.sys deferred)
fe5f0000 fe5fb320 afd (load from afd.sys deferred)
fe610000 fe62bf00 tcpip (load from tcpip.sys deferred)
fe630000 fe648600 netbt (load from netbt.sys deferred)
fe650000 fe6572a0 netbios (load from netbios.sys deferred)
fe660000 fe660000 Parport (load from Parport.SYS deferred)
fe670000 fe670000 Parallel (load from Parallel.SYS deferred)
fe680000 fe6bcf20 rdr (rdr.sys, \\ntstress\symbols\sys\rdr.DBG)
fe6c0000 fe6f0920 srv (load from srv.sys deferred)
第二步,打印下栈指针
kd> dd esp
fe4cc97c 80136039 00000270 00000000 00000000
fe4cc98c fe682ae4 801036fe 00000000 fe68f57a
fe4cc99c fe682a78 ffb5b030 00000000 00000000
fe4cc9ac ff680e08 801036fe 00000000 00000000
fe4cc9bc fe6a1198 00000001 fe4cca78 ffae9d98
fe4cc9cc 02000901 fe4cca68 ffb50030 ff680e08
fe4cc9dc ffa449a8 8011c901 fe4cca78 00000000
fe4cc9ec 80127797 80110008 00000246 fe6a1430
kd> dd
fe4cc9fc 00000270 fe6a10ae 00000270 ffa44abc
fe4cca0c ffa449a8 ff680e08 fe6b2c04 ff680e08
fe4cca1c ffa449a8 e12820c8 e1235308 ffa449a8
fe4cca2c fe685968 ff680e08 e1235308 ffa449a8
fe4cca3c ffb0ad48 ffb0ad38 00100000 ffb0ad38
fe4cca4c 00000000 ffa44a84 e1235308 0000000a
fe4cca5c c00000d6 00000000 004ccb28 fe4ccbc4
fe4cca6c fe680ba4 fe682050 00000000 fe4ccbd4
第三步,看一下哪个值更像是函数地址,以及哪个值像是参数或者保存寄存器。
判断依据:
(1)整数参数通常值比较小,比如00000270
(2)局部变量指针参数,通常接近栈指针esp,比如fe4cca78
(3)状态码通常是一个以c开头的值,比如c00000d6
(4)Unicode和ASCII字符串通常范围是20-7f,并且一般以0结尾。如果是KD模式下,使用dc(Display Memory)会把字符显示在右边
(5)函数地址,就是在x *!列的范围内,可能的函数地址比如:80136039、801036fe(这个出现了两次,则更有可能是作为一个参数)
(5)连续的函数地址,更可能是作为参数
第四步,针对每一个可能的函数地址,使用ln来查看最接近的符号
kd> ln 80136039
(80136039) NT!_KiServiceExit+0x1e | (80136039) NT!_KiServiceExit2-0x177
kd> ln fe682ae4
(fe682ae4) rdr!_RdrSectionInfo+0x2c | (fe682ae4) rdr!_RdrFcbReferenceLock-0xb4
kd> ln 801036fe
(801036fe) NT!_KeWaitForSingleObject | (801036fe) NT!_MmProbeAndLockPages-0x2f8
kd> ln fe68f57a
(fe68f57a) rdr!_RdrDereferenceDiscardableCode+0xb4
(fe68f57a) rdr!_RdrUninitializeDiscardableCode-0xa
kd> ln fe682a78
(fe682a78) rdr!_RdrDiscardableCodeLock | (fe682a78) rdr!_RdrDiscardableCodeTimeout-0x38
kd> ln fe6a1198
(fe6a1198) rdr!_SubmitTdiRequest+0xae | (fe6a1198) rdr!_RdrTdiAssociateAddress-0xc
kd> ln 8011c901
(8011c901) NT!_KeSuspendThread+0x13 | (8011c901) NT!_FsRtlCheckLockForReadAccess-0x55
kd> ln 80127797
(80127797) NT!_ZwCloseObjectAuditAlarm+0x7 | (80127797) NT!_ZwCompleteConnectPort-0x9
kd> ln 80110008
(80110008) NT!_KeWaitForMultipleObjects+0x27c | (80110008) NT!_FsRtlLookupMcbEntry-0x164
kd> ln fe6a1430
(fe6a1430) rdr!_RdrTdiCloseConnection+0xa | (fe6a1430) rdr!_RdrDoTdiConnect-0x4
kd> ln fe6a10ae
(fe6a10ae) rdr!_RdrTdiDisconnect+0x56 | (fe6a10ae) rdr!_SubmitTdiRequest-0x3c
kd> ln fe6b2c04
(fe6b2c04) rdr!_CleanupTransportConnection+0x64 | (fe6b2c04)rdr!_RdrReferenceServer-0x20
kd> ln fe685968
(fe685968) rdr!_RdrReconnectConnection+0x1b6
(fe685968) rdr!_RdrInvalidateServerConnections-0x32
kd> ln fe682050
(fe682050) rdr!__strnicmp+0xaa | (fe682050) rdr!_BackPackSpinLock-0xa10
第五步,总结栈回溯
NT!_KiServiceExit+0x1e
rdr!_RdrSectionInfo+0x2c
rdr!_RdrDereferenceDiscardableCode+0xb4
rdr!_SubmitTdiRequest+0xae
NT!_KeSuspendThread+0x13
NT!_ZwCloseObjectAuditAlarm+0x7
NT!_KeWaitForMultipleObjects+0x27c
rdr!_RdrTdiCloseConnection+0xa
rdr!_RdrTdiDisconnect+0x56
rdr!_CleanupTransportConnection+0x64
rdr!_RdrReconnectConnection+0x1b6
rdr!__strnicmp+0xaa
第六步,为了确认正确,可以进一步反汇编,查看下每一个函数地址之前,是否存在call。因为可能存在jmp,所以这个前面的偏移得尝试不同偏移长度
kd> u 80136039-2 l1 // looks ok, its a call
NT!_KiServiceExit+0x1c:
80136037 ffd3 call ebx
kd> u fe682ae4-2 l1 // paged out (all zeroes) unknown
rdr!_RdrSectionInfo+0x2a:
fe682ae2 0000 add [eax],al
kd> u fe68f57a-6 l1 // looks ok, its a call, but not anything above
rdr!_RdrDereferenceDiscardableCode+0xae:
fe68f574 ff15203568fe call dword ptr [rdr!__imp__ExReleaseResourceForThreadLite]
kd> u fe682a78-6 l1 // paged out (all zeroes) unknown
rdr!_DiscCodeInitialized+0x2:
fe682a72 0000 add [eax],al
kd> u fe6a1198-5 l1 // looks good, call to something above
rdr!_SubmitTdiRequest+0xa9:
fe6a1193 e82ee3feff call rdr!_RdrDereferenceDiscardableCode (fe68f4c6)
kd> u 8011c901-2 l1 // not good, its a jump in the function
NT!_KeSuspendThread+0x11:
8011c8ff 7424 jz NT!_KeSuspendThread+0x37 (8011c925)
kd> u 80127797-2 l1 // looks good, an int 2e -> KiServiceExit
NT!_ZwCloseObjectAuditAlarm+0x5:
80127795 cd2e int 2e
kd> u 80110008-2 l1 // not good, its a test instruction not a call
NT!_KeWaitForMultipleObjects+0x27a:
80110006 85c9 test ecx,ecx
kd> u 80110008-5 l1 // paged out (all zeroes) unknown
NT!_KeWaitForMultipleObjects+0x277:
80110003 0000 add [eax],al
kd> u fe6a1430-6 l1 // looks good its a call to ZwClose...
rdr!_RdrTdiCloseConnection+0x4:
fe6a142a ff15f83468fe call dword ptr [rdr!__imp__ZwClose (fe6834f8)]
kd> u fe6a10ae-2 l1 // paged out (all zeroes) unknown
rdr!_RdrTdiDisconnect+0x54:
fe6a10ac 0000 add [eax],al
kd> u fe6b2c04-5 l1 // looks good, call to something above
rdr!_CleanupTransportConnection+0x5f:
fe6b2bff e854e4feff call rdr!_RdrTdiDisconnect (fe6a1058)
kd> u fe685968-5 l1 // looks good, call to immediately above
rdr!_RdrReconnectConnection+0x1b1:
fe685963 e838d20200 call rdr!_CleanupTransportConnection (fe6b2ba0)
kd> u fe682050-2 l1 // paged out (all zeroes) unknown
rdr!__strnicmp+0xa8:
fe68204e 0000 add [eax],al
第七步,进一步确定了回溯栈
kd> k
ChildEBP RetAddr
fe4cc978 80136039 NT!_NtClose+0xd
fe4cc978 80127797 NT!_KiServiceExit+0x1e
fe4cc9f4 fe6a1430 NT!_ZwCloseObjectAuditAlarm+0x7
fe4cca10 fe6b2c04 rdr!_RdrTdiCloseConnection+0xa
fe4cca28 fe685968 rdr!_CleanupTransportConnection+0x64
fe4cca78 fe688157 rdr!_RdrReconnectConnection+0x1b6
fe4ccbd4 80106b1e rdr!_RdrFsdCreate+0x45b
fe4ccbe8 8014b289 NT!IofCallDriver+0x38
fe4ccc98 8014decd NT!_IopParseDevice+0x693
fe4ccd08 8014d6d2 NT!_ObpLookupObjectName+0x487
fe4ccde4 8014d3ad NT!_ObOpenObjectByName+0xa2
fe4cce90 8016660d NT!_IoCreateFile+0x433
fe4cced0 80136039 NT!_NtCreateFile+0x2d
Debugging an Application Failure
最常见的失败:非法地址访问,字节对齐错误
其他失败:异常,临界区超时(死锁),in-Page I/O错误(通常是硬件错误)
Crashing and Rebooting the Target Computer
等系统启动
Finding a Memory Leak
发生场景:程序分配了分页或非分页内存,但是却没有释放。
Determining Whether a Leak Exists
Win+R,然后输入Perfmon,打开性能计数器
然后添加计数点:
Memory–>Pool Nonpaged Bytes
Memory–>Pool Paged Bytes
Paging File–>% Usage
无内存泄漏的情况:
有内存泄漏的情况:
int main()
{
while (true)
{
char* p = new char[2048];
}
return 0;
}
用户模式内存泄漏,通常造成pageable pool和Paging File Usage计数增加
内核模式内存泄漏,通常造成pool nonpaged计数增加
Finding a Kernel-Mode Memory Leak
参考链接:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/debugger/finding-a-kernel-mode-memory-leak
(1)使用PoolMon监控内存使用,多次打快照,对比查看是哪个tag的pool或nonp有增长
(2)使用内核调试,然后使用命令:kd> !poolused 4 ,可以查看到对应tag的使用量,明确确实比较多。这一步也可以看到对应tag是属于哪个驱动程序的
(3)找到对应的内存使用量最大的,这里是abc,然后使用命令:kd> ed nt!poolhittag ' cbA' ,设置一个全局变量下断点
(4)使用命令:kd> db nt!poolhittag L4,检查设置的值是否正确
(5)调试器将会在每一次分配这个tag的pool或释放的时候,中断下来,使用kb,就可以查看回溯栈
(6)去掉断点,使用命令:kd> ed nt!poolhittag 0
Finding a User-Mode Memory Leak
参考链接:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/debugger/finding-a-user-mode-memory-leak
使用umdh.exe工具,来监控内存使用
Debugging a Time Out
超时也会导致异常发生,这个时间阈值是设定在SessionManager注册表中的。通常造成超时的原因,不外乎就是一个线程正在长时间等待一个资源释放,或者长时间等待一个临界区释放。
Resource Time Outs
Resource @ fc664ee0 // Here's the resource lock address
ActiveCount = 0001 Flags = IsOwnedExclusive ExclusiveWaiter
NumberOfExclusiveWaiters = 0001
Thread = ffaf5410, Count = 01 // Here's the owning thread
Thread = 00000000, Count = 00
ntoskrnl!_DbgBreakPoint:
80131400 cc int 3
kd> kb // Start with a stack
ChildEBP RetAddr Args to Child
fcd44980 801154c0 fc664ee0 ffab45d0 00110001 ntoskrnl!_DbgBreakPoint
fcd4499c 80102521 fc664ee0 ffb08ea8 fcd44a4c ntoskrnl!_ExpWaitForResource+0x114 // Lock being waited on...
fcd449e8 fc6509fa e12597c8 fef27c08 fee4fca8 ntoskrnl!_ExAcquireResourceExclusiveLite+0xa5
00380020 00000000 00000000 00000000 00000000 nwrdr!_CreateScb+0x2ff
kd> !locks fc664ee0 // !locks resource address gives lock info
Resource @ nwrdr!_NwScavengerSpinLock (0xfc664ee0) Exclusively owned
Contention Count = 45
NumberOfExclusiveWaiters = 1
Threads: ffaf5410-01 // Owning thread again
1 total locks, 1 locks currently held
kd> !thread ffaf5410 // Check the owning thread
THREAD ffaf5410 Cid e7.e8 Teb: 7ffde000 WAIT: (Executive) KernelMode Non-Alertable
feecf698 SynchronizationEvent
IRP List:
fef29208: (0006,00b8) Flags: 00000884 Mdl: feed8328
Not impersonating
Owning Process ffaf5690
WaitTime (seconds) 2781250
Context Switch Count 183175
UserTime 0:00:23.0153
KernelTime 0:01:01.0187
Start Address 0x77f04644
Initial Sp fec6c000 Current Sp fec6b938
Priority 11 BasePriority 7 PriorityDecrement 0 DecrementCount 8
ChildEBP RetAddr Args to Child
fec6b950 801044fc feecf668 feecf668 00000080 ntoskrnl!KiSwapContext+0x25
fec6b974 fc655976 feecf698 00000000 00000000 ntoskrnl!_KeWaitForSingleObject+0x218
fec6ba5c fc6509fa e1263968 fef29208 feecf668 nwrdr!_ExchangeWithWait+0x38
fec6ba28 fc6533e5 feecf668 e125b3c8 ffafae08 nwrdr!_CreateScb+0x2ff
fec6bac0 fc652f26 feecf668 fec6bae4 fef29208 nwrdr!_CreateRemoteFile+0x2c9
fec6bb6c fc652b14 feecf668 fef29208 fee50b60 nwrdr!_NwCommonCreate+0x3a2
fec6bbac 80107aea fee50b60 fef29208 804052ac nwrdr!_NwFsdCreate+0x56
fec6bbc0 80142792 fef37700 fec6bdbc fee50b28 ntoskrnl!IofCallDriver+0x38
fec6bd10 80145403 fee50b60 00000000 fec6bdbc ntoskrnl!_IopParseDevice+0x6a0
fec6bd7c 80144c0c 00000000 fec6be34 00000040 ntoskrnl!_ObpLookupObjectName+0x479
fec6be5c 80127803 0012dd64 00000000 80127701 ntoskrnl!_ObOpenObjectByName+0xa2
fec6bef4 801385c3 0012dd64 0012dd3c 00000000 ntoskrnl!_NtQueryAttributesFile+0xc1
fec6bef4 77f716ab 0012dd64 0012dd3c 00000000 ntoskrnl!_KiSystemService+0x83
0012dd20 00000000 00000000 00000000 00000000 ntdll!_ZwQueryAttributesFile+0xb
Critical Section Time Outs
0:024> kb
ChildEBP RetAddr Args to Child
0569fca4 77f79c78 77f71000 002a6b88 7fffffff ntdll!_DbgBreakPoint
0569fd04 77f71048 5ffa9f9c 5fef0b4b 5ffa9f9c ntdll!_RtlpWaitForCriticalSection+0x89
0569fd0c 5fef0b4b 5ffa9f9c 002a6b88 002a0019 ntdll!_RtlEnterCriticalSection+0x48
0569fd70 5fedf83f 002a6b88 0569fdc0 0000003e winsrv!_StreamScrollRegion+0x1f0
0569fd8c 5fedfa5b 002a6b88 00190000 00000000 winsrv!_AdjustCursorPosition+0x8e
0569fdc0 5fedf678 0569ff18 0031c200 0335ee88 winsrv!_DoWriteConsole+0x104
0569fefc 5fe6311b 0569ff18 0569ffd0 00000005 winsrv!_SrvWriteConsole+0x96
0569fff4 00000000 00000000 00000024 00000024 csrsrv!_CsrApiRequestThread+0x4ff
0:024> !locks
CritSec winsrv!_ScrollBufferLock at 5ffa9f9c 5ffa9f9c is the first one
LockCount 5
RecursionCount 1
OwningThread 88 // here's the owning thread ID
EntryCount 11c
ContentionCount 135
*** Locked
CritSec winsrv!_gcsUserSrv+0 at 5ffa91b4 //second critical section found below
LockCount 8
RecursionCount 1
OwningThread 6d // second owning thread
EntryCount 1d6c
ContentionCount 1d47
*** Locked
0:024> ~
0 id: 16.15 Teb 7ffdd000 Unfrozen
1 id: 16.13 Teb 7ffdb000 Unfrozen
2 id: 16.30 Teb 7ffda000 Unfrozen
3 id: 16.2f Teb 7ffd9000 Unfrozen
4 id: 16.2e Teb 7ffd8000 Unfrozen
5 id: 16.6c Teb 7ff6c000 Unfrozen
6 id: 16.6d Teb 7ff68000 Unfrozen // this thread owns the second critical section
7 id: 16.2d Teb 7ffd7000 Unfrozen
8 id: 16.33 Teb 7ffd6000 Unfrozen
9 id: 16.42 Teb 7ff6f000 Unfrozen
10 id: 16.6f Teb 7ff6e000 Unfrozen
11 id: 16.6e Teb 7ffd5000 Unfrozen
12 id: 16.52 Teb 7ff6b000 Unfrozen
13 id: 16.61 Teb 7ff6a000 Unfrozen
14 id: 16.7e Teb 7ff69000 Unfrozen
15 id: 16.43 Teb 7ff67000 Unfrozen
16 id: 16.89 Teb 7ff50000 Unfrozen
17 id: 16.95 Teb 7ff65000 Unfrozen
18 id: 16.90 Teb 7ff64000 Unfrozen
19 id: 16.71 Teb 7ff63000 Unfrozen
20 id: 16.bb Teb 7ff62000 Unfrozen
21 id: 16.88 Teb 7ff61000 Unfrozen // this thread owns the first critical section
22 id: 16.cd Teb 7ff5e000 Unfrozen
23 id: 16.c1 Teb 7ff5f000 Unfrozen
24 id: 16.bd Teb 7ff5d000 Unfrozen
0:024> ~21s
ntdll!_ZwWaitForSingleObject+0xb:
77f71bfb c20c00 ret 0xc
0:021> kb
ChildEBP RetAddr Args to Child
0556fc44 77f79c20 00000110 00000000 77fa4700 ntdll!_ZwWaitForSingleObject+0xb
0556fcb0 77f71048 5ffa91b4 5feb4f7e 5ffa91b4 ntdll!_RtlpWaitForCriticalSection+0x31
0556fcb8 5feb4f7e 5ffa91b4 0556fd70 77f71000 ntdll!_RtlEnterCriticalSection+0x48
0556fcf4 5fef0b76 01302005 00000000 fffffff4 winsrv!__ScrollDC+0x14
0556fd70 5fedf83f 002bd880 0556fdc0 00000025 winsrv!_StreamScrollRegion+0x21b
0556fd8c 5fedfa5b 002bd880 00190000 00000000 winsrv!_AdjustCursorPosition+0x8e
0556fdc0 5fedf678 0556ff18 002bdf70 002a4d58 winsrv!_DoWriteConsole+0x104
0556fefc 5fe6311b 0556ff18 0556ffd0 00000005 winsrv!_SrvWriteConsole+0x96
0556fff4 00000000 00000000 00000024 00000024 csrsrv!_CsrApiRequestThread+0x4ff
0:021> ~6s
winsrv!_PtiFromThreadId+0xd:
5fe8429a 394858 cmp [eax+0x58],ecx ds:0023:7f504da8=000000f8
0:006> kb
ChildEBP RetAddr Args to Child
01ecfeb4 5fecd0d7 00000086 00000000 7f5738e0 winsrv!_PtiFromThreadId+0xd
01ecfed0 5feccf62 00000086 01ecfff4 00000113 winsrv!__GetThreadDesktop+0x12
01ecfefc 5fe6311b 01ecff18 01ecffd0 00000005 winsrv!___GetThreadDesktop+0x8b
01ecfff4 00000000 00000000 00000024 00000024 csrsrv!_CsrApiRequestThread+0x4ff
Debugging a Stalled System
系统停止响应,有以下几种情形:
(1)鼠标能移动,但是不影响任何窗口
(2)全屏还在,鼠标不能动,但是磁盘和内存依旧在使用中:通常是由于Client Server Run-Time Subsystem (CSRSS)问题导致的
(3)屏幕在,但是磁盘不转了。鼠标不能动且磁盘不转,基本可以确认是内核问题。
Finding the Failed Process
参考链接:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/debugger/finding-the-failed-process
找到导致停止响应的进程,通常是带着猜测性的去查找。
首先,使用~0、~1等,分别切换到不同CPU核,然后使用!pcr查看是否存在异常链(NtTib.ExceptionList不为0xFFFFFFFF即表明异常)。
找到可疑的进程所在处理器后,使用!process,查看当前在这个处理器的进程信息,通常关注以下信息:
(1)UserTime和KernelTime(高时间占用,说明可疑性大)
(2)HandleCount
(3)线程状态(idle表明可能是)
另外就是一些其他辅助命令:
!ready(判断线程是否是ready状态)
!kdext*.locks(判断是否是有死锁)
!vm(查看虚拟内存的使用,是否是使用过多导致内存不够等引发的其他异常)
!poolused(查看分页或非分页内存的使用情况,是否是分配过多导致的其他异常)
!memusage(查看物理内存使用状态)
!heap(检查有效堆的情况)
!irpfind(查看分配给活动irp的非分页内存)
Debugging an Interrupt Storm
系统停止响应,最常见的就是中断风暴,常由以下原因造成:
(1)硬件设备不释放中断信号
(2)驱动程序不认识中断,因而导致硬件设备不释放中断
(3)多个IRQ共享时,驱动程序误认为产生了硬件中断
(4)边沿电平控制寄存器未正确设置
(5)低级别中断共享IRQ(例如COM端口和PCI SCSI控制器)
Displaying a Critical Section
临界区的几种信息显示方法:
0:000> !locks
CritSec ntdll!FastPebLock+0 at 77FC49E0
LockCount 0
RecursionCount 1
OwningThread c78
EntryCount 0
ContentionCount 0
*** Locked
....
Scanned 37 critical sections
0:000> !critsec 77fc49e0
CritSec ntdll!FastPebLock+0 at 77FC49E0
LockCount 0
RecursionCount 1
OwningThread c78
EntryCount 0
ContentionCount 0
*** Locked
## 0:000> !cs 77fc49e0
Critical section = 0x77fc49e0 (ntdll!FastPebLock+0x0)
DebugInfo = 0x77fc3e00
LOCKED
LockCount = 0x0
OwningThread = 0x00000c78
RecursionCount = 0x1
LockSemaphore = 0x0
SpinCount = 0x00000000
0:000> dt RTL_CRITICAL_SECTION 77fc49e0
+0x000 DebugInfo : 0x77fc3e00
+0x004 LockCount : 0
+0x008 RecursionCount : 1
+0x00c OwningThread : 0x00000c78
+0x010 LockSemaphore : (null)
+0x014 SpinCount : 0
Specialized Debugging Techniques
参考链接:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/debugger/specialized-debugging-techniques
Windows Runtime Debugging
Kernel-Mode Driver Framework Debugging
User-Mode Driver Framework Debugging
Debugging Managed Code
Debugging Device Nodes and Device Stacks
Debugging Plug and Play and Power Issues
Debugging a User-Mode Failure with KD
Debugging a Device Installation Co-Installer
Debugging a Dual-Boot Machine
Debugging Windows Setup and the OS Loader
Debugging CSRSS
Debugging WinLogon
Debugging BIOS Code
Specifying Module and Function Owners