Windbg介绍

文章目录

  • 1. 介绍
    • 1.1. 相关网站
    • 1.2. 下载
    • 1.3. 符号配置
  • 2. 常用命令
    • 2.1. 常用的标准命令
    • 2.2. 常用的`.`命令有
    • 2.3. 常用的`!`命令有
  • 3. 调试环境搭建
    • 3.1. 实时调试进程
    • 3.2. 调试进程dump
    • 3.3. 调试内核dump
    • 3.4. 配置可调试的内核
    • 3.5. 远程调试进程
    • 3.6. 调试.net进程或dump
  • 4 函数调用约定与栈回溯
    • 4.1 函数调用约定
      • 4.1.1 stdcall
        • 特征
        • 常见适用范围
      • 4.1.2 cdecl
        • 特征
        • 常见适用范围
      • 4.1.3 fastcall
        • 特征
        • 常见适用范围
      • 4.1.4 thiscall
        • 特征
        • 常见适用范围
    • 4.2 栈帧和寄存器
      • 4.2.1 32位程序
      • 4.2.2 64位程序
    • 4.3 栈回溯
      • 示例
      • 4.3.1 cdecl一例
      • 4.3.2 stdcall一例
      • 4.3.3 fastcall一例
      • 4.3.4 64位程序的fastcall
  • 5 崩溃卡死等调试示例
    • 5.1 死锁导致的应用卡死
      • 故障现象
      • 分析
    • 5.2 应用卡死——陷入内核不返回的应用卡死
    • 5.3. 调试进程崩溃dump
      • 4.3.1 简单异常
        • 4.3.1.1 demo
        • 5.3.1.2 explorer无尽崩溃一例
          • 故障现象
          • 分析
        • 5.3.1.3 win7文件扩展名关联打开方式时崩溃(RtlQueryInformationAcl+0x9)的调试
    • 5.4 windows卡死
      • 5.4.1 原理
      • 5.4.2 为什么通常用ctrl+alt+del判定hang
      • 5.4.3 案例 没有从延迟调用(DPC)例程中返回
      • 5.4.4 案例 许多线程被某个执行体资源卡住
        • 故障现象
        • 分析
        • 解法
      • 5.4.5 案例 疑似内存占用高导致的Windows卡死
        • 故障现象
        • 分析
    • 5.5 蓝屏
      • 故障现象
      • 分析
    • 6 用Windbg逆向api的实际入参
  • 7 内核态调试时,在进程刚刚创建时暂停
    • 背景
    • 原理
    • 举例

1. 介绍

1.1. 相关网站

微软网站
社区网站
调试分享

1.2. 下载

对于Win10,推荐使用微软商店下载windbg preview版本。其它的os,可以下载windows sdk,在安装选项中选择windbg。
preview版本皆可调试32,64位进程和内核。传统版本分为32和64两个版本,但优势在小巧便携。这里有现成的32和64windbg下载。

1.3. 符号配置

微软的动态库,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

之类的提示,那一般就是网络不通,没法下载

2. 常用命令

windbg的命令分为标准命令.命令和**!命令**。!命令实际上windbg的扩展插件里引入的,只不过有时候略写了插件名。
例如!kdexts.locks!ntsdexts.locks是不同的命令。在windbg目录里搜索kdext.dllntsdexts.dll能直接找到。
windbg里按下F1,即可检索各个命令,查看详情与示例

2.1. 常用的标准命令

命令 作用
d 查看内存数据
k 查看栈回溯
u 将地址反汇编为汇编代码
r 查看寄存器数据
~ 切换线程(用户态)
dt 查看结构体的定义
b 下断点
lmf 查看各个模块的文件路径和符号载入
dt 查看结构体的定义
x 查看符号的地址

2.2. 常用的.命令有

命令 作用
.reload 载入pdb
.load 载入扩展插件
.process 切换到进程(内核态)
.thread 切换到线程(内核态)
.detach 脱离调试目标
.kill 关闭进程

2.3. 常用的!命令有

命令 作用
!process 罗列进程(内核态)
!running 展示cpu当前运行的线程(内核态)
!analyze 自动分析当前的异常和bugcheck
!handle 逆向handle
!fileobj 逆向文件对象
!locks 分析CrticalSection(用户态)或执行体资源(内核态)
!poolused 展示内存池消耗(内核态)
!memusage 展示物理内存的统计信息(内核态)

