分类 调试 下的文章

好的,假如正在调试一个.NET应用程序。您需要找出从访问数据库的线程执行的查询和存储过程,但是您不知道如何。好消息!这已经不是问题了!此脚本显示与SQLCommand或OracleCommand对象关联的所有查询或存储过程。此外,您可以单击它以获取更多详细信息和使用它的线程。。
以下是截图:

 Source code for GET_SQLCOMMAND.TXT:

$

$$

$$ =============================================================================

$$ It shows the SQL commands from a .NET application. It gives you detailed information

$$ and the threads using the query/stored procedure you selected.

$$ Useful for Oracle and SQL Server.

$$

$$ Compatibility: Win32.

$$

$$ Usage: Use $$>< to run the program.

$$

$$ Requirements: Public symbols.

$$

$$ If necessary change the filename below to include your path and filename.

$$ By default it uses the WinDbg path and the default file name is GET_SQLCOMMAND.TXT

$$

$$ Roberto Alexis Farah

$$ Blog: http://blogs.msdn.com/debuggingtoolbox/

$$

$$ All my scripts are provided "AS IS" with no warranties, and confer no rights.

$$ =============================================================================

$$

ad /q *

r @$t0 = 0

r @$t1 = 0

.printf /D "<b>\nClick on the queries/stored procedures below to get more details and to find out the threads using it.\n\n</b>"

.block

{

    .block

    {

        as ${/v:ScriptName} MYSCRIPTS\\GET_SQLCOMMAND.TXT

    }

    .block

    {

        as SQLCommand .block

        {

            !DumpObj poi(@$t0+0x10)

            !DumpObj @$t0

            !GCRoot @$t0

        }

    }

    .block

    {

        as OracleCommand .block

        {

            !DumpObj poi(@$t0+0x14)

            !DumpObj @$t0

            !GCRoot @$t0

        }

    }

}

.foreach(obj {!dumpheap -short -type System.Data.SqlClient.SqlCommand } )

{

    r @$t1 = 1

    .printf /D "<link cmd=\"r @$t0 = ${obj}; ${SQLCommand} ;ad ${/v:ScriptName}; $$><${ScriptName}\"><b>%mu</b></link>\n\n", poi(${obj}+0x10)+0xc

}

.foreach(obj {!dumpheap -short -type System.Data.OracleClient.OracleCommand } )

{

    r @$t1 = 1

    .printf /D "<link cmd=\"r @$t0 = ${obj}; ${OracleCommand} ;ad ${/v:ScriptName}; $$><${ScriptName}\"><b>%mu</b></link>\n\n", poi(${obj}+0x14)+0xc

}

.if(0 = @$t1)

{

    .printf /D "<b>\nNo SQL commands found.\n</b>"

}

我们希望在WinDbg中自动显示、搜索和过滤std::map对象。std::vectors的脚本相对简单,因为vectors中数据的平面结构;map是更复杂的野兽。
具体地说,Visual C++ STL中的映射是作为红黑树实现的。每个树节点都有三个重要的指针:左指针、右指针和父指针。此外,每个节点都有一个Myval字段,其中包含std::对以及节点表示的键和值。
迭代树结构需要递归,WinDbg脚本没有任何语法来定义函数。但是,我们可以递归地调用脚本——允许脚本包含$$>a<命令,该命令使用不同的参数集再次调用脚本。脚本的路径也可以在${$arg0}中找到。
当递归调用脚本时,伪寄存器的值(如$t0)将被递归调用破坏。当我偶然发现.push和.pop命令时,我正处于动态分配内存或调用shell进程来存储和加载变量的边缘,这两个命令分别存储和加载寄存器上下文。这些是递归WinDbg脚本必须的。
好,假设您想显示std::map<int,point>中键小于或等于2的值。

0:000> $$>a< traverse_map.script my_map -c ".block { .if (@@(@$t9.first) <= 2) { .echo —-; ?? @$t9.second } }"

size = 10
—-
struct point
   +0x000 x                : 0n1
   +0x004 y                : 0n2
   +0x008 data             : extra_data
—-
struct point
   +0x000 x                : 0n0
   +0x004 y                : 0n1
   +0x008 data             : extra_data
—-
struct point
   +0x000 x                : 0n2
   +0x004 y                : 0n3
   +0x008 data             : extra_data

对于每个pair(存储在$t9伪寄存器中),块检查第一个组件是否小于或等于2,如果小于或等于2,则输出第二个组件。
接下来是剧本。注意,它比我们对向量的处理要复杂得多,因为它本质上是用一组不同的参数调用自己,然后递归地重复。

