cc682/NetRoc/小喂
http://netroc682.spaces.live.com/
数值表达式语法部分直接使用了小喂翻译的文档。新版本WinDbg文档中更新过的部分作了一些修改。非常感谢小喂的共享。他的主页在
http://www.ztssoft.com/
调试器命令
本节包含以下主题:
语法规则
命令关键字
命令
元命令
控制键
语法规则
本节描述了在使用调试器命令时必须遵守的语法规则。
进行调试时,必须遵从以下常规语法规则:
- 除了本节中专门注明的地方,所有命令和参数都可以使用任意的大小写的组合。
- 可以使用一个或数个空格、逗号(,)来分隔多个命令参数。
- 一般可以省略命令和它第一个参数之间的空格。在不产生歧义的情况下也常常可以省略其他空格。
本节中的命令参考主题使用了下面一些格式:
- 以粗体显示的字符表示使用命令时必须完整输入该项。
- 以斜体显示的字符表示它是在参考主题后面的参数节有说明的参数。
- 中括号([ xxx ])中的参数是可选的。中括号中的竖线([ xxx | yyy ])表明可以使用其中一项参数或者不使用。
- 大括号中的竖线({ xxx | yyy }) 表明必须使用其中一项参数。
下面的主题描述了参数使用的语法:
数值表达式语法
字符串通配符语法
寄存器语法
伪寄存器语法
源码行语法
地址和地址范围语法
线程语法
进程语法
系统语法
多处理器语法
数值表达式语法
调试器可接受两种不同的数值表达式:C++ 表达式和 MASM 表达式。每种都有自己的输入输出语法规则。
每种语法类型的使用信息,请看表达式求值。
本节包括:
MASM 数值和运算符
C++数值和运算符
MASM表达式vs. C++表达式
表达式示例
符号扩展
MASM数值和运算符
MASM(Microsoft 宏汇编器)表达式语法是 Debugging Tools for Windows 4.0 之前版本中的 NTSD, CDB, KD 和 WinDbg 惟一使用的语法。
MASM 表达式中的数值
MASM 表达式中的数值能够以 16, 10, 8 或者 2 为基数方式输入。
n (Set Number Base)命令可用来设置缺省基数为 16, 10 或者 8。所有不带前缀的数值都以该基数来解释。通过指定 0x 前缀(十六进制),0n 前缀(十进制),0t 前缀(八进制),或者 0y 前缀(二进制)能够掩盖缺省基数。
也可以添加一个 h 后缀表示十六进制数值。数值里面的字母可以是大写或者小写,所以 "0x4AB3", "0X4aB3", "4AB3h", "4ab3h" "4aB3H" 都表示相同意思。
如果表达式中前缀后面没有跟一个数值,它被读作 0。所以,0 可以写成三种形式:0,前缀后面跟一个 0,单独一个前缀。例如,在十六进制下,"0", "0x0" 和 "0x" 都表示相同意思。
能够以 xxxxxxxx`xxxxxxxx 格式输入 64 位的十六进制数值,或者忽略当中的重音符号(`)。包含重音符号会使得自动符号扩展无效。
MASM 表达式中的符号
在 MASM 表达式中,任意符号的数值都是它的内存地址。根据引用符号的不同,可以是全局变量、局部变量、函数、段、模块或者任何其它可识别标签的地址。
如果会混淆,你可以给符号添加一个模块名和一个感叹号(!)做为前缀,或者只加一个感叹号。关于符号识别,详细请看符号语法和符号匹配。
两个冒号(::)或者两条下划线(__)可以用来标志类的成员。
只有以模块名和感叹号为前缀的符号名中才能使用重音符号(`)或者撇号(')。
MASM 表达式中的数值运算符
表达式的任意组件都可以用一元运算符来修改,任意两个组件都可以用二元运算符组合起来。一元运算符优先于二元运算符。当用到多个二元运算符时,遵循下面表格中描述的已定的优先级规则。
总是可以用圆括号来掩盖优先级规则。
如果 MASM 表达式的某一部分被圆括号括住并有两个 @ 前缀 (@@ ) ,该部分将根据 C++ 表达式规则来解析。在两个 @ 记号之间以及和左圆括号之间不能有空格。也可以通过使用 @@c++( ... ) 或者 @@masm( ... ) 指定表达式求值器。
当执行算术运算时,MASM 表达式求解器把所有的数值和符号当做 ULONG64 类型。
一元地址运算符寻址时假定 DS 为缺省段。按运算符的优先级顺序计算表达式。如果两个相邻的运算符优先级一样,则从左往右计算表达式。
一元运算符有:
运算符
|
含义
|
+
|
正数
|
-
|
负数
|
not
|
如果参数为零返回 1;任何非零参数返回 0。
|
hi
|
高 16 位
|
low
|
低 16 位
|
by
|
指定地址处的低位字节
|
$pby
|
除了限制为物理内存地址之外,和by一样。
|
wo
|
指定地址处的低位字
|
$pwo
|
除了限制为物理内存地址之外,和wo一样。
|
dwo
|
指定地址处的双字。
|
$pdwo
|
除了限制为物理内存地址之外,和dwo一样。
|
qwo
|
指定地址处的四字节。
|
$pqwo
|
除了限制为物理内存地址之外,和qwo一样。
|
poi
|
指定地址处的指针大小的数据。指针大小或者是 32 位或者是 64 位。在内核调试模式,大小基于目标计算机上的处理器。在 Intel Itanium 计算机上用户模式调试下,大小或者是 32 位或者是 64 位,依赖于目标应用程序。所以,如果你想得到指针大小的数据最好使用 poi 运算符。
|
$ppoi
|
除了限制为物理内存地址之外,和poi一样。
|
可以使用下表中列出的二元运算符。每个单元格中的运算符优先级从上往下依次降低。同一格的各个运算符优先级相同,以从左往右的方式计算。
操作符
|
含义
|
*
/
mod (或 %)
|
乘法
整数除法
模数(余数)
|
+
-
|
加法
减法
|
<<
>>
>>>
|
左移位
逻辑右移位
算术右移位
|
= (或 ==)
<
>
<=
>=
!=
|
等于
小于
大于
小于等于
大于等于
不等于
|
and (或&)
|
按位与
|
xor (或^)
|
按位异或(不同于 OR)
|
or (或|)
|
按位或
|
比较运算符 <, >, =, == 和 != 计算后如果为真则得到 1,如果为假则得到 0。单个等于号(=)和双等于号(==)相同;在 MASM 表达式中不会产生副作用,也不会赋值。
非法操作(例如除零)会返回给调试器命令窗口一个"Operand error"。
MASM 表达式中的非数值运算符
下表列出了可在 MASM 表达式中使用的其它运算符。
操作符
|
含义
|
$fnsucc(FnAddress, RetVal, Flag)
|
将RetVal作为位于FnAddress处的函数地返回值。如果返回值是一个成功码,$fnsucc 返回 TRUE,否则返回 FALSE。
如果返回值类型是 BOOL、 bool、 HANDLE、 HRESULT 或者 NTSTATUS,$fnsucc 可以正确理解指定的返回值是否一个成功码。如果返回值类型是一个指针,所有 NULL 以外的值都是成功码。对于其它返回值类型,根据 Flag 值来定义成功与否,如果 Flag 是 0,那么一个非零 RetVal 值表示成功;如果 Flag 是 1,则 RetVal 值为 0 表示成功。
|
$iment (Address)
|
返回加载模块列表中映像入口点地址。Address 指定 PE 映像基地址。通过查找 Address 指定映像的 PE 头中的映像入口点找到入口点。
该函数既可以用在模块列表中已有的模块上,也可以通过bu命令设置未确定的断点。
|
$scmp("String1", "String2")
|
计算后得到 -1、0 或者 1;就像 C 函数中的 strcmp。
|
$sicmp("String1", "String2")
|
计算后得到 -1、0 或者 1;就像 Win32 函数 stricmp。
|
$spat("String", "Pattern")
|
根据 String 是否匹配 Pattern 计算得到 TRUE 或 FALSE。Pattern 可以包含多种通配符和特定符(specifiers);详细请看字符串通配符语法。
|
$vvalid(Address, Length)
|
判断一段起始地址为 Address 长度为 Length字节的内存范围是否有效。如果这段内存有效,$vvalid 计算得到 1;否则,$vvalid 计算得到 0。
|
MASM 表达式中的寄存器和伪寄存器
MASM 表达式中可以使用寄存器和伪寄存器。所有寄存器和伪寄存器可以包含单个at符号( @) 的前缀。使用该前缀可以使调试器存取这些值更快。该记号对于大多数 x86 通用寄存器不需要。对于其它寄存器和伪寄存器实际上也不是必须的,但强烈建议使用。对于少数通用寄存器如果忽略该前缀,调试器首先会尝试解释该文本为十六进制数值,然后解释为符号,最后才把它解释为寄存器。
你也能用一个点号(.)表示当前指令指针。该点号不能带 @ 前缀,不能用作 r 命令的第一个参数。该点号和 $ip 伪寄存器含义相同。
详细请看寄存器语法和伪寄存器语法。
MASM 表达式中的源代码行数
MASM 表达式中可以使用源代码文件和行数表达式。必须用重音符号(`)把它们括住。详细请看源码行语法。
C++ 数值和运算符
C++ 表达式解析器支持所有 C++ 表达式语法形式。包括所有数据类型(包括指针,浮点数,数组)以及 C++ 所有一元和二元运算符。
C++ 表达式中的数值
除非另有说明,C++ 表达式中的数值被当作十进制来解析。添加 0x 前缀指定十六进制整数。添加 0(零)指定八进制整数。
调试器使用的缺省基数不影响 C++ 表达式的输入。不能直接输入二进制数值(除非在 C++ 表达式中嵌套 MASM 表达式)。
能够以 xxxxxxxx`xxxxxxxx 格式输入 64 位十六进制数值(可以忽略重音符号(`))。两种格式值相同。
整型值后可以使用 L、U 和 I64 后缀。合成数的实际大小依据使用的后缀和输入的数值;详细请看 C++ 语言参考。
C++ 表达式求值后的输出结果保留 C++ 表达式规则指定的数据类型。然而,如果表达式被用作某个命令的参数,总是会产生强制类型转换。例如,在命令参数中作为地址时你不需要把整型值强制转换为指针。如果表达式的值不能被合法地强制转换为整型或者指针,则会导致语法错误。
某些输出中会用到 0n(十进制)前缀,虽然在输入 C++ 表达式时不允许使用该前缀。
C++ 表达式中的字符和字符串
输入字符时用单引号(')括住。可以使用标准的 C++ 转义字符。
输入字符串时用双引号(")括住。可以在字符串中使用转义字符序列 \" 。然而,字符串对于表达式求值器无意义。
C++ 表达式中的符号
在 C++ 表达式中,每个符号都根据它的类型来解析。根据引用符号的不同,可以被解析为整数、数据结构、函数指针或者其它的数据类型。在 C++ 表达式中使用一个没有 C++ 数据类型与之对应的符号(例如一个没有修饰的模块名)会导致语法错误。
如果会混淆,可以在符号前添加模块名和感叹号(!)前缀,或者单独一个感叹号前缀。关于符号识别,详细请看符号语法和符号匹配。
只有以模块名和感叹号为前缀的符号名中才能使用重音符号(`)或者撇号(')。
当模板名用 < 和 > 分隔符时,分隔符之间可以有空格。
C++ 表达式中的运算符
总是可以用圆括号来掩盖优先级规则。
如果 C++ 表达式的某一部分被圆括号括住并且在表达式前添加两个at符号(@@),该部分将根据 MASM 表达式规则来解析。在两个 @ 记号之间以及和左圆括号之间不能有空格。该表达式的最终值将以 ULONG64 类型传递给 C++ 表达式求解器。也可以通过使用 @@c++( ... ) 或者 @@masm( ... ) 指定表达式求解器。
数据类型按C++语言的常规方法来指定。可以识别表示数组([ ]),指针成员(->),UDT(译注:用户定义类型,User defined types)成员(.),以及所有的类成员(::)。支持所有算术运算符,包括赋值运算符和带副作用的运算符。但是,不允许使用 new、delete 和 throw 运算符,当然,也不能调用函数。
支持指针运算,也可以正确计算偏移。注意不能给函数指针加上某个偏移值 (如果需要这样做,首先把它强制转换为指向字符的指针)。
和在 C++ 中一样,使用带非法数据类型的运算符将导致语法错误。调试器中的 C++ 表达式解析器比大多数 C++ 编译器使用的规则要稍微宽松一些,但所有的主要规则都支持。例如,非整型数移位操作是被禁止的。
下表列出了所有可用的运算符。各个单元格中的运算符优先级从上往下依次降低。同一格中的各个运算符优先级相同,以从左往右的方式解析。和 C++ 一样,如果表达式的值已经可以确定,则终止继续求值过程;这个规则允许有效地使用如 ?? myPtr && *myPtr 样子的表达式。
操作符
|
含义
|
Expression // Comment
|
注释,忽略后面的所有文本
|
Class :: Member
Class ::~Member
:: Name
|
类成员
类成员(析构函数)
全局的
|
Structure . Field
Pointer -> Field
Name [integer]
LValue ++
LValue --
dynamic_cast <type>(Value)
static_cast <type>(Value)
reinterpret_cast <type>(Value)
const_cast <type>(Value)
|
结构成员
被引用结构的成员
数组下标
加一(计算后)
减一(计算后)
类型强制转换(总会执行)
类型强制转换(总会执行)
类型强制转换(总会执行)
类型强制转换(总会执行)
|
(type) Value
sizeof value
sizeof( type )
++ LValue
-- LValue
~ Value
! Value
- Value
+ Value
& LValue
* Value
|
类型强制转换(总会执行)
表达式大小
数据类型大小
加一(计算前)
减一(计算前)
按位反
逻辑非
一元运算符,负数
一元运算符,正数
数据类型的地址
解引用,取值
|
Structure . * Pointer
Pointer -> * Pointer
|
指向结构成员的指针
指向被引用结构的成员的指针
|
Value * Value
Value / Value
Value % Value
|
乘法
除法
求余
|
Value + Value
Value - Value
|
加法
减法
|
Value << Value
Value >> Value
|
左移位
右移位
|
Value < Value
Value <= Value
Value > Value
Value >= Value
|
小于(比较)
小于等于(比较)
大于(比较)
大于等于(比较)
|
Value == Value
Value != Value
|
等于(比较)
不等于(比较)
|
Value & Value
|
按位与
|
Value ^ Value
|
按位异或(不同于 OR)
|
Value | Value
|
按位或
|
Value && Value
|
逻辑与
|
Value || Value
|
逻辑或
|
LValue = Value
LValue *= Value
LValue /= Value
LValue %= Value
LValue += Value
LValue -= Value
LValue <<= Value
LValue >>= Value
LValue &= Value
LValue |= Value
LValue ^= Value
|
赋值
乘之后赋值
除之后赋值
求余之后赋值
加之后赋值
减之后赋值
左移位之后赋值
右移位之后赋值
与之后赋值
或之后赋值
异或之后赋值
|
Value ? Value : Value
|
三元条件运算符
|
Value , Value
|
逗号运算符。计算所有值,保留最右边的值
|
C++ 表达式中的寄存器和伪寄存器
C++ 表达式中可以使用寄存器和伪寄存。它们必须带一个 at符号(@) 前缀。
表达式求解器会自动执行正确的强制类型转换。实际的寄存器和整型值伪寄存器被强制转换为 ULONG64。所有地址被强制转换为 PUCHAR,$thread 被强制转换为 ETHREAD*,$proc 被强制转换为 EPROCESS*,$teb 被强制转换为 TEB*,$peb 被强制转换为 PEB*。
不能使用赋值运算符或者带副作用的运算符修改寄存器或者伪寄存器。必须使用 r (Registers)命令修改它们的值。
详细请看寄存器语法和伪寄存器语法。
C++表达式中的宏
在C++表达式中可以使用宏。必须在宏前面加上数字符号(#)。
可以使用下表中的宏。这些宏的作用和Microsoft Windows中定义的同名宏有相同的定义。(Windows的宏在Winnt.h中定义。)
宏
|
返回值
|
#CONTAINING_RECORD(Address, Type, Field)
|
给定该结构的类型和它包含的一个字段的地址,返回该结构实例的基地址。
|
#FIELD_OFFSET(Type, Field)
|
返回一个已知的结构类型中某个命名字段的字节偏移。
|
#RTL_CONTAINS_FIELD (Struct, Size, Field)
|
指出是否给定的字节大小包含了需要的字段。
|
#RTL_FIELD_SIZE(Type, Field)
|
返回已知类型的结构中某个字段的大小,不需要给出该字段的类型。
|
#RTL_NUMBER_OF(Array)
|
返回一个静态分配的数组的元素个数。
|
#RTL_SIZEOF_THROUGH_FIELD(Type, Field)
|
返回已知类型的结构中,从基地址到包含指定字段的位置的大小。
|
MASM 表达式 vs. C++ 表达式
MASM 表达式求值和 C++ 表达式求值之间最重要的区别有如下几种:
- 在 MASM 表达式中,任何符号的数值都是它的内存地址。在 C++ 表达式中,变量的数值就是它的实际值,不是地址。数据结构没有数值;它们必须声明为实际结构再使用。函数名或者任何其它入口点的值是内存地址,当作为函数指针。没有 C++ 数据类型与之对应的符号(例如没有修饰的模块名)会导致语法错误。
- MASM 表达式求解器把所有的数值当做 ULONG64 处理。C++ 表达式求解器把数值强制转换为 ULONG64,而且保留所有数据类型的类型信息。
- MASM 表达式求解器允许任何运算符操作任何数值。如果某个运算符使用了不恰当的数据类型,C++ 表达式求解器将产生错误。
- 在 MASM 表达式求解器中,所有算术运算都是按字面意义操作。在 C++ 表达式求解器中,指针运算会计算正确的偏移比例,执行不相符的操作会被禁止。
- MASM 表达式中既可以用双下划线(__)也可以用双冒号(::)来标志类的成员。C++ 表达式求解器仅能识别双冒号语法。但调试器输出总是使用双冒号。
- 在 MASM 表达式中,建议你在大多数通用寄存器前添加 @ 前缀。如果忽略该前缀,寄存器名可能被解析为十六进制数值或者符号。在 C++ 表达式中,所有寄存器都需要使用该前缀。
- MASM 表达式中可以包含源代码行数的引用,用重音符号(`)标志它们。C++ 表达式中不能引用源代码行数。
表达式示例
本节包含了一些在各种命令中使用 MASM 和 C++ 表达式的示例。
在这份帮助文档的其它所有章节中,示例都使用了 MASM 表达式语法(除非另有说明)。C++ 表达式语法对于管理结构和变量非常有用,但它不是很适合解析调试命令的参数。
当为了常用目的使用调试命令或者扩展命令时,应该设置 MASM 表达式语法为缺省语法。如果某个参数需要使用 C++ 表达式语法,那么用 @@( ) 语法。
条件断点
可以用比较运算符来创建条件断点。下面的例子使用 MASM 表达式语法。因为当前的缺省基数是 16,所以数值 20 使用了 0n 前缀表示十进制数:
0:000> bp MyFunction+0x43 "j ( poi(MyVar)>0n20 ) ''; 'gc' "
在本例中,MyVar 是 C 源代码中的整数。因为 MASM 解析器把所有符号当做地址,所以需要 poi 运算符取 MyVar 的值。
条件表达式
下面的命令如果 eax 大于 ebx 打印出 ecx 的值,如果 eax 小于 ebx 打印出 7,如果 eax 等于 ebx 打印出 3。使用 MASM 表达式求解器,所以单个等于号(=)当做比较运算符使用,而不是赋值运算符:
0:000> ? ecx*(eax>ebx) + 7*(eax<ebx) + 3*(eax=ebx)
在 C++ 语法中,指示寄存器需要用 @ 记号,比较运算符是双等于号(==),需要把 BOOL 显式强制转化为 int。所以,在 C++ 语法中,这条命令变成:
0:000> ?? @ecx*(int)(@eax>@ebx) + 7*(int)(@eax<@ebx) + 3*(int)(@eax==@ebx)
C++ 表达式示例
如果 myInt 类型是 ULONG32,而且用 MASM 表达式求解器,那么下面两条命名都将显示 myInt 的值:
0:000> ?? myInt
0:000> dd myInt L1
然而,下面的命令将显示 myInt 的地址。
0:000> ? myInt
混合表达式示例
C++ 表达式中不能使用源代码行数表达式。下面的例子在 C++ 表达式中使用 @@( ) 语法嵌入了一个 MASM 表达式。本例把MyPtr 的值设置为Myfile.c 文件的第43行代码所在的地址。
0:000> ?? MyPtr = @@( `myfile.c:43` )
下面的命令将 MASM 设置为缺省的表达式求解器,然后,把 Expression1 和 Expression3 做为 MASM 表达式求解,把 Expression2 做为 C++ 表达式求解:
0:000> .expr /s masm
0:000> bp Expression1 + @@( Expression2 ) + Expression3
如果 myInt 是 ULONG64 类型,而且知道在内存中紧跟着它的是另外一个 ULONG64 类型,可以用下面任一条命令在该位置设置一个存取断点。(注意用了指针运算)
0:000> ba r8 @@( &myInt + 1 )
0:000> ba r8 myInt + 8
结构体
C++ 表达式求解器把伪寄存器强制转换为正确的类型。例如,$teb 被强制转换为 TEB*。所以下面的命令将显示进程 ID:
kd> ?? @$teb->ClientId.UniqueProcess
符号扩展
当一个 32 位整数为负数时,它的最高位等于一。强制转换为 64 位数值时,有两种可能性:最高位全设为零,保留无符号整数和十六进制数值,或者最高位全设为一,保留带符号数值。后面一种被称为符号扩展。
在 MASM 表达式中、C++ 表达式中以及显示数值时,调试器遵循不同的规则实现符号扩展。
MASM 表达式中的符号扩展
在某种情况下,MASM 表达式求解器会把数值自动进行符号扩展。符号扩展只影响 0x80000000 和 0xFFFFFFFF 之间的数,包括 - 换句话说,就是最高位等于 1 的32 位数。
所以,当调试器把 0x12345678 转换为 64 位数时总是保持 0x00000000`12345678。另一方面,当 0x890ABCDE 被转换为 64 位值时可能保持 0x00000000`890ABCDE 或者可能被扩展为 0xFFFFFFFF`890ABCDE。
0x80000000 和 0xFFFFFFFF 之间的数是否会被符号扩展,根据下面的标准:
C++ 表达式中的符号扩展
调试器使用下面的规则计算一个 C++ 表达式:
- 寄存器和伪寄存器永远不会被符号扩展
- 所有其它数值都被以 C++的方式按类型转换为正确的值。
符号扩展数和 64 位数的显示
不同于 32 位和 16 位寄存器,在调试器内部所有数值都被当做 64 位值存储。然而,当数值满足一定条件时,在命令输出中它将被当做 32 位数显示。
调试器显示数值的算法如下:
- 如果某个数的高 32 位都为零 (换句话说,如果该数处在 0x00000000`00000000 到 0x00000000`FFFFFFFF 范围),那么它被当做 32 位数显示。
- 如果某个数的高 32 位都为一,而且低 32 位中的最高位也为一 (换句话说,如果该数处在 0xFFFFFFFF`80000000 到 0xFFFFFFFF`FFFFFFFF 范围),那么调试器假定它是一个被符号扩展的 32 位数,当做 32 位数显示。
- 如果不是上面两种情况 (换句话说,如果该数处在 0x00000001`00000000 到 0xFFFFFFFF`7FFFFFFF 范围)那么它被当做 64 位数显示。
这些规则会产生一个结果,当一个数被显示为 0x80000000 到 0xFFFFFFFF 之间的 32 位数值时,你不能确定它的最高 32 位全部是一还是零。要区别这两种情况你需要在这个数上执行一个额外的计算操作(例如屏蔽最高的一位或多位然后显示结果)。