3. 调试环境搭建

3.1. 实时调试进程

File->attach to process
如果使用传统版本windbg,注意调试32位进程使用32位windbg,64的则用64的。

3.2. 调试进程dump

进程dump分两种。一种是手工抓的,例如任务管理器里右击进程条目创建dump。另一种是崩溃后,windows帮我们抓的崩溃现场。简易的做法是用管理权限执行
procdump -i -ma c:\windows\temp
那么进程崩溃后,会在此目录下发现2个dmp文件。
windbg的file->Open dump file选择dmp文件打开。

3.3. 调试内核dump

内核dump分为mini,core,full三种。在windows蓝屏后,即生成。minidump通常位于C:\windows\minidump\,后两者则通常是C:\windows\memory.dmp文件。coredmp只包含内核层的数据。
coredump和fulldump需要事先增加一定的windows虚拟内存,否则可能会生成失败,参见《深入解析Windows操作系统》崩溃转储分析章节。在磁盘分区较满时也会在开机时自动删除,可通过注册表配置解决。
对于能预测到windows内核层卡死的情况,可事先配置注册表,卡死按下
右Ctrl+双击scrolllock,手工触发蓝屏。再从dump中来分析卡死当时的内核。
本小节配置参见蓝屏设置.zip。

3.4. 配置可调试的内核

对于复杂的内核卡死的现象,还可以配置实时调试环境。以下仅述winxp,win7,win10在idv,vdi的内核调试环境的搭建。
被调试机一般称为server机,windbg所在机称为client机。
通用的方法是通过串口调试。host端给虚机windows提供模拟串口设备,windows配置以支持由串口调试。随后host将串口设备和socket listen端口相连接。调试机通过tcp连接上那个socket端口。调试机里创建模拟2个串口,1个连接socket端口,一个被windbg连接。如下图所示:
Windbg介绍_第1张图片
具体做法如下:
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刻意如此设计。但是,对于使用者来说十分不便。甚至引发客户误报故障。

3.5. 远程调试进程

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。

3.6. 调试.net进程或dump

首先许多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。

4 函数调用约定与栈回溯

4.1 函数调用约定

4.1.1 stdcall

特征

  • 参数从右向左一次压入堆栈
  • 由被调用函数自己来恢复堆栈
  • 比较容易从栈上找回函数实际参数

常见适用范围

  • Windows的api的调用方式多数是WINAPI,他就是 __stdcall的宏定义
  • 在64位程序中较少见

4.1.2 cdecl

特征

  • 参数从右向左依次压入堆栈
  • 由调用者恢复堆栈
  • 比较容易从栈上找回函数实际参数

常见适用范围

  • C语言标准库的函数默认此法
  • printf这类不定参数数量的只能使用此法
  • 是visual studio默认的调用方式

4.1.3 fastcall

特征

  • 优先从寄存器传入(ECX和EDX),剩下的参数再从右向左从栈传入
  • 64位程序中,在函数调用前,前6个参数从左到右,分别通过rcx,rdx,r8,r9,rsp+20h,rsp+28h入参
  • 32位程序中,在函数调用前,前3个参数从左到右,分别通过ecx,edx,push入参
  • 一般很难栈上找回函数实际参数

常见适用范围

  • 在64位程序中常见

4.1.4 thiscall

特征

  • 由ecx或rcx传递this指针

常见适用范围

  • 关于C++类的成员函数一种调用方式

4.2 栈帧和寄存器

栈空间的增长实际上表现为栈顶寄存器esp(32位程序)或rsp(64)的减少,例如push指令,sub esp指令都会使得栈空间增长。
在编译阶段,就已经生成代码,让局部变量存放在栈上。
当执行完毕call指令时,会使栈顶增加一格(32位程序是8字节,64位的是16字节),把函数的ret地址存入之。
函数的执行体内,最后一句一般是ret。实际功能就是跳转到之前的ret地址,并且把栈削减一格。

4.2.1 32位程序

函数体内的开头通常是

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 当前函数的入参二

4.2.2 64位程序

据我所知,除了thiscall都是stdcall。rbp没有栈帧的作用。
stdcall的函数体内:rsp+28h表示第5个参数。如果函数没有被编译器优化,rsp+8,rsp+10h,rsp+18h,rsp+20h表示从左起1~4的参数。
大多数情况都是有优化的,rsp+8这些不一定代表参数。

