zoukankan      html  css  js  c++  java
  • 扩展PowerDbg自动化调试过程

    在前面的文章使用PowerDbg自动化Windbg调试过程里,简单介绍了如何使用PowerDbg自动化一些Windbg的命令。但问题是,PowerDbg自身提供的命令太少了,幸好PowerDbg提供了源代码,可以让我们了解它是如何工作的,因此我也有机会自己扩展PowerDbg

    上次在用Windbg调试一个问题的时候,需要查看一个Dictionary对象里面所有的值,用来确认有些特殊的值是否被正确的加入到Dictionary对象里。本来是用Visual Studio调试这个问题的,但是后面发现Visual Studio实在是太慢了—其实我调试的程序就是Visual Studio本身,只不过用另外一个Visual Studio调试它而已。在没有符号文件和源代码的情况下,在Visual Studio里面设置一个托管代码的函数断点的速度的确很慢。不得已,只好切换到Windbg,但是同时就没有了Visual Studio强大的变量显示的功能(Visualizer)。

    Windbg里面,如果要查看Dictionary对象的值,一般是通过下面几个命令实现的:

    #  1. 查看Dictionary对象本身的值,找到保存元素的槽(bucket)。

     

    0:000> !do 018e2e0c

    Name: System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.String, mscorlib]], mscorlib]]

    MethodTable: 002b201c

    EEClass: 62e00e18

    Size: 52(0x34) bytes

     (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

    Fields:

          MT    Field   Offset                 Type VT     Attr    Value Name

    6304aa5c 40009af        4       System.Int32[] 0 instance 018e30e0 buckets

    00000000 40009b0        8              SZARRAY 0 instance 018e30f8 entries

    6304ab0c 40009b1       20         System.Int32 1 instance        2 count

    6304ab0c 40009b2       24         System.Int32 1 instance        2 version

    6304ab0c 40009b3       28         System.Int32 1 instance       -1 freeList

    6304ab0c 40009b4       2c         System.Int32 1 instance        0 freeCount

    00000000 40009b5        c                       0 instance 018e2e7c comparer

    00000000 40009b6       10                      0 instance 00000000 keys

    00000000 40009b7       14                       0 instance 00000000 values

    630484dc 40009b8       18        System.Object 0 instance 00000000 _syncRoot

    6302f3d0 40009b9       1c ...SerializationInfo 0 instance 00000000 m_siInfo

    #  2.  使用!DumpArray打印数组并且显示每个元素的详细信息。

    0:000> !da -details 018e30f8

    Name: System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.String, mscorlib]], mscorlib]][]

    MethodTable: 002b23e8

    EEClass: 002b2368

    Size: 60(0x3c) bytes

    Array: Rank 1, Number of elements 3, Type VALUETYPE

    Element Methodtable: 002b2318

    #  每个元素的详细信息

    [0] 018e3100

        Name: System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.String, mscorlib]], mscorlib]]

        MethodTable 002b2318

        EEClass: 62e00f5c

        Size: 24(0x18) bytes

         (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

        Fields:

              MT    Field   Offset                 Type VT     Attr    Value Name

        6304ab0c 40009ba        8         System.Int32 1 instance 1497890914 hashCode

    6304ab0c 40009bb        c         System.Int32 1 instance       -1 next

    #  找到键值对以及他们的地址

        63048530 40009bc        0       System.__Canon 0 instance 018e2c08 key

        63048530 40009bd        4       System.__Canon 0 instance 018e2e88 value

    [1] 018e3110

        Name: System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.String, mscorlib]], mscorlib]]

        MethodTable 002b2318

        EEClass: 62e00f5c

        Size: 24(0x18) bytes

         (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

        Fields:

              MT    Field   Offset                 Type VT     Attr    Value Name

        6304ab0c 40009ba        8         System.Int32 1 instance 1306722402 hashCode

        6304ab0c 40009bb        c         System.Int32 1 instance       -1 next

        63048530 40009bc        0       System.__Canon 0 instance 018e2dbc key

        63048530 40009bd        4       System.__Canon 0 instance 018e3134 value

    [2] 018e3120

        Name: System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.String, mscorlib]], mscorlib]]

        MethodTable 002b2318

        EEClass: 62e00f5c

        Size: 24(0x18) bytes

         (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

        Fields:

              MT    Field   Offset                 Type VT     Attr    Value Name

        6304ab0c 40009ba        8         System.Int32 1 instance        0 hashCode

        6304ab0c 40009bb        c         System.Int32 1 instance        0 next

        63048530 40009bc        0       System.__Canon 0 instance 00000000 key

    63048530 40009bd        4       System.__Canon 0 instance 00000000 value

    #  3.  查看键值对的详细信息,这一步可以通过!DumpObject命令完成。

    0:000> !do 018e2c08

    Name: System.String

    MethodTable: 630488c0

    EEClass: 62e0a498

    Size: 26(0x1a) bytes

     (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

    String: key1

    Fields:

          MT    Field   Offset                 Type VT     Attr    Value Name

    6304ab0c 4000096        4         System.Int32 1 instance        5 m_arrayLength

    6304ab0c 4000097        8         System.Int32 1 instance        4 m_stringLength

    630495a0 4000098        c          System.Char 1 instance       6b m_firstChar

    630488c0 4000099       10        System.String 0   shared   static Empty

        >> Domain:Value 003829d0:018e1198 <<

    630494f0 400009a       14        System.Char[] 0   shared   static WhitespaceChars

    >> Domain:Value 003829d0:018e1924 <<

    #  4.  针对Dictionary对象的每一个元素,重复第二步和第三步。

     

    从上表里面可以看出,在Windbg里面查看一个Dictionary对象的确不是一件轻松的事情,特别是在Dictionary对象的Value参数也是一个 Dictionary对象的时候,那就更痛苦了。

    我当时调试那个问题的时候,就是使用!DumpArray!DumpObject以及DU(用来显示字符串)命令手工遍历一个40个元素的Dictionary对象。分析完那一个问题以后,我决定再也不做类似的事情了!

    因此我用PowerShell结合PowerDbg已有的命令写了下面一个脚本,用来自动递归打印Dictionary对象里面所有的元素(里面有必要的注释),如果要使用这个脚本,只需要把下面的代码合并到PowerDbg的源代码里面就好了。如果你连合并都懒得做,没关系,下面的链接是已经合并好的代码:

     /Files/killmyday/ParseDumpDic.zip

    #

    # 这个函数就是入口函数,你需要提供一个Dictionary对象的地址,这个地址你需要自己去查看Windbg+SOS

    # 的输出才能找到,它只支持x86平台,如果需要支持x64平台,那你需要自己修改一下下面的脚本

    #

    function Parse-PowerDbgDUMPDIC([string] $address = $(throw "Error! You must provide the address of Dictionaryo object."))

    {

        set-psdebug -strict

        $ErrorActionPreference = "stop"

    trap {"Error message: $_"}

     

    # 根据Dictionary对象的地址组合一个SOS命令(!DumpArray),因为Dictionary对象保存键值对数组

    # 的属性距离Dictionary对象的地址有8个字节的位置(如果是64位系统,就是16个字节)。

    #  

    # poi命令是Windbg用来获取一个指针指向的内存的内容。这是因为Dictionary对象的地址加上8个字节

    # 的偏移量,只是获取了键值对数组的属性的地址,而不是键值对数组的地址。

    $cmd = "!da -details poi(0x{0:x8}+8)" -f $address

    # 保存最后格式化的结果,创建的是一个.NETStringBuilder对象

        $builder = New-Object System.Text.StringBuilder

        $builder.AppendLine("key,value")

       

        Invoke-WindbgDUMPARR $cmd $builder 0

        return $builder.ToString()

    }

     

    function Invoke-WindbgDUMPARR([string] $cmd = $(throw "Error! You must provide windbg !DumpArray command to invoke."),

                                  [System.Text.StringBuilder] $builder = $(throw "Error! You must provide buffer for result."),

                                  [int] $level)

    {

       

    # 将格式化好的Windbg命令发送到windbg远程调试服务器中执行

    Invoke-WinDbgCommand $cmd

    # 执行完毕以后,$global:g_commandOutput全局变量保存了Windbg的输出

    # 注意,Invoke-WinDbgCommand只会将上一次命令的输出保存在这个变量里面

    # 至于它是如何做到的,你可以阅读Invoke-WinDbgCommand的源代码

        $stringReader = [System.IO.StringReader] $global:g_commandOutput

       

    # 一行行处理Windbg输出,使用正则表达式提取出我们需要的信息,例如

    # 变量类型,变量地址甚至是变量的值

        while(($line = $stringReader.ReadLine()) -ne $null)

    {

    # 递归处理键值对数组里面的每一个键值对

            if ($line -match "^\s*\[\d+\]\s+(?<addr>[0-9a-fA-F]+)$")

            {

         # 获取键值对的地址,后面我们可以用!DumpObject命令来处理它

                $addr = $matches["addr"]

                

                $line = $stringReader.ReadLine();

                if ( $line -eq $null )

                {

                    throw "Errors! There is error in Windbg output. Expect more output for dictionary entry."

                }

               

    # 获取键值对的类型,因为我期望这个程序可以处理尽量多的从Dictionary<,>派生出来

    # 的类型。

                if ( $line -match "Name:\s+(?<type>(.+))" )

                {

                    Parse-PowerDbgDictionary $addr $matches["type"] $builder $level

                }

            }

        }

    }

     

    # 这个命令模板用来打印键值对里面的键(Key)的值,注意,它只处理字符串类型

    # 如果指定的键值对地址是一个空值(NULL),则什么都不做。因为Dictionary对象采取

    # ArrayList相似的动态扩展内存的逻辑,键值对数组并不一定都是满的

    $global:g_WindbgViewKeyCmd = "j poi(0x{0:8})=0 ;du poi(0x{0:8})+c"

    # 这个命令模板用来打印键值对里面的值(Value)的值,注意,它只处理字符串类型

    $global:g_WindbgViewValueCmd = "j poi(0x{0:8})=0 ;du poi(0x{0:8}+4)+c"

    # 如果Dictionary对象的值类型不是字符串(String)类型的话,而是另外一个Dictionary对象的话

    # 下面这个命令模板用来递归处理这个Dictionary对象

    $global:g_WindbgDumpArrCmd = "j poi(0x{0:8})=0 ;!da -details poi(poi(0x{0:8}+4)+8)"

    function Parse-PowerDbgDictionary(

        [string] $objAddr = $(throw "Error! You must provide the address of Dictionaryo object."),   

        [string] $typeName = $(throw "Error! you must provide full type name of Dictionary entry."),

        [System.Text.StringBuilder] $builder = $(throw "Error! You must provide buffer for result."),

        [int] $level)

    {

        # 根据键值对的类型获取键(Key)的类型和值(Value)的类型

        $result = Parse-PowerDbgDictionaryEntry $typeName

        $keyType = $result[0]

        $valueType = $result[1]

       

        # 只处理键(Key)类型为字符串的情况

        if ( [String]::Compare($keyType, 0, "System.String", 0, "System.String".Length) -eq 0 )

        {

             $cmd = $global:g_WindbgViewKeyCmd -f $objAddr

             Invoke-WinDbgCommand $cmd

            

             if ( $global:g_commandOutput -match "[0-9A-Za-z]{8}\s+""(?<text>.+)""\s*$" )

             {

                 $builder.AppendLine("")

                 for ( [int]$i = 0; $i -lt $level; $i++ )

                 {

                      $builder.Append(" ,")

                 }

                

                 $builder.Append($matches["text"])

             }        

    }

     

        # 如果值(Value)类型为字符串的话,打印出它的值

        if ( [String]::Compare($valueType, 0, "System.String", 0, "System.String".Length) -eq 0 )

        {

             $cmd = $global:g_WindbgViewValueCmd -f $objAddr

             Invoke-WinDbgCommand $cmd

            

             if ( $global:g_commandOutput -match "[0-9A-Za-z]{8}\s+""(?<text>.+)""\s*$" )

             {

                 $builder.Append(" = ")            

                 $builder.Append($matches["text"])

             }        

    }

    # 看看值(Value)类型是不是另外一个Dictionary对象

        elseif ([String]::Compare($valueType, 0, "System.Collections.Generic.Dictionary", 0, "System.Collections.Generic.Dictionary".Length) -eq 0)

        {

             $cmd = $global:g_WindbgDumpArrCmd -f $objAddr

             $level = $level + 1

         # 是的话,递归处理这个Dictionary对象,然后再打印下一个键值对

             Invoke-WindbgDUMPARR $cmd $builder $level

        }

        else

        {

             throw "Value type {0} is not supported." -f $valueType

        }

    }

     

    $global:g_WindbgGenericDicName = "System.Collections.Generic.Dictionary"

     

    function Parse-PowerDbgDictionaryEntry(

        [string] $typeName = $(throw "Error! you must provide full type name of Dictionary entry."))

    {

        if([String]::Compare($typeName, 0, $global:g_WindbgGenericDicName, 0, $global:g_WindbgGenericDicName.Length) -ne 0)

        {

            throw "Error! Just Dictionary or generic Dictionary are supported."

        }

       

        $typeName = $typeName -replace "\[\]", ""

        if ( $typeName -match "^[^\[\]]*(((?'Open'\[)(?<key>[^\[\]]*))+((?'Close-Open'\])[^\[\]]*)+)*$" )

        {

            $keyType = $matches["key"]        

            $valueType = $matches["Close"]

            # skip [$keyType], and get the result

            $valueType = $valueType.SubString($keyType.Length + 3)

            $valueType = $valueType.SubString(1, $valueType.Length - 2)

           

            # output the type of DictionaryEntry.Key

            $keyType

            # output the type of DictionaryEntry.Value

            $valueType

        }

        else

        {

            throw "Error! Parenthese in input DictionaryEntry's type name are not balanced." 

        }

    }

     

    # 将里面的函数导出,这样可以在PowerShell里面使用下面这几个函数

    Export-ModuleMember -Function Parse-PowerDbgDUMPDIC

    Export-ModuleMember -Function Parse-PowerDbgDictionaryEntry

    Export-ModuleMember -Function Invoke-WindbgDUMPARR

    Export-ModuleMember -Function Parse-PowerDbgDictionary

     

    下面是一个输出的例子 脚本里面有一个Bug,不知道为什么那个Capacity等东西被PowerShell打印出来了,但是不影响我的使用,就放在那里没理它了):

    > $result = Parse-PowerDbgDUMPDIC "018e2e0c"

    > $result

     

                             Capacity                      MaxCapacity                           Length

                             --------                      -----------                           ------

                                  512                       2147483647                              275

                                 

    key,value

     

    key1

     ,subkey1 = value1

     ,subkey2 = value2

     ,subkey3 = value3

     ,subkey4 = value4

     ,subkey5 = value5

     ,subkey6 = value6

    key2

     ,subkey-1 = value-1

     ,subkey-2 = value-2

     ,subkey-3 = value-3

     ,subkey-4 = value-4

     ,subkey-5 = value-5

     ,subkey-6 = value-6

  • 相关阅读:
    算法题(1): 如何不用加号,求两整数之和
    使用 SSH key 和 ssh-agent
    数据序列化格式-YAML
    Golang 编码规范
    拦路虎-- vscode 与 golang
    Golang 基础 12 -- 基础小结
    Golang 基础 11 -- 并发 concurrency
    Golang 基础 09 -- 接口 interface
    Golang 基础 10 -- 反射 reflection
    根据第三方提供的图片或pdf的url进行预览
  • 原文地址:https://www.cnblogs.com/killmyday/p/1743509.html
Copyright © 2011-2022 走看看