Debuggen von optimiertem Code und Inlinefunktionen

Für Windows 8 wurden der Debugger und der Windows-Compiler erweitert, sodass Sie optimierten Code debuggen und Inlinefunktionen debuggen können. Der Debugger zeigt Parameter und lokale Variablen an, unabhängig davon, ob sie in Registern oder im Stapel gespeichert sind. Der Debugger zeigt auch Inlinefunktionen im Aufrufstapel an. Für Inlinefunktionen zeigt der Debugger lokale Variablen, aber keine Parameter an.

Wenn Code optimiert wird, wird er transformiert, um schneller auszuführen und weniger Arbeitsspeicher zu verwenden. Manchmal werden Funktionen aufgrund der Entfernung von unzustellbarem Code, der Zusammenführung von Code oder durch inline platzierte Funktionen entfernt. Lokale Variablen und Parameter können ebenfalls entfernt werden. Viele Codeoptimierungen entfernen lokale Variablen, die nicht benötigt oder verwendet werden. Andere Optimierungen entfernen Induktionsvariablen in Schleifen. Bei der Entfernung allgemeiner Unterausdrücke werden lokale Variablen zusammengeführt.

Einzelhandelsbuilds von Windows sind optimiert. Wenn Sie also einen Einzelhandelsbuild von Windows ausführen, ist es besonders hilfreich, über einen Debugger zu verfügen, der gut mit optimiertem Code funktioniert. Damit das Debuggen von optimiertem Code effektiv ist, sind zwei Hauptfeatures erforderlich: 1) genaue Anzeige lokaler Variablen und 2) Anzeige von Inlinefunktionen auf dem Aufrufstapel.

Genaue Anzeige lokaler Variablen und Parameter

Um die genaue Anzeige lokaler Variablen und Parameter zu erleichtern, zeichnet der Compiler Informationen über die Speicherorte lokaler Variablen und Parameter in PDB-Dateien (Symboldateien) auf. Diese Speicherortdatensätze verfolgen die Speicherorte der Variablen und die spezifischen Codebereiche, in denen diese Speicherorte gültig sind. Diese Datensätze helfen nicht nur, die Speicherorte (in Registern oder in Stapelslots) der Variablen nachzuverfolgen, sondern auch die Bewegung der Variablen. Beispielsweise kann sich ein Parameter zuerst im RCX-Register befinden, wird aber in einen Stapelslot verschoben, um RCX freizugeben, dann zum Registrieren von R8 verschoben, wenn er stark in einer Schleife verwendet wird, und dann in einen anderen Stapelslot verschoben, wenn sich der Code nicht in der Schleife befindet. Der Windows-Debugger nutzt die umfangreichen Speicherortdatensätze in den PDB-Dateien und verwendet den aktuellen Anweisungszeiger, um die entsprechenden Speicherortdatensätze für die lokalen Variablen und Parameter auszuwählen.

Dieser Screenshot des Fensters "Locals" in Visual Studio zeigt die Parameter und lokalen Variablen für eine Funktion in einer optimierten 64-Bit-Anwendung an. Die Funktion ist nicht inline, sodass sowohl Parameter als auch lokale Variablen angezeigt werden.

Screenshot des Fensters

Sie können den Befehl dv -v verwenden, um die Speicherorte der Parameter und lokalen Variablen anzuzeigen.

Screenshot der Befehlsausgabe mit den Speicherorten von Parametern und lokalen Variablen mithilfe des Befehls dv -v

Beachten Sie, dass im Fenster Lokal die Parameter ordnungsgemäß angezeigt werden, obwohl sie in Registern gespeichert sind.

Zusätzlich zur Nachverfolgung von Variablen mit primitiven Typen zeichnet der Speicherort Datenmember lokaler Strukturen und Klassen auf. Die folgende Debuggerausgabe zeigt lokale Strukturen an.

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

Im Folgenden finden Sie einige Beobachtungen zur vorherigen Debuggerausgabe.

  • Die lokale Struktur My1 veranschaulicht, dass der Compiler lokale Strukturdatenmember auf Register und nicht zusammenhängende Stapelslots verteilen kann.
  • Die Ausgabe des Befehls dt My2 unterscheidet sich von der Ausgabe des Befehls dt _IntSum 0xefa60. Sie können nicht davon ausgehen, dass die lokale Struktur einen zusammenhängenden Block des Stapelspeichers belegt. Im Fall von My2 verbleibt nur sum4 im ursprünglichen Stapelblock; die anderen drei Datenmember werden in Register verschoben.
  • Einige Datenmember können mehrere Speicherorte aufweisen. My2.sum2 verfügt beispielsweise über zwei Speicherorte: Eine ist das Registrieren von ECX (das der Windows-Debugger auswähelt) und der andere ist 0xefa60+0x4 (der ursprüngliche Stapelslot). Dies kann auch für lokale Variablen vom Grundtyp auftreten, und der Windows-Debugger erzwingt eine Präzedenz heuristik, um zu bestimmen, welcher Speicherort verwendet werden soll. Beispielsweise können Sie Speicherorte immer übertrumpfen.