4.3 栈回溯

示例

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.swkv

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,也就不知道那些函数名。

4.3.1 cdecl一例

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调用约定

4.3.2 stdcall一例

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调用约定

4.3.3 fastcall一例

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。

4.3.4 64位程序的fastcall

将该代码编译成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!fgetccall 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传参。

5 崩溃卡死等调试示例

5.1 死锁导致的应用卡死

故障现象

反复关开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。可能对分析根因有帮助。

5.2 应用卡死——陷入内核不返回的应用卡死

参见《格蠹汇编》第8章
参见《某医院影像客户端卡死分析》

5.3. 调试进程崩溃dump

进程崩溃一般都是由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。

4.3.1 简单异常

4.3.1.1 demo

试写一个访问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。

5.3.1.2 explorer无尽崩溃一例

故障现象

旧镜像正常。编辑镜像,升级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版本,也没必要接受输出日志。

5.3.1.3 win7文件扩展名关联打开方式时崩溃(RtlQueryInformationAcl+0x9)的调试

win7文件扩展名关联打开方式时崩溃(RtlQueryInformationAcl+0x9)的调试

5.4 windows卡死

5.4.1 原理

如果一个系统变得不再响应了(也就是说,对于你的键盘和鼠标输入,你没有收到任何响应),鼠标被冻结了,或者你虽然可以移动鼠标,但是系统不再响应鼠标点击事件,那么,该系统称为已经__挂起__(hung)了。以下的一些事情可以导致一个系统被挂起。

  • 一个设备驱动程序没有从它的中断服务(ISR)例程或者延迟调用(DPC)例程中返回。

  • 一个高优先级的实时线程抢占了窗口系统驱动程序的输入线程。

  • 在内核模式中发送了死锁(两个线程或处理器相互之间持有对方想要的资源,而且都不愿意放弃自己的资源所有权)

内核层的同步方式有很多,具体我没弄明白,以下列举一些:
高IRQL的同步

  • 自旋锁
  • 排队的自选锁(queued spinlock)
    低IRQL的同步
  • 内核分发器对象
  • 带键的事件(keyed event)
  • 快速互斥体(fast mutex)和守护互斥体
  • 执行体资源
  • 推锁
  • 临界区
  • 条件变量
  • Slim读写锁
    我们通常用windbg的!locks分析执行体资源来分析挂起案例。至于其它同步模式会不会导致挂起,我尚不知。

5.4.2 为什么通常用ctrl+alt+del判定hang

  • 鼠标点击无反应,可能是explorer或者某个应用程序卡死
  • 模拟usb场景的无法判定鼠标能否移动
  • 没有应用程序能截取ctrl+alt+del组合,也无法阻止winlogon接收此组合。win32k.sys保留了ctrl+alt+delete组合,因此,每单windows输入系统(实现与win32k中的原始输入线程)看到此组合时,它给winlogon的消息服务器发送一个RPC消息,该消息服务器监听这样的通知。凡是被映射到一个已登记热键的键击都只被发送到登记改键击的那个进程,而不再发送至任何其他的进程。

5.4.3 案例 没有从延迟调用(DPC)例程中返回

使用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

5.4.4 案例 许多线程被某个执行体资源卡住

故障现象

虚机刚开机展示桌面过程中卡死,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调用而启动。

5.4.5 案例 疑似内存占用高导致的Windows卡死

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个线程在等待它。感觉比较可疑。查看该线程的栈回溯。
Windbg介绍_第2张图片
这里是在申请内存池的时候卡住了。先用!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后不再出现此问题。

5.5 蓝屏

蓝屏的关键信息一般是一个错误码加上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寄存器。

6 用Windbg逆向api的实际入参

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。

7 内核态调试时,在进程刚刚创建时暂停

背景

如果你预料到某个程序或进程会调用某个函数,或者说会执行某个地址的代码,同时你又希望能从进程的初始开始就能一个不漏的全部捕捉到每一个调用。那么你就需要从进程刚刚创建时暂停,再给每对应的地址下断点。

若是用户态调试时较简单,要么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

你可能感兴趣的:(Windows,逆向,windows,内核,windbg)