對優化程式碼和內嵌函式進行偵錯
針對Windows 8,偵錯工具和 Windows 編譯器已增強,因此您可以偵錯優化的程式碼和對內嵌函式進行偵錯。 不論參數和區域變數是儲存在暫存器還是堆疊上,偵錯工具都會顯示參數和區域變數。 偵錯工具也會在呼叫堆疊中顯示內嵌函式。 針對內嵌函式,偵錯工具會顯示區域變數,但不會顯示參數。
當程式碼優化時,它會轉換為更快執行,並使用較少的記憶體。 有時候會移除函式,因為移除不正確程式碼、正在合併的程式碼,或內嵌放置的函式。 您也可以移除區域變數和參數。 許多程式碼優化都會移除不需要或使用的區域變數;其他優化會移除迴圈中的變數。 常見的子運算式刪除會將區域變數合併在一起。
Windows 的零售組建已優化。 因此,如果您執行的是 Windows 的零售組建,設計成能與優化程式碼搭配運作的偵錯工具特別有用。 若要讓優化程式碼的偵錯生效,需要兩個主要功能:1) 區域變數的精確顯示,以及在呼叫堆疊上顯示 2 個) 內嵌函式。
正確顯示區域變數和參數
為了協助正確顯示區域變數和參數,編譯器會記錄符號 (PDB) 檔案中區域變數和參數位置的相關資訊。 這些位置記錄會追蹤變數的儲存位置,以及這些位置有效的特定程式碼範圍。 這些記錄不僅有助於追蹤暫存器或堆疊位置) 變數中 (的位置,也有助於追蹤變數的移動。 例如,參數可能先在暫存器 RCX 中,但移至堆疊位置以釋出 RCX,然後在迴圈中大量使用時移至註冊 R8,然後在程式碼離開迴圈時移至不同的堆疊位置。 Windows 偵錯工具會取用 PDB 檔案中的豐富位置記錄,並使用目前的指令指標來選取區域變數和參數的適當位置記錄。
Visual Studio 中 [區域變數] 視窗的這個螢幕擷取畫面會顯示優化 64 位應用程式中函式的參數和區域變數。 函式不是內嵌的,因此我們同時看到參數和區域變數。
您可以使用 dv -v 命令來查看參數和區域變數的位置。
請注意,[區域變數] 視窗會顯示參數正確,即使參數儲存在暫存器中也一樣。
除了使用基本類型追蹤變數之外,位置也會記錄追蹤本機結構和類別的資料成員。 下列偵錯工具輸出會顯示本機結構。
0:000> dt My1
Local var Type _LocalStruct
+0x000 i1 : 0n0 (edi)
+0x004 i2 : 0n1 (rsp+0x94)
+0x008 i3 : 0n2 (rsp+0x90)
+0x00c i4 : 0n3 (rsp+0x208)
+0x010 i5 : 0n4 (r10d)
+0x014 i6 : 0n7 (rsp+0x200)
0:000> dt My2
Local var @ 0xefa60 Type _IntSum
+0x000 sum1 : 0n4760 (edx)
+0x004 sum2 : 0n30772 (ecx)
+0x008 sum3 : 0n2 (r12d)
+0x00c sum4 : 0n0
以下是先前偵錯工具輸出的相關一些觀察。
- 本機結構 My1 說明編譯器可以將本機結構資料成員分散到暫存器和非連續堆疊位置。
- 命令 dt My2的輸出與命令dt _IntSum 0xefa60的輸出不同。 您無法假設本機結構會佔用連續的堆疊記憶體區塊。 在 My2的情況下,只會
sum4
保留在原始堆疊區塊中;其他三個資料成員會移至暫存器。 - 某些資料成員可以有多個位置。 例如, My2.sum2 有兩個位置:一個是註冊 ECX (Windows 偵錯工具選擇) ,另一個則是0xefa60+0x4 (原始堆疊位置) 。 這也適用于基本類型區域變數,而 Windows 偵錯工具會強制執行前導啟發學習法來判斷要使用的位置。 例如,註冊位置一律會位在堆疊位置上。
在呼叫堆疊上顯示內嵌函式
在程式碼優化期間,某些函式會依序排列。 也就是說,函式的主體會直接放在程式碼中,例如宏展開。 沒有函式呼叫,也不會傳回給呼叫端。 為了方便顯示內嵌函式,編譯器會將資料儲存在 PDB 檔案中,以協助解碼內嵌函式的程式碼區塊 (,也就是屬於內嵌) 之呼叫端函式中的程式碼區塊序列,以及這些程式碼區塊中已設定範圍區域變數的區域變數 (區域變數) 。 此資料可協助偵錯工具在堆疊回溯過程中包含內嵌函式。
假設您編譯應用程式,並強制名為 func1
的函式內嵌。
__forceinline int func1(int p1, int p2, int p3)
{
int num1 = 0;
int num2 = 0;
int num3 = 0;
...
}
您可以使用 bm 命令在 設定 func1
中斷點。
0:000> bm MyApp!func1
1: 000007f6`8d621088 @!"MyApp!func1" (MyApp!func1 inlined in MyApp!main+0x88)
0:000> g
Breakpoint 1 hit
MyApp!main+0x88:
000007f6`8d621088 488d0d21110000 lea rcx,[MyApp!`string' (000007f6`8d6221b0)]
進入 一個步驟 func1
之後,您可以使用 k 命令在呼叫堆疊上查看 func1
。 您可以使用 dv 命令來查看 的 func1
區域變數。 請注意,區域變數 num3
會顯示為無法使用。 區域變數在優化程式碼中可能因為許多原因而無法使用。 可能是變數不存在於優化程式碼中。 可能是變數尚未初始化,或變數已不再使用。
0:000> p
MyApp!func1+0x7:
000007f6`8d62108f 8d3c33 lea edi,[rbx+rsi]
0:000> knL
# Child-SP RetAddr Call Site
00 (Inline Function) --------`-------- MyApp!func1+0x7
01 00000000`0050fc90 000007f6`8d6213f3 MyApp!main+0x8f
02 00000000`0050fcf0 000007ff`c6af0f7d MyApp!__tmainCRTStartup+0x10f
03 00000000`0050fd20 000007ff`c7063d6d KERNEL32!BaseThreadInitThunk+0xd
04 00000000`0050fd50 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
0:000> dv -v
00000000`0050fcb0 num1 = 0n0
00000000`0050fcb4 num2 = 0n0
<unavailable> num3 = <value unavailable>
如果您在堆疊追蹤中查看框架 1,您可以看到函式的 main
區域變數。 請注意,其中兩個變數會儲存在暫存器中。
0:000> .frame 1
01 00000000`0050fc90 000007f6`8d6213f3 MyApp!main+0x8f
0:000> dv -v
00000000`0050fd08 c = 0n7
@ebx b = 0n13
@esi a = 0n6
Windows 偵錯工具會匯總 PDB 檔案中的資料,以尋找特定函式內嵌的所有位置。 您可以使用 x 命令來列出內嵌函式的所有呼叫端網站。
0:000> x simple!MoreCalculate
00000000`ff6e1455 simple!MoreCalculate = (inline caller) simple!wmain+8d
00000000`ff6e1528 simple!MoreCalculate = (inline caller) simple!wmain+160
0:000> x simple!Calculate
00000000`ff6e141b simple!Calculate = (inline caller) simple!wmain+53
因為 Windows 偵錯工具可以列舉內嵌函式的所有呼叫端月臺,所以它可以藉由計算來自呼叫端月臺的位移,在內嵌函式內設定中斷點。 您可以使用 bm 命令 (,用來設定符合正則運算式模式的中斷點) 來設定內嵌函式的中斷點。
Windows 偵錯工具會將針對特定內嵌函式設定的所有中斷點分組至中斷點容器。 您可以使用bd、bc等命令來操作整個中斷點容器。 請參閱下列 bd 3 和 bc3 命令範例。 您也可以操作個別中斷點。 請參閱下列 為 2 個命令範例。
0:000> bm simple!MoreCalculate
2: 00000000`ff6e1455 @!"simple!MoreCalculate" (simple!MoreCalculate inlined in simple!wmain+0x8d)
4: 00000000`ff6e1528 @!"simple!MoreCalculate" (simple!MoreCalculate inlined in simple!wmain+0x160)
0:000> bl
0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52] 0001 (0001) 0:**** simple!wmain
3 e <inline function> 0001 (0001) 0:**** {simple!MoreCalculate}
2 e 00000000`ff6e1455 [n:\win7\simple\simple.cpp @ 58] 0001 (0001) 0:**** simple!wmain+0x8d (inline function simple!MoreCalculate)
4 e 00000000`ff6e1528 [n:\win7\simple\simple.cpp @ 72] 0001 (0001) 0:**** simple!wmain+0x160 (inline function simple!MoreCalculate)
0:000> bd 3
0:000> be 2
0:000> bl
0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52] 0001 (0001) 0:**** simple!wmain
3 d <inline function> 0001 (0001) 0:**** {simple!MoreCalculate}
2 e 00000000`ff6e1455 [n:\win7\simple\simple.cpp @ 58] 0001 (0001) 0:**** simple!wmain+0x8d (inline function simple!MoreCalculate)
4 d 00000000`ff6e1528 [n:\win7\simple\simple.cpp @ 72] 0001 (0001) 0:**** simple!wmain+0x160 (inline function simple!MoreCalculate)
0:000> bc 3
0:000> bl
0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52] 0001 (0001) 0:**** simple!wmain
因為內嵌函式沒有明確的呼叫或傳回指令,所以偵錯工具對於來源層級逐步執行特別具挑戰性。 例如,如果下 (一個指令是內嵌函式) 的一部分,或者您可以多次逐步執行並跳出相同的內嵌函式 (,因為內嵌函式的程式碼區塊已由編譯器) 分割並移動。 為了保留熟悉的逐步執行體驗,Windows 偵錯工具會維護每個程式碼指令位址的小型概念呼叫堆疊,並建置內部狀態機器來執行逐步執行逐步執行、逐步執行和逐步執行作業。 這會為非內嵌函式的逐步執行體驗提供合理的精確近似值。
其他資訊
注意 您可以使用 .inline 0 命令來停用內嵌函式偵錯。 .inline 1命令會啟用內嵌函式偵錯。 標準偵錯技術