本主题介绍如何使用 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
现在默认表达式计算器已更改,可以使用 ?(Evaluate Expression) 命令来显示 MASM 表达式。 本示例将十六进制数值 8 加载到 rip 寄存器。
0:000> ? @rip + 8
Evaluate expression: 140709230544760 = 00007ff9`6bb40778
在寄存器语法中,寄存器@rip的引用被更详细地介绍。
调试器 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'xxxxxxxxxxx 格式输入十六进制 64 位值。 还可以省略重音符号(`)。 如果包含重音符,自动符号扩展 将被禁用。
此示例演示如何添加十进制值、八进制值和二进制值以注册 10。
? @r10 + 0x10 + 0t10 + 0y10
Evaluate expression: 26 = 00000000`0000001a
调试器 MASM 表达式中的符号
在 MASM 表达式中,任何符号的数值都是其内存地址。 根据符号引用的内容,此地址是全局变量、局部变量、函数、段、模块或任何其他已识别标签的地址。
若要指定与地址关联的模块,请在符号名称之前包括模块名称和感叹号(!)。 如果符号可以解释为十六进制数,请在符号名称之前包括模块名称和感叹号,或只是感叹号。 有关符号识别的详细信息,请参阅 符号语法和符号匹配。
使用两个冒号 (::) 或两个下划线 (__) 来表示类的成员。
仅当在符号名称前面添加模块名称和感叹号时,才在符号名称中使用重音符(`)或撇号(')。
MASM 表达式中的数值运算符
可以使用一元运算符修改表达式的任何组件。 可以使用二元运算符合并任意两个组件。 一元运算符优先于二进制运算符。 使用多个二进制运算符时,运算符遵循下表中所述的固定优先规则。
始终可以使用括号来替代优先规则。
如果 MASM 表达式的一部分括在括号中,并且两个符号(@@)出现在表达式之前,则表达式将按照 C++表达式规则进行解释。 不能在两个标志和左括号之间添加一个空格。 还可以通过使用 @@c++(... ) 或 @@masm(... )来指定表达式计算器。
执行算术计算时,MASM 表达式计算器会将所有数字和符号视为ULONG64类型。
一元地址运算符假定 DS 是地址的默认段。 运算符优先级决定表达式的计算顺序。 如果相邻运算符具有相等的优先级,则表达式从左到右计算。
可以使用以下一元运算符。
| 操作员 | 含义 |
|---|---|
+ |
一元加 |
- |
一元减 |
不 |
如果参数为零,则返回 1。 对任何非零参数返回零。 |
你好 |
高 16 位 |
低 |
低 16 位 |
由 |
指定地址中的低序字节。 |
$pby |
与它采用物理地址不同。 只能读取使用默认缓存行为的物理内存。 |
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) |
或 (或 |) |
按位 OR |
如果表达式为 true,则 =<>、 == 和 != 比较运算符的计算结果为 1;如果表达式为 false,则计算结果为零。 单一等号 (=) 与双等号 (==) 相同。 不能在 MASM 表达式中使用副作用或赋值。
无效操作(如除以零)会导致“操作数错误”返回到 调试器命令窗口。
可以使用 == 比较运算符检查返回的值是否与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)映像基址。 通过在 Address 指定的映像的 PE 映像标头中查找图像入口点来找到该条目。 |
$scmp(“String1”、“String2”) |
评估结果为 -1、0 或 1,就像使用 strcmp C 函数一样。 |
$sicmp(“String1”、“String2”) |
计算结果为 -1、0 或 1,如 stricmp Microsoft Win32 函数。 |
$spat(“String”, “Pattern”) |
根据字符串是否与模式匹配,计算结果为 TRUE 或 FALSE。 匹配不区分大小写。 模式 可以包含各种通配符和说明符。 有关语法的详细信息,请参阅 字符串通配符语法。 |
$vvalid(地址,长度) |
确定从 Address 开始并持续 Length 字节的内存范围是否有效。 如果内存有效, 则$vvalid 计算结果为 1。 如果内存无效, 则$vvalid 的计算结果为 0。 |
例子
下面显示如何调查已加载模块周围的有效内存范围。
首先,使用 lm(List Loaded Modules 命令)确定感兴趣的区域的地址。
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 查看我们之前用 lm 命令确定地址的 COMCTL32 的入口点。 它从 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 表达式中的寄存器和Pseudo-Registers
可以在 MASM 表达式中使用寄存器和伪寄存器。 可以在所有寄存器和伪寄存器之前添加符号 (@)。 at 符号会导致调试器更快地访问该值。 对于最常见的基于 x86 的寄存器,不需要此 @ 符号。 对于其他寄存器和伪寄存器,建议添加符号'@',但实际上并非必需。 如果省略不太常见的寄存器的 at 符号,调试器会尝试将文本分析为十六进制数,然后作为符号,最后作为寄存器。
还可以使用句点 (.) 来指示当前指令指针。 不应在此时间段之前添加 @ 符号,并且不能使用句点作为 r 命令的第一个参数。 此时间段的含义与 $ip 伪寄存器相同。
有关寄存器和伪寄存器的详细信息,请参阅 寄存器语法 和 Pseudo-Register 语法。
使用 “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:000> ? NOT(($ip = .) AND ($ip = @rip) AND (@rip =. ))
Evaluate expression: 0 = 00000000`00000000
MASM 表达式中的源行号
可以在 MASM 表达式中使用源文件和行号表达式。 必须使用反引号(`)将这些表达式括起来。 有关语法的详细信息,请参阅 源行语法。