WinDbg中的.natvis文件和类型模板

在处理二进制数据时,我们经常使用dt命令将字节分组到有意义的字段中,例如。

0:000> dt ntdll!_PEB @$peb
   +0x000 InheritedAddressSpace : 0 ''
   +0x001 ReadImageFileExecOptions : 0 ''
   +0x002 BeingDebugged    : 0x1 ''
   +0x003 BitField         : 0x8 ''
   +0x003 ImageUsesLargePages : 0y0
   +0x003 IsProtectedProcess : 0y0
   +0x003 IsLegacyProcess  : 0y0
   +0x003 IsImageDynamicallyRelocated : 0y1
   +0x003 SkipPatchingUser32Forwarders : 0y0
    ...

当库所有者未在符号文件中提供类型信息时,会出现问题。我们通常只需要在二进制编辑器中手动分解字节(010编辑器有一个很好的模板系统)。如果调试器中也有一些可用的模板系统,不是很好吗?我有个好消息要告诉你:随着WinDbg的最新发布,我们收到了一个非常强大的功能:.natvis文件。甚至有两个碎片整理工具集专门用于此功能:Defrag Tools #138Defrag Tools #139。我们首先分析.natvis文件是如何构建的,以便以后在二进制数据分析中使用它们。

.natvis文件

.natvis文件在Visual Studio中已经使用了一段时间来自定义变量在监视窗口中的显示方式。您可以在%VSINSTALLDIR%\Common7\Packages\Debugger\Visualizers中找到Visual Studio使用的.natvis文件。它们是XML文件,根据%VSINSTALLDIR%\XML\Schemas\1033\natvis.xsd文件中定义的架构构造。您可以在项目中定义自己的.natvis文件,Visual Studio会将它们嵌入到.pdb文件中(此处提供更多信息)。示例.natvis文件可能如下所示:

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="tagRECT">
    <AlternativeType Name="CRect"></AlternativeType>
    <DisplayString>{{LT({left}, {top}) RB({right}, {bottom})  [{right-left} x {bottom-top}]}}</DisplayString>
    <Expand>
        <Item Name="[top]">top,x</Item>
        <Item Name="[right]">right,x</Item>
        <Item Name="[width]">right - left</Item>
        <Item Name="[bottom]">bottom</Item>
        <Item Name="[left]">left</Item>
    </Expand>
  </Type>
</AutoVisualizer>
类型标记的Name属性非常重要–它是类型标识符,并指定此模板将仅用于类型与此字符串匹配的对象。displaystingtag值用于显示对象的单行视图,Item标记表示字段。每个字段是一个C++表达式,它在当前对象的上下文中被评估。在DisplayString中,标记表达式放在大括号中,例如{left}。此外,我们可以通过格式说明符来控制表达式值的显示方式。可以在MSDN上找到可用格式说明符的列表。在我们的示例中,我们对顶部和右侧字段使用十六进制说明符。
要将.natvis文件加载到WinDbg中,可以使用.nvload<path To the file>命令。要卸载它,请使用.nvunload<file name>命令或.nvunload all命令卸载所有.natvis文件。如果希望WinDbg自动加载.natvis文件,请将其放置在调试工具安装目录中的Visualizers文件夹中。dx命令允许您使用.natvis类型定义来转储对象实例的内容。在官方的WinDbg.chm文件中没有这个命令的帮助,所以您只剩下-?切换。为了完成这一段,让我们看一看WinDbg会话的示例,我们将在其中加载上述.natvis文件:
0:000> .nvload c:\temp\windbg-dx\test.natvis
Successfully loaded visualizers in "c:\temp\windbg-dx\test.natvis"
0:000> .nvlist
Loaded NatVis Files:
    c:\temp\windbg-dx\test.natvis
0:000> dx rect
rect             : {LT(1, 2) RB(3, 4)  [2 x 2]} [Type: tagRECT]
    [<Raw View>]
    [top]            : 0x2
    [right]          : 0x3
    [width]          : 2
    [bottom]         : 4
    [left]           : 1

WinDbg中类型模板

在前一段中,我们研究了使用.natvis文件的常用方法。但是,当我们没有可用的私有符号时,原始二进制数据呢?好消息是仍然可以使用dx命令。在下一个示例中,我们将使用以下C#类:

public struct TestClass
{
    public Guid Id { get; set; }
 
    public int Count { get; set; }
 
    public String Name { get; set; }
}

一个非常简单的程序:

public static void Main() {
    var t = new TestClass() {
        Id = Guid.NewGuid(),
        Count = 2,
        Name = "test class"
    };
    Console.ReadLine();
}

让我们在应用程序等待用户输入时中断执行,并使用转储TestClass实例netext扩展名中的!wdo命令:

0:000> !Name2EE Test TestClass
Module:      01263fbc
Assembly:    Test.exe
Token:       02000002
MethodTable: 01264db0
EEClass:     012617b8
Name:        TestClass
0:000> !wdo -mt 01264db0 0x010ff1d0
...
629ae918                                    System.String +0000                    _Name_k__BackingField 032f2754 test class
629b07a0                                     System.Int32 +0004                   _Count_k__BackingField 2 (0n2)
629aba00                                      System.Guid +0008                      _Id_k__BackingField -mt 629ABA00 00000000 {c41e14c4-95fc-402b-8e54-9f2ec1f4865e}

