NetRoc's Blog

N-Tech

 

WinDbg 文档翻译----15

cc682/NetRoc

http://netroc682.spaces.live.com/

 

使用调试器命令程序

本节包含下面的主题:

调试器命令程序的组成

流程控制符

调试器命令程序的执行

调试器命令程序示例

调试器命令程序概述

调试器命令程序是由调试器命令和如.if.for.while.这样的流程控制符组成的小程序。 (流程控制符和语法的完整列表,查看流程控制符。)

可以用大括号( { } )将大的命令块中的声明块括起来。输入每个块的时候,块中的所有别名都会被解析。如果改变了命令块中别名的值,之后除了次级块中的命令之外的命令都不会使用新的别名值。

不能用一对大括号来创建块。必须在{之前加上流程控制符。如果要创建只用来展开别名的块,应该在{之前使用.block 标记。

调试器命令程序可以使用自定义别名和固定别名作为它的本地变量。如果要使用数值或类型变量,可以使用$tn 伪寄存器

自定义别名仅在不和其他文本连接时会被替代。如果要展开和其他文本连接的别名,使用${ }  (Alias Interpreter)标记。该标记有一些选项开关可以以不同方式展开别名。

使用双美元符号 ($$  (Comment Specifier))来为调试器命令程序添加注释。不能在标记和它的成员之间插入注释(例如大括号或条件) 。

注意  不能使用星号 (*  (Comment Line Specifier))。 由于星号指定的注释不是以分号结尾的,所以程序的其余部分都会被忽略掉。

一般来说在调试器命令程序中都使用MASM语法。如果要使用C++的内容(例如指定结构或类成员),可以用@@c++( ) 标记来切换到C++ 语法。

MASM语法中的$scmp$sicmp$spat 字符串操作符非常有用。这些操作符的更多信息,查看MASM数字和操作符

流程控制符

可以使用流程控制符 来在调试器命令程序中创建条件执行和循环语句。

除了下面一些例外之外,流程控制符和C/C++中的控制符行为都是相似的:

  • 即使只有一条命令,所有条件执行和循环也必须用大括号括起来。例如,不能省略下面命令中的大括号。

    0:000> .if (ebx>0) { r ebx }

  • 所有条件都必须是一个表达式。不能使用命令作为条件。例如下面的例子会出现语法错误。

    0:000> .while (r ebx) { .... }

  • 反的大括号(})之前的最后一条命令不需要用分号结束。

调试器命令程序中支持以下流程控制符。关于每个标记的语法的详细信息,查看他们各自的参考信息。

  • .if 标记类似C中的if关键字。
  • .else标记类似C中的else关键字。
  • .elsif标记类似C中的else if关键字。
  • .foreach 标记解析调试器命令的输出、字符串或文本文件。然后将它找到的每条数据作为指定的调试器命令的输入。
  • .for标记类似C中的for关键字,但是必须用分号分割多个增量命令,而不是逗号。
  • .while标记类似C中的while关键字。
  • .do 和C中的do 关键字类似,但是不能在条件之前使用"while" 。
  • .break和C中的break关键字类似。可以在任何.for.while.do循环中使用。
  • .continue和C中的continue关键字类似。可以在任何.for.while.do循环中使用。
  • .catch 标记避免程序由于出现错误而终止。.catch标记后跟用大括号括起来的一条或多条命令。如果某条命令产生错误,则显示错误信息并跳过在大括号中剩下的命令,接着继续执行大括号之后的第一条命令。
  • .leave 标记用于退出一个.catch 块。
  • .printf 和C语言中的printf类似。
  • .block标记没有操作。使用该标记来创建一个块,因为不能仅使用大括号创建块。在前大括号之前必须加上流程控制符。

调试器命令程序中,!for_each_module, !for_each_frame!for_each_local 扩展命令也非常有用。

执行调试器命令程序

可以使用下面的方法之一来执行调试器命令程序:

  • 以单个字符串在调试器命令窗口中输入所有语句和命令,每个语句和命令之间用分号隔开。
  • 将所有语句写在脚本文件的单行中,每个语句和命令之间用分号隔开。然后用使用脚本文件中描述的方法运行脚本。
  • 在脚本文件中输入所有语句,每条语句占一行。 (既用回车或分号来分割不同的语句。) 然后使用$>< (Run Script File)$$>< (Run Script File) 命令运行脚本。 这些命令会打开脚本文件,用分号替换所有的回车符,然后将结果文本当作单个命令块执行。

调试器命令程序示例

本主题的下面一小节描述了调试器命令程序的示例。

使用 .foreach 标记

下面的例子是用.foreach 标记搜索word值然后将他们以DWORD和字符的方式显示出来。

0:000> .foreach (place { s-[1]w 77000000 L?4000000 5a4d }) { dc place L8 } 

s (Search Memory)命令和-[1]选项一起使得输出只包含它找到的地址,而不是在这些地址上找到的值:

下面的命令显示所有位于0x77000000 到0x7F000000地址范围内的模块的详细信息。

0:000> .foreach (place { lm1m }) { .if ((${place} >= 0x77000000) & (${place} <= 0x7f000000)) { lmva place } } 

lm (List Loaded Modules)1m选项 使得它的输出只包含模块地址,而不是模块的完整描述。

上面的例子使用${ }  (Alias Interpreter)标记来确保别名即使和其他文本连接在一起也能被正确替换。如果不这样,和place 连在一起的圆括号可能使得别名无法被替换掉。注意这对于.foreach 和真实的别名使用的变量都起效。

遍历进程列表

下面的示例遍历内核模式的进程列表并显示列表中所有入口的可执行文件名。

该示例应该保存在文本文件中并用$$>< (Run Script File) 命令执行。该命令加载整个文件,用分号替换所有回车然后再执行。该命令使得可以通过使用多行和缩进来编写更具可读性的程序,而不是将所有程序挤压到单独一行中。

该示例使用了下面一些特性:

  • 程序中使用$t0$t1$t2 伪寄存器作为变量。还使用了别名Procc$ImageName
  • 该程序使用MASM表达式语法。但是@@c++( )标记也出现了一次。 这个标记使得圆括号中的表达式使用C++ 语法。这里用来在程序中直接使用C++结构。
  • r (Registers)?标志一起使用。该标志为伪寄存器$t2指派有类型的值。

$$  Get process list LIST_ENTRY in $t0.
r $t0 = nt!PsActiveProcessHead

$$  Iterate over all processes in list.
.for (r $t1 = poi(@$t0);
      (@$t1 != 0) & (@$t1 != @$t0);
      r $t1 = poi(@$t1))
{
    r? $t2 = #CONTAINING_RECORD(@$t1, nt!_EPROCESS, ActiveProcessLinks);
    as /x Procc @$t2

    $$  Get image name into $ImageName.
    as /ma $ImageName @@c++(&@$t2->ImageFileName[0])

    .block
    {
        .echo ${$ImageName} at ${$Procc}
    }

    ad $ImageName
    ad Procc
}

遍历LDR_DATA_TABLE_ENTRY 链表

下面的例子遍历用户模式的LDR_DATA_TABLE_ENTRY 链表,并显示每个链表入口中的基地址和完整路径。

和上面的示例一样,需要将程序保存到文本文件中,并用$$>< (Run Script File)命令执行。

该示例使用了下面一些特性:

  • 该程序使用MASM表达式语法。但是在两个地方使用了@@c++( ) 标记。这个标记使得圆括号中的表达式使用C++ 语法。这里用来在程序中直接使用C++结构。
  • r (Registers)?标志一起使用。该标志为伪寄存器$t0$t1指派有类型的值。循环体中,$t1具有ntdll!_LDR_DATA_TABLE_ENTRY*类型,所以程序可以直接引用它的成员。
  • 使用了自定义别名$Base$Mod。双美元符号避免了这些别名在当前调试器会话之前被使用过的可能性。美元符号不是必须的。${/v: } 标记会逐字解释别名,避免如果脚本运行之前定义过同名的别名使得它被替换的情况。 也可以将该标记和语句块一起使用,避免在语句块之前定义的别名的影响。
  • .block 标记用于增加一个额外的别名替换步骤。别名在整个脚本被加载时会进行一次替换,在进入每个语句块的时候还会进行一次替换。如果没有.block 标记和它的大括号,.echo 命令无法接收到上一行中赋给$Mod$Base 别名的值。

$$ Get module list LIST_ENTRY in $t0.
r? $t0 = &@$peb->Ldr->InLoadOrderModuleList
 
$$ Iterate over all modules in list.
.for (r? $t1 = *(ntdll!_LDR_DATA_TABLE_ENTRY**)@$t0;
      (@$t1 != 0) & (@$t1 != @$t0);
      r? $t1 = (ntdll!_LDR_DATA_TABLE_ENTRY*)@$t1->InLoadOrderLinks.Flink)
{
    $$ Get base address in $Base.
    as /x ${/v:$Base} @@c++(@$t1->DllBase)
    
    $$ Get full name into $Mod.
    as /msu ${/v:$Mod} @@c++(&@$t1->FullDllName)
 
    .block
    {
        .echo ${$Mod} at ${$Base}
    }
 
    ad ${/v:$Base}
    ad ${/v:$Mod}
}

posted on 2008-04-24 01:08 NetRoc 阅读(985) 评论(0)  编辑 收藏 引用 所属分类: WinDbg文档翻译

只有注册用户登录后才能发表评论。

导航

统计

常用链接

留言簿(7)

随笔档案(99)

文章分类(35)

文章档案(32)

Friends

Mirror

搜索

最新评论

阅读排行榜

评论排行榜