[转载]扩展PowerDbg自动化调试过程

[转载]扩展PowerDbg自动化调试过程 – Killmyday – 博客园.

在前面的文章使用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

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