本文說明如何搭配 Windows 偵錯工具使用 C++ 運算式語法。
偵錯工具接受兩種不同類型的數值運算式:C++ 運算式和 Microsoft 巨集組合器 (MASM) 運算式。 這些運算式中的每一個都遵循自己的輸入和輸出語法規則。
如需何時使用每一個語法類型的詳細資訊,請參閱 評估運算式 和 ? evaluate 運算式 指令。
C++ 運算式剖析器支援所有形式的 C++ 運算式語法。 語法包括所有資料類型,包括指標、浮點數和陣列,以及所有 C++ 一元和二進位運算子。
偵錯工具中的 Watch 和 Locals 視窗一律會使用 C++ 運算式評估器。
在下列範例中, ?? evaluate C++ 運算式 命令會顯示指令指標暫存器的值。
0:000> ?? @eip
unsigned int 0x771e1a02
我們可以使用 C++ sizeof 函數來確定結構的大小。
0:000> ?? (sizeof(_TEB))
unsigned int 0x1000
將運算式評估器設定為 C++
使用 .expr 選擇運算式 評估器來查看預設運算式評估器,並將其變更為 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 暫存器參考,請參閱 暫存器語法。
在本示例中,0xD的十六進位值被添加到eip暫存器中。
0:000> ? @eip + 0xD
Evaluate expression: 1998461455 = 771e1a0f
C++ 運算式中的暫存器和虛擬暫存器
您可以在 C++ 運算式中使用暫存器和虛擬暫存器。 @ 符號必須在暫存器或虛擬暫存器之前新增。
運算式評估器會自動執行適當的轉換。 實際暫存器和整數值偽暫存器會轉換為 ULONG64。 所有位址都被鑄造為 PUCHAR,$thread 被鑄造為 ETHREAD*,$proc 被鑄造為 EPROCESS*,$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.
參考和類型轉換
| 操作員 | 意義 |
|---|---|
| 詞 // 註解 | 忽略所有後續文字 |
| 班級 :: 會員 | 班級成員 |
| 班級 ::~會員 | 類別成員 (析構函數) |
| :: 名稱 | 全球 |
| 結構 。 欄位 | 結構中的欄位 |
| 指標->欄位 | 參考結構中的欄位 |
| 名稱 [整數] | 陣列下標 |
| LValue ++ | 增加(評估後) |
| LValue -- | 遞減(評估後) |
| dynamic_cast<型>(值) | 類型轉換 (一律執行) |
| static_cast<類型>(值) | 類型轉換 (一律執行) |
| reinterpret_cast<type>(Value) | 類型轉換 (一律執行) |
| const_cast<型>(Value) | 類型轉換(一律執行) |
價值操作
| 操作員 | 意義 |
|---|---|
| (類型) 價 | 類型轉換 (一律執行) |
| sizeofvalue | 表達式大小 |
| sizeof( 類型 ) | 資料類型的大小 |
| ++ LValue | 增量(評估前) |
| -- LValue | 遞減(評估前) |
| ~ 價值 | 位元補碼 |
| ! 價值 | 不是 (布林值) |
| 價值 | 一元減號 |
| + 價值 | 一元加號 |
| & LValue | 資料類型的位址 |
| 價值 | 取消參考 |
| 結構 。 指標 | 結構成員的指標 |
| 指標 -> * 指標 | 參考結構成員的指標 |
算術
| 操作員 | 意義 |
|---|---|
| Value Value | 乘法 |
| 值 / 值 | 部門 |
| 值 % 值 | 模數 |
| 值 + 值 | 加法 |
| 值 - 值 | 減法 |
| 值<<值 | 按位左移 |
| 值>>值 | 按位向右移動 |
| 值<值 | 小於(比較) |
| 價值<= 價值 | 小於或等於 (比較) |
| 值>值 | 大於 (用於比較) |
| 值>= 值 | 大於或等於 (比較) |
| 值 == 值 | 相等(比較) |
| 值 != 值 | 不相等(比較) |
| 價值 與 價值 | 位 AND |
| 價值 ^ 價值 | 位元異或(XOR, exclusive OR) |
| 值 | 值 | 位 OR |
| 邏輯與運算 | |
| 值 || 值 | 邏輯或 |
下列範例假設虛擬暫存器的設定如下所示。
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
任務
| 操作員 | 意義 |
|---|---|
| L值 = 值 | 指派 |
| L值 *= 值 | 乘法 和賦值 |
| L值 /= 值 | 劃分和分配 |
| L值 %= 值 | 模數和賦值 |
| L值 += 值 | 新增和分配 |
| L值 -= 值 | 減法和賦值 |
| LValue<<= Value | 左移並分配 |
| L值>>= 值 | 向右移動並分配 |
| LValue &= 值 | AND 並分配 |
| LValue |= Value | OR 並指派 |
| LValue ^= Value | 異或並分配 |
評估
| 操作員 | 意義 |
|---|---|
| 價值 ? 值 : 值 | 條件式評估 |
| 值 , 值 | 評估所有值,然後捨棄最右邊值以外的所有值 |
C++ 運算式中的巨集
您可以在 C++ 運算式中使用巨集。 您必須在巨集之前新增數字符號 (#)。
您可以使用下列巨集。 這些巨集與具有相同名稱的 Microsoft Windows 巨集具有相同的定義。 Windows 巨集定義在 Winnt.h 中。
| 巨集 | 返回值 |
|---|---|
| #CONTAINING_RECORD(地址、類型、欄位) | 傳回結構實例的基底位址,指定結構的類型和結構內欄位的位址。 |
| #FIELD_OFFSET(類型、欄位) | 傳回已知結構類型中具名字段的位元組位移。 |
| #RTL_CONTAINS_FIELD(結構體、大小、欄位) | 指出給定的位元組大小是否包含所需的欄位。 |
| #RTL_FIELD_SIZE(類型、欄位) | 傳回已知類型結構中欄位的大小,而不需要欄位的類型。 |
| #RTL_NUMBER_OF(陣列) | 傳回靜態大小陣列中的元素數目。 |
| #RTL_SIZEOF_THROUGH_FIELD(類型、欄位) | 傳回已知類型結構的大小,包括指定的欄位。 |
此範例示範如何使用巨集 #FIELD_OFFSET 來計算結構中欄位的位元組位移。
0:000> ?? #FIELD_OFFSET(_PEB, BeingDebugged)
long 0n2