系统会遇到随机错误检查,内存损坏。有趣的是,腐败有一个非常特殊的模式——它看起来像是一个PFN地址,在这个过程中,标志被随机地放置在页面表页面的几个地方。内存管理器永远不会做这种事情,我怀疑驱动程序正在编辑用户页表页,这是不应该做的。

让我们看看堆栈:

kd>kb*** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr Args to Child
f15b1308
80523096 c00862d8 10c5b000 00000000 nt!MiDeletePte+0x198f15b13d080519776 000001d8 10d20fff 00000000 nt!MiDeleteVirtualAddresses+0x164f15b13ec 805b1d74 10c20000 10d20fff f15b14a4 nt!MiDeleteFreeVm+0x20f15b148c 8054060c ffffffff 049c6aa8 049c6ab0 nt!NtFreeVirtualMemory+0x42ef15b148c 7c90eb94 ffffffff 049c6aa8 049c6ab0 nt!KiFastCallEntry+0xfc03e4a398 7c90da54 7c8209b3 ffffffff 049c6aa8 ntdll!KiFastSystemCallRet
03e4a39c 7c8209b3 ffffffff 049c6aa8 049c6ab0 ntdll
!NtFreeVirtualMemory+0xc

最近要求我在升级过程中跟踪一个问题。问题归结为在安装过程中捕获打开特定注册表服务项的Microsoft组件。像这样的问题经常需要实时调试来实时捕获注册表访问。我本可以在RegOpenKeyExW()上设置一个断点,并检查传递到函数中的每个请求的键,但是考虑到RegOpenKeyExW()是一个使用率很高的代码路径,所以这个方法非常耗时。此方法包括在函数上设置断点,等待插入,检查第二个参数(lpSubKey)是否与所需的注册表项(string)匹配,如果该项不匹配,则单击“g”。在找到包含我所需密钥的调用之前,我可以重复这些步骤100次。我想找个办法,只有在我的Key被碰过的时候才会设置“中断访问”。我们遇到了一个类似的问题,需要在可能有数百个文件正在使用时捕获文件系统函数处理特定文件。那么,如何实现这种类型的调试所需的自动化呢?答案是调试器脚本。

让我们来看看Windows的一个例子资源管理器使用这种方法。在这个场景中,我们将尝试捕捉资源管理器正在打开HKEY U LOCAL U MACHINE\SYSTEM\Setup键。Windows资源管理器每秒打开多个键,因此重点是在将此特定键传递给RegOpenKeyEx()时“插入”,而不必手动遍历传递给函数的数百个可能的键。

1、确定有问题的函数

根据MSDN,RegOpenKeyEx定义如下,我们对第二个参数感兴趣,因为它包含键的名称。

最近,我处理了一个问题,在系统帐户下安装32位Office时没有发生注册表反射。这需要同时研究32位Office代码和实现注册表反射的Wow64代码。使用32位调试器附加到Wow64进程时,就像在32位计算机上调试32位进程一样;进程中没有64位二进制文件。但是,我们需要调试Wow64来调试注册表反射代码。为此,我们使用64位调试器附加到Wow64进程,它允许您查看Wow64二进制文件。

lm

Base TimeStamp Module
400000 42435b2a Mar 24 18:28:26 2005C:\Program Files (x86)\Internet Explorer\IEXPLORE.EXE

77ec0000 45d6cc72 Feb
17 03:35:46 2007C:\WINDOWS\system32\ntdll.dll

6b000000 45d6943d Feb
16 23:35:57 2007C:\WINDOWS\system32\wow64.dll

6b280000 45d695f3 Feb
16 23:43:15 2007C:\WINDOWS\system32\wow64win.dll

78b80000 42438b7a Mar
24 21:54:34 2005 C:\WINDOWS\system32\wow64cpu.dll

我们很多人都认为调试器中的一些简单命令是理所当然的,这些命令可以让生活变得轻松。我想了几个特别能搭配的。第一个命令是kn。Kn将显示当前调用堆栈,并在单个调用的最左边包含堆栈帧编号。
我在Windbg中运行BadWindow.EXE文件,通过~0s将我的线程设置为线程0。然后我只需键入kn。这将转储调用堆栈,如您所见,在最左边的每个调用都有堆栈帧号。

 

使用上面堆栈中的堆栈帧2,让我们看看它使用的局部变量。首先,我们必须使用.frame和frame number获取上下文。

 

 

 

现在要转储局部变量,我们可以使用dv(Display Local Variables)命令。(请注意,您需要为正在调试的二进制文件使用专用符号才能使其正常工作)。

 

如你所见,我们这里有几个当地人。现在,因为我有这个二进制的私有符号,所以调试器已经理解了每个变量的结构和数据类型。
此外,因为我已经在堆栈帧上下文中,所以我可以简单地dt(display type)加上变量的名称,调试器就可以算出地址。让我们dt msg看看它包含什么数据。

 

如你所见,有几个结构是这个结构的一部分。请看上面输出的右边。您将注意到右侧的数据类型名称HWND_和tagPOINT。这些都是结构。好消息是dt将递归地工作,并将在当前结构下转储结构。您只需发出DT–r。您可以通过在–r后面附加一个数字来指定深度。

 

说到现场调试,断点是最重要的。通常,在生产环境中解决一个非常复杂的问题需要在本地、非生产环境中调试我自己的一台测试机器。我通常会调试有问题的进程或代码,以便更好地了解它是如何工作的,以及在我进入时需要收集哪些数据。这通常涉及一些逆向工程,并对相关流程或模块进行代码审查。我们总是希望在进行实时调试时尽量减少对生产环境的影响。我称之为设计完美的断点。

我们通常使用几种类型的断点。

最常见的断点是bp,在windbg中设置它很简单:bp 0xaddress。请注意,当您进入用户模式进程时,它会停止进程的执行。当您进入内核调试器时,它会停止整个机器。在这种情况下,我们想知道一旦断点命中,我们将发出什么命令。然后,可以设置断点,以便在插入时自动发出命令。

在遇到断点时运行命令的语法很简单:bp 0xaddress “comand1;command2;…”。例如-bp 0xMYADDRESS “kv;dd g_myGlobal;g” 此命令将在0xMYADDRESS上中断,通过kv转储调用堆栈;将内存转储为从g_MyGlobal地址开始的dword;然后使用g命令继续执行。