看完前一节,你可能会说,所谓的DUMP分析毫无技术含量啊,直接一个 !analyze -v就搞定了啊。不错,第一条输入的命令一般都是!analyze -v,但不代表着自动分析可以解决所有的问题。有时候,!analyze -v出来的结果会将你带入歧途,如果你不抱着怀疑态度的话。本节的示例依然是前一节中的,但是我们不自动保存DUMP,而是在程序崩溃之后,从任务管理器中手动保存。操作很简单,我就不演示了。

我们将保存的DUMP载入Windbg中:


同样的,Windbg给出了Minidump的类型,可以看到,任务管理器创建的是Full Memory类型的内存转储。

接下来,拿出杀手锏 !analyze -v,得到以下结果:



什么鬼?异常代码不应该是C0000005吗?而且出错的模块都不对。这是因为Windbg所处的模式不对,我们的应用程序是32位的,而使用64位的任务管理器进行DUMP,Windbg将其当作了64位程序。用 .effmach 查看当前Windbg模式:


上一节中处于的是x86模式,读者可以自行查看。可以通过 .effmach x86或 !wow64exts.sw 命令切换Windbg的模式,我们将其切换到32位下,然后再执行 !analyze -v 命令:


这次好像是对了,可是异常代码依旧是 8000003 :


而且异常地址,异常类型都不是程序实际发生的啊。输入 kb 命令查看一下函数调用栈:


从调用栈中我们可以清晰的看到异常处理的过程。同时,可以定位异常发生的位置(即图中画圈的位置)。不知道由于什么原因,Windbg没有解析出来0x24ef2c的符号。不过没有关系,我们可以手动来看一下,输入命令 dd 0x24ef2c,查看栈上的内容:


你是否发现了熟悉的内容,实际上,栈上保存了一个_EXCEPTION_RECORD结构,从中可以知道,异常代码为c0000005,异常发生的地址为 009831d。为什么?这个_EXCEPTION_RECORD结构实际上是KiUserExceptionDispatcher的参数,KiUserExceptionDispatcher函数是用户空间异常处理的入口,异常发生时,CPU进入内核空间,然后再返回用户空间,返回时就进入KiUserExceptionDispatcher,_EXCEPTION_RECORD结构也是内核放置在栈上的。实际上,你仔细看KiUserExceptionDispatcher的参数,也能够发现:


知道了异常发生的地址,我们反汇编该地址即可:


连源代码的位置也给了出来。

回过头来,使用任务管理器采集的DUMP已经过了异常最初发生的点,而进入了默认异常处理函数的操作中了,通过 !analyze -v将只能看到最近的异常代码 80000003(断点,断点异常会发生两次,first chance和second chance),因此,我们要自己在函数栈中找到异常发生的实际位置。这也说明了通过代码来自动获取DUMP文件的好处。