本主題說明如何搭配 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加到指令指標暫存器中。
0:000> ? @rip + 8
Evaluate expression: 140709230544760 = 00007ff9`6bb40778
暫存器語法中更詳細地描述了@rip的暫存器參考。
偵錯程式 MASM 運算式中的數字
您可以將數字放入以 16、10、8 或 2 為基數的 MASM 表達式中。
使用 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 是位址的預設區段。 運算式會依運算子優先順序進行評估。 如果相鄰運算子具有相等的優先順序,則會從左到右評估運算式。
您可以使用下列一元運算子。
| 操作員 | 意義 |
|---|---|
+ |
一元加號 |
- |
一元減號 |
不是 |
如果引數為零,則傳回 1。 針對任何非零引數傳回零。 |
嗨 |
高 16 位元 |
低 |
低 16 位元 |
由 |
來自指定位址的低階位元組。 |
$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, exclusive OR) |
或 (或 |) |
位 OR |
如果表達式為 true,則 <、>、=、== 和 != 比較運算符會求值為 1;如果表達式為 false,則為 0。 單一等號 (=) 與雙等號 (==) 相同。 您無法在 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”) |
使用 strcmp C 函式時,會像使用 strcmp 一樣,評估結果為 -1、0 或 1。 |
$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
當記憶體範圍有效時,使用 非 來傳回零。
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 運算式中的暫存器和 Pseudo-Registers
您可以在 MASM 運算式內使用暫存器和虛擬暫存器。 您可以在所有暫存器和虛擬暫存器之前新增 at 符號 (@)。 at 符號會導致偵錯工具更快速地存取值。 對於最常見的 x86 型暫存器來說,不需要此 @ 符號。 對於其他暫存器和虛擬暫存器,我們建議您新增 at 符號,但實際上不是必需的。 如果您省略在較不常見的暫存器中使用的 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 運算式中使用來源檔案和行號運算式。 您必須使用重音符號 (') 來括住這些運算式。 如需語法的詳細資訊,請參閱 原始碼行語法。