.if ($sicmp("${$arg1}", "-n") == 0) {
    .if (@@(@$t0->_Isnil) == 0) {
        .if (@$t2 == 1) {
            .printf /D "<exec cmd=\"db %p L10\">%p</exec>\n", @$t0, @$t0
            .printf "key = "
            ?? @$t0->_Myval.first
            .printf "value = "
            ?? @$t0->_Myval.second
        } .else {
            r? $t9 = @$t0->_Myval
            command
        }
    }

    $$ Recurse into _Left, _Right unless they point to the root of the tree
    .if (@@(@$t0->_Left) != @@(@$t1)) {
        .push /r /q
        r? $t0 = @$t0->_Left
        $$>a< ${$arg0} -n
        .pop /r /q
    }
    .if (@@(@$t0->_Right) != @@(@$t1)) {
        .push /r /q
        r? $t0 = @$t0->_Right
        $$>a< ${$arg0} -n
        .pop /r /q
    }
} .else {
    r? $t0 = ${$arg1}

    .if (${/d:$arg2}) {
        .if ($sicmp("${$arg2}", "-c") == 0) {
            r $t2 = 0
            aS ${/v:command} "${$arg3}"
        }
    } .else {
        r $t2 = 1
        aS ${/v:command} " "
    }

    .printf "size = %d\n", @@(@$t0._Mysize) 
    
    r? $t0 = @$t0._Myhead->_Parent
    r? $t1 = @$t0->_Parent

    $$>a< ${$arg0} -n

    ad command
}

特别值得注意的是,as命令配置了一个别名,然后递归调用使用该别名为映射的每个元素调用一个命令块;比较字符串的$sicmp函数;以及输出DML块的.printf/D函数。最后,当_Left或_Right等于树的根时,递归终止(在本例中就是这样实现树的)。

Windbg有很多命令和命令变体,因此有时您可能会忘记其中的一些命令,比如当您需要挖掘调用堆栈以提取更多信息时。或者您可能记得获取调用堆栈详细信息的命令,但您需要确保获得整个堆栈。或者,您可能需要一种快速挖掘堆栈的方法,首先从帧中获取信息,然后从局部变量获取信息,而无需花费太多时间键入命令或使用箭头键。
如果你在下面场景中看到你自己需要的,这个脚本就是为你准备的!

此脚本使您能够从调用堆栈快速获取以下信息:

  • ANSI字符串。
  • Unicode字符串。
  • 符号。
  • 指针引用。
  • 帧局部变量。(需要专用符号)

界面非常简单,可能不太漂亮。下面是两个截图,让您了解:

 

 

 

 

 

Source code - DIG_STACK.TXT:

$$

$$ =============================================================================

$$ Dig information from the current call stack:

$$ - Unicode Strings

$$ - ANSI Strings

$$ - Symbols

$$ - Pointer references

$$ - Local variables by frames

$$

$$ Compatibility: Win32, should work on Win64.

$$

$$ Usage: $$>< to run the script.

$$

$$ If necessary change the filename below to include your path and filename.

$$ By default it uses the WinDbg path and the default file name is DIG_STACK.TXT

$$

$$ Roberto Alexis Farah

$$ Blog: http://blogs.msdn.com/debuggingtoolbox/

$$

$$ All my scripts are provided "AS IS" with no warranties, and confer no rights.

$$ =============================================================================

$$

.block

{

as ${/v:ScriptName} MYSCRIPTS\\DIG_STACK.txt

}

.block

{

.printf /D "<link cmd=\"dpu @$csp poi(@$teb+0x4);ad ${/v:ScriptName}; $$><${ScriptName}\"><b>Unicode Strings</b></link>\n\n"

.printf /D "<link cmd=\"dpa @$csp poi(@$teb+0x4);ad ${/v:ScriptName}; $$><${ScriptName}\"><b>ANSI Strings</b></link>\n\n"

.printf /D "<link cmd=\"dps @$csp poi(@$teb+0x4);ad ${/v:ScriptName}; $$><${ScriptName}\"><b>Symbols</b></link>\n\n"

.printf /D "<link cmd=\"dpp @$csp poi(@$teb+0x4);ad ${/v:ScriptName}; $$><${ScriptName}\"><b>Pointer References</b></link>\n\n"

.printf /D "<link cmd=\"kpM 2000;ad ${/v:ScriptName}; $$><${ScriptName}\"><b>Local Variables by Frames</b></link>\n"

}

$$ ===========================================================================

WinDbg从来都不擅长可视化。尽管Visual Studio一直都有autoexp.dat,而且最近还出现了本机调试器可视化工具,但WinDbg用户不得不满足于转储内存区域和搜索内存来识别模式。
另一方面,如果希望简化调试过程,Visual Studio目前没有提供任何自动化机会。从Visual Studio 2012开始,不能再编写宏。WinDbg继续提供脚本支持,因此可以自动执行遇到的任何日常调试任务。如果通过了初始学习曲线并掌握了WinDbg脚本,几乎可以保证比在Visual Studio中手动遍历对象的同事获得更好更快的结果。
有一些不错的在线教程可以指导了解WinDbg脚本的基本知识,而且内置文档(debugger.chm)也有一些不错的场景。在这篇文章中,我想写一个简单的脚本,它搜索std::vector实例中匹配特定条件的对象,然后显示它们。
下面是如何使用这个脚本。假设有一个点类型的复杂对象向量。反过来,点类型有一个字段数据,该字段有一个类型为int的名为z的字段。正在查找其data.z字段等于0的点。如下:

 

0:000> $$>a< traverse_vector.script my_points ".block { .if (@@(@$t0->data.z) == 0) { ?? @$t0 } }"

struct point * 0x0129e4d0
   +0x000 x                : 0n0
   +0x004 y                : 0n0
   +0x008 data             : extra_data

脚本将$t0伪寄存器传递给向量的每个元素的命令块。在上面的命令块中,if语句将提供的表达式作为C++表达式求值(@ @运算符强制C++表达式求值器),如果它是真的,则使用“??”运算符来显示当前元素。这个??运算符只显示一个C++表达式。

以下是脚本和一些注释:

$$ save pointer to first element (current element)
r? $t0=${$arg1}._Myfirst

$$ save pointer to last element
r? $t1=${$arg1}._Mylast

$$ save first forever
r? $t2=@$t0

.while (@$t0 != @$t1)
{
    .if (${/d:$arg2} == 0) {
        $$ display element, no command provided
        .printf "index %d, address %p\n", @@(@$t0 – @$t2), @$t2
        ?? @$t0
    } .else {
        ${$arg2}
    }

    $$ advance current element
    r? $t0=@$t0 + 1
}

最初的挑战是如何处理所有的向量元素。这依赖于向量有Myfirst和Mylast成员这一事实。完成后,可以对向量内容运行任意命令、打印元素、筛选元素、将它们存储在文件中,等等。

如果您像我一样,您可能会忘记需要查找某些特定信息的类和名称空间。或者你忘了要查找的字段名。例如当我需要从System.Data.SqlClient命名空间查找信息。
此脚本从连接检索特定信息。扩展它也很容易,因此可以使用相同的方法从其他名称空间和字段中提取信息。
就个人而言,我更喜欢基于DML(调试标记语言)的方法,因为我可以使用超链接和默认脚本调用:$$><。DML方法需要更多的工作和更低级的编程,但是可以提供更好的结果。

需要使用$$<而不是$$><。

 

Source code for CONNECTIONS_POOL.TXT:

$$

$$ =============================================================================

$$ Verify Connections from Pool.

$$

$$ Compatilibity: Win32, should work on Win64.

$$

$$ Attention! For .Net Framework 2.0, edit the script and remove the clr10\\ from it so it can use the

$$ sos.dll version 2.0

$$

$$ Usage: $$< to run the script. (note: Just $$< not $$><)

$$

$$ Requirements: Public symbols.

$$

$$ Roberto Alexis Farah


$$ All my scripts are provided "AS IS" with no warranties, and confer no rights.

$$ =============================================================================

$$

.shell -i - -ci ".foreach ( obj {!clr10\\sos.dumpheap -type System.Data.SqlClient.SqlInternalConnection -short} ) {!do ${obj} }" FIND "_fInPool"

.shell -i - -ci ".foreach ( obj {!clr10\\sos.dumpheap -type System.Data.SqlClient.SqlConnectionPoolControl -short} ) {!do ${obj} }" FIND "_fResetConnection"

.shell -i - -ci ".foreach ( obj {!clr10\\sos.dumpheap -type System.Data.SqlClient.SqlConnectionPoolControl -short} ) {!do ${obj} }" FIND "_maxPool"

$$

$$ Number of Connection Objects

$$ ============================

$$

!clr10\\sos.dumpheap -type System.Data.OleDb.OleDbConnection -stat

$$

$$ ===================================================================