扩展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

posted @ 2010-05-25 12:59  donjuan  阅读(2013)  评论(2编辑  收藏  举报