MASM 数字和运算符

本主题介绍在 Windows 调试工具中使用 Microsoft 宏汇编器 (MASM) 表达式语法。

调试器接受两种不同类型的数字表达式: C++ 表达式和 MASM 表达式。 这些表达式中的每一个都遵循自己的输入和输出语法规则。

有关每种语法类型的使用时间,请参阅评估表达式?(评估表达式)

在本示例中,? 命令使用 MASM 表达式计算器来显示指令指针寄存器的值。

0:000> ? @rip
Evaluate expression: 140709230544752 = 00007ff9`6bb40770

将表达式计算器设置为 MASM

使用 .expr(选择表达式评估器)查看默认表达式计算器,并将其更改为 MASM。

0:000> .expr /s masm
Current expression evaluator: MASM - Microsoft Assembler expressions

既然默认表达式求值器已经更改,那么 ?(评估表达式)命令可用于显示 MASM 表达式。 此示例将十六进制值 8 添加到 rip 寄存器中。

0:000> ? @rip + 8
Evaluate expression: 140709230544760 = 00007ff9`6bb40778

@rip 的寄存器引用在寄存器语法中有更详细的说明。

调试器 MASM 表达式中的数字

可以在 MASM 表达式中输入基数为 16、10、8 或 2 的数字。

使用 n(设置基数)命令将默认基数设置为 16、10 或 8。 所有无后缀数字都以此为基数进行解释。 可以通过指定 0x 前缀(十六进制)、0n 前缀(十进制)、0t 前缀(八进制)或 0y 前缀(二进制)来替代默认基数。

也可以在数字后添加 h 来指定十六进制数字。 可以在数字中使用大写或小写字母。 例如,“0x4AB3”、“0X4aB3”、“4AB3h”、“4ab3h”和“4aB3H”的含义相同。

如果不在表达式的前缀后添加数字,则数字读作 0。 因此,0 可以写成 0、前缀后跟 0,也可以只写前缀。 例如,在十六进制中,“0”、“0x0”和“0x”具有相同的含义。

可以输入 xxxxxxxx`xxxxxxxx 格式的十六进制 64 位数值。 也可以省略重音 (`)。 如果包含严重重音符号,自动符号扩展将被禁用。

本例演示如何在寄存器 10 中添加十进制、八进制和二进制数值。

? @r10 + 0x10 + 0t10 + 0y10
Evaluate expression: 26 = 00000000`0000001a

调试器 MASM 表达式中的符号

在 MASM 表达式中,任何符号的数值都是其内存地址。 根据符号所指的内容,该地址是全局变量、局部变量、函数、段、模块或任何其他已识别标签的地址。

要指定地址与哪个模块相关联,请在符号名称前加上模块名称和感叹号 (!)。 如果符号可解释为十六进制数,则在符号名称前加上模块名称和感叹号,或只加上感叹号。 有关符号识别的详细信息,请参阅符号语法和符号匹配

使用两个冒号 (::) 或两个下划线 (__) 来表示类的成员。

只有在符号前添加模块名称和感叹号时,才能在符号名称中使用重音符号 (`) 或撇号 ( `)。

MASM 表达式中的数字运算符

可以使用一元运算符来修改表达式中的任何组件。 可以使用二进制运算符来合并任意两个组件。 一元运算符优先于二元运算符。 在使用多个二元运算符时,运算符遵循下表所述的固定优先级规则。

总是可以使用括号来覆盖优先级规则。

如果 MASM 表达式的部分内容被括号括起来,且表达式前出现两个 at 符号 (@@),则将根据 C++ 表达式规则来解释该表达式。 两个 at 符号和开头的括号之间不能加空格。 还可以使用 @@c++( ... )@@masm( ... ) 来指定表达式计算器

在执行算术计算时,MASM 表达式计算器会将所有数字和符号都视为 ULONG64 类型。

一元地址运算符将 DS 作为地址的默认分段。 表达式按运算符优先级顺序进行计算。 如果相邻运算符具有相同优先级,则表达式从左到右依次求值。

可以使用以下一元运算符。

运算符 含义

+

一元加

-

一元减

not

如果参数为零,则返回 1。 对于任何非零参数,返回值均为零。

你好

高 16 位

low

低 16 位

by

指定地址的低位字节。

$pby

by 相同,但它需要一个物理地址。 只能读取使用默认缓存行为的物理内存。

wo

指定地址的低位字。

$pwo

wo 相同,但它需要一个物理地址。 只能读取使用默认缓存行为的物理内存。

dwo

从指定地址输入双字。

$pdwo

dwo 相同,但它需要一个物理地址。 只能读取使用默认缓存行为的物理内存。

qwo

从指定地址输入四字。

$pqwo

qwo 相同,但它需要一个物理地址。 只能读取使用默认缓存行为的物理内存。

poi

从指定地址读取指针大小的数据。 指针大小为 32 位或 64 位。 在内核调试中,此大小基于目标计算机的处理器。 因此,如果需要指针大小的数据,最好使用 poi 运算符。

$ppoi

poi 相同,但它需要一个物理地址。 只能读取使用默认缓存行为的物理内存。

示例

以下示例展示了如何使用 poi 取消引用指针,以查看存储在该内存位置的值。

首先确定感兴趣的内存地址。 例如,我们可以查看线程结构,然后决定要查看 CurrentLocale 的值。

0:000> dx @$teb
@$teb                 : 0x8eed57b000 [Type: _TEB *]
    [+0x000] NtTib            [Type: _NT_TIB]
    [+0x038] EnvironmentPointer : 0x0 [Type: void *]
    [+0x040] ClientId         [Type: _CLIENT_ID]
    [+0x050] ActiveRpcHandle  : 0x0 [Type: void *]
    [+0x058] ThreadLocalStoragePointer : 0x1f8f9d634a0 [Type: void *]
    [+0x060] ProcessEnvironmentBlock : 0x8eed57a000 [Type: _PEB *]
    [+0x068] LastErrorValue   : 0x0 [Type: unsigned long]
    [+0x06c] CountOfOwnedCriticalSections : 0x0 [Type: unsigned long]
    [+0x070] CsrClientThread  : 0x0 [Type: void *]
    [+0x078] Win32ThreadInfo  : 0x0 [Type: void *]
    [+0x080] User32Reserved   [Type: unsigned long [26]]
    [+0x0e8] UserReserved     [Type: unsigned long [5]]
    [+0x100] WOW32Reserved    : 0x0 [Type: void *]
    [+0x108] CurrentLocale    : 0x409 [Type: unsigned long]

CurrentLocale 位于 TEB 开始位置之后的 0x108。

0:000> ? @$teb + 0x108
Evaluate expression: 613867303176 = 0000008e`ed57b108

使用 poi 来取消引用该地址。

0:000> ? poi(0000008e`ed57b108)
Evaluate expression: 1033 = 00000000`00000409

返回值 409 与 TEB 结构中 CurrentLocale 的值相匹配。

或者使用 poi 和括号来取消引用计算出的地址。

0:000> ? poi(@$teb + 0x108)
Evaluate expression: 1033 = 00000000`00000409

使用 bywo 一元操作符从目标地址返回字节或字。

0:000> ? by(0000008e`ed57b108)
Evaluate expression: 9 = 00000000`00000009
0:000> ? wo(0000008e`ed57b108)
Evaluate expression: 1033 = 00000000`00000409

二元运算符

你可以使用以下二元运算符。 每个单元格中的运算符优先于较低单元格中的那些操作符。 同一单元格中的运算符具有相同的优先级,并且从左到右进行分析。

运算符 含义

*

/

mod就(或 %)

乘法

整数除法

取模(余数)

+

-

加法

减法

<<

>>

>>>

左移

逻辑右移

算术右移

=(或 ==)

<

>

<=

>=

!=

等于

小于

大于

小于或等于

大于或等于

不等于

and(或 &)

位与

xor(或 ^)

按位 XOR(异或)

or(或 |)

按位“或”

如果表达式为 true,则 <、>、=、== 和 != 比较运算符的值为 1;如果表达式为 false,则值为 0。 单等号 (=) 与双等号 (==) 相同。 不能在 MASM 表达式中使用副作用或赋值。

无效操作(如除以 0)会导致调试器命令窗口返回“操作符错误”。

可以使用 == 比较运算符来检查返回值是否与 0x409 匹配。

0:000> ? poi(@$teb + 0x108)==0x409
Evaluate expression: 1 = 00000000`00000001

MASM 表达式中的非数字运算符

还可以在 MASM 表达式中使用以下附加运算符。

运算符 含义

$fnsucc(FnAddress, RetVal, Flag)

RetVal 值解释为位于 FnAddress 地址的函数的返回值。 如果该返回值符合成功代码的条件,$fnsucc 将返回 TRUE。 否则,$fnsucc 将返回 FALSE

如果返回类型是 BOOL、bool、HANDLE、HRESULT 或 NTSTATUS,则 $fnsucc 将正确理解指定的返回值是否符合成功代码的条件。 如果返回类型是指针,则除 NULL 以外的所有值都是成功代码。 对于任何其他类型,成功与否由 Flag 的值决定。 如果 Flag 为 0,则 RetVal 的非零值为成功。 如果 Flag 为 1,则 RetVal 的零值为成功。

$iment(地址

返回已加载模块列表中图像入口点的地址。 地址指定可移植可执行文件 (PE) 映像的基址。 在地址指定的图像的 PE 图像头中查找图像入口点,即可找到该入口。

该功能既可用于模块列表中已有的模块,也可通过使用 bu 命令来设置未解析的断点

$scmp("String1", "String2")

值为-1、0 或 1,就像使用 strcmp C 函数的 strcmp 一样。

$sicmp("String1", "String2")

stricmp Microsoft Win32 函数一样,求值为-1、0 或 1。

$spat("String", "Pattern")

根据 String 是否匹配 Pattern,计算结果为 TRUEFALSE。 匹配不区分大小写。 Pattern 可以包含各种通配符字符和说明符。 有关语法的详细信息,请参阅字符串通配符语法

$vvalid(地址长度)

确定从 Address 开始并延伸至 Length 字节的内存范围是否有效。 如果内存有效,则 $vvalid 的计算结果为 1。 如果内存无效,则 $vvalid 的计算结果为 0。

示例

下面显示了如何对已加载模块周围有效内存的范围使用调查

首先确定相关区域的地址,例如使用 lm(列出加载的模块)命令。


0:000> lm
start             end                 module name
00007ff6`0f620000 00007ff6`0f658000   notepad    (deferred)
00007ff9`591d0000 00007ff9`5946a000   COMCTL32   (deferred)        
...

使用 $vvalid 检查内存范围。

0:000> ? $vvalid(0x00007ff60f620000, 0xFFFF)
Evaluate expression: 1 = 00000000`00000001

使用 $vvalid 确认这个较大的范围是否为无效内存范围。

0:000> ? $vvalid(0x00007ff60f620000, 0xFFFFF)
Evaluate expression: 0 = 00000000`00000000

这也是一个无效范围。

0:000> ? $vvalid(0x0, 0xF)
Evaluate expression: 0 = 00000000`00000000

当内存范围有效时,使用 not 返回零。

0:000> ? not($vvalid(0x00007ff60f620000, 0xFFFF))
Evaluate expression: 0 = 00000000`00000000

使用 $imnet 查看 COMCTL32 的入口点,我们之前使用 lm 命令确定了地址。 它从 00007ff9'591d0000 开始。

0:000> ? $iment(00007ff9`591d0000)
Evaluate expression: 140708919287424 = 00007ff9`59269e80

反汇编返回的地址,以便检查入口点代码。

0:000> u 00007ff9`59269e80
COMCTL32!DllMainCRTStartup:
00007ff9`59269e80 48895c2408      mov     qword ptr [rsp+8],rbx
00007ff9`59269e85 4889742410      mov     qword ptr [rsp+10h],rsi
00007ff9`59269e8a 57              push    rdi

输出中显示 COMCTL32,确认这是该模块的入口点。

MASM 表达式中的寄存器和伪寄存器

可以在 MASM 表达式中使用寄存器和伪寄存器。 可以在所有寄存器和伪寄存器前添加 at 符号 (@)。 at 符号会导致调试器更快地访问数值。 对于最常见的基于 x86 的寄存器来说,@ 符号并非必需的。 对于其他寄存器和伪寄存器,我们建议添加 at 号,但实际上并非必须。 如果对不常用的寄存器省略 at 符号,调试器会尝试将文本解析为十六进制数,然后解析为符号,最后解析为寄存器。

也可以使用句点 (.) 来表示当前指令指针。 不能在句号前添加 @ 符号,也不能使用句号作为 r 命令的第一个参数。 该期间与 $ip 伪寄存器的含义相同。

有关寄存器和伪寄存器的详细信息,请参阅寄存器语法伪寄存器语法

使用 r register 命令查看 @rip 寄存器的值是否为 00007ffb`7ed00770。

0:000> r
rax=0000000000000000 rbx=0000000000000010 rcx=00007ffb7eccd2c4
rdx=0000000000000000 rsi=00007ffb7ed61a80 rdi=00000027eb6a7000
rip=00007ffb7ed00770 rsp=00000027eb87f320 rbp=0000000000000000
 r8=00000027eb87f318  r9=0000000000000000 r10=0000000000000000
r11=0000000000000246 r12=0000000000000040 r13=0000000000000000
r14=00007ffb7ed548f0 r15=00000210ea090000
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!LdrpDoDebuggerBreak+0x30:
00007ffb`7ed00770 cc              int     3

使用 . 句点快捷键也可以显示相同的值。

0:000> ? .
Evaluate expression: 140718141081456 = 00007ffb`7ed00770

我们可以使用这个 MASM 表达式确认这些值是否都等效,如果等效则返回 0。

0:000>  ? NOT(($ip = .) AND ($ip = @rip) AND (@rip =. ))
Evaluate expression: 0 = 00000000`00000000

MASM 表达式中的源行号

可以在 MASM 表达式中使用源文件和行号表达式。 必须使用重音符号 (`) 将这些表达式括起来。 有关语法的详细信息,请参阅源行语法

另请参阅

MASM 表达式与 C++ 表达式

混合表达式示例

C++ 数字和运算符

符号扩展

? (计算表达式)