微软网站
社区网站
调试分享
对于Win10,推荐使用微软商店下载windbg preview版本。其它的os,可以下载windows sdk,在安装选项中选择windbg。
preview版本皆可调试32,64位进程和内核。传统版本分为32和64两个版本,但优势在小巧便携。这里有现成的32和64windbg下载。
微软的动态库,exe等,微软一般会公开pdb文件的下载,windbg经过配置后,会自动下载,从而利于解析程序的数据结构。
简易的方法是配置环境变量_NT_SYMBOL_PATH
为
srv*c:\Symbols*http://msdl.microsoft.com/download/symbols
这样,windbg会优先在c:\Symbols里寻找pdb,若没有,再去下载。
可以通过windbg目录内的symchk.exe来验证是否可以下载符号
symchk.exe /if C:\Windows\System32\ntdll.dll /s srv*C:\Symbols*http://msdl.microsoft.com/download/symbols
若成功,则C:\Symbols内会多出一个ntdll.pdb文件夹,里面还会有个ntdll.pdb文件。
但是近来这个国内实际没法通过这个网站下载,可尝试使用镜像站(最近好像也不行了)
http://sym.ax2401.com:9999/symbols
https://gitee.com/lisjgitee/microsoft_debugging_symbols/symbol/
https://gitee.com/lisjgitee/microsoft_debugging_symbols/tree/master/symbol
或寻找一个可以科学上网的http代理(不能是sock5代理),然后配置环境变量_NT_SYMBOL_PROXY
为代理地址,形如127.0.0.1:10809
以下演示一下:
a. 新建notepad进程。
b. windbg里attach到notepad进程
c. 输入!sym noisy
使之展示符号载入细节
0:000> !sym noisy
noisy mode - symbol prompts on
d. 输入.reload /f ntdll.dll
让windbg下载ntdll.pdb,如果看到
SYMSRV: HTTPGET: /download/symbols/ntdll.pdb/54A4ABD98E75ECCB93B912D2747051301/ntdll.pdb
SYMSRV: HttpSendRequest: 800C2EFD - ERROR_INTERNET_CANNOT_CONNECT
之类的提示,那一般就是网络不通,没法下载
windbg的命令分为标准命令,.
命令和**!
命令**。!
命令实际上windbg的扩展插件里引入的,只不过有时候略写了插件名。
例如!kdexts.locks
和!ntsdexts.locks
是不同的命令。在windbg目录里搜索kdext.dll
和ntsdexts.dll
能直接找到。
windbg里按下F1,即可检索各个命令,查看详情与示例
命令 | 作用 |
---|---|
d | 查看内存数据 |
k | 查看栈回溯 |
u | 将地址反汇编为汇编代码 |
r | 查看寄存器数据 |
~ | 切换线程(用户态) |
dt | 查看结构体的定义 |
b | 下断点 |
lmf | 查看各个模块的文件路径和符号载入 |
dt | 查看结构体的定义 |
x | 查看符号的地址 |
.
命令有命令 | 作用 |
---|---|
.reload | 载入pdb |
.load | 载入扩展插件 |
.process | 切换到进程(内核态) |
.thread | 切换到线程(内核态) |
.detach | 脱离调试目标 |
.kill | 关闭进程 |
!
命令有命令 | 作用 |
---|---|
!process | 罗列进程(内核态) |
!running | 展示cpu当前运行的线程(内核态) |
!analyze | 自动分析当前的异常和bugcheck |
!handle | 逆向handle |
!fileobj | 逆向文件对象 |
!locks | 分析CrticalSection(用户态)或执行体资源(内核态) |
!poolused | 展示内存池消耗(内核态) |
!memusage | 展示物理内存的统计信息(内核态) |
File
->attach to process
如果使用传统版本windbg,注意调试32位进程使用32位windbg,64的则用64的。
进程dump分两种。一种是手工抓的,例如任务管理器里右击进程条目创建dump。另一种是崩溃后,windows帮我们抓的崩溃现场。简易的做法是用管理权限执行
procdump -i -ma c:\windows\temp
那么进程崩溃后,会在此目录下发现2个dmp文件。
windbg的file
->Open dump file
选择dmp文件打开。
内核dump分为mini,core,full三种。在windows蓝屏后,即生成。minidump通常位于C:\windows\minidump\,后两者则通常是C:\windows\memory.dmp文件。coredmp只包含内核层的数据。
coredump和fulldump需要事先增加一定的windows虚拟内存,否则可能会生成失败,参见《深入解析Windows操作系统》崩溃转储分析章节。在磁盘分区较满时也会在开机时自动删除,可通过注册表配置解决。
对于能预测到windows内核层卡死的情况,可事先配置注册表,卡死按下
右Ctrl+双击scrolllock
,手工触发蓝屏。再从dump中来分析卡死当时的内核。
本小节配置参见蓝屏设置.zip。
对于复杂的内核卡死的现象,还可以配置实时调试环境。以下仅述winxp,win7,win10在idv,vdi的内核调试环境的搭建。
被调试机一般称为server机,windbg所在机称为client机。
通用的方法是通过串口调试。host端给虚机windows提供模拟串口设备,windows配置以支持由串口调试。随后host将串口设备和socket listen端口相连接。调试机通过tcp连接上那个socket端口。调试机里创建模拟2个串口,1个连接socket端口,一个被windbg连接。如下图所示:
具体做法如下:
a. 对于idv,编辑
/etc/vmmanger/vm.template.oa.xml
添加
……….
b. 对于vdi,在启动虚机前,先编辑
/etc/libvirt/rco_spice.xml
亦添加这段。tcp端口一般不能再用9090,通常都是早就被别人占了。
启动虚机后,再换回rco_spice.xml。否则会影响其它vdi虚机的启动。
c. 在虚机里用cmd执行
bcdedit /set debug on
bcdedit /set debugtype serial
bcdedit /set debugport 1
bcdedit /set baudrate 115200
这样虚机每次开机都会支持从串口发起调试。
d. 调试client端,安装虚拟串口工具,如Virtual Serial Port Driver。运行之,添加两个串口comX,comY。将二者连在一起。
e. client端,安装combytcp。左边写上host机的ip和端口,点击client。右边选择comX,baudrate选择115200。
f. 打开windbg,执行File->attach to kernel->填写comY和baudrate。点击OK
g. windbg提示开始载入符号,一般就表示连接成功。太久的话,可以试试按下Ctrl+Break
是否有反应。
对于裸机装win10,可以使用usb端口调试或者网卡调试。usb端口调试需要物理端口支持。
网卡调试参阅https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/setting-up-a-network-debugging-connection。较简单,client和server之间直接进行网络连接。值得注意的是,此会把client机的Windows的网卡名字给了。
另外,如果server机开启了内核调试,即使用户态代码执行了汇编int 3
,那么整个内核也会被暂停。可能是windows刻意如此设计。但是,对于使用者来说十分不便。甚至引发客户误报故障。
windbg旧版小巧便携,可以发送到客户现场。如果遇到用户态程序的疑难杂症,可以进行远程调试。大致是客户的Windows里开启windbg server端,连接目标程序。我们的windows开启windbg客户端,连接客户的windbg。
至于两个windbg的网络连接,可能需要内网穿透技术的支持。
a. 客户的Windows里运行dbgsrv.exe -t -tcp:port=5002
b. 我们的windows里运行windbg,选择File->Connect to Remote Stub Server。输入tcp:port=5002,server=XXX
,然后attach to process,可以调试server机的进程。参见https://blog.csdn.net/xiaohua_de/article/details/78779434。
c. 可能需要防火墙放通相关端口,建议事先试验一下。
远程调试进程的方法有不少,之所以用这种,是因为pdb是用client的网络来下载的。毕竟客户现场的环境通常没法配代理下载pdb。
首先许多donet程序文件,使用dotpeek可以很容易反编译得到源码。并且可以生成代码文件和pdb。
分析汇编代码当然较晦涩,如果Windbg能直接结合pdb和源码,那么分析过程就清晰得多。
分析donet部分线程还需要donet的相关dll文件,这样才能直接看到donet代码而不是汇编码。
以下以调试一个dump为例:
a. 将崩溃现场的donet%windir%\microsoft.net\framework\<.NET 版本>
的相关dll复制到自机,需要sos.dll,mscordacwks.dll(需要改名mscordacwks_AAA_AAA_x.x.x.xxxx.dll
其中AAA是dump的bit位对应的(x86 or AMD64),x.x.x.x实际上就是mscordacwks.dll的版本号(可以从文件的property dialog中查到),比如2.0.50272.8763, 4.7.2114.0等。
)。或者自己也安装相同版本的donet,需要版本号严格相同。
c. 推荐使用旧版windbg。windbg打开dmp,配置source path,填入源码的所在路径。配置symbol file path,添加b所得的pdb。配置image file path,添加a,b中的exe,dll路径。
d. 输入
.load X:\Y\Z\sos.dll
.load X:\Y\Z\clr.dll
成功后输入!clrstack,可以看到clr的栈
0:000> !clrstack
OS Thread Id: 0x1b54 (0)
Child SP IP Call Site
01b7dfb8 77802bec [InlinedCallFrame: 01b7dfb8]
01b7dfb4 687f1c3c *** WARNING: Unable to verify checksum for System.Windows.Forms.ni.dll
DomainBoundILStubClass.IL_STUB_PInvoke(System.Runtime.InteropServices.HandleRef, System.String, System.String, Int32)
01b7dfb8 68903382 [InlinedCallFrame: 01b7dfb8] System.Windows.Forms.SafeNativeMethods.MessageBox(System.Runtime.InteropServices.HandleRef, System.String, System.String, Int32)
01b7dffc 68903382 System.Windows.Forms.MessageBox.ShowCore(System.Windows.Forms.IWin32Window, System.String, System.String, System.Windows.Forms.MessageBoxButtons, System.Windows.Forms.MessageBoxIcon, System.Windows.Forms.MessageBoxDefaultButton, System.Windows.Forms.MessageBoxOptions, Boolean)
01b7e000 68902fd3 [InlinedCallFrame: 01b7e000]
01b7e088 68902fd3 System.Windows.Forms.MessageBox.Show(System.String, System.String, System.Windows.Forms.MessageBoxButtons, System.Windows.Forms.MessageBoxIcon)
01b7e098 08e2a46c AT.SoaFace.Core.Tools.Utility.MsgBox.ShowError(System.String)
01b7e0ac 08e2a257 AT.SoaFace.AppEngine.Program.ApplicationThreadException(System.Object, System.Threading.ThreadExceptionEventArgs)
01b7e0bc 68848c25 System.Windows.Forms.Application+ThreadContext.OnThreadException(System.Exception)
01b7e0f8 68852da7 System.Windows.Forms.Control.WndProcException(System.Exception)
01b7e104 68aa215f System.Windows.Forms.Control+ControlNativeWindow.OnThreadException(System.Exception)
01b7e108 6824764e System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
01b7e3ec 01fad0ce [InlinedCallFrame: 01b7e3ec]
01b7e3e8 682a302c DomainBoundILStubClass.IL_STUB_PInvoke(MSG ByRef)
01b7e3ec 68256ce1 [InlinedCallFrame: 01b7e3ec] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
01b7e420 68256ce1
但是kb看汇编就完全不一样了。
0:000> kb
ChildEBP RetAddr Args to Child
01b7dc58 7644870d 00070860 00000001 00000008 win32u!NtUserWaitMessage+0xc
01b7dc98 764485fd 00000008 00000001 00000000 user32!DialogBox2+0x103
01b7dcc8 764a2a08 00070860 764a0f30 01b7df08 user32!InternalDialogBox+0xe2
01b7dd8c 77803b3c 01b7def0 764a1897 01b7df08 user32!SoftModalMessageBox+0x728
01b7dd94 764a1897 01b7df08 21ff2084 00000000 win32u!NtUserModifyUserStartupInfoFlags+0xc
01b7df9c 687f1c3c 00070860 21ff2084 10bb7278 user32!MessageBoxWorker+0x2ca
01b7dfe8 68903382 00000010 00000000 00070860 System_Windows_Forms_ni+0x761c3c
01b7e068 68902fd3 00000000 00000000 00000000 System_Windows_Forms_ni+0x873382
01b7e0b4 68848c25 00000000 21ff1f78 03dda280 System_Windows_Forms_ni+0x872fd3
01b7e0f0 68852da7 21e1542c 01b7e218 68aa215f System_Windows_Forms_ni+0x7b8c25
01b7e0fc 68aa215f 6824764e 01b7e234 6b74c240 System_Windows_Forms_ni+0x7c2da7
01b7e24c 7646626b 00010880 00000202 00000000 System_Windows_Forms_ni+0xa1215f
01b7e278 764571bc 0680b23e 00010880 00000202 user32!_InternalCallWinProc+0x2b
01b7e35c 764562eb 0680b23e 00000000 00000202 user32!UserCallWinProcCheckWow+0x3ac
01b7e3d0 764560c0 01b7e458 01b7e418 682a302c user32!DispatchMessageWorker+0x21b
01b7e3dc 682a302c 01b7e458 fa8a32e1 6b66faf0 user32!DispatchMessageW+0x10
01b7e418 68256ce1 fa8a32e1 6b66faf0 01b7e540 System_Windows_Forms_ni+0x21302c
01b7e49c 682568f3 00000000 00000004 00000000 System_Windows_Forms_ni+0x1c6ce1
01b7e4f0 68256760 21e1c314 00000000 00000000 System_Windows_Forms_ni+0x1c68f3
01b7e51c 68847f87 21e1c314 21e695e4 000d059a System_Windows_Forms_ni+0x1c6760
01b7e534 6886dd92 fa8a32e1 6b66faf0 01b7e930 System_Windows_Forms_ni+0x7b7f87
01b7e604 6b66ebb6 21b75258 01d40008 01b7e668 System_Windows_Forms_ni+0x7ddd92
01b7e614 6b671e10 01b7e984 01b7e658 6b749b20 clr!CallDescrWorkerInternal+0x34
01b7e668 6b7fff7b fba4f215 00000002 00000000 clr!CallDescrWorkerWithHandler+0x6b
01b7e6a8 6b800055 21cac6a4 21b853d8 01b7e9c8 clr!CallDescrWorkerReflectionWrapper+0x55
01b7e9bc 6a5afbb1 00000000 1116acdc 6a5b1530 clr!RuntimeMethodHandle::InvokeMethod+0x84e
更多内容请参阅《格蠹汇编》第22,23章,参阅https://www.cnblogs.com/kissdodog/p/3731743.html。
栈空间的增长实际上表现为栈顶寄存器esp(32位程序)或rsp(64)的减少,例如push指令,sub esp指令都会使得栈空间增长。
在编译阶段,就已经生成代码,让局部变量存放在栈上。
当执行完毕call指令时,会使栈顶增加一格(32位程序是8字节,64位的是16字节),把函数的ret地址存入之。
函数的执行体内,最后一句一般是ret。实际功能就是跳转到之前的ret地址,并且把栈削减一格。
函数体内的开头通常是
push ebp
mov ebp,esp
意思是把存放“当前函数RetAddr”的地址-4,作为当前函数的childebp。childebp上的数据是父函数的childebp。
在函数体内,通常用ebp-4表示第一个局部变量,ebp-8表示第二个局部变量。ebp+8表示函数的第一个参数,ebp+c表示函数的第二个参数。
相对地址 | 存放数据 | 地址俗名 |
---|---|---|
10000000 | 子函数的第二个局部变量 | |
10000004 | 子函数的第一个局部变量 | |
10000008 | 当前帧的childebp | 子函数的childebp |
1000000c | 子函数的RetAddr | |
10000010 | 子函数的入参一 | |
10000014 | 子函数的入参二 | |
10000018 | 当前函数的第二个局部变量 | |
1000002c | 当前函数的第一个局部变量 | |
10000030 | 父函数的childebp | 当前帧的childebp |
10000034 | 当前函数的RetAddr | |
10000038 | 当前函数的入参一 | |
1000003c | 当前函数的入参二 |
据我所知,除了thiscall都是stdcall。rbp没有栈帧的作用。
stdcall的函数体内:rsp+28h表示第5个参数。如果函数没有被编译器优化,rsp+8,rsp+10h,rsp+18h,rsp+20h表示从左起1~4的参数。
大多数情况都是有优化的,rsp+8这些不一定代表参数。
visual studio中暂且把工程属性里的C/C++的优化的优化改成已禁用。
void __cdecl cd(int a, int b, int c, int d, int e) { getchar(); }
void __stdcall s(int a, int b, int c, int d, int e) { cd(a, b, c, d, e); }
void __fastcall f(int a, int b, int c, int d, int e) { s(a, b, c, d, e); }
void main() { f(1, 2, 3, 4, 5); }
编译称32位,执行exe,然后用32位的任务管理器(\Windows\Syswow64\taskmgr.exe)对该进程创建dmp,再用windbg打开此dmp文件。
输入kv
,查看栈回溯,如果它是64位的任务管理器抓取的dmp,要先输入!wow64exts.sw
再kv
0:000> !wow64exts.sw;kv
Switched to Guest (WoW) mode
# ChildEBP RetAddr Args to Child
00 012ffd18 75b40d6c 00000054 00000000 00000000 ntdll_774b0000!NtReadFile+0xc (FPO: [9,0,0])
01 012ffd7c 76596fe4 00000054 013897e0 00001000 KERNELBASE!ReadFile+0xec (FPO: [Non-Fpo])
02 012ffddc 76596dbf 00001000 1d177a7b 00000000 ucrtbase!_read_nolock+0x110 (FPO: [Non-Fpo])
03 012ffe1c 76598d78 00000000 013897e0 00001000 ucrtbase!_read+0x8f (FPO: [Non-Fpo])
04 012ffe3c 765a4af8 766802f0 1d177ae3 0138a910 ucrtbase!common_refill_and_read_nolock+0x7d (FPO: [Non-Fpo])
05 012ffe84 7661164a 766802f0 00541009 012ffeb0 ucrtbase!fgetc+0x108 (FPO: [SEH])
06 012ffe8c 00541009 012ffeb0 0054102c 00000001 ucrtbase!_fgetchar+0xa (FPO: [0,0,0])
07 012ffe94 0054102c 00000001 00000002 00000003 block!cd+0x9 (FPO: [Non-Fpo]) (CONV: cdecl) [XXX\main.cpp @ 3]
08 012ffeb0 00541065 00000001 00000002 00000003 block!s+0x1c (FPO: [Non-Fpo]) (CONV: stdcall) [XXX\main.cpp @ 4]
09 012ffed4 00541088 00000003 00000004 00000005 block!f+0x25 (FPO: [Non-Fpo]) (CONV: fastcall) [XXX\main.cpp @ 5]
0a 012ffee8 00541259 00000001 01385d00 0138a910 block!main+0x18 (FPO: [Non-Fpo]) (CONV: cdecl) [XXX\main.cpp @ 6]
0b (Inline) -------- -------- -------- -------- block!invoke_main+0x1c (Inline Function @ 00541259) (CONV: cdecl) [d:\a01\_work\2\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]
0c 012fff30 7542fa29 01051000 7542fa10 012fff9c block!__scrt_common_main_seh+0xfa (FPO: [Non-Fpo]) (CONV: cdecl) [d:\a01\_work\2\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
0d 012fff40 77517a7e 01051000 f173d2e3 00000000 kernel32!BaseThreadInitThunk+0x19 (FPO: [Non-Fpo])
0e 012fff9c 77517a4e ffffffff 77538a22 00000000 ntdll_774b0000!__RtlUserThreadStart+0x2f (FPO: [SEH])
0f 012fffac 00000000 005412e1 01051000 00000000 ntdll_774b0000!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
visual studio默认会生成pdb,exe里包含了自己pdb的路径,windbg会顺藤摸瓜找到pdb并加载,故而你能看到此exe文件里的函数名。实际对于别的厂商的exe和dll,我们都不知道他们的pdb,也就不知道那些函数名。
看block!cd+0x9这一行
07 012ffe94 0054102c 00000001 00000002 00000003 block!cd+0x9 (FPO: [Non-Fpo]) (CONV: cdecl) [XXX\main.cpp @ 3]
它的意思是block.exe里调用cd函数,它的调用约定是cdecl,进入函数后的ebp是012ffe94,栈上的三个参数是1,2,3函数执行完毕后ret到0054102c
。在block!cd+0x9的前面一点的位置它调用了函数ucrtbase!_fgetchar。
我们查看0054102c前方一点点的代码,将它们转为汇编
0:000:x86> ub 0054102c l10
block!cd+0xd:
0054100d cc int 3
0054100e cc int 3
0054100f cc int 3
block!s [XXX\main.cpp @ 4]:
00541010 55 push ebp
00541011 8bec mov ebp,esp
00541013 8b4518 mov eax,dword ptr [ebp+18h]
00541016 50 push eax
00541017 8b4d14 mov ecx,dword ptr [ebp+14h]
0054101a 51 push ecx
0054101b 8b5510 mov edx,dword ptr [ebp+10h]
0054101e 52 push edx
0054101f 8b450c mov eax,dword ptr [ebp+0Ch]
00541022 50 push eax
00541023 8b4d08 mov ecx,dword ptr [ebp+8]
00541026 51 push ecx
00541027 e8d4ffffff call block!cd (00541000)
这里显示call cd函数之前,push了5次。可知cd函数的实际参数有5个,从childebp 012ffe94来查看栈上的数据
0:000:x86> dps 012ffe94
012ffe94 012ffeb0
012ffe98 0054102c block!s+0x1c [XXX\main.cpp @ 4]
012ffe9c 00000001
012ffea0 00000002
012ffea4 00000003
012ffea8 00000004
012ffeac 00000005
012ffeb0 012ffed4
012ffeb4 00541065 block!f+0x25 [XXX\main.cpp @ 5]
012ffeb8 00000001
012ffebc 00000002
它的意思是block!s调用了某个函数(其实是cd),ret地址是0054102c block!s+0x1c
,这个函数的入参从右到左是5,4,3,2,1。cd函数在执行
push ebp
mov ebp,esp
前,ebp=012ffeb0,执行后,ebp=012ffe94。
查看0054102c后方一点点的代码,将它们转为汇编
0:000:x86> u 0054102c
block!s+0x1c [XXX\main.cpp @ 4]:
0054102c 83c414 add esp,14h
0054102f 5d pop ebp
00541030 c21400 ret 14h
00541033 cc int 3
00541034 cc int 3
这里有个大幅缩减栈空间的指令add esp,14h
然后再pop ebp
说明这是由函数调用者自己来恢复栈的,所以这个block!cd函数是cdecl调用约定。
看block!s+0x1c这一行
08 012ffeb0 00541065 00000001 00000002 00000003 block!s+0x1c (FPO: [Non-Fpo]) (CONV: stdcall)
它的意思是block.exe里调用s函数,它的调用约定是stdcall,进入函数后的ebp是012ffeb0,栈上的三个参数是1,2,3。函数执行完毕后ret到00541065。在block!s+0x1c的前面一点的位置它调用了函数block!cd。
我们查看00541065前方一点点的代码,将它们转为汇编
0:000:x86> ub 00541065 l10
block!f [XXX\main.cpp @ 5]:
00541040 55 push ebp
00541041 8bec mov ebp,esp
00541043 83ec08 sub esp,8
00541046 8955fc mov dword ptr [ebp-4],edx
00541049 894df8 mov dword ptr [ebp-8],ecx
0054104c 8b4510 mov eax,dword ptr [ebp+10h]
0054104f 50 push eax
00541050 8b4d0c mov ecx,dword ptr [ebp+0Ch]
00541053 51 push ecx
00541054 8b5508 mov edx,dword ptr [ebp+8]
00541057 52 push edx
00541058 8b45fc mov eax,dword ptr [ebp-4]
0054105b 50 push eax
0054105c 8b4df8 mov ecx,dword ptr [ebp-8]
0054105f 51 push ecx
00541060 e8abffffff call block!s (00541010)
这里显示call block!s函数之前,push了5次。可知s函数的实际参数有5个,从childebp 012ffeb0来查看栈上的数据
0:000:x86> dps 012ffeb0
012ffeb0 012ffed4
012ffeb4 00541065 block!f+0x25 [XXX\main.cpp @ 5]
012ffeb8 00000001
012ffebc 00000002
012ffec0 00000003
012ffec4 00000004
012ffec8 00000005
012ffecc 00000001
012ffed0 00000002
012ffed4 012ffee8
012ffed8 00541088 block!main+0x18 [XXX\main.cpp @ 6]
012ffedc 00000003
012ffee0 00000004
它的意思是block!f调用了某个函数(其实是block!s),ret地址是00541065 block!f+0x25
,这个函数的入参从右到左是5,4,3,2,1。block!s函数在执行
push ebp
mov ebp,esp
后,ebp=012ffeb0。
查看00541065后方一点点的代码,将它们转为汇编
0:000:x86> u 00541065
block!f+0x25 [XXX\main.cpp @ 5]:
00541065 8be5 mov esp,ebp
00541067 5d pop ebp
00541068 c20c00 ret 0Ch
0054106b cc int 3
因此这是一个栈上传参,调用者自己不用恢复栈空间的调用约定,属stdcall调用约定。
看block!f+0x25这一行
09 012ffed4 00541088 00000003 00000004 00000005 block!f+0x25 (FPO: [Non-Fpo]) (CONV: fastcall) [XXX\main.cpp @ 5]
它的意思是block.exe里调用f函数,它的调用约定是fastcall,childebp是012ffed4,栈上的三个参数是3,4,5(未必表示block!f的实际参数是3,4,5)。函数执行完毕后ret到00541088。在block!f+0x25的前面一点的位置它调用了函数block!s。
我们查看00541088前方一点点的代码,将它们转为汇编
block!main [XXX\main.cpp @ 6]:
00541070 55 push ebp
00541071 8bec mov ebp,esp
00541073 6a05 push 5
00541075 6a04 push 4
00541077 6a03 push 3
00541079 ba02000000 mov edx,2
0054107e b901000000 mov ecx,1
00541083 e8b8ffffff call block!f (00541040)
这里显示call block!f函数之前,push了3次,同时给ecx,edx寄存器赋值了。可知block!f函数的实际参数有5个,从childebp 012ffed4来查看栈上的数据
0:000:x86> dps 012ffed4
012ffed4 012ffee8
012ffed8 00541088 block!main+0x18 [XXX\main.cpp @ 6]
012ffedc 00000003
012ffee0 00000004
012ffee4 00000005
012ffee8 012fff30
012ffeec 00541259 block!__scrt_common_main_seh+0xfa [d:\a01\_work\2\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
所以能够知道第5,4,3个参数是5,4,3。但是这里就不容易弄清当时的ecx,edx的值是多少了
查看block!f里如何存储ecx,edx
0:000:x86> u block!f
block!f [XXX\main.cpp @ 5]:
00541040 55 push ebp
00541041 8bec mov ebp,esp
00541043 83ec08 sub esp,8
00541046 8955fc mov dword ptr [ebp-4],edx
00541049 894df8 mov dword ptr [ebp-8],ecx
可知ecx保存到了dword ptr [ebp-8],edx保存到了dword ptr [ebp-4]。查看dword ptr [ebp-8]和[ebp-4]的值:
0:000:x86> dd 012ffed4-8 l2
012ffecc 00000001 00000002
有此可知block!f的第1,2个参数是1,2。
将该代码编译成64位程序。从dmp中可以看到
0:000> kv
# Child-SP RetAddr : Args to Child : Call Site
00 00000014`d82ff768 00007ffe`5899ae23 : 00000000`00001000 00000000`00000000 0000020c`7326bf10 00000014`d82ff794 : ntdll!NtReadFile+0x14
01 00000014`d82ff770 00007ffe`587584a9 : 00630072`0075006f 00000000`00000000 00000000`00000000 00000014`d82ff8a8 : KERNELBASE!ReadFile+0x73
02 00000014`d82ff7f0 00007ffe`58758342 : 00007ffe`5882f400 0000020c`7326de90 00000000`00001000 00007ffe`00000000 : ucrtbase!_read_nolock+0x119
03 00000014`d82ff890 00007ffe`587581b0 : 00007ffe`00000000 00007ffe`5882f490 00000000`00000000 00000000`00000000 : ucrtbase!_read+0xa2
04 00000014`d82ff8d0 00007ffe`58758b4e : 00007ffe`5882f490 00000000`00000000 00000000`00000000 00007ffe`5aee07b0 : ucrtbase!common_refill_and_read_nolock+0x70
05 00000014`d82ff900 00007ff6`28c5101c : 00007ffe`5882f490 00007ffe`5882f490 00007ffe`5882f490 0000020c`73266f90 : ucrtbase!fgetc+0xce
06 00000014`d82ff940 00007ff6`28c51065 : 00000000`00000001 00000000`00000002 000086fb`00000003 00000000`00000004 : block!cd+0x1c [XXX\main.cpp @ 3]
07 00000014`d82ff970 00007ff6`28c510a5 : 00000000`00000001 00000000`00000002 00000000`00000003 00007ffe`00000004 : block!s+0x35 [XXX\main.cpp @ 4]
08 00000014`d82ff9b0 00007ff6`28c510d7 : 00007ff6`00000001 00007ff6`00000002 00000000`00000003 00000000`00000004 : block!f+0x35 [XXX\main.cpp @ 5]
09 00000014`d82ff9f0 00007ff6`28c51300 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : block!main+0x27 [XXX\main.cpp @ 6]
0a (Inline Function) --------`-------- : --------`-------- --------`-------- --------`-------- --------`-------- : block!invoke_main+0x22 (Inline Function @ 00007ff6`28c51300) [d:\a01\_work\2\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]
0b 00000014`d82ffa30 00007ffe`5a9e7034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : block!__scrt_common_main_seh+0x10c [d:\a01\_work\2\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
0c 00000014`d82ffa70 00007ffe`5aee2651 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
0d 00000014`d82ffaa0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
已经没有cdecl和stdcall,编译器都把他们转为fastcall。
查看block!cd+0x1c
这一行
06 00000014d82ff940 00007ff628c51065 : 0000000000000001 0000000000000002 000086fb00000003 0000000000000004 : block!cd+0x1c [XXX\main.cpp @ 3]
它的意思是block.exe里调用block!cd函数,栈上的四个参数是1,2,86fb00000003,4(未必表示block!cd的实际参数是1,2,86fb00000003,4)。函数执行完毕后ret到7ff628c51065。在block!cd+0x1c的前面一点的位置它调用了函数ucrtbase!fgetc,call ucrtbase!fgetc前的rsp是14d82ff940。
我们查看7ff628c51065前方一点点的代码,将它们转为汇编
0:000> ub 7ff6`28c51065
block!s+0x12 [XXX\main.cpp @ 4]:
00007ff6`28c51042 4883ec38 sub rsp,38h
00007ff6`28c51046 8b442460 mov eax,dword ptr [rsp+60h]
00007ff6`28c5104a 89442420 mov dword ptr [rsp+20h],eax
00007ff6`28c5104e 448b4c2458 mov r9d,dword ptr [rsp+58h]
00007ff6`28c51053 448b442450 mov r8d,dword ptr [rsp+50h]
00007ff6`28c51058 8b542448 mov edx,dword ptr [rsp+48h]
00007ff6`28c5105c 8b4c2440 mov ecx,dword ptr [rsp+40h]
00007ff6`28c51060 e89bffffff call block!cd (00007ff6`28c51000)
很典型的fastcall,4个参数用rcx,rdx,r8,r9传参,第5个参数用rsp+20h传参
查看函数体内的函数实现:
0:000> u 7ff6`28c51000
block!cd [C:\Users\lida\source\repos\Project1\block\main.cpp @ 3]:
00007ff6`28c51000 44894c2420 mov dword ptr [rsp+20h],r9d
00007ff6`28c51005 4489442418 mov dword ptr [rsp+18h],r8d
00007ff6`28c5100a 89542410 mov dword ptr [rsp+10h],edx
00007ff6`28c5100e 894c2408 mov dword ptr [rsp+8],ecx
00007ff6`28c51012 4883ec28 sub rsp,28h
00007ff6`28c51016 ff1554110000 call qword ptr [block!_imp_getchar (00007ff6`28c52170)]
00007ff6`28c5101c 4883c428 add rsp,28h
00007ff6`28c51020 c3 ret
刚刚进入函数体内是rsp里存放的ret地址,即block!s+0x35,它表示将第一个函数参数rcx存入rsp+8,第二个存入rsp+10。已知第5个参数是函数调用前的rsp+20h,进入函数体时,rsp增加一格用于存放ret地址,那么第5个参数在函数体内是rsp+28h
查看这些参数:
0:000> dps 14d82ff940
00000014`d82ff940 00007ffe`5882f490 ucrtbase!iob
00000014`d82ff948 00007ffe`5882f490 ucrtbase!iob
00000014`d82ff950 00007ffe`5882f490 ucrtbase!iob
00000014`d82ff958 0000020c`73266f90
00000014`d82ff960 00000000`00000000
00000014`d82ff968 00007ff6`28c51065 block!s+0x35 [XXX\main.cpp @ 4]
00000014`d82ff970 00000000`00000001
00000014`d82ff978 00000000`00000002
00000014`d82ff980 000086fb`00000003
00000014`d82ff988 00000000`00000004
00000014`d82ff990 00000000`00000005
00000014`d82ff998 00007ffe`587439ce ucrtbase!__crt_state_management::get_current_state_index+0x6a
00000014`d82ff9a0 00000000`00000000
00000014`d82ff9a8 00007ff6`28c510a5 block!f+0x35 [XXX\main.cpp @ 5]
00000014`d82ff9b0 00000000`00000001
00000014`d82ff9b8 00000000`00000002
即block!cd的前五个参数是1,2,3,4,5。
对于编译器优化fastcall的场景(这是很常见的),经常并不会将rcx存入rsp+8,但是如果有第5个参数,一般会调用函数时使用rsp+20h传参。
反复关开powerpnt.exe24小时,有几率出现powerpnt窗口已关闭,但是进程仍然存留的问题。
此时该进程的cpu并不高,因此不是相关线程陷入简单死循环。耗时良久,还是不能结束,一般来说也不是卡顿。推测可能是死锁,或者陷入内核不返回。
当然,也可以实时调试。本次是调试dump。
输入!locks
分析正在被Wait的CRITICAL_SECTION(并不一定直接得到死锁),得到
0:000> !locks
CritSec ntdll!LdrpLoaderLock+0 at 77a17340
WaiterWoken No
LockCount 2
RecursionCount 1
OwningThread b70
EntryCount 0
ContentionCount 8
*** Locked
CritSec GDI32!semLocal+0 at 774f9140
WaiterWoken No
LockCount 1
RecursionCount 1
OwningThread fc8
EntryCount 0
ContentionCount 1
*** Locked
可见有两个criticalsection类型正在被锁住。一个是77a17340,持有线程是b70。另一个是774f9140,持有线程是fc8。
输入!threads
,得到线程概览
0:000> !threads
Index TID TEB StackBase StackLimit DeAlloc StackSize ThreadProc
0 00000b70 0x7ffdf000 0x001b0000 0x001a2000 0x000b0000 0x0000e000 0x0
1 00000abc 0x7ffde000 0x02bd0000 0x02bcf000 0x02ad0000 0x00001000 0x0
2 00000784 0x7ffdd000 0x02cf0000 0x02cee000 0x02bf0000 0x00002000 0x0
3 00000fc8 0x7ffdc000 0x02fb0000 0x02fac000 0x02eb0000 0x00004000 0x0
4 00000218 0x7ffda000 0x03120000 0x0311f000 0x03020000 0x00001000 0x0
5 000004d0 0x7ffd9000 0x03790000 0x0378c000 0x03690000 0x00004000 0x0
Total VM consumed by thread stacks 0x0001a000
切换到b70,即0号线程,输入~0s
想看看0号线程正在做什么,为什么不释放cs77a17340。输入kv
0:000> kv
ChildEBP RetAddr Args to Child
001a6a40 77985e6c 7796fc72 00000268 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
001a6a44 7796fc72 00000268 00000000 00000000 ntdll!NtWaitForSingleObject+0xc (FPO: [3,0,0])
001a6aa8 7796fb56 00000000 00000000 00000001 ntdll!RtlpWaitOnCriticalSection+0x13e (FPO: [Non-Fpo])
001a6ad0 774b7acf 774f9140 37010818 01570ae0 ntdll!RtlEnterCriticalSection+0x150 (FPO: [Non-Fpo])
001a6ae4 774b7a3d 37010818 001a6af4 00000010 GDI32!GetFontRealizationInfo+0x4e (FPO: [Non-Fpo])
001a6b0c 77021af7 37010818 001a6b20 37010818 GDI32!GdiRealizationInfo+0x1b (FPO: [Non-Fpo])
001a6b38 77021c26 37010818 000004e4 001a6c8c LPK!FontHasWesternScript+0x21 (FPO: [Non-Fpo])
001a6b48 774bd81c 37010818 72959818 00000001 LPK!LpkUseGDIWidthCache+0x91 (FPO: [Non-Fpo])
001a6c8c 774bd8d0 37010818 72959818 00000001 GDI32!GetTextExtentPointAInternal+0x134 (FPO: [Non-Fpo])
001a6ca8 729433c9 37010818 72959818 00000001 GDI32!GetTextExtentPointA+0x18 (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
001a6ccc 7799316f 7799316f 72943218 72940000 MSVBVM60!EbLibraryLoad+0x170
001a6dd4 7799fd0d 00000000 00000000 6e7a981a ntdll!RtlpFreeHeap+0xb7a (FPO: [Non-Fpo])
001a6ef0 7799349f 779934ca 000001de 000001e8 ntdll!LdrpRunInitializeRoutines+0x24b (FPO: [Non-Fpo])
001a6fc4 75bfb8a4 003a7cd4 001a7004 001a6ff0 ntdll!RtlpAllocateHeap+0xe73 (FPO: [Non-Fpo])
猜测0号线程正在wait什么CriticalSection而未返回。先欲查看wait的是谁。ntdll!NtWaitForSingleObject
,ntdll!RtlpWaitOnCriticalSection
,ntdll!RtlEnterCriticalSection
这三个API都不是公开的。windbg解析函数的实际入参也经常不准。因此不易判断。
API虽然不公开,但是ntdll可以使用IDA反汇编,我们可以大致知道他们的形参。
推测正在wait的cs是774f9140,因为它是ntdll!RtlEnterCriticalSection的参数。
这样,就说明0号线程被3号线程卡住。
接下来看看3号线程持有cs774f9140,但为什么不释放。
输入~3s
切换到3号线程,查看栈回溯
0:003> kb
ChildEBP RetAddr Args to Child
02fae634 77985e6c 7796fc72 00000204 00000000 ntdll!KiFastSystemCallRet
02fae638 7796fc72 00000204 00000000 00000000 ntdll!NtWaitForSingleObject+0xc
02fae69c 7796fb56 00000000 00000000 00000001 ntdll!RtlpWaitOnCriticalSection+0x13e
02fae6c4 7799f6d0 77a17340 75397f2e 779870da ntdll!RtlEnterCriticalSection+0x150
02fae830 7799f5f9 02fae890 02fae85c 00000000 ntdll!LdrpLoadDll+0x287
02fae864 75bfb8a4 00340924 02fae8a4 02fae890 ntdll!LdrLoadDll+0x92
02fae89c 778b28c3 00000000 00000000 00000002 KERNELBASE!LoadLibraryExW+0x15a
02fae8b0 774c2cf5 774c31e0 00000000 774c2cbd kernel32!LoadLibraryW+0x11
02fae8bc 774c2cbd 00000000 015e83b8 00000000 GDI32!bLoadSpooler+0x24
同理,可以看到RtlEnterCriticalSection正在等待cs77a17340而未返回。
即0号,3号线程互相死锁。
更多地,还可以分析3号线程在进程退出的时候为什么还要LoadLibrary,load的是那个dll。可能对分析根因有帮助。
参见《格蠹汇编》第8章
参见《某医院影像客户端卡死分析》
进程崩溃一般都是由Windows异常触发。c++编译器实现异常实际上就是依赖于windows异常。本节简述Windows异常为异常。
Windows异常明细参见https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55。具体原理参阅《软件调试》第11章。
c语言从虽然从main函数开始编写,但是vc编译器实际上在main函数的上层编写了两层捕获异常的代码。如果异常走到这里,就会调用HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug
的调试器接管。procdump自动捕获它程序崩溃的原理就是设置Aebug为procdump.exe。因此procdump也会抓到两个dump。
试写一个访问null指针的程序崩溃:
void func1(int i) {
char* p = NULL;
*p = i;
}
void func2(int i) { func1(i); }
void func3(int i) { func2(i); }
int main() {
func3(5);
return 0;
}
windbg打开dmp,用!analyze -v
分析崩溃原因
KEY_VALUES_STRING: 1
Key : AV.Dereference
Value: NullPtr
Key : AV.Fault
Value: Write
Key : Analysis.CPU.mSec
Value: 640
Key : Analysis.DebugAnalysisManager
Value: Create
Key : Analysis.Elapsed.mSec
Value: 1252
Key : Analysis.Init.CPU.mSec
Value: 218
Key : Analysis.Init.Elapsed.mSec
Value: 5192
Key : Analysis.Memory.CommitPeak.Mb
Value: 58
Key : Timeline.OS.Boot.DeltaSec
Value: 593773
Key : Timeline.Process.Start.DeltaSec
Value: 2
Key : WER.OS.Branch
Value: vb_release
Key : WER.OS.Timestamp
Value: 2019-12-06T14:06:00Z
Key : WER.OS.Version
Value: 10.0.19041.1
NTGLOBALFLAG: 0
APPLICATION_VERIFIER_FLAGS: 0
CONTEXT: (.ecxr)
rax=0000000000000000 rbx=000002236e986f90 rcx=0000000000000005
rdx=000002236e986f90 rsi=0000000000000000 rdi=000002236e986220
rip=00007ff6d96a1119 rsp=000000e972eff7b0 rbp=0000000000000000
r8=000002236e986220 r9=000000e972eff838 r10=0000000000000012
r11=000000e972eff850 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz na pe nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
block+0x1119:
00007ff6`d96a1119 8808 mov byte ptr [rax],cl ds:00000000`00000000=??
Resetting default scope
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 00007ff6d96a1119 (block+0x0000000000001119)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 0000000000000001
Parameter[1]: 0000000000000000
Attempt to write to address 0000000000000000
PROCESS_NAME: block.exe
WRITE_ADDRESS: 0000000000000000
ERROR_CODE: (NTSTATUS) 0xc0000005 - 0x%p 0x%p %s
EXCEPTION_CODE_STR: c0000005
EXCEPTION_PARAMETER1: 0000000000000001
EXCEPTION_PARAMETER2: 0000000000000000
STACK_TEXT:
000000e9`72eff7b0 00007ff6`d96a1131 : 00000000`00000005 00007ffe`5aee07b0 000031b8`6a808ea7 00000000`00000001 : block+0x1119
000000e9`72eff7d0 00007ff6`d96a1151 : 00000000`00000005 00000000`00000000 000031b8`6a808ef7 00000000`00000000 : block+0x1131
000000e9`72eff800 00007ff6`d96a1180 : 00000000`00000005 00000000`00000000 00000000`00000000 00000000`00000000 : block+0x1151
000000e9`72eff830 00007ff6`d96a1430 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : block+0x1180
000000e9`72eff8c0 00007ffe`5a9e7034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : block+0x1430
000000e9`72eff900 00007ffe`5aee2651 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : kernel32!BaseThreadInitThunk+0x14
000000e9`72eff930 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
SYMBOL_NAME: block+1119
MODULE_NAME: block
IMAGE_NAME: block.exe
STACK_COMMAND: ~0s ; .ecxr ; kb
FAILURE_BUCKET_ID: NULL_POINTER_WRITE_c0000005_block.exe!Unknown
OS_VERSION: 10.0.19041.1
BUILDLAB_STR: vb_release
OSPLATFORM_TYPE: x64
OSNAME: Windows 10
FAILURE_ID_HASH: {dc59659c-6d40-ad2c-5af8-3e79cf6788ef}
Followup: MachineOwner
---------
可以看到崩溃原因,是因为触发了异常c0000005
,即STATUS_ACCESS_VIOLATION
。执行崩溃代码的模块是block.exe,在0号线程的block+1119处。
注意,此处的异常码c0000006并不是GetLastError的返回值,而是ntstatus.h里的错误码。
所以要看看此处执行了什么代码导致访问违例。~0s
切换到0号线程,用u
命令查看这一段代码
0:000> u block+1119
block+0x1119:
00007ff6`d96a1119 8808 mov byte ptr [rax],cl
00007ff6`d96a111b 4883c418 add rsp,18h
00007ff6`d96a111f c3 ret
00007ff6`d96a1120 894c2408 mov dword ptr [rsp+8],ecx
00007ff6`d96a1124 4883ec28 sub rsp,28h
00007ff6`d96a1128 8b4c2430 mov ecx,dword ptr [rsp+30h]
00007ff6`d96a112c e8cfffffff call block+0x1100 (00007ff6`d96a1100)
00007ff6`d96a1131 4883c428 add rsp,28h
block+1119
处代码为即mov byte ptr [rax],cl
。
一般欲查看rax寄存器里的值是多少,一般是r rax
。不过此时已经事过境迁,没法查看了。
输入.thread;k;r rax
可知当前的线程已经离开block+0x1119
,继续执行到抛出异常部分的代码了,在最新的栈帧内,rax=5b了。
0:000> .thread;k;r rax
Implicit thread is now 000000e9`72c44000
# Child-SP RetAddr Call Site
00 000000e9`72efe258 00007ffe`589bcab0 ntdll!NtWaitForMultipleObjects+0x14
01 000000e9`72efe260 00007ffe`589bc9ae KERNELBASE!WaitForMultipleObjectsEx+0xf0
02 000000e9`72efe550 00007ffe`5aa3f6aa KERNELBASE!WaitForMultipleObjects+0xe
03 000000e9`72efe590 00007ffe`5aa3f0e6 kernel32!WerpReportFaultInternal+0x58a
04 000000e9`72efe6b0 00007ffe`58a7c439 kernel32!WerpReportFault+0xbe
05 000000e9`72efe6f0 00007ffe`5af35270 KERNELBASE!UnhandledExceptionFilter+0x3d9
06 000000e9`72efe810 00007ffe`5af1c776 ntdll!RtlUserThreadStart$filt$0+0xa2
07 000000e9`72efe850 00007ffe`5af3217f ntdll!_C_specific_handler+0x96
08 000000e9`72efe8c0 00007ffe`5aee1454 ntdll!RtlpExecuteHandlerForException+0xf
09 000000e9`72efe8f0 00007ffe`5af30cae ntdll!RtlDispatchException+0x244
0a 000000e9`72eff000 00007ff6`d96a1119 ntdll!KiUserExceptionDispatch+0x2e
0b 000000e9`72eff7b0 00007ff6`d96a1131 block+0x1119
0c 000000e9`72eff7d0 00007ff6`d96a1151 block+0x1131
0d 000000e9`72eff800 00007ff6`d96a1180 block+0x1151
0e 000000e9`72eff830 00007ff6`d96a1430 block+0x1180
0f 000000e9`72eff8c0 00007ffe`5a9e7034 block+0x1430
10 000000e9`72eff900 00007ffe`5aee2651 kernel32!BaseThreadInitThunk+0x14
11 000000e9`72eff930 00000000`00000000 ntdll!RtlUserThreadStart+0x21
rax=000000000000005b
欲得知异常的故障现场的寄存器的值和调用栈,须执行.ecxr ; kb
0:000> .ecxr ; kb
rax=0000000000000000 rbx=000002236e986f90 rcx=0000000000000005
rdx=000002236e986f90 rsi=0000000000000000 rdi=000002236e986220
rip=00007ff6d96a1119 rsp=000000e972eff7b0 rbp=0000000000000000
r8=000002236e986220 r9=000000e972eff838 r10=0000000000000012
r11=000000e972eff850 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz na pe nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
block+0x1119:
00007ff6`d96a1119 8808 mov byte ptr [rax],cl ds:00000000`00000000=??
*** Stack trace for last set context - .thread/.cxr resets it
# RetAddr : Args to Child : Call Site
00 00007ff6`d96a1131 : 00000000`00000005 00007ffe`5aee07b0 000031b8`6a808ea7 00000000`00000001 : block+0x1119
01 00007ff6`d96a1151 : 00000000`00000005 00000000`00000000 000031b8`6a808ef7 00000000`00000000 : block+0x1131
02 00007ff6`d96a1180 : 00000000`00000005 00000000`00000000 00000000`00000000 00000000`00000000 : block+0x1151
03 00007ff6`d96a1430 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : block+0x1180
04 00007ffe`5a9e7034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : block+0x1430
05 00007ffe`5aee2651 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : kernel32!BaseThreadInitThunk+0x14
06 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
如此,可知故障时rax=0,执行block+1119
处代码为即mov byte ptr [rax],cl
,自然会报错c0000005。
旧镜像正常。编辑镜像,升级guestttool重启后黑屏。按Ctrl+alt+del有反应。安全模式下正常。屏蔽Guestttool正常。
按Ctrl+alt+del有反应说明不是卡死,也不是显卡的显示问题。猜测是explorer崩溃导致黑屏。一般explorer崩溃后会自动被启动,猜测是无尽地崩溃,无尽的重启。
升级Guesttool后导致的问题,故而推测是Guesttool某个dll hook了explorer导致崩溃。
使用procdump收集崩溃dump。
windbg打开dump,输入!analyze -v
自动分析
PROCESS_NAME: explorer.exe
WRITE_ADDRESS: 0000000009910d48
ERROR_CODE: (NTSTATUS) 0xc0000005 - 0x%p 0x%p %s
EXCEPTION_CODE_STR: c0000005
EXCEPTION_PARAMETER1: 0000000000000001
EXCEPTION_PARAMETER2: 0000000009910d48
IP_ON_HEAP: 0074007500420074
The fault address in not in any loaded module, please check your build's rebase
log at \bin\build_logs\timebuild\ntrebase.log for module which may
contain the address if it were loaded.
STACK_TEXT:
00000000`09910d50 00000000`773c3c3f : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlDispatchException+0x1d
00000000`09911430 000007fe`fd05bded : 00000000`00000000 00000000`09912238 00000000`0ba9557e 00000000`00000000 : ntdll!RtlRaiseException+0x22f
00000000`09911de0 000007fe`fd06802d : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNELBASE!RaiseException+0x39
00000000`09911eb0 000007fe`fd08ea06 : 00000000`099121a0 00000000`00000000 00000000`09912238 00000000`0000001d : KERNELBASE!OutputDebugStringA+0x6d
00000000`09912180 000007fe`fce1208e : 00000000`0bbdfdc0 00000000`099123e0 00000000`09912238 00000000`00000208 : KERNELBASE!OutputDebugStringW+0x66
00000000`099121d0 000007fe`fce12193 : 000007fe`fce45da0 00000000`c0000034 00000000`09912228 00000000`099123e0 : RCD_AppInit+0x208e
00000000`09912230 000007fe`fce12358 : 00000000`00000000 00000000`09912730 00000000`00000104 00000000`09912b00 : RCD_AppInit+0x2193
00000000`099122e0 000007fe`fce12744 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`09912e88 : RCD_AppInit+0x2358
00000000`09912970 00000000`772790e9 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000030 : RCD_AppInit+0x2744
00000000`09912df0 000007fe`f4db1851 : 00000000`774a7ff0 00000000`00000000 00000000`09077b30 00000000`00000000 : kernel32!SetUnhandledExceptionFilter+0xc9
00000000`099130c0 00000000`773c4918 : 00000000`00000000 00000000`f3ae8b8a 00000000`774a7ff0 00000000`00000000 : baiducn+0x1851
00000000`099130f0 00000000`773c0002 : 00000000`772fe670 00000000`00000000 00000000`00000000 00000000`09077b20 : ntdll!RtlpCallVectoredHandlers+0xa8
00000000`09913160 00000000`773eb61e : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlDispatchException+0x22
00000000`09913840 00000000`773a186a : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!KiUserExceptionDispatch+0x2e
00000000`09913f40 00000000`773a009a : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlpDosPathNameToRelativeNtPathName_Ustr+0x2a
00000000`09914210 000007fe`fd055ee7 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlDosPathNameToRelativeNtPathName_U_WithStatus+0x6a
00000000`09914260 00000000`772b6c55 : 00000000`00000000 00000000`00000000 00000000`00000003 00000000`00000000 : KERNELBASE!CreateFileW+0xa7
00000000`099143c0 000007fe`fce12240 : 00000000`00000000 00000000`09914540 00000000`00000000 00000000`09914d10 : kernel32!FindFirstVolumeW+0x45
00000000`09914440 000007fe`fce12744 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`09914fe8 : RCD_AppInit+0x2240
00000000`09914ad0 00000000`772790e9 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000030 : RCD_AppInit+0x2744
00000000`09914f50 000007fe`f4db1851 : 00000000`774a7ff0 00000000`7748aa00 00000000`09077b30 00000000`00000000 : kernel32!SetUnhandledExceptionFilter+0xc9
00000000`09915220 00000000`773c4918 : 00000000`00000000 00000000`f3ae8b8a 00000000`774a7ff0 00000000`00000000 : baiducn+0x1851
00000000`09915250 00000000`773c0002 : 00000000`0000000e 00000000`00000000 00000000`00000000 00000000`09077b20 : ntdll!RtlpCallVectoredHandlers+0xa8
00000000`099152c0 00000000`773c3c3f : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlDispatchException+0x22
00000000`099159a0 000007fe`fd05bded : 00000000`00000000 00000000`099167a8 00000000`0ba9548e 00000000`00000000 : ntdll!RtlRaiseException+0x22f
00000000`09916350 000007fe`fd06802d : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNELBASE!RaiseException+0x39
00000000`09916420 000007fe`fd08ea06 : 00000000`09916710 00000000`00000000 00000000`099167a8 00000000`0000001d : KERNELBASE!OutputDebugStringA+0x6d
00000000`099166f0 000007fe`fce1208e : 00000000`0bbdfd60 00000000`09916950 00000000`099167a8 00000000`00000208 : KERNELBASE!OutputDebugStringW+0x66
00000000`09916740 000007fe`fce12193 : 000007fe`fce45da0 00000000`c0000034 00000000`09916798 00000000`09916950 : RCD_AppInit+0x208e
00000000`099167a0 000007fe`fce12358 : 00000000`00000000 00000000`09916ca0 00000000`00000104 00000000`09917100 : RCD_AppInit+0x2193
00000000`09916850 000007fe`fce12744 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`099173f8 : RCD_AppInit+0x2358
00000000`09916ee0 00000000`772790e9 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000030 : RCD_AppInit+0x2744
00000000`09917360 000007fe`f4db1851 : 00000000`774a7ff0 00000000`7748aa00 00000000`09077b30 00000000`00000000 : kernel32!SetUnhandledExceptionFilter+0xc9
00000000`09917630 00000000`773c4918 : 00000000`00000000 00000000`f3ae8b8a 00000000`774a7ff0 00000000`00000000 : baiducn+0x1851
00000000`09917660 00000000`773c0002 : 00000000`0000000e 00000000`00000000 00000000`00000000 00000000`09077b20 : ntdll!RtlpCallVectoredHandlers+0xa8
00000000`099176d0 00000000`773c3c3f : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlDispatchException+0x22
00000000`09917db0 000007fe`fd05bded : 00000000`00000000 00000000`09918bb8 00000000`0ba954be 00000000`00000000 : ntdll!RtlRaiseException+0x22f
00000000`09918760 000007fe`fd06802d : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNELBASE!RaiseException+0x39
00000000`09918830 000007fe`fd08ea06 : 00000000`09918b20 00000000`00000000 00000000`09918bb8 00000000`0000001d : KERNELBASE!OutputDebugStringA+0x6d
00000000`09918b00 000007fe`fce1208e : 00000000`0bbdfd00 00000000`09918d60 00000000`09918bb8 00000000`00000208 : KERNELBASE!OutputDebugStringW+0x66
00000000`09918b50 000007fe`fce12193 : 000007fe`fce45da0 00000000`c0000034 00000000`09918ba8 00000000`09918d60 : RCD_AppInit+0x208e
00000000`09918bb0 000007fe`fce12358 : 00000000`00000000 00000000`099190b0 00000000`00000104 00000000`09919500 : RCD_AppInit+0x2193
00000000`09918c60 000007fe`fce12744 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`09919808 : RCD_AppInit+0x2358
00000000`099192f0 00000000`772790e9 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000030 : RCD_AppInit+0x2744
00000000`09919770 000007fe`f4db1851 : 00000000`774a7ff0 00000000`7748aa00 00000000`09077b30 00000000`00000000 : kernel32!SetUnhandledExceptionFilter+0xc9
00000000`09919a40 00000000`773c4918 : 00000000`00000000 00000000`f3ae8b8a 00000000`774a7ff0 00000000`00000000 : baiducn+0x1851
00000000`09919a70 00000000`773c0002 : 00000000`0000000e 00000000`00000000 00000000`00000000 00000000`09077b20 : ntdll!RtlpCallVectoredHandlers+0xa8
00000000`09919ae0 00000000`773c3c3f : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlDispatchException+0x22
00000000`0991a1c0 000007fe`fd05bded : 00000000`00000000 00000000`0991afc8 00000000`0ba9542e 00000000`00000000 : ntdll!RtlRaiseException+0x22f
00000000`0991ab70 000007fe`fd06802d : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNELBASE!RaiseException+0x39
00000000`0991ac40 000007fe`fd08ea06 : 00000000`0991af30 00000000`00000000 00000000`0991afc8 00000000`0000001d : KERNELBASE!OutputDebugStringA+0x6d
00000000`0991af10 000007fe`fce1208e : 00000000`0bbdfca0 00000000`0991b170 00000000`0991afc8 00000000`00000208 : KERNELBASE!OutputDebugStringW+0x66
00000000`0991af60 000007fe`fce12193 : 000007fe`fce45da0 00000000`c0000034 00000000`0991afb8 00000000`0991b170 : RCD_AppInit+0x208e
00000000`0991afc0 000007fe`fce12358 : 00000000`00000000 00000000`0991b4c0 00000000`00000104 00000000`0991b900 : RCD_AppInit+0x2193
00000000`0991b070 000007fe`fce12744 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`0991bc18 : RCD_AppInit+0x2358
00000000`0991b700 00000000`772790e9 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000030 : RCD_AppInit+0x2744
00000000`0991bb80 000007fe`f4db1851 : 00000000`774a7ff0 00000000`7748aa00 00000000`09077b30 00000000`00000000 : kernel32!SetUnhandledExceptionFilter+0xc9
00000000`0991be50 00000000`773c4918 : 00000000`00000000 00000000`f3ae8b8a 00000000`774a7ff0 00000000`00000000 : baiducn+0x1851
00000000`0991be80 00000000`773c0002 : 00000000`0000000e 00000000`00000000 00000000`00000000 00000000`09077b20 : ntdll!RtlpCallVectoredHandlers+0xa8
00000000`0991bef0 00000000`773c3c3f : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlDispatchException+0x22
00000000`0991c5d0 000007fe`fd05bded : 00000000`00000000 00000000`0991d3d8 00000000`0ba9545e 00000000`00000000 : ntdll!RtlRaiseException+0x22f
00000000`0991cf80 000007fe`fd06802d : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNELBASE!RaiseException+0x39
00000000`0991d050 000007fe`fd08ea06 : 00000000`0991d340 00000000`00000000 00000000`0991d3d8 00000000`0000001d : KERNELBASE!OutputDebugStringA+0x6d
00000000`0991d320 000007fe`fce1208e : 00000000`0bbdfc40 00000000`0991d580 00000000`0991d3d8 00000000`00000208 : KERNELBASE!OutputDebugStringW+0x66
00000000`0991d370 000007fe`fce12193 : 000007fe`fce45da0 00000000`c0000034 00000000`0991d3c8 00000000`0991d580 : RCD_AppInit+0x208e
00000000`0991d3d0 000007fe`fce12358 : 00000000`00000000 00000000`0991d8d0 00000000`00000104 00000000`0991dd00 : RCD_AppInit+0x2193
00000000`0991d480 000007fe`fce12744 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`0991e028 : RCD_AppInit+0x2358
00000000`0991db10 00000000`772790e9 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000030 : RCD_AppInit+0x2744
00000000`0991df90 000007fe`f4db1851 : 00000000`774a7ff0 00000000`7748aa00 00000000`09077b30 00000000`00000000 : kernel32!SetUnhandledExceptionFilter+0xc9
00000000`0991e260 00000000`773c4918 : 00000000`00000000 00000000`f3ae8b8a 00000000`774a7ff0 00000000`00000000 : baiducn+0x1851
00000000`0991e290 00000000`773c0002 : 00000000`0000000e 00000000`00000000 00000000`00000000 00000000`09077b20 : ntdll!RtlpCallVectoredHandlers+0xa8
00000000`0991e300 00000000`773c3c3f : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlDispatchException+0x22
00000000`0991e9e0 000007fe`fd05bded : 00000000`00000000 00000000`0991f7e8 00000000`0ba953ce 00000000`00000000 : ntdll!RtlRaiseException+0x22f
00000000`0991f390 000007fe`fd06802d : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNELBASE!RaiseException+0x39
00000000`0991f460 000007fe`fd08ea06 : 00000000`0991f750 00000000`00000000 00000000`0991f7e8 00000000`0000001d : KERNELBASE!OutputDebugStringA+0x6d
00000000`0991f730 000007fe`fce1208e : 00000000`0bad1280 00000000`0991f990 00000000`0991f7e8 00000000`00000208 : KERNELBASE!OutputDebugStringW+0x66
00000000`0991f780 000007fe`fce12193 : 000007fe`fce45da0 00000000`c0000034 00000000`0991f7d8 00000000`0991f990 : RCD_AppInit+0x208e
00000000`0991f7e0 000007fe`fce12358 : 00000000`00000000 00000000`0991fce0 00000000`00000104 00000000`09920100 : RCD_AppInit+0x2193
00000000`0991f890 000007fe`fce12744 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`09920438 : RCD_AppInit+0x2358
00000000`0991ff20 00000000`772790e9 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000030 : RCD_AppInit+0x2744
00000000`099203a0 000007fe`f4db1851 : 00000000`774a7ff0 00000000`7748aa00 00000000`09077b30 00000000`00000000 : kernel32!SetUnhandledExceptionFilter+0xc9
00000000`09920670 00000000`773c4918 : 00000000`00000000 00000000`f3ae8b8a 00000000`774a7ff0 00000000`00000000 : baiducn+0x1851
00000000`099206a0 00000000`773c0002 : 00000000`0000000e 00000000`00000000 00000000`00000000 00000000`09077b20 : ntdll!RtlpCallVectoredHandlers+0xa8
00000000`09920710 00000000`773c3c3f : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlDispatchException+0x22
00000000`09920df0 000007fe`fd05bded : 00000000`00000000 00000000`09921bf8 00000000`0ba9536e 00000000`00000000 : ntdll!RtlRaiseException+0x22f
00000000`099217a0 000007fe`fd06802d : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNELBASE!RaiseException+0x39
00000000`09921870 000007fe`fd08ea06 : 00000000`09921b60 00000000`00000000 00000000`09921bf8 00000000`0000001d : KERNELBASE!OutputDebugStringA+0x6d
同样是c0000005
异常,在往0000000009910d48写数据的时候访问违例。代码发生在ntdll!RtlDispatchException+0x1d
。当然Windows自己的bug还是比较少的,先不去怀疑是ntdll的bug。看看别的疑点。
从崩溃处的栈回溯来看,似乎若干栈帧似乎是在不停循环,像是栈溢出了,即栈不停的增长,最后试图增长到9910d48时,由于这个地址不可写,或者不属于栈区,因此崩溃了。用!address
看看该进程内存分布
+ 0`098d0000 0`09910000 0`00040000 MEM_FREE PAGE_NOACCESS Free
+ 0`09910000 0`09911000 0`00001000 MEM_PRIVATE MEM_RESERVE Stack [~35; 73c.d2c]
0`09911000 0`09990000 0`0007f000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [~35; 73c.d2c]
09910d48虽然在栈区,但是似乎没有可读写的属性。表明上原因找到了。
继续看为什么栈帧在循环。KERNELBASE!RaiseException
表明此处抛出了异常,kernel32!SetUnhandledExceptionFilter
表明windows程序框架会去SetUnhandledExceptionFilter里寻找异常接受者。KERNELBASE!OutputDebugStringA
内部实现里其实就是抛出异常0x40010006。但是一般这个异常是被忽略的。猜测这个异常被baiducn.dll给接管了,但是它没有处理好。
至于appinit为什么要调用OutputDebugString,这也是一个问题。毕竟Release版本,也没必要接受输出日志。
win7文件扩展名关联打开方式时崩溃(RtlQueryInformationAcl+0x9)的调试
如果一个系统变得不再响应了(也就是说,对于你的键盘和鼠标输入,你没有收到任何响应),鼠标被冻结了,或者你虽然可以移动鼠标,但是系统不再响应鼠标点击事件,那么,该系统称为已经__挂起__(hung)了。以下的一些事情可以导致一个系统被挂起。
一个设备驱动程序没有从它的中断服务(ISR)例程或者延迟调用(DPC)例程中返回。
一个高优先级的实时线程抢占了窗口系统驱动程序的输入线程。
在内核模式中发送了死锁(两个线程或处理器相互之间持有对方想要的资源,而且都不愿意放弃自己的资源所有权)
内核层的同步方式有很多,具体我没弄明白,以下列举一些:
高IRQL的同步
!locks
分析执行体资源来分析挂起案例。至于其它同步模式会不会导致挂起,我尚不知。使用notmyfault的Hang with DPC
功能,即可触发Windows卡死。分析dump,查看每一个cpu的pcr,可以看到有一个cpu的dpc队列里有3个未完成的dpc。
16.kd> !pcr 0
KPCR for Processor 0 at fffff800041f8d00:
Major 1 Minor 1
NtTib.ExceptionList: fffff80000b95000
NtTib.StackBase: fffff80000b96080
NtTib.StackLimit: 00000000002fecd8
NtTib.SubSystemTib: fffff800041f8d00
NtTib.Version: 00000000041f8e80
NtTib.UserPointer: fffff800041f94f0
NtTib.SelfTib: 000007fffffdd000
SelfPcr: 0000000000000000
Prcb: fffff800041f8e80
Irql: 0000000000000000
IRR: 0000000000000000
IDR: 0000000000000000
InterruptMode: 0000000000000000
IDT: 0000000000000000
GDT: 0000000000000000
TSS: 0000000000000000
CurrentThread: fffffa8003669060
NextThread: 0000000000000000
IdleThread: fffff80004206cc0
DpcQueue: 0xfffffa8001f2b670 0xfffff88004fc905c [Normal] i8042prt!I8042MouseIsrDpc
0xfffffa8001f19e10 0xfffff88004fc8390 [Normal] i8042prt!I8042KeyboardIsrDpc
0xfffffa8001f2b508 0xfffff88004fc7544 [Normal] i8042prt!I8042ErrorLogDpc
虚机刚开机展示桌面过程中卡死,Ctrl+alt+del无反应。一般持续30-60min后恢复。非必现,反复重启复现频率大约1/30。
无法呼出winlogon桌面,当作Windows卡死处理。Ctrl+ScrollLock蓝屏,或直接Windows内核调试观测。
Windows内核调试时,输入running
观察每个cpu正在跑的线程,看看是否有某些线程长期占用cpu。结果并无。
输入!locks
分析执行体资源的占用情况。大约半小时后分析得到:
2: kd> !locks
**** DUMP OF ALL RESOURCE OBJECTS ****
KD: Scanning for held locks...
Resource @ 0xfffff8800128efa8 Exclusively owned
Contention Count = 2
Threads: fffffa800c776660-01<*>
KD: Scanning for held locks.........................
Resource @ 0xfffffa800e52c880 Exclusively owned
Contention Count = 67
NumberOfSharedWaiters = 13
Threads: fffffa800f09e540-02<*> fffffa800c89e340-01 fffffa800d6b2b50-01 fffffa800cc76b50-01
fffffa800c8a5060-01 fffffa800cbd0060-01 fffffa800ee2b060-01 fffffa800ec13650-01
fffffa800f241930-01 fffffa800efa3640-01 fffffa800f1fd060-01 fffffa800f71e060-01
fffffa800f515a20-01 fffffa800c776660-01
KD: Scanning for held locks...........................................................................................................
Resource @ 0xfffffa800f175050 Exclusively owned
Contention Count = 8
Threads: fffffa800f09e540-01<*>
KD: Scanning for held locks..........
Resource @ 0xfffffa800f22d2a8 Exclusively owned
Threads: fffffa800f09e540-01<*>
KD: Scanning for held locks.....................................................................................................................
8342 total locks, 4 locks currently held
这里主要观察Exclusively Resource。其中Resource 0xfffffa800e52c880甚为异常,一个线程持有,13个线程等待。用!locks -v [addr]
查看该资源和14个相关线程的明细。
2: kd> !locks -v 0xfffffa800e52c880
Resource @ 0xfffffa800e52c880 Exclusively owned
Contention Count = 67
NumberOfSharedWaiters = 13
Threads: fffffa800f09e540-02<*>
THREAD fffffa800f09e540 Cid 0cd8.0cf0 Teb: 000007fffffd9000 Win32Thread: 0000000000000000 WAIT: (Executive) KernelMode Alertable
fffffa800f703fd8 NotificationEvent
fffffa800f703f98 Semaphore Limit 0x7fffffff
IRP List:
fffffa800ecf1c60: (0006,03a0) Flags: 00060043 Mdl: fffffa800cc7f440
fffffa800cbbea20: (0006,03a0) Flags: 00060901 Mdl: 00000000
Not impersonating
DeviceMap fffff8a000008b30
Owning Process fffffa800f5ef060 Image: vds.exe
Attached Process N/A Image: N/A
Wait Start TickCount 167828 Ticks: 3611 (0:00:00:56.421)
Context Switch Count 234 IdealProcessor: 0
UserTime 00:00:00.000
KernelTime 00:00:00.015
Win32 Start Address 0x000000007700f6f0
Stack Init fffff8800526bfb0 Current fffff8800526b510
Base fffff8800526c000 Limit fffff88005266000 Call 0000000000000000
Priority 14 BasePriority 8 PriorityDecrement 80 IoPriority 2 PagePriority 5
Child-SP RetAddr Call Site
fffff880`0526b550 fffff800`04ec1772 nt!KiSwapContext+0x7a
fffff880`0526b690 fffff800`04ec0c8a nt!KiCommitThreadWait+0x1d2
fffff880`0526b720 fffff800`051583ec nt!KeWaitForMultipleObjects+0x272
fffff880`0526b9e0 fffff880`00c0dde5 nt!FsRtlCancellableWaitForMultipleObjects+0xac
fffff880`0526ba40 00000000`00000000 0xfffff880`00c0dde5
fffffa800c89e340-01
THREAD fffffa800c89e340 Cid 05c8.053c Teb: 000000007eee9000 Win32Thread: 0000000000000000 WAIT: (WrResource) KernelMode Non-Alertable
fffffa800e499840 Semaphore Limit 0x7fffffff
IRP List:
fffffa800c8ab010: (0006,03a0) Flags: 00060000 Mdl: 00000000
fffffa800c8aaa30: (0006,03a0) Flags: 00060000 Mdl: 00000000
fffffa800c8a8010: (0006,03a0) Flags: 00060000 Mdl: 00000000
fffffa800c8a4340: (0006,03a0) Flags: 00060000 Mdl: 00000000
Not impersonating
DeviceMap fffff8a000d516e0
Owning Process fffffa800f04cb10 Image: 360EntClient.exe
...
...
持有线程fffffa800f09e540属于进程vds.exe。它有两个irp。分析其中一个!irp [addr]
2: kd> !irp fffffa800cbbea20
Irp is active with 9 stacks 9 is current (= 0xfffffa800cbbed30)
No Mdl: No System Buffer: Thread fffffa800f09e540: Irp stack trace.
cmd flg cl Device File Completion-Context
[N/A(0), N/A(0)]
0 0 00000000 00000000 00000000-00000000
Args: 00000000 00000000 00000000 00000000
[N/A(0), N/A(0)]
0 0 00000000 00000000 00000000-00000000
Args: 00000000 00000000 00000000 00000000
[N/A(0), N/A(0)]
0 0 00000000 00000000 00000000-00000000
Args: 00000000 00000000 00000000 00000000
[N/A(0), N/A(0)]
0 0 00000000 00000000 00000000-00000000
Args: 00000000 00000000 00000000 00000000
[N/A(0), N/A(0)]
0 0 00000000 00000000 00000000-00000000
Args: 00000000 00000000 00000000 00000000
[N/A(0), N/A(0)]
0 0 00000000 00000000 00000000-00000000
Args: 00000000 00000000 00000000 00000000
[N/A(0), N/A(0)]
0 0 00000000 00000000 00000000-00000000
Args: 00000000 00000000 00000000 00000000
[N/A(0), N/A(0)]
0 0 00000000 00000000 00000000-00000000
Args: 00000000 00000000 00000000 00000000
>[IRP_MJ_READ(3), N/A(0)]
0 0 fffffa800e52c030 fffffa800f59c070 00000000-00000000
\FileSystem\Ntfs
Args: 00000200 00000000 00000000 00000000
未果。
终止vds.exe.kill fffffa800f5ef060
即可立刻恢复正常。这里确实不知vds在等待什么,亦或vds自己的bug,亦或别人的程序不释放资源,使得vds卡死。
vds.exe即Windows服务Virtual Disk。它因为Guesttool的某个diskpart调用而启动。
Windows管理内存原理见《深入解析Windows操作系统》内存管理章节(第4版,第7版)。
在系统初始化时,内存管理器创建了两种类型的动态大小的内存池(或者叫堆),大多数内核模式的组件从这两种内存池中分配系统内存。
非换页池:它是由一些“可保证总是驻留在物理内存中”的虚拟地址范围构成的,由于这些地址范围总是驻留在内存中,因此任何时候从任何IRQL级别都可以访问它们,而不会导致页面错误。
换页池:系统空间中的一段虚拟内存区域,它可以被换入和换出系统。凡是不需要从DPC/Dispatch级别或更高级别上访问内存的设备驱动程序都可以使用换页池。在任何进程环境中他都是可以访问的。
以下试举一例:
虚机Windows7不定期卡死。难手工复现,但3-5天总会出现一例。
故障时,host机查看qemu进程cpu消耗并不高。不像是内核层的死循环问题。windbg分析dump文件,
1: kd> !locks
**** DUMP OF ALL RESOURCE OBJECTS ****
KD: Scanning for held locks..
Resource @ nt!IopDeviceTreeLock (0xfffff80004487340) Shared 1 owning threads
Threads: fffffa8002451640(wininit.exe)-01<*>
KD: Scanning for held locks.
Resource @ nt!PiEngineLock (0xfffff80004487240) Exclusively owned
Contention Count = 8
Threads: fffffa8002451640(wininit.exe)-01<*>
KD: Scanning for held locks..
Resource @ Ntfs!NtfsData (0xfffff880014b6fa8) Exclusively owned
Contention Count = 1
NumberOfExclusiveWaiters = 1
Threads: fffffa8002462040(System)-01<*>
Threads Waiting On Exclusive Access:
fffffa8002479660
KD: Scanning for held locks..
Resource @ 0xfffffa80038e0888 Exclusively owned
Contention Count = 21
NumberOfSharedWaiters = 8
Threads: fffffa8003ab2060(System)-02<*> fffffa80029c7650-01 fffffa8002462040(System)-01 fffffa800320da00(svchost.exe)-01
fffffa80058fbb50-01 fffffa8005129b50-01 fffffa8002c45b50-01 fffffa8005265990-01
fffffa8005dffb50-01
KD: Scanning for held locks..........................................................................
Resource @ 0xfffffa8004eb1460 Exclusively owned
Contention Count = 1
NumberOfExclusiveWaiters = 1
Threads: fffffa800320da00(svchost.exe)-01<*>
Threads Waiting On Exclusive Access:
fffffa80058d7a00
KD: Scanning for held locks.
Resource @ 0xfffffa8004ee0c80 Exclusively owned
Contention Count = 2
NumberOfSharedWaiters = 1
NumberOfExclusiveWaiters = 1
Threads: fffffa800320da00(svchost.exe)-01<*> fffffa8004e93b50-01
Threads Waiting On Exclusive Access:
fffffa8005e95060
KD: Scanning for held locks.............................................................................................................................................................................
Resource @ 0xfffffa80026d1d20 Exclusively owned
Contention Count = 4
Threads: fffffa8003ab2060(System)-434b<*>
Resource @ 0xfffffa80026d1dc8 Exclusively owned
Threads: fffffa8003ab2060(System)-01<*>
KD: Scanning for held locks.............................................................................................................................................................
13012 total locks, 8 locks currently held
Exclusively Resource0xfffffa80038e0888被单个线程fffffa8003ab2060持有,但是8个线程在等待它。感觉比较可疑。查看该线程的栈回溯。
这里是在申请内存池的时候卡住了。先用!memusage
查看物理内存的使用和各个进程的消耗。
1: kd> !memusage
loading PFN database
loading (100% complete)
Compiling memory usage data (99% Complete).
Zeroed: 9 ( 36 kb)
Free: 2 ( 8 kb)
Standby: 4437 ( 17748 kb)
Modified: 839 ( 3356 kb)
ModifiedNoWrite: 135 ( 540 kb)
Active/Valid: 752253 ( 3009012 kb)
Transition: 17266 ( 69064 kb)
SLIST/Temp: 0 ( 0 kb)
Bad: 0 ( 0 kb)
Unknown: 0 ( 0 kb)
TOTAL: 774941 ( 3099764 kb)
Dangling Yes Commit: 17246 ( 68984 kb)
Dangling No Commit: 638353 ( 2553412 kb)
Building kernel map
Finished building kernel map
Scanning PFN database - (100% complete)
Usage Summary (in Kb):
Control Valid Standby Dirty Shared Locked PageTables name
ffffffffffffd 1644 0 0 0 1644 0 AWE
fffffa80023d9b80 0 52 0 0 0 0 mapped_file( Can't read file name buffer at fffff8a007375ec0 )
...
6762c 4 0 0 0 4 0 Page File Section
-------- 204 128 0 ----- ----- 128 session 0 fffff88005745000
-------- 3696 0 0 ----- ----- 260 session 1 fffff8800576d000
-------- 24 0 0 ----- ----- 24 process ( System ) fffffa8002445040
-------- 32 36 0 ----- ----- 68 process ( csrss.exe ) fffffa800244f060
-------- 28 28 0 ----- ----- 48 process ( wininit.exe ) fffffa80024545c0
-------- 4 0 0 ----- ----- 4 process ( iexplore.exe ) fffffa8002625aa0
-------- 4 0 0 ----- ----- 4 process ( iexplore.exe ) fffffa8002a4f060
-------- 20 0 0 ----- ----- 20 process ( LogonUI.exe ) fffffa8002c207b0
-------- 4 0 0 ----- ----- 4 process ( QQExternal.exe ) fffffa8002ff5060
-------- 4 0 0 ----- ----- 4 process ( QQExternal.exe ) fffffa80030a7b00
-------- 32 0 0 ----- ----- 32 process ( LogonUI.exe ) fffffa80031691f0
...
这里物理内存仅剩8kb。再用!poolused
查看内存池的占用,
1: kd> !poolused 4
....
Sorting by Paged Pool Consumed
NonPaged Paged
Tag Allocs Used Allocs Used
fpRD 0 0 21339 3233968128 Compressed file large read buffer , Binary: wof.sys
fprd 0 0 65114 2133655552 Compressed file small read buffer , Binary: wof.sys
CM31 0 0 20994 119910400 Internal Configuration manager allocations , Binary: nt!cm
CM25 0 0 3369 15839232 Internal Configuration manager allocations , Binary: nt!cm
...
1: kd> !poolused 2
....
Sorting by NonPaged Pool Consumed
NonPaged Paged
Tag Allocs Used Allocs Used
FMic 34737 53344128 0 0 IRP_CTRL structure , Binary: fltmgr.sys
Irp 35838 33702832 0 0 Io, IRP packets
Cont 2397 10666272 0 0 Contiguous physical memory allocations for device drivers
Mdl 18614 6753040 0 0 Io, Mdls
MmIn 17255 6625920 0 0 Mm inpaged io structures , Binary: nt!mm
EtwB 101 4259840 2 131072 Etw Buffer , Binary: nt!etw
fpcx 17248 4139520 0 0 Compressed file IO context , Binary: wof.sys
Pool 5 3315280 0 0 Pool tables, etc.
...
可以看到无论时分页还是非分页的池内存,wof.sys占用量都是非常靠前的。
重做镜像,不再引入wof.sys后不再出现此问题。
蓝屏的关键信息一般是一个错误码加上4个参数,我们照Windows文档指南进行排查。
以下举一例
编辑镜像原正常,一旦安装radmin软件,重启后则蓝屏。蓝屏频率90%。
与radmin有关。推测是guesttool安装的驱动和radmin冲突有关。windbg打开dmp后,输入!analyze -v
分析,亦可直接用’.bugcheck’获知蓝屏的错误码和4个参数。
2: kd> .bugcheck
Bugcheck code 0000001E
Arguments ffffffff`c0000096 fffff804`307c9bbe 00000000`00000000 00000000`00000000
windbg里按F1
打开帮助文档,索引
里搜索Bug Check 0x1E
。
Bug Check 0x1E: KMODE_EXCEPTION_NOT_HANDLED
The KMODE_EXCEPTION_NOT_HANDLED bug check has a value of 0x0000001E. This indicates that a kernel-mode program generated an exception which the error handler did not catch.
Important This topic is for programmers. If you are a customer who has received a blue screen error code while using your computer, see Troubleshoot blue screen errors.
KMODE_EXCEPTION_NOT_HANDLED Parameters
The following parameters are displayed on the blue screen.
Parameter Description
1 The exception code that was not handled
2 The address at which the exception occurred
3 Parameter 0 of the exception
4 Parameter 1 of the exception
主要看第二参数,即异常发生的地址。反汇编该地址
2: kd> u fffff804`307c9bbe
nt!KiSaveDebugRegisterState+0x8e:
fffff804`307c9bbe 0f32 rdmsr
...
rdmsr是个很简单的寄存器内数值搬运的动作。蓝屏的原因是因为虚拟化不支持msr寄存器。
64位程序的函数传参,从左至右分别通过rcx,rdx,r8,r9,rsp+20h,rsp+28h…来传参。32位程序较复杂,有stdcall,cdecl,fastcall等。我们用windbg断点时,能分析出当时所调用的实际参数。以下举一例。
通过process monitor监控注册表等手段,猜测某项功能最终调用的是SetDisplayConfig,但是涉及相关概念复杂,API文档不易读懂。
LONG SetDisplayConfig(
UINT32 numPathArrayElements,
DISPLAYCONFIG_PATH_INFO *pathArray,
UINT32 numModeInfoArrayElements,
DISPLAYCONFIG_MODE_INFO *modeInfoArray,
UINT32 flags
);
而且指针数组所指向的多个结构体变量也不易逆向分析。API MONITOR也没有记录此api(api monitor其实可以添加此api)。
windbg菜单处点击file->attach to process,选择目标程序,给SetDisplayConfig下断点。最好带上它所属的动态库名
bp User32!SetDisplayConfig
。函数名大小写严格,动态库名无所谓。
bl
罗列断点,e表示断点有效。bd
命令来disable断点。
0:016> bl
0 e Disable Clear 00007ff8`c7413400 0001 (0001) 0:**** USER32!SetDisplayConfig
而后运行目标程序,直到断点触发。由于这是64位程序,所以直接用r
命令看寄存器即可知前4个参数的值。
0:000> r
rax=0000000000000000 rbx=0000005a7ebfd450 rcx=0000000000000001
rdx=000001cd7eec1e60 rsi=000001cd7a16de10 rdi=000001cd7a16de20
rip=00007ff8c7413400 rsp=0000005a7ebfba18 rbp=0000005a7ebfbb20
r8=0000000000000003 r9=000001cd78f6add0 r10=0000000000000000
r11=0000005a7ebfb7c0 r12=0000005a7ebfcfa4 r13=0000000000000001
r14=0000005a7ebfd450 r15=000001cd7a16dda0
...
参数2和4是结构体数组。以第4个参数为例,结构体的数量为r8即3,三个结构体的地址是分别是1cd78f6add0,1cd78f6add8,1cd78f6ade0。
得知结构体指针后,用dt
命令参看结构体成员。由于事先不知道此结构体DISPLAYCONFIG_MODE_INFO的符号属于哪个pdb,事先需要用.reload /f
载入所有的pdb。再用dt命令参看。
0:000> dt wintypes!DISPLAYCONFIG_MODE_INFO 000001cd`78f6ade0
+0x000 infoType : 0x8d7aaa0 (No matching name)
+0x004 id : 0
+0x008 adapterId : _LUID
+0x010 targetMode : DISPLAYCONFIG_TARGET_MODE
+0x010 sourceMode : DISPLAYCONFIG_SOURCE_MODE
+0x010 desktopImageInfo : DISPLAYCONFIG_DESKTOP_IMAGE_INFO
windbg preview里可以直接点击,查看具体的成员。例如点击adpterId
可以直接查看adapterId字段的具体成员
0:000> dx -r1 (*((wintypes!_LUID *)0x1cd78f6ade8))
(*((wintypes!_LUID *)0x1cd78f6ade8)) [Type: _LUID]
[+0x000] LowPart : 0x107ac [Type: unsigned long]
[+0x004] HighPart : 1 [Type: long]
参数5在栈上,一般是函数入口处的rsp+28h。使用dps rsp
查看栈上数据
0:000> dps rsp
0000005a`7ebfba18 00007ff8`9e66c313 igfxDI!DllUnregisterServer+0x17af3
0000005a`7ebfba20 0000005a`7ebfd450
0000005a`7ebfba28 0000005a`7ebfbb20
0000005a`7ebfba30 000001cd`7a16de10
0000005a`7ebfba38 000001cd`7a16dda0
0000005a`7ebfba40 0000005a`000082a0
0000005a`7ebfba48 0000005a`7ebfba01
...
即参数5存储在0000005a7ebfba40上。由于参数5是UINT32类型,故而使用dd
来查看
0:000> dd 0000005a`7ebfba40
0000005a`7ebfba40 000082a0 0000005a 7ebfba01 0000005a
...
参数5为0x82a0。
如果你预料到某个程序或进程会调用某个函数,或者说会执行某个地址的代码,同时你又希望能从进程的初始开始就能一个不漏的全部捕捉到每一个调用。那么你就需要从进程刚刚创建时暂停,再给每对应的地址下断点。
若是用户态调试时较简单,要么windbg载入exe,用windbg来创建目标进程。要么windbg调试目标进程的父进程,在Events&exceptions
处给Create process on all processes
设置为block。
内核态场景下就比较麻烦,Create process on all processes
在内核态下无效。
父进程一般调用nt!PspInsertProcess
创建子进程。这样我们能在每一个子进程刚刚诞生时中断os系统。
参数一就是子进程的地址。子进程的地址为一个结构体nt!_EPROCESS
,通过dt nt!_EPROCESS
得知其中win7下+2e0
(win10下则是+450)位置就是子进程的image名字。这样我们快速筛选出目标进程。
dt nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x2e0 ProcessLock : _EX_PUSH_LOCK
+0x2e8 UniqueProcessId : Ptr64 Void
+0x2f0 ActiveProcessLinks : _LIST_ENTRY
……
+0x448 ImageFilePointer : Ptr64 _FILE_OBJECT
+0x450 ImageFileName : [15] UChar
+0x45f PriorityClass : UChar
新进程的新线程的主体代码一般从ntdll!RtlUserThreadStart
开始。如果我们需要在目标进程的用户空间下断点,先断点下ntdll!RtlUserThreadStart
。
欲断点在ntdll!RtlUserThreadStart
,需要该进程已经载入ntdll,但是刚开始该进程并未载入。不过Windows内各个进程的ntdll的载入地址都是相同的,所以我们可以直接用其它进程的ntdll!RtlUserThreadStart
地址。
Win7的lsm.exe进程,它的启动时机非常早,比explorer还早。我猜测它会调用ntdll!AlpcSendWaitReceivePort
,我想捕获它每一次调用,那么就得用内核态调试,在进程刚刚创建时暂停,然后给目标进程下断点。
用procmon的enable bootlog法可以知道lsm的父进程是wininit.exe,wininit.exe的父进程是0号session的smss.exe,0号session的smss.exe的父进程是none session的smss.exe。
我们配置可调试的内核,windbg先连上,再开启windows,随后看到windbg暂停了windows,现在先给nt!PspInsertProcess
下断点,并且在断点触发时列出rcx,查看子进程的image名字,查看它是否是lsm.exe
,若不是则系统继续运行:
bp nt!PspInsertProcess "r rcx;r $t1=rcx+2e0;as /ma exename $t1;.block{.echo exename;.if($sicmp(\"${exename}\",\"lsm.exe\")!=0){gc}}";g
期间会遇到一些莫名其妙的暂停,按F5继续执行,直到看到断点触发:
rcx=fffffa8002bc3670
fffffa80`02bc3950 "lsm.exe"
nt!PspInsertProcess:
fffff800`043411f0 4489442418 mov dword ptr [rsp+18h],r8d
这里是lsm.exe的父进程wininit.exe调用PspInsertProcess
如果被调试机是个虚拟机可以在这时候下一个快照。
我们查看一下ntdll.dll的载入地址,可能需要事先强制载入一下ntdll.dll的符号文件。由此可以知道ntdll!RtlUserThreadStart
的地址:
kd> .reload /f /user ntdll.dll
kd> lmvm ntdll
Browse full module list
start end module name
00000000`77c60000 00000000`77e09000 ntdll (pdb symbols) d:\pdb\ntdll.pdb\6192BFDB9F04442995FFCB0BE95172E12\ntdll.pdb
kd> x ntdll!RtlUserThreadStart
00000000`77c8c500 ntdll!RtlUserThreadStart (RtlUserThreadStart)
删除其它断点,给lsm进程的ntdll!RtlUserThreadStart下断点:
bc 0;bp /p fffffa8002bc3670 77c8c500;g
待断点触发后,lsm进程的大部分用户态dll已载入。可以对我们感兴趣的用户态的函数名直接下断点了:
bp /p fffffa8002bc3670 ntdll!NtAlpcSendWaitReceivePort