Anzeige von Inlinefunktionen auf dem Aufrufstapel

Während der Codeoptimierung werden einige Funktionen in der Zeile platziert. Das heißt, der Textkörper der Funktion wird wie eine Makroerweiterung direkt im Code platziert. Es gibt keinen Funktionsaufruf und keine Rückkehr zum Aufrufer. Um die Anzeige von Inlinefunktionen zu erleichtern, speichert der Compiler Daten in den PDB-Dateien, mit denen die Codeblöcke für die Inlinefunktionen (d. h. Sequenzen von Codeblöcken in Aufruferfunktionen, die zu den aufgerufenen Funktionen gehören, die inline platziert werden) sowie die lokalen Variablen (bereichsbezogene lokale Variablen in diesen Codeblöcken) decodiert werden. Diese Daten helfen dem Debugger, Inlinefunktionen als Teil der Stapelentladung einzubeziehen.

Angenommen, Sie kompilieren eine Anwendung und erzwingen eine Funktion namens func1 inline.

__forceinline int func1(int p1, int p2, int p3)
{
   int num1 = 0;
   int num2 = 0;
   int num3 = 0;
   ...
}

Sie können den Befehl bm verwenden, um einen Haltepunkt auf func1festzulegen.

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)]

Nachdem Sie einen Schritt ausgeführt func1haben, können Sie den Befehl k verwenden, um func1 die Aufrufliste anzuzeigen. Sie können den Befehl dv verwenden, um die lokalen Variablen für func1anzuzeigen. Beachten Sie, dass die lokale Variable num3 als nicht verfügbar angezeigt wird. Eine lokale Variable kann im optimierten Code aus verschiedenen Gründen nicht verfügbar sein. Es kann sein, dass die Variable im optimierten Code nicht vorhanden ist. Es kann sein, dass die Variable noch nicht initialisiert wurde oder dass die Variable nicht mehr verwendet wird.

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>

Wenn Sie sich Frame 1 in der Stapelablaufverfolgung ansehen, sehen Sie die lokalen Variablen für die main Funktion. Beachten Sie, dass zwei der Variablen in Registern gespeichert werden.

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

Der Windows-Debugger aggregiert Daten aus PDB-Dateien, um alle Stellen zu finden, an denen eine bestimmte Funktion inline platziert wurde. Sie können den Befehl x verwenden, um alle Aufruferwebsites der Inlinefunktion aufzulisten.

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

Da der Windows-Debugger alle Aufruferstandorte einer Inlinefunktion auflisten kann, kann er einen Haltepunkt innerhalb der Inlinefunktion festlegen, indem er die Offsets der Aufruferwebsites berechnet. Sie können den Befehl bm (der verwendet wird, um Haltepunkte festzulegen, die regulären Ausdrucksmustern entsprechen), um Haltepunkte für Inlinefunktionen festzulegen.

Der Windows-Debugger gruppiert alle Haltepunkte, die für eine bestimmte Inlinefunktion festgelegt sind, in einem Haltepunktcontainer. Sie können den Haltepunktcontainer als Ganzes mithilfe von Befehlen wie be, bd, bc bearbeiten. Sehen Sie sich die folgenden Befehle bd 3 und bc 3 an. Sie können auch einzelne Haltepunkte bearbeiten. Sehen Sie sich das folgende Beispiel für 2 Befehle an.

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

Da es keine expliziten Aufruf- oder Rückgabeanweisungen für Inlinefunktionen gibt, stellt das Schrittschritten auf Quellebene für einen Debugger eine besondere Herausforderung dar. Beispielsweise können Sie unbeabsichtigt in eine Inlinefunktion einschritten (wenn die nächste Anweisung Teil einer Inlinefunktion ist), oder Sie können dieselbe Inlinefunktion mehrmals ein- und austreten (da die Codeblöcke für die Inlinefunktion vom Compiler geteilt und verschoben wurden). Um die vertraute Schritterfahrung zu erhalten, verwaltet der Windows-Debugger einen kleinen konzeptionellen Aufrufstapel für jede Codeanweisungsadresse und erstellt einen internen Zustandscomputer zum Ausführen von Step-In-, Step-Over- und Step-Out-Vorgängen. Dies gibt eine einigermaßen genaue Annäherung an die Schritterfahrung für Nicht-Inlinefunktionen.

Weitere Informationen

Hinweis Sie können den Befehl .inline 0 verwenden, um das Debuggen von Inlinefunktionen zu deaktivieren. Der Befehl .inline 1 ermöglicht das Debuggen von Inlinefunktionen. Standarddebugverfahren

Siehe auch

Standarddebugverfahren