C++ 数字和运算符
本文介绍在 Windows 调试工具中使用 C++ 表达式语法。
调试器接受两种不同类型的数值表达式:C++ 表达式和 Microsoft 宏汇编程序 (MASM) 表达式。 这些表达式中的每一个都遵循自己的输入和输出语法规则。
有关何时使用每种语法类型的详细信息,请参阅 Evaluating expressions 和 ? evaluate expression 命令。
C++ 表达式分析器支持所有形式的 C++ 表达式语法。 语法包括所有数据类型,包括指针、浮点数和数组,以及所有 C++ 一元运算符和二进制运算符。
调试器中的 Watch 和 Locals 窗口始终使用 C++ 表达式计算器。
在以下示例中,?? evaluate C++ 表达式 命令显示指令指针寄存器的值。
0:000> ?? @eip
unsigned int 0x771e1a02
可以使用 C++ sizeof
函数来确定结构的大小。
0:000> ?? (sizeof(_TEB))
unsigned int 0x1000
将表达式计算器设置为 C++
使用 .expr 选择表达式计算器可以查看默认的表达式计算器,并将其更改为 C++。
0:000> .expr
Current expression evaluator: MASM - Microsoft Assembler expressions
0:000> .expr /s c++
Current expression evaluator: C++ - C++ source expressions
更改默认表达式计算器后,可以使用 ? evaluate expression 命令显示 C++ 表达式。 以下示例显示指令指针寄存器的值。
0:000> ? @eip
Evaluate expression: 1998461442 = 771e1a02
若要了解有关 @eip
寄存器引用的详细信息,请参阅寄存器语法。
在本例中,十六进制值 0xD 被添加到 eip 寄存器中。
0:000> ? @eip + 0xD
Evaluate expression: 1998461455 = 771e1a0f
C++ 表达式中的寄存器和伪寄存器
可以在 C++ 表达式中使用寄存器和伪寄存器。 @ 符号必须添加在寄存器或伪寄存器之前。
表达式计算器会自动执行正确的强制转换。 实际寄存器和整数值伪寄存器被强制转换为 ULONG64
。 所有地址都强制转换为 PUCHAR
,$thread
被强制转换为 ETHREAD*
,$proc
被强制转换为 EPROCESS*
,$teb
被强制转换为 TEB*
,$peb
被强制转换为 PEB*
。
此示例显示 TEB。
0:000> ?? @$teb
struct _TEB * 0x004ec000
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : (null)
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : (null)
+0x02c ThreadLocalStoragePointer : 0x004ec02c Void
+0x030 ProcessEnvironmentBlock : 0x004e9000 _PEB
+0x034 LastErrorValue : 0xbb
+0x038 CountOfOwnedCriticalSections : 0
不能通过赋值或副作用运算符更改寄存器或伪寄存器。 必须使用 r registers 命令更改这些值。
以下示例将伪寄存器设置为值 5,然后显示。
0:000> r $t0 = 5
0:000> ?? @$t0
unsigned int64 5
有关寄存器和伪寄存器的详细信息,请参阅寄存器语法和伪寄存器语法。
C++ 表达式中的数字
C++ 表达式中的数字被解释为十进制数字,除非以其他方式指定。 若要指定十六进制整数,请在数字之前添加 0x。 若要指定八进制整数,请在数字之前添加 0(零)。
默认调试器基数不会影响输入 C++ 表达式的方式。 不能直接输入二进制数,除非在 C++ 表达式中嵌套 MASM 表达式。
支持输入十六进制的 64 位值,格式为 xxxxxxxx`xxxxxxxx。 也可以省略重音 (`)。 这两种格式都生成相同的值。
可以将 L
、U
和 I64
后缀与整数值一起使用。 创建的数字的实际大小取决于后缀和输入的数字。 有关此解释的详细信息,请参阅 C++ 语言参考。
C++ 表达式计算器的 output 保留 C++ 表达式规则指定的数据类型。 但是,如果将此表达式用作命令的参数,则始终会进行强制转换。 例如,当整数值用作命令参数中的地址时,不必将它们强制转换为指针。 如果表达式的值无法有效强制转换为整数或指针,则会发生语法错误。
对于某些 output,可以使用 0n
(十进制)前缀;但是对于 C++ 表达式输入,则不能使用。
C++ 表达式中的字符和字符串
可以通过用单引号 (') 将字符括起来来输入字符。 允许使用标准 C++ 转义字符。
以通过用双引号 (") 将字符串文字括起来来输入字符串文字。 以在这样的字符串中使用 \" 作为转义序列。 但是,字符串对表达式计算器没有意义。
C++ 表达式中的符号
在 C++ 表达式中,每个符号都根据其类型进行解释。 根据符号所指的内容,它可能被解释为整数、数据结构、函数指针或任何其他数据类型。 如果在 C++ 表达式中使用与 C++ 数据类型不对应的符号(如未修改的模块名称),则会发生语法错误。
只有在符号名称之前添加模块名称和感叹号,才能在符号名称中使用重音符 (`) 或撇号 (')。 在模板名称后添加 < 和 > 分隔符时,可以在这些分隔符之间添加空格。
如果符号可能有歧义,可以在符号前添加模块名和感叹号 (!) 或仅添加感叹号。 若要指定符号是本地符号,请省略模块名称,并在符号名称之前包括美元符号和感叹号 ($!)。 有关符号识别的详细信息,请参阅符号语法和符号匹配。
C++ 表达式中的结构
C++ 表达式计算器将伪寄存器强制转换为其适当的类型。 例如,$teb
被强制转换为 TEB*
。
0:000> ?? @$teb
struct _TEB * 0x004ec000
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : (null)
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : (null)
+0x02c ThreadLocalStoragePointer : 0x004ec02c Void
+0x030 ProcessEnvironmentBlock : 0x004e9000 _PEB
+0x034 LastErrorValue : 0xbb
+0x038 CountOfOwnedCriticalSections : 0
以下示例显示 TEB 结构中的进程 ID,显示指向所引用结构成员的指针的使用。
0:000> ?? @$teb->ClientId.UniqueProcess
void * 0x0000059c
C++ 表达式中的运算符
可以使用圆括号代替优先级规则。
如果将 C++ 表达式的一部分括在括号中,并在表达式之前添加两个 at 符号 (@@),则会根据 MASM 表达式规则来解释该表达式。 不能在两个 at 符号和左括号之间添加空格。 此表达式的最终值作为 ULONG64 值传递给 C++ 表达式计算器。 还可以使用 @@c++( ... )
或 @@masm( ... )
指定表达式计算器。
在 C++ 语言中,数据类型的表示与往常一样。 表示数组 ([ ])、指针成员 (->)、UDT 成员 (.) 和类成员 (::) 的符号都可以识别。 支持所有算术运算符,包括赋值运算符和副作用运算符。 但是,不能使用 new
、delete
和 throw
运算符,并且实际上无法调用函数。
支持指针运算,偏移量缩放正确。 请注意,不能向函数指针添加偏移量。 如果必须向函数指针添加偏移量,请先将偏移量强制转换为字符指针。
与 C++ 中一样,如果使用具有无效数据类型的运算符,则会发生语法错误。 调试器的 C++ 表达式分析器使用的规则比大多数 C++ 编译器稍微宽松一些,但所有主要规则都是强制执行的。 例如,不能移动非整数值。
可以使用以下运算符。 每个单元格中的运算符优先于较低单元格中的操作符。 同一单元格中的运算符具有相同的优先级,并且从左到右进行分析。
与 C++ 一样,当表达式的值已知时,表达式求值结束。 此结尾使你能够有效地使用表达式,例如 ?? myPtr && *myPtr
。
引用和类型强制转换
运算符 | 含义 |
---|---|
表达式 // 注释 | 忽略所有后续文本 |
Class :: Member | 类的成员 |
Class ::~Member | 类的成员(析构函数) |
:: Name | 全局 |
结构。 字段 | 结构中的字段 |
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 值 | 表达式的大小 |
sizeof( type ) | 数据类型的大小 |
++ LValue | 增量(计算前) |
-- LValue | 减量(计算前) |
~ 值 | 位补充 |
! 值 | 非(布尔值) |
值 | 一元减 |
+ 值 | 一元加 |
& LValue | 数据类型的地址 |
值 | Dereference |
结构。 指针 | 指向结构成员的指针 |
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 | 按位 XOR(异或) |
Value | Value | 按位“或” |
值 && 值 | 逻辑与 |
Value || Value | 逻辑或 |
以下示例假设伪寄存器的设置如图所示。
0:000> r $t0 = 0
0:000> r $t1 = 1
0:000> r $t2 = 2
0:000> ?? @$t1 + @$t2
unsigned int64 3
0:000> ?? @$t2/@$t1
unsigned int64 2
0:000> ?? @$t2|@$t1
unsigned int64 3
分配
运算符 | 含义 |
---|---|
LValue = Value | 分配 |
LValue *= Value | 相乘并赋值 |
LValue /= Value | 除并赋值 |
LValue %= Value | 取模并赋值 |
LValue += Value | 添加并赋值 |
LValue -= Value | 相减并赋值 |
LValue<<= Value | 向左移动并赋值 |
LValue>>= Value | 向右移动并赋值 |
LValue &= 值 | AND 和赋值 |
LValue |= Value | OR 和赋值 |
LValue ^= Value | XOR 和赋值 |
计算
运算符 | 含义 |
---|---|
Value ? Value : Value | 条件计算 |
Value , Value | 计算所有值,然后丢弃除最右边的值以外的所有值 |
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) | 返回已知类型的结构的大小,包括指定字段。 |
此示例显示使用 #FIELD_OFFSET
宏计算结构中字段的字节偏移量。
0:000> ?? #FIELD_OFFSET(_PEB, BeingDebugged)
long 0n2