前言
Yarden Shafir 分享了两篇非常通俗易懂的,关于 windbg
新引入的调试数据模型的文章。链接如下:
part1
:https://medium.com/@yardenshafir2/windbg-the-fun-way-part-1-2e4978791f9b
part2
:https://medium.com/@yardenshafir2/windbg-the-fun-way-part-2-7a904cba5435
本文是第一部分的译文。在有道词典、必应词典、谷歌翻译的大力帮助下完成,感谢以上翻译工具,我只是一个搬运工。强烈建议英文好的朋友阅读原文,因为在翻译的过程中不可避免的按我的理解做了调整。
说明:
翻译已征得原作者同意。
translation-permission
鸽了太久了,对不住原作者
标题实在想不到更好的翻译了
作者的 github
以下是译文!
不久前,WinDbg
添加了对新调试数据模型(debugger data model)的支持,这一变化彻底改变了我们使用 WinDbg
的方式。不再有可怕的 MASM
命令和晦涩的语法。无需再将地址或参数复制到记事本以便在后续命令中使用它们。不用再一遍又一遍地运行相同的命令(传递不同的地址)来遍历列表或数组。
这是该指南的第一部分,因为我认为实际上不会有人从头到尾读完长达 8000
字的 WinDbg
命令解释。所以,我准备了 2
篇 4000
字的文章!这样会好些,对吧?
在第一篇文章中,我们将学习如何使用这个新数据模型的基础知识 —— 使用自定义寄存器和新的内置寄存器,遍历对象,使用匿名类型来搜索、过滤、自定义对象。最后,我们将学习如何以一种比以前更好、更简单的方式解析数组和列表。
在这两篇文章中,我们将学习这个数据模型为我们提供的更复杂、更高级的方法和特性。现在我们都知道将会发生什么,准备好一杯咖啡,让我们开始吧!
这个数据模型,可以在 WinDbg
中通过 dx
命令使用,是一个极其强大的工具,能够定义自定义变量、结构体、函数并使用很多其它新功能。它还允许我们使用 LINQ
(一种建立在 SQL
数据库语言之上的自然查询语言)进行查询和过滤信息。
这个数据模型已经被文档化了,甚至在 GitHub
上还有使用范例。此外,所有模块都有对应的文档,可以在调试器中通过 dx -v <method>
查看。(您也可以通过运行不带 -v
的 dx <method>
命令获得相同的文档 ) :
dx -v Debugger.Utility.Collections.FromListEntry
Debugger.Utility.Collections.FromListEntry [FromListEntry(ListEntry, [<ModuleName | ModuleObject>], TypeName, FieldExpression) — Method which converts a LIST_ENTRY specified by the ‘ListEntry’ parameter of types whose name is specified by the string ‘TypeName’ and whose embedded links within that type are accessed via an expression specified by the string ‘FieldExpression’ into a collection object. If an optional module name or object is specified, the type name is looked up in the context of such module]
除此之外,还有一些外部文档,但我觉得有些事情需要进一步解释,并且这个特性值得更多的关注。
译注:
debugger data model
文档链接:https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/data-model-cpp-overview)使用范例
github
链接:https://github.com/microsoft/WinDbg-Samples)
自定义寄存器(Custom Registers)
首先,NatVis
允许我们添加自定义寄存器。有点像 MASM
中的 @$t1
、@$t2
、@$t3
等。只是,现在你可以起任何想要的名字,并且可以指定类型:
dx @$myString = "My String"
dx @$myInt = 123
我们可以使用 dx @$vars
来查看所有变量,使用 dx @$vars.Remove("var name")
来移除某个变量,或者使用 @$vars.Clear()
清除所有变量。我们还可以使用 dx
来处理更复杂的结构,比如 EPROCESS
。您可能知道,公共调试符号(public PDBs)中的符号不包含类型信息。使用旧调试器,这并不总是一个问题,因为在 MASM
中,反正也没有类型,我们可以使用 poi
命令对指针进行解引用。
0: kd> dt nt!_EPROCESS poi(nt!PsInitialSystemProcess)
+0x000 Pcb : _KPROCESS
+0x2e0 ProcessLock : _EX_PUSH_LOCK
+0x2e8 UniqueProcessId : (null)
...
但当变量不是指针时,事情就变得更混乱了,比如 PsIdleProcess
:
0: kd> dt nt!_KPROCESS @@masm(nt!PsIdleProcess)
+0x000 Header : _DISPATCHER_HEADER
+0x018 ProfileListHead : _LIST_ENTRY [ 0x00000048`0411017e - 0x00000000`00000004 ]
+0x028 DirectoryTableBase : 0xffffb10b`79f08010
+0x030 ThreadListHead : _LIST_ENTRY [ 0x00001388`00000000 - 0xfffff801`1b401000 ]
+0x040 ProcessLock : 0
+0x044 ProcessTimerDelay : 0
+0x048 DeepFreezeStartTime : 0xffffe880`00000000
...
我们必须先使用显式的 MASM
操作符来获取 PsIdleProcess
的地址,然后将它当作 EPROCESS
显示出来。通过使用 dx
,我们可以更聪明地直接使用 C
风格的类型转换对符号进行强制转换。但是当我们尝试把 nt!Psinitialsystemprocess
转换为一个指向 EPROCESS
的指针:
dx @$systemProc = (nt!_EPROCESS*)nt!PsInitialSystemProcess
Error: No type (or void) for object at Address 0xfffff8074ef843a0
我们得到了一个错误。
就像我提到的那样,符号没有类型。我们不能转换没有类型的东西。所以我们需要获取符号的地址,并转换成一个指向我们想要的类型的指针(在当前示例中,PsInitialSystemProcess
已经是一个指向 EPROCESS
的指针,所以我们需要将其地址转换为一个指向 EPROCESS
指针的指针)。
dx @$systemProc = *(nt!_EPROCESS**)&nt!PsInitialSystemProcess
现在,我们有了一个有类型的变量,我们可以像在 C
中那样访问它的字段:
0: kd> dx @$systemProc->ImageFileName
@$systemProc->ImageFileName [Type: unsigned char [15]]
[0] : 0x53 [Type: unsigned char]
[1] : 0x79 [Type: unsigned char]
[2] : 0x73 [Type: unsigned char]
[3] : 0x74 [Type: unsigned char]
[4] : 0x65 [Type: unsigned char]
[5]