• 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%Common7PackagesDebuggerVisualizers中找到Visual Studio使用的.natvis文件。它们是XML文件,根据%VSINSTALLDIR%XMLSchemas1033 atvis.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:	empwindbg-dx	est.natvis
    Successfully loaded visualizers in "c:	empwindbg-dx	est.natvis"
    0:000> .nvlist
    Loaded NatVis Files:
        c:	empwindbg-dx	est.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:devsrclldextWin32DebugNatvisTypes.dll
    0:000> .nvload c:	empTestClass.natvis
    Successfully loaded visualizers in "c:	empTestClass.natvis"
    0:000> ld NatvisTypes
    *** WARNING: Unable to verify checksum for d:devsrclldextWin32DebugNatvisTypes.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调试器调用的输出。

  • 相关阅读:
    《Java技术》第一次作业
    链队列基本操作
    行编辑器,数制转换,杨辉三角
    顺序队列基本操作
    链栈基本操作
    顺序栈基本操作
    PTA链表
    PTA顺序表
    《Java技术》第三次作业
    《Java技术》第二次作业
  • 原文地址:https://www.cnblogs.com/yilang/p/12432873.html
Copyright © 2020-2023  润新知