扩展PowerDbg自动化调试过程

本文涉及的产品
语种识别,语种识别 100万字符
文本翻译,文本翻译 100万字符
文档翻译,文档翻译 1千页
简介:

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


本文转自 donjuan 博客园博客,原文链接: http://www.cnblogs.com/killmyday/archive/2010/05/25/1743509.html  ,如需转载请自行联系原作者


相关文章
|
Web App开发 JavaScript 前端开发
Python 自动化 - 浏览器chrome打开F12开发者工具自动Paused in debugger调试导致无法查看网站资源问题原因及解决方法,javascript反调试问题处理实例演示
Python 自动化 - 浏览器chrome打开F12开发者工具自动Paused in debugger调试导致无法查看网站资源问题原因及解决方法,javascript反调试问题处理实例演示
715 0
Python 自动化 - 浏览器chrome打开F12开发者工具自动Paused in debugger调试导致无法查看网站资源问题原因及解决方法,javascript反调试问题处理实例演示
|
2月前
|
测试技术 C# 图形学
掌握Unity调试与测试的终极指南:从内置调试工具到自动化测试框架,全方位保障游戏品质不踩坑,打造流畅游戏体验的必备技能大揭秘!
【9月更文挑战第1天】在开发游戏时,Unity 引擎让创意变为现实。但软件开发中难免遇到 Bug,若不解决,将严重影响用户体验。调试与测试成为确保游戏质量的最后一道防线。本文介绍如何利用 Unity 的调试工具高效排查问题,并通过 Profiler 分析性能瓶颈。此外,Unity Test Framework 支持自动化测试,提高开发效率。结合单元测试与集成测试,确保游戏逻辑正确无误。对于在线游戏,还需进行压力测试以验证服务器稳定性。总之,调试与测试贯穿游戏开发全流程,确保最终作品既好玩又稳定。
79 4
|
Web App开发 存储
Python+selenium 自动化-操作已启用的chrome浏览器实例演示,chrome启用调试端口方法
Python+selenium 自动化-操作已启用的chrome浏览器实例演示,chrome启用调试端口方法
574 0
Python+selenium 自动化-操作已启用的chrome浏览器实例演示,chrome启用调试端口方法
|
28天前
|
机器学习/深度学习 人工智能 运维
构建高效运维体系:从自动化到智能化的演进
本文探讨了如何通过自动化和智能化手段,提升IT运维效率与质量。首先介绍了自动化在简化操作、减少错误中的作用;然后阐述了智能化技术如AI在预测故障、优化资源中的应用;最后讨论了如何构建一个既自动化又智能的运维体系,以实现高效、稳定和安全的IT环境。
52 4
|
19天前
|
运维 Linux Apache
,自动化运维成为现代IT基础设施的关键部分。Puppet是一款强大的自动化运维工具
【10月更文挑战第7天】随着云计算和容器化技术的发展,自动化运维成为现代IT基础设施的关键部分。Puppet是一款强大的自动化运维工具,通过定义资源状态和关系,确保系统始终处于期望配置状态。本文介绍Puppet的基本概念、安装配置及使用示例,帮助读者快速掌握Puppet,实现高效自动化运维。
42 4
|
19天前
|
运维 jenkins 持续交付
自动化部署的魅力:如何用Jenkins和Docker简化运维工作
【10月更文挑战第7天】在现代软件开发周期中,快速且高效的部署是至关重要的。本文将引导你理解如何使用Jenkins和Docker实现自动化部署,从而简化运维流程。我们将从基础概念开始,逐步深入到实战操作,让你轻松掌握这一强大的工具组合。通过这篇文章,你将学会如何利用这些工具来提升你的工作效率,并减少人为错误的可能性。
|
24天前
|
运维 Prometheus 监控
运维中的自动化实践每月一次的系统维护曾经是许多企业的噩梦。不仅因为停机时间长,更因为手动操作容易出错。然而,随着自动化工具的引入,这一切正在悄然改变。本文将探讨自动化在IT运维中的重要性及其具体应用。
在当今信息技术飞速发展的时代,企业对系统的稳定性和效率要求越来越高。传统的手动运维方式已经无法满足现代企业的需求。自动化技术的引入不仅提高了运维效率,还显著降低了出错风险。本文通过几个实际案例,展示了自动化在IT运维中的具体应用,包括自动化部署、监控告警和故障排除等方面,旨在为读者提供一些实用的参考。
|
25天前
|
机器学习/深度学习 数据采集 运维
智能化运维:机器学习在故障预测和自动化响应中的应用
【10月更文挑战第1天】智能化运维:机器学习在故障预测和自动化响应中的应用
61 3