我们现在将尝试使用.natvis文件和dx命令模拟上述输出。我们的.natvis文件如下:

<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="T1">
    <DisplayString>CLR string</DisplayString>
    <Expand>
      <ArrayItems>
        <Size>*((int *)(this + 4))</Size>
        <ValuePointer>(NvWchar *)(this + 8)</ValuePointer>
      </ArrayItems>
    </Expand>
  </Type>
  <Type Name="T0">
    <Expand>
        <Item Name="Id">*((NvGuid *)(this + 8))</Item>
        <Item Name="Count">*((int *)(this + 4))</Item>
        <Item Name="Name">*((T1 *)(*(int *)this))</Item>
    </Expand>
  </Type>
</AutoVisualizer>

不要害怕字段定义中*的数量,因为我们没有任何依赖的符号,我们需要处理指针。我们的基本指针总是这样。为了使求值器工作,我们总是需要指定期望输出的类型。例如,Count字段位于TestClass实例的偏移量4处。因此,我们首先向这个地址添加4个字节,将地址强制转换为int*,然后在对其值感兴趣时解除对它的引用,从而得到表达式*((int*)(this+4)。CLR字符串稍微复杂一些,但规则是相同的。我最不需要解释的是类型名。您可能已经注意到模板中使用的那些奇怪的T0、T1和T2类型名称以及NvWchar和NvGuid。dx命令只能对其具有符号的类型进行操作。因此,如果我们在.natvis文件中创建一个完全虚构的类型并尝试向其强制转换内存地址,那么dx命令将不起作用。这里提供了一个NatvisTypes库的帮助,我在这里为您定义了一些模拟类型:T0、T1、T2、…、T9。另外还有像NvGuid和NvWchar这样的类型(我计划在将来添加其他类型)。源代码提交给与lld扩展相同的repo:https://github.com/lowleveldesign/lldext,可以在发布页面上找到二进制文件。但有一个问题:我们需要将NatvisTypes.dll加载到进程中。有人来帮忙!injectdll命令,我已经用lld WinDbg扩展名发布了该命令。Nv*类型的可视化工具在项目中定义,并自动添加到NatvisTypes.pdb文件中。WinDbg足够友好,可以用.pdb文件加载可视化工具。让我们看看示例TestClass实例的调试器输出是什么样子的:

0:000> .load lld
0:000> !injectdll d:\dev\src\lldext\Win32\Debug\NatvisTypes.dll
0:000> .nvload c:\temp\TestClass.natvis
Successfully loaded visualizers in "c:\temp\TestClass.natvis"
0:000> ld NatvisTypes
*** WARNING: Unable to verify checksum for d:\dev\src\lldext\Win32\Debug\NatvisTypes.dll
Symbols loaded for NatvisTypes
0:000> dx *((T0 *)0x010ff1d0)
*((T0 *)0x010ff1d0) :  [Type: T0]
    [<Raw View>]
    Id               : 0xc41e14c4-0x95fc-0x402b-0x8e0x54-0x9f0x2e0xc10xf40x860x5e [Type: NvGuid]
    Count            : 2
    Name             : CLR string [Type: T1]
0:000> dx -r1 (*((NatvisTypes!T1 *)0x32f2754))
(*((NatvisTypes!T1 *)0x32f2754)) : CLR string [Type: T1]
    [<Raw View>]
    [0]              : 116 't' [Type: NvWchar]
    [1]              : 101 'e' [Type: NvWchar]
    [2]              : 115 's' [Type: NvWchar]
    [3]              : 116 't' [Type: NvWchar]
    [4]              : 32 ' ' [Type: NvWchar]
    [5]              : 99 'c' [Type: NvWchar]
    [6]              : 108 'l' [Type: NvWchar]
    [7]              : 97 'a' [Type: NvWchar]
    [8]              : 115 's' [Type: NvWchar]
    [9]              : 115 's' [Type: NvWchar]

我知道这个例子不是最好的,但是请注意,我们已经将原始二进制数据转换为有意义的数据。我还没有谈到死后调试的问题。无法将DLL插入转储。当您需要分析转储时,必须使用具有符号但通常不会使用的任何类型,例如:

0:000> dt ntdll!*
          ...
          ntdll!_ALTERNATIVE_ARCHITECTURE_TYPE
          ntdll!_KUSER_SHARED_DATA
          ntdll!_TP_POOL
          ntdll!_TP_CLEANUP_GROUP
          ntdll!_ACTIVATION_CONTEXT
          ntdll!_TP_CALLBACK_INSTANCE

您可以在.natvis文件中覆盖它们,然后投射内存。很乏味,但我还没有找到更好的办法。最后,如果您还没有对dx命令印象深刻,请查看WinDbg会话中dx调试器调用的输出。

posted on 2020-03-09 10:30  活着的虫子  阅读(868)  评论(0编辑  收藏  举报

导航