Windbg命令脚本流程控制语句详解
在Windbg命令脚本一文里,我们介绍了命令脚本语言的的组成要素,在本文里将对语句进行展开的讲解。这些语句主要是流程控制的语句,比如我们常见的条件分子和循环语句等。
; (命令分隔符)
分号(;)字符用于在一行中分隔多个命令。
Command1 ; Command2 [; Command3 ...]
参数
命令1,命令2,…
要执行的命令。
0:000> g `:123`; ? poi(counter); g
{ } (块分割符)
Statements { Statements } Statements
输入每个块后,将计算块中的所有别名。如果在命令块中的某个点更改别名的值,则该点后面的命令将不会使用新的别名值,除非它们位于从属块中。每个块必须以控制流标记开头。如果您希望创建一个仅用于评估别名的块,则应在其前面加上.block标记。
${ } (别名解释器)
后面跟一对大括号(${})的美元符号计算出与指定的用户别名相关的各种值。
Text ${Alias} Text Text ${/d:Alias} Text Text ${/f:Alias} Text Text ${/n:Alias} Text Text ${/v:Alias} Text
参数
- Alias
-
指定要展开或计算的别名的名称。 别名必须是用户命名别名或变量使用值 .foreach语句
- /d
-
计算结果为一个或零个具体情况取决于是否当前定义别名。 如果定义别名,则 ${/ v 部分:别名} 替换为 1; 如果未定义别名, ${/ v 部分:别名} 替换为 0。
- /f
-
如果当前定义了别名,则计算为等效别名。如果定义了别名,${/f:alias}将替换为别名等效项;如果未定义别名,${/f:alias}将替换为空字符串。
- /n
-
如果当前定义了别名,则计算为别名名称。如果定义了别名,${/n:alias}将被别名替换;如果未定义别名,${/n:alias}将不被替换,但保留其文字值${/n:alias}。
- /v
-
阻止任何别名计算。无论是否定义了别名,${/v:alias}始终保留其文本值${/v:alias}。
$$ (注释说明符)
$$ [any text]
0:000> r eax; $$ some text; r ebx; * more text; r ecx
不以任何方式处理前缀为*或$$标记的文本。如果正在执行远程调试,则在调试服务器中输入的注释在调试客户端中将不可见,反之亦然。如果希望使注释文本以所有参与方都可见的方式显示在调试器命令窗口中,则应使用.echo(echo comment)。
* (注释行说明符)
* [any text]
*标记的解析方式与任何其他调试器命令相同。因此,如果要在另一个命令之后创建注释,则必须在*标记前面加上分号。*标记将导致忽略行的其余部分,即使行后面出现分号。这与$$(注释说明符)不同,后者创建的注释可以用分号终止。
0:000> r eax; $$ some text; r ebx; * more text; r ecx
不以任何方式处理前缀为*或$$标记的文本。如果正在执行远程调试,则在调试服务器中输入的注释在调试客户端中将不可见,反之亦然。如果希望使注释文本以所有参与方都可见的方式显示在调试器命令窗口中,则应使用.echo(echo comment)。
.block
Commands ; .block { Commands } ; Commands
命令块由大括号包围。输入每个块后,将计算块中的所有别名。如果在命令块中的某个点更改别名的值,则该点后面的命令将不会使用新的别名值,除非它们位于从属块中。每个块必须以控制流标记开头。如果您希望创建一个仅用于评估别名的块,则应在其前面加上.block标记,因为此标记除了允许引入块之外没有其他作用。
.break
.for (...) { ... ; .if (Condition) .break ; ...} .while (...) { ... ; .if (Condition) .break ; ...} .do { ... ; .if (Condition) .break ; ...} (...)
.break标记可以在任何.for、.while或.do循环中使用。由于没有与c goto语句等效的控制流标记,因此通常在.if条件中使用.break标记,如上面的语法示例所示。然而,这实际上不是必需的。
.catch
Commands ; .catch { Commands } ; Commands
.catch标记后面跟着大括号,其中包含一个或多个命令。如果.catch块中的命令生成错误,将显示错误消息,大括号内的所有剩余命令将被忽略,并在右大括号后使用第一个命令继续执行。如果不使用.catch,则错误将终止整个调试器命令程序。可以使用.leave退出.catch块。
.leave
.catch { ... ; .if (Condition) .leave ; ... }
当在.catch块中遇到.leave标记时,程序退出该块,并在右大括号后使用第一个命令继续执行。
.continue
.for (...) { ... ; .if (Condition) .continue ; ... } .while (...) { ... ; .if (Condition) .continue ; ... } .do { ... ; .if (Condition) .continue ; ... } (...)
.Continue标记可以在任何.for、.while或.do循环中使用。
.do
.do标记的行为类似于c中的do关键字,但条件之前不使用单词“while”。
.do { Commands } (Condition)
语法元素:
- 命令
指定一个或多个将在条件为真时重复执行的命令,但始终至少执行一次。这个命令块需要用大括号括起来,即使它由一个命令组成。多个命令应该用分号分隔,但右大括号前的最后一个命令不需要后跟分号。 - 条件
指定条件。如果计算结果为零,则视为假;否则为真。括号中的封闭条件是可选的。条件必须是表达式,而不是调试器命令。它将由默认表达式求值器(MASM或C++)进行计算。
.else
.if (Condition) { Commands } .else { Commands } .if (Condition) { Commands } .elsif (Condition) { Commands } .else { Commands }
- 命令
指定将按条件执行的一个或多个命令。这个命令块需要用大括号括起来,即使它由一个命令组成。多个命令应该用分号分隔,但右大括号前的最后一个命令不需要后跟分号。
.elsif
.elsif标记的行为与c中的else-if关键字组合类似。
.if (Condition) { Commands } .elsif (Condition) { Commands } .if (Condition) { Commands } .elsif (Condition) { Commands } .else { Commands }
语法要素:
- 条件
指定条件。如果计算结果为零,则视为假;否则为真。括号中的封闭条件是可选的。条件必须是表达式,而不是调试器命令。它将由默认表达式求值器(MASM或C++)进行评估。 - 命令
指定将按条件执行的一个或多个命令。这个命令块需要用大括号括起来,即使它由一个命令组成。多个命令应该用分号分隔,但右大括号前的最后一个命令不需要后跟分号。
.if
.if标记的行为类似于c中的if关键字。
.if (Condition) { Commands } .if (Condition) { Commands } .else { Commands } .if (Condition) { Commands } .elsif (Condition) { Commands } .if (Condition) { Commands } .elsif (Condition) { Commands } .else { Commands }
语法要素:
- 条件
指定条件。如果计算结果为零,则视为假;否则为真。括号中的封闭条件是可选的。条件必须是表达式,而不是调试器命令。它将由默认表达式求值器(MASM或C++)进行计算。 - 命令
指定将按条件执行的一个或多个命令。这个命令块需要用大括号括起来,即使它由一个命令组成。多个命令应该用分号分隔,但右大括号前的最后一个命令不需要后跟分号。
.for
.for标记的行为类似于c中的for关键字,但多个增量命令必须用分号分隔,而不是用逗号分隔。
.for (InitialCommand ; Condition ; IncrementCommands) { Commands }
语法要素:
- 初始化命令
指定将在循环开始之前执行的命令。只允许使用一个初始命令。 - 条件
指定条件。如果计算结果为零,则视为假;否则为真。括号中的封闭条件是可选的。条件必须是表达式,而不是调试器命令。它将由默认表达式求值器(MASM或C++)进行计算。 - 递增命令
指定将在每个循环结束时执行的一个或多个命令。如果要使用多个增量命令,请用分号分隔它们,但不要将它们括在大括号中。 - 命令
指定一个或多个将在条件为真时重复执行的命令。这个命令块需要用大括号括起来,即使它由一个命令组成。多个命令应该用分号分隔,但右大括号前的最后一个命令不需要后跟分号。
下面是.for语句的示例,其中包含多个递增命令:
0:000> .for (r eax=0; @eax < 7; r eax=@eax+1; r ebx=@ebx+1) { .... }
.foreach
.foreach标记解析一个或多个调试器命令的输出,并将此输出中的每个值用作一个或多个附加命令的输入。
.foreach [Options] ( Variable { InCommands } ) { OutCommands } .foreach [Options] /s ( Variable "InString" ) { OutCommands } .foreach [Options] /f ( Variable "InFile" ) { OutCommands }
语法要素:
- 选项
可以是以下选项的任意组合:
/PS首字母kipnumber 导致跳过某些初始令牌。initialkipnumber指定不会传递给指定outcommands的输出令牌数。
/PS跳过编号 导致每次处理命令时重复跳过令牌。每次将令牌传递给指定的outcommands后,将忽略相当于skipNumber值的一些令牌。 - 变量
指定变量名。此变量将用于保存incommands字符串中每个命令的输出;您可以在传递给outcommands的参数中按名称引用变量。可以使用任何字母数字字符串,但不建议使用还可以传递有效十六进制数或调试器命令的字符串。如果用于变量的名称恰好与现有全局变量、局部变量或别名匹配,则它们的值不会受到.foreach命令的影响。 - 命令
指定将分析其输出的一个或多个命令;生成的标记将传递给outcommands。不显示来自incommands的输出。 - 输入字符串
与/s一起使用。指定要分析的字符串;生成的令牌将传递给outcommands。 - 输入文件
与/f一起使用。指定要分析的文本文件;生成的令牌将传递给outcommands。文件名内嵌必须用引号括起来。 - 输出命令
指定将为每个令牌执行的一个或多个命令。每当变量字符串出现时,它将被当前标记替换。
当字符串变量出现在outcommands中时,它必须被空格包围。如果它与任何其他文本相邻(甚至是括号),则不会被当前标记值替换,除非您使用$(别名解释器)标记。当分析来自incommands、instring字符串或infile文件的输出时,任何数量的空格、制表符或回车都被视为单个分隔符。当变量出现在outcommands中时,将使用生成的每一段文本来替换它。
下面是举例 .foreach使用语句 dds 命令在文件中找到的每个标记myfile.txt:
0:000> .foreach /f ( place "g:\myfile.txt") { dds place }
/PS并 /ps标志可用于将仅某些标记传递到指定OutCommands。 例如,下面的语句将跳过myfile.txt文件中的前两个标记,然后将第三个标记传递给dds。在传递每个令牌之后,它将跳过四个令牌。结果是DDS将与第3、8、13、18和23个令牌一起使用,依此类推:
0:000> .foreach /pS 2 /ps 4 /f ( place "g:\myfile.txt") { dds place }
.while
.while (Condition) { Commands }
语法要素:
- 条件
指定条件。如果计算结果为零,则视为假;否则为真。括号中的封闭条件是可选的。条件必须是表达式,而不是调试器命令。它将由默认表达式求值器(MASM或C++)进行评估。 - 命令
指定一个或多个将在条件为真时重复执行的命令。这个命令块需要用大括号括起来,即使它由一个命令组成。多个命令应该用分号分隔,但右大括号前的最后一个命令不需要后跟分号。
.printf
.printf标记的行为与c中的printf语句类似。
.printf [/D] [Option] "FormatString" [, Argument , ...]
语法要素:
- /D
指定格式字符串包含调试器标记语言(DML)。 - Option
(仅限windbg)指定windbg应将格式字符串解释为的文本消息类型。windbg为每种类型的调试器命令窗口消息分配背景和文本颜色;选择其中一个选项将使消息以适当的颜色显示。默认设置是将文本显示为普通级别的消息。
有一下选项:
Option 消息类型 选项对话框中的颜色的标题 /od
调试对象
调试对象级别的命令窗口
/oD
调试对象提示符
调试对象级别命令提示窗口
/oe
错误
错误级别的命令窗口
/on
正常
标准级别的命令窗口
/op
prompt
级别命令提示窗口
/oP
提示符下注册
提示符下注册级别的命令窗口
/os
符号
符号消息级别的命令窗口
/ov
详细
详细级别的命令窗口
/ow
警告
警告级别的命令窗口
- Arguments
为指定参数的格式字符串,如printf。 指定的参数数目应与匹配的转换中的字符数FormatString。 每个自变量是将默认表达式计算器计算的表达式 (MASM 或C++)。 - FormatString
指定格式字符串,如printf中所示。一般来说,转换字符的工作方式与C完全相同。对于浮点转换字符,除非使用L修饰符,否则64位参数将被解释为32位浮点数字。支持%p转换字符,但它表示目标虚拟地址空间中的指针。它不能有任何修饰符,并且使用调试器的内部地址格式。支持以下附加转换字符。
字符 自变量类型 参数 打印的文本 %p
ULONG64
目标的虚拟地址空间中的指针。
指针的值。
%N
DWORD_PTR (32 位或 64 位,具体取决于主机的体系结构)
主机的虚拟地址空间中的指针。
指针的值。 (这等效于标准 C %p 字符。)
%ma
ULONG64
目标的虚拟地址空间中的以 NULL 结尾的 ASCII 字符串的地址。
指定的字符串。
%mu
ULONG64
目标的虚拟地址空间中的 NULL 终止的 Unicode 字符串的地址。
指定的字符串。
%msa
ULONG64
ANSI_STRING 结构目标的虚拟地址空间中的地址。
指定的字符串。
%msu
ULONG64
目标的虚拟地址空间中的 UNICODE_STRING 结构地址。
指定的字符串。
%y
ULONG64
目标的虚拟地址空间中的调试器符号的地址。
包含指定的符号 (和偏移量,如果有) 的名称的字符串。
%ly
ULONG64
目标的虚拟地址空间中的调试器符号的地址。
包含名称的指定符号 (和偏移量,如果有),以及任何可用的源行信息的字符串。
下面的示例演示如何在格式字符串中包含DML标记。
.printf /D "Click <link cmd=\".chain /D\">here</link> to see extensions DLLs."
执行和点击链接结果如下: