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
使用 by 或 wo 一元操作符从目标地址返回字节或字。
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 图像头中查找图像入口点,即可找到该入口。 |
$scmp("String1", "String2") |
值为-1、0 或 1,就像使用 strcmp C 函数的 strcmp 一样。 |
$sicmp("String1", "String2") |
与 stricmp Microsoft Win32 函数一样,求值为-1、0 或 1。 |
$spat("String", "Pattern") |
根据 String 是否匹配 Pattern,计算结果为 TRUE 或 FALSE。 匹配不区分大小写。 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 表达式中使用源文件和行号表达式。 必须使用重音符号 (`) 将这些表达式括起来。 有关语法的详细信息,请参阅源行语法。