C++ 数字和运算符
本文介绍如何在 Windows 调试工具中使用 C++ 表达式语法。
调试器接受两种不同类型的数值表达式:C++ 表达式和 Microsoft 宏汇编程序 (MASM) 表达式。 其中每个表达式都遵循自己的输入和输出语法规则。
有关何时使用每种语法类型的详细信息,请参阅 计算表达式 和 ? evaluate expression 命令。
C++ 表达式分析器支持所有形式的 C++ 表达式语法。 语法包括所有数据类型,包括指针、浮点数和数组,以及所有 C++ 一元运算符和二进制运算符。
调试器中的 “监视 ”和“ 局部变量” 窗口始终使用 C++ 表达式计算器。
在以下示例中, ?? evaluate C++ 表达式 命令显示指令指针寄存器的值。
0:000> ?? @eip
unsigned int 0x771e1a02
可以使用 C++ sizeof
函数来确定结构的大小。
0:000> ?? (sizeof(_TEB))
unsigned int 0x1000
将表达式计算器设置为 C++
使用 .expr choose 表达式 计算器查看默认表达式计算器并将其更改为 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
寄存器参考,请参阅 Register 语法。
在此示例中,0xD 的十六进制值将添加到 eip 寄存器。
0:000> ? @eip + 0xD
Evaluate expression: 1998461455 = 771e1a0f
C++ 表达式中的寄存器和伪寄存器
可以在 C++ 表达式中使用寄存器和伪寄存器。 必须在寄存器或伪寄存器之前添加 @ 符号。
表达式计算器会自动执行正确的强制转换。 实际寄存器和整数值伪寄存器将强制转换为 ULONG64
。 所有地址都强制转换为 PUCHAR
、$thread
强制转换为 ETHREAD*
、EPROCESS*
$proc
强制转换为 、$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 表达式。
可以输入 xxxxxxxx'xxxxxxxx 格式的十六进制 64 位值。 还可以省略重音符 (“) 。 这两种格式生成相同的值。
可以将 L
、 U
和 I64
后缀与整数值一起使用。 创建的数字的实际大小取决于后缀和输入的数字。 有关此解释的详细信息,请参阅 C++ 语言参考。
C++ 表达式计算器的 输出 保留 C++ 表达式规则指定的数据类型。 但是,如果将此表达式用作命令的参数,则始终会进行强制转换。 例如,当整数值用作命令参数中的地址时,不必将整数值强制转换为指针。 如果表达式的值无法有效强制转换为整数或指针,则会发生语法错误。
可以将 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
表达式。
引用和类型强制转换
运算符 | 含义 |
---|---|
表达 // 评论 | 忽略所有后续文本 |
类 :: Member | 类的成员 |
类 ::~Member | 类 (析构函数) 的成员 |
:: 名称 | 全球 |
结构 。 字段 | 结构中的字段 |
指针 ->Field | 引用结构中的字段 |
名称 [integer] | 数组下标 |
LValue ++ | 评估) 后递增 ( |
LValue -- | 评估) 后 (递减 |
dynamic_cast<类型> (值) | 始终) 执行 Typecast ( |
< static_cast类型> (值) | 始终) 执行 Typecast ( |
reinterpret_cast<类型> (值) | 始终) 执行 Typecast ( |
const_cast<类型> (值) | 始终) 执行 Typecast ( |
值操作
运算符 | 含义 |
---|---|
(类型) 值 | 始终) 执行 Typecast ( |
sizeof值 | 表达式的大小 |
sizeof ( 类型 ) | 数据类型的大小 |
++ LValue | 在评估) 之前递增 ( |
-- LValue | 评估) 之前的递减 ( |
~ Value | 位补码 |
! 值 | 不 (布尔) |
值 | 一元负 |
+ Value | 一元加 |
& LValue | 数据类型的地址 |
值 | Dereference |
结构 。 指针 | 指向 结构成员的指针 |
指针 -> * 指针 | 指向所引用结构的成员的指针 |
算术
运算符 | 含义 |
---|---|
值 | 乘法 |
价值 / 价值 | 部门 |
价值 % 价值 | Modulus |
价值 + 价值 | 加法 |
价值 - 价值 | 减法 |
价值<<价值 | 按位左移 |
价值>>价值 | 按位向右移 |
价值<价值 | 小于 (比较) |
价值<= 值 | 小于或等于 (比较) |
价值>价值 | 大于 (比较) |
价值>= 值 | 大于或等于 (比较) |
价值 == 价值 | 相等 (比较) |
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 = 价值 | 分配 |
LValue *= 价值 | 相乘 和赋值 |
LValue /= 价值 | 除并赋值 |
LValue %= 价值 | 取模并赋值 |
LValue += 价值 | 添加并赋值 |
LValue -= 价值 | 相减并赋值 |
LValue<<= 值 | 向左移并分配 |
LValue>>= 值 | 向右移动并分配 |
LValue &= Value | AND 和分配 |
LValue |= 价值 | OR 和分配 |
LValue ^= 价值 | XOR 和分配 |
计算
运算符 | 含义 |
---|---|
值 ? 值 : 值 | 条件计算 |
Value 、 Value | 计算所有值,然后丢弃除最右侧值以外的所有值 |
C++ 表达式中的宏
可以在 C++ 表达式中使用宏。 必须在宏之前添加数字符号 (#) 。
可以使用以下宏。 这些宏与具有相同名称的 Microsoft Windows 宏具有相同的定义。 Windows 宏在 中 Winnt.h
定义。
宏 | 返回值 |
---|---|
#CONTAINING_RECORD (地址、类型、字段) | 返回结构实例的基址,给定结构的类型和结构内字段的地址。 |
#FIELD_OFFSET (类型,字段) | 返回已知结构类型中命名字段的字节偏移量。 |
#RTL_CONTAINS_FIELD (结构、大小、字段) | 指示给定字节大小是否包含所需字段。 |
#RTL_FIELD_SIZE (类型,字段) | 返回已知类型结构中字段的大小,而无需字段的类型。 |
#RTL_NUMBER_OF (Array) | 返回静态大小数组中的元素数。 |
#RTL_SIZEOF_THROUGH_FIELD (类型,字段) | 返回已知类型结构的大小,包括指定字段。 |
此示例演示如何使用 #FIELD_OFFSET
宏来计算结构中字段的字节偏移量。
0:000> ?? #FIELD_OFFSET(_PEB, BeingDebugged)
long 0n2
另请参阅
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