Zeitreise-Debugging – Vorstellung der Beispiel-App
In dieser Übung wird das Time Travel Debugging (TTD) anhand eines kleinen Beispielprogramms mit einem Code-Fehler vorgestellt. TTD wird zur Fehlersuche, Identifizierung und Beseitigung der Ursache des Problems eingesetzt. Obwohl das Problem in diesem kleinen Programm leicht zu finden ist, kann das allgemeine Verfahren auch auf komplexeren Code angewendet werden. Dieses allgemeine Verfahren lässt sich wie folgt zusammenfassen.
- Erfassen Sie eine Zeitreise-Ablaufverfolgung des fehlgeschlagenen Programms.
- Verwenden Sie den Befehl dx (Display Debugger Object Model Expression), um das in der Aufzeichnung gespeicherte Ausnahmeereignis zu finden.
- Verwenden Sie den Befehl !tt (time travel), um zur Position des Ausnahmeereignisses in der Ablaufverfolgung zu gelangen.
- Von diesem Punkt aus geht man einen Schritt zurück, bis der betreffende fehlerhafte Code in den Bereich kommt.
- Sehen Sie sich die lokalen Werte des fehlerhaften Codes an und entwickeln Sie eine Hypothese über eine Variable, die einen falschen Wert enthalten könnte.
- Ermitteln Sie die Speicheradresse der Variablen mit dem falschen Wert.
- Setzen Sie einen Haltepunkt für den Speicherzugriff (ba) an der Adresse der verdächtigen Variablen mit dem Befehl ba (Break on Access).
- Verwenden Sie g-, um zum letzten Speicherzugriff der verdächtigen Variablen zurückzugehen.
- Prüfen Sie, ob diese Stelle oder einige Anweisungen davor der Punkt ist, an dem der Code fehlerhaft ist. Wenn ja, sind Sie fertig. Wenn der falsche Wert von einer anderen Variablen stammt, setzen Sie einen weiteren Haltepunkt für den Zugriff auf die zweite Variable.
- Verwenden Sie g-, um zum letzten Punkt des Speicherzugriffs auf die zweite verdächtige Variable zurückzugehen. Prüfen Sie, ob diese Stelle oder einige Anweisungen davor den Codefehler enthält. Wenn ja, sind Sie fertig.
- Wiederholen Sie diesen Vorgang, bis Sie den Code gefunden haben, der den falschen Wert gesetzt hat, der den Fehler verursacht hat.
Obwohl die in diesem Verfahren beschriebenen allgemeinen Techniken für eine breite Palette von Code-Problemen gelten, gibt es einzigartige Code-Probleme, die einen speziellen Ansatz erfordern. Die in der exemplarischen Vorgehensweise erläuterten Techniken sollen dazu dienen, Ihr Debugging-Toolset zu erweitern, und veranschaulichen, was mit einer TTD-Ablaufverfolgung alles möglich ist.
Ziele des Labs
Nach Abschluss dieser Lektion werden Sie in der Lage sein, das allgemeine Verfahren mit einer Zeitreise-Ablaufverfolgung anzuwenden, um Probleme im Code zu finden.
Setup der Übungsumgebung
Sie benötigen die folgende Hardware, um die Übung durchführen zu können.
- Ein Laptop oder Desktop-Computer (Host) mit Windows 10 oder Windows 11
Sie benötigen die folgende Software, um die Übung durchführen zu können.
- Die WinDbg. Für Informationen zur Installation von WinDbg siehe WinDbg – Installation
- Visual Studio, um den C++-Beispielcode zu erstellen.
Die Übung besteht aus den folgenden drei Abschnitten.
- Abschnitt 1: Erstellen Sie den Beispielcode
- Abschnitt 2: Zeichnen Sie eine Ablaufverfolgung des Beispiels „DisplayGreeting“ auf
- Abschnitt 3: Analysieren Sie die Aufzeichnung der Ablaufverfolgungsdatei, um das Code-Problem zu identifizieren
Abschnitt 1: Erstellen Sie den Beispielcode
In Abschnitt 1 erstellen Sie den Beispielcode mit Visual Studio.
Erstellen Sie die Beispielanwendung in Visual Studio
Klicken Sie in Microsoft Visual Studio auf File>New>Project/Solution..., und klicken Sie auf die Visual C++-Vorlagen.
Wählen Sie die Win32 Console Application aus.
Geben Sie einen Projektnamen DisplayGreeting ein, und klicken Sie auf OK.
Deaktivieren Sie die Prüfungen des Security Development Lifecycle (SDL).
Klicken Sie auf Finish.
Fügen Sie den folgenden Text in das Fenster DisplayGreeting.cpp in Visual Studio ein.
// DisplayGreeting.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <array> #include <stdio.h> #include <string.h> void GetCppConGreeting(wchar_t* buffer, size_t size) { wchar_t const* const message = L"HELLO FROM THE WINDBG TEAM. GOOD LUCK IN ALL OF YOUR TIME TRAVEL DEBUGGING!"; wcscpy_s(buffer, size, message); } int main() { std::array <wchar_t, 50> greeting{}; GetCppConGreeting(greeting.data(), sizeof(greeting)); wprintf(L"%ls\n", greeting.data()); return 0; }
Klicken Sie in Visual Studio auf Project>DisplayGreeting properties. Klicken Sie dann auf C/C++ und Code Generation.
Legen Sie die folgenden Eigenschaften fest.
Einstellung Wert Sicherheitsüberprüfung Sicherheitsüberprüfung deaktivieren (/GS-) Grundlegende Laufzeitüberprüfungen Standard Hinweis
Obwohl diese Einstellungen nicht empfohlen werden, ist es möglich, sich ein Szenario vorzustellen, in dem empfohlen wird, diese Einstellungen zu verwenden, um die Codierung zu beschleunigen oder bestimmte Testumgebungen zu erleichtern.
Klicken Sie in Visual Studio auf Build>Build Solution.
Wenn alles gut geht, sollte im Erstellungsfenster eine Meldung erscheinen, dass die Erstellung erfolgreich war.
Suchen Sie die erstellten Beispielanwendungsdateien
Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt DisplayGreeting, und wählen Sie Open Folder in File Explorer aus.
Navigieren Sie zum Ordner Debug, der die kompilierte exe- und Symbol-pdb-Datei für das Beispiel enthält. Sie würden zum Beispiel zu C:\Projects\DisplayGreeting\Debug navigieren, wenn dies der Ordner ist, in dem Ihre Projekte gespeichert sind.
Führen Sie die Beispielanwendung mit dem Code-Fehler aus
Doppelklicken Sie auf die Exe-Datei, um die Beispielanwendung auszuführen.
Wenn dieses Dialogfeld angezeigt wird, wählen Sie Close Program aus.
Im nächsten Abschnitt der exemplarischen Vorgehensweise werden wir die Ausführung der Beispielanwendung aufzeichnen, um zu sehen, ob wir feststellen können, warum diese Ausnahme auftritt.
Abschnitt 2: Zeichnen Sie eine Ablaufverfolgung des Beispiels „DisplayGreeting“ auf
In Abschnitt 2 zeichnen Sie eine Ablaufverfolgung der fehlerhaften Beispiel-App „DisplayGreeting“ auf
Gehen Sie folgendermaßen vor, um die Beispielanwendung zu starten und eine TTD-Aufzeichnung zu erstellen. Allgemeine Informationen zur Aufzeichnung von TTD-Ablaufverfolgungen finden Sie unter Zeitreise-Debugging – Aufzeichnung einer Ablaufverfolgung
Führen Sie WinDbg als Administrator aus, um Zeitreise-Ablaufverfolgungen aufzeichnen zu können.
Wählen Sie in WinDbg File>Start Debugging>Launch Executable (Advanced) aus.
Geben Sie den Pfad zu der ausführbaren Datei im Benutzermodus ein, die Sie aufzeichnen möchten, oder wählen Sie Browse aus, um zu der ausführbaren Datei zu navigieren. Informationen zum Arbeiten mit dem Menü Launch Executable in WinDbg finden Sie unter WinDbg – Starten einer Sitzung im Benutzermodus.
Aktivieren Sie das Kontrollkästchen Record with Time Travel Debugging, um eine Ablaufverfolgung aufzuzeichnen, wenn die ausführbare Datei gestartet wird.
Klicken Sie auf Configure und Record, um die Aufzeichnung zu starten.
Wenn das Dialogfeld „Configure recording“ angezeigt wird, klicken Sie auf Record, um die ausführbare Datei zu starten und die Aufzeichnung zu beginnen.
Der Aufzeichnungsdialog erscheint und zeigt an, dass die Ablaufverfolgung aufgezeichnet wird. Kurze Zeit später stürzt die Anwendung ab.
Klicken Sie auf Close Program, um das Dialogfeld „DisplayGreeting has stopped working“ zu schließen.
Wenn das Programm abstürzt, wird die Ablaufverfolgungsdatei geschlossen und auf die Festplatte geschrieben.
Der Debugger öffnet automatisch die Ablaufverfolgungsdatei und indiziert sie. Die Indizierung ist ein Prozess, der eine effiziente Fehlersuche in der Ablaufverfolgungsdatei ermöglicht. Dieser Indizierungsprozess dauert bei größeren Ablaufverfolgungsdateien länger.
(5120.2540): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: D:0 [Unindexed] Index !index Indexed 10/22 keyframes Indexed 20/22 keyframes Indexed 22/22 keyframes Successfully created the index in 755ms.
Hinweis
Ein Keyframe ist eine Stelle in einer Ablaufverfolgung, die für die Indexierung verwendet wird. Keyframes werden automatisch erzeugt. Größere Ablaufverfolgungen enthalten mehr Keyframes.
An diesem Punkt befinden Sie sich am Anfang der Ablaufverfolgungsdatei und können in der Zeit vor- und zurückreisen.
Jetzt, wo Sie eine TTD-Aufzeichnung aufgezeichnet haben, können Sie die Aufzeichnung wieder abspielen oder mit der Aufzeichnungsdatei arbeiten, z. B. indem Sie sie mit einem Kollegen teilen. Weitere Informationen zum Arbeiten mit Ablaufverfolgungsdateien finden Sie unter Zeitreise-Debugging – Arbeiten mit Ablaufverfolgungsdateien
Im nächsten Abschnitt dieser Übung analysieren wir die Ablaufverfolgungsdatei, um das Problem mit unserem Code zu finden.
Abschnitt 3: Analysieren Sie die Aufzeichnung der Ablaufverfolgungsdatei, um das Code-Problem zu identifizieren
In Abschnitt 3 analysieren Sie die Aufzeichnung der Ablaufverfolgungsdatei, um das Code-Problem zu identifizieren.
Konfigurieren Sie die WinDbg-Umgebung
Fügen Sie Ihre lokale Symbolposition zum Symbolpfad hinzu und laden Sie die Symbole neu, indem Sie die folgenden Befehle eingeben.
.sympath+ C:\MyProjects\DisplayGreeting\Debug .reload
Fügen Sie den Ort Ihres lokalen Codes zum Quellcodepfad hinzu, indem Sie den folgenden Befehl eingeben.
.srcpath+ C:\MyProjects\DisplayGreeting\DisplayGreeting
Um den Zustand des Stacks und der lokalen Variablen zu sehen, wählen Sie in der Multifunktionsleiste von WinDbg View und Locals und View und Stack aus. Organisieren Sie die Fenster so, dass Sie sie, den Quellcode und die Befehlsfenster gleichzeitig sehen können.
Wählen Sie in der Multifunktionsleiste von WinDbg Source und Open Source File aus. Suchen Sie die Datei DisplayGreeting.cpp und öffnen Sie sie.
Prüfen Sie die Ausnahme
Wenn die Ablaufverfolgungsdatei geladen wurde, zeigt sie die Information an, dass eine Ausnahme aufgetreten ist.
2fa8.1fdc): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 15:0 eax=68ef8100 ebx=00000000 ecx=77a266ac edx=69614afc esi=6961137c edi=004da000 eip=77a266ac esp=0023f9b4 ebp=0023fc04 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 ntdll!LdrpInitializeProcess+0x1d1c: 77a266ac 83bdbcfeffff00 cmp dword ptr [ebp-144h],0 ss:002b:0023fac0=00000000
Verwenden Sie den Befehl dx, um alle Ereignisse der Aufzeichnung aufzulisten. Das Ausnahmeereignis ist in den Ereignissen aufgeführt.
0:000> dx -r1 @$curprocess.TTD.Events ... [0x2c] : Module Loaded at position: 9967:0 [0x2d] : Exception at 9BDC:0 [0x2e] : Thread terminated at 9C43:0 ...
Hinweis
In dieser exemplarischen Vorgehensweise werden drei Punkte verwendet, um anzuzeigen, dass überflüssige Ausgaben entfernt wurden.
Klicken Sie auf das Ereignis „Exception“, um Informationen zu diesem TTD-Ereignis anzuzeigen.
0:000> dx -r1 @$curprocess.TTD.Events[17] @$curprocess.TTD.Events[17] : Exception at 68:0 Type : Exception Position : 68:0 [Time Travel] Exception : Exception of type Hardware at PC: 0X540020
Klicken Sie auf das Feld „Exception“, um die Ausnahmedaten weiter aufzuschlüsseln.
0:000> dx -r1 @$curprocess.TTD.Events[17].Exception @$curprocess.TTD.Events[17].Exception : Exception of type Hardware at PC: 0X540020 Position : 68:0 [Time Travel] Type : Hardware ProgramCounter : 0x540020 Code : 0xc0000005 Flags : 0x0 RecordAddress : 0x0
Die Ausnahmedaten zeigen an, dass es sich um einen von der CPU ausgelösten Hardwarefehler handelt. Sie stellt auch den Ausnahmecode 0xc0000005 bereit, der anzeigt, dass es sich um eine Zugriffsverletzung handelt. Dies deutet normalerweise darauf hin, dass wir versucht haben, in einen Speicher zu schreiben, auf den wir keinen Zugriff haben.
Klicken Sie auf den Link [Zeitreise] im Ausnahmeereignis, um zu dieser Position in der Aufzeichnung zu gelangen.
0:000> dx @$curprocess.TTD.Events[17].Exception.Position.SeekTo() Setting position: 68:0 @$curprocess.TTD.Events[17].Exception.Position.SeekTo() (16c8.1f28): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 68:0 eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046 eip=00540020 esp=00effe4c ebp=00520055 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 00540020 ??
Bemerkenswert an dieser Ausgabe ist, dass der Stack und der Basiszeiger auf zwei sehr unterschiedliche Adressen verweisen.
esp=00effe4c ebp=00520055
Dies könnte darauf hinweisen, dass der Stack beschädigt ist – möglicherweise wurde eine Funktion zurückgegeben und hat dann den Stack beschädigt. Um dies zu überprüfen, müssen wir zurückgehen, bevor der CPU-Status beschädigt wurde, und prüfen, ob wir feststellen können, wann die Stack-Beschädigung aufgetreten ist.
Untersuchen Sie die lokalen Variablen und setzen Sie einen Code-Haltepunkt
An der Stelle, an der in der Ablaufverfolgung ein Fehler auftritt, kommt es häufig vor, dass der Fehler einige Schritte hinter der wahren Ursache im Fehlerbehandlungscode liegt. Mit Zeitreisen können wir eine Anweisung nach der anderen zurückgehen, um die wahre Ursache zu finden.
Verwenden Sie im Menüband Home den Befehl Step Into Back, um drei Anweisungen zurückzugehen. Untersuchen Sie dabei weiterhin den Stack und die Speicherfenster.
Im Befehlsfenster werden die Zeitreiseposition und die Register angezeigt, wenn Sie drei Anweisungen zurückgehen.
0:000> t- Time Travel Position: 67:40 eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046 eip=00540020 esp=00effe4c ebp=00520055 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 00540020 ?? ??? 0:000> t- Time Travel Position: 67:3F eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046 eip=0019193d esp=00effe48 ebp=00520055 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 DisplayGreeting!main+0x4d: 0019193d c3 0:000> t- Time Travel Position: 67:39 eax=0000004c ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046 eip=00191935 esp=00effd94 ebp=00effe44 iopl=0 nv up ei pl nz ac po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212 DisplayGreeting!main+0x45:
Hinweis
In dieser exemplarischen Vorgehensweise zeigt die Befehlsausgabe die Befehle, die anstelle der Optionen des UI-Menüs verwendet werden können, damit Benutzer, die die Verwendung der Befehlszeile bevorzugen, die Befehlszeilenbefehle verwenden können.
An diesem Punkt in der Ablaufverfolgung haben unser Stack und unser Basiszeiger Werte, die mehr Sinn ergeben, so dass es scheint, dass wir uns dem Punkt im Code nähern, an dem die Beschädigung aufgetreten ist.
esp=00effd94 ebp=00effe44
Interessant ist auch, dass das Locals-Fenster Werte aus unserer Zielanwendung enthält und das Quellcode-Fenster die Codezeile hervorhebt, die an diesem Punkt der Ablaufverfolgung zur Ausführung bereit ist.
Zur weiteren Untersuchung können wir ein Speicherfenster öffnen, um den Inhalt in der Nähe der Basiszeiger-Speicheradresse 0x00effe44 zu betrachten.
Um die zugehörigen ASCII-Zeichen anzuzeigen, wählen Sie im Menüband Speicher Text und dann ASCII aus.
Anstatt dass der Basiszeiger auf eine Anweisung zeigt, zeigt er auf unseren Nachrichtentext. Irgendetwas stimmt hier also nicht, möglicherweise ist der Stapel zu diesem Zeitpunkt bereits beschädigt. Zur weiteren Untersuchung setzen wir einen Haltepunkt.
Hinweis
In diesem sehr kleinen Beispiel wäre es ziemlich leicht, einfach in den Code zu schauen, aber wenn es Hunderte von Codezeilen und Dutzende von Unterprogrammen gibt, können die hier beschriebenen Techniken verwendet werden, um die Zeit zu verkürzen, die notwendig ist, um das Problem zu finden.
TTD und Haltepunkte
Die Verwendung von Haltepunkten ist eine gängige Methode, um die Codeausführung bei einem bestimmten Ereignis von Interesse anzuhalten. Mit TTD können Sie einen Haltepunkt setzen und in der Zeit zurückreisen, bis dieser Haltepunkt nach der Aufzeichnung der Ablaufverfolgung erreicht wird. Die Möglichkeit, den Prozessstatus zu untersuchen, nachdem ein Problem aufgetreten ist, um die beste Stelle für einen Haltepunkt zu bestimmen, ermöglicht zusätzliche Debugging-Workflows, die es nur bei TTD gibt.
Haltepunkte für den Speicherzugriff
Sie können Haltepunkte setzen, die ausgelöst werden, wenn auf eine Speicherstelle zugegriffen wird. Verwenden Sie den Befehl ba (break on access) mit der folgenden Syntax.
ba <access> <size> <address> {options}
Option | Beschreibung |
---|---|
e | Ausführen (wenn die CPU einen Befehl von der Adresse abruft) |
r | Lesen/Schreiben (wenn die CPU die Adresse liest oder schreibt) |
w | Schreiben (wenn die CPU an die Adresse schreibt) |
Beachten Sie, dass Sie immer nur vier Daten-Haltepunkte setzen können, und es liegt an Ihnen, sicherzustellen, dass Sie Ihre Daten korrekt ausrichten, sonst wird der Haltepunkt nicht ausgelöst (Wörter müssen auf durch 2 teilbare Adressen enden, Doppelwörter müssen durch 4 teilbar sein, und Vierfachwörter durch 0 oder 8).
Setzen des Haltepunkts für den Basiszeiger beim Speicherzugriff
An diesem Punkt der Ablaufverfolgung möchten wir einen Haltepunkt für den Schreibspeicherzugriff auf den Basiszeiger – ebp setzen, der in unserem Beispiel 00effe44 ist. Verwenden Sie dazu den Befehl ba mit der Adresse, die Sie überwachen wollen. Wir wollen Schreibvorgänge für vier Bytes überwachen, also geben wir w4 an.
0:000> ba w4 00effe44
Wählen Sie View und dann Breakpoints aus, um zu überprüfen, ob sie wie vorgesehen eingestellt sind.
Wählen Sie im Menü Home die Option Go Back aus, um in der Zeit zurückzureisen, bis der Haltepunkt erreicht ist.
0:000> g- Breakpoint 0 hit Time Travel Position: 5B:92 eax=0000000f ebx=003db000 ecx=00000000 edx=00cc1a6c esi=00d41046 edi=0053fde8 eip=00d4174a esp=0053fcf8 ebp=0053fde8 iopl=0 nv up ei pl nz ac pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000216 DisplayGreeting!DisplayGreeting+0x3a: 00d4174a c745e000000000 mov dword ptr [ebp-20h],0 ss:002b:0053fdc8=cccccccc
Wählen Sie View und dann Locals aus. Im Fenster locals sehen wir, dass die Variable destination nur einen Teil der Nachricht enthält, während die Variable source den gesamten Text enthält. Diese Informationen sprechen dafür, dass der Stapel beschädigt wurde.
An diesem Punkt können wir den Programmstapel untersuchen, um zu sehen, welcher Code aktiv ist. Wählen Sie in der Multifunktionsleiste View die Option Stack aus.
Da es sehr unwahrscheinlich ist, dass die von Microsoft bereitgestellte Funktion wscpy_s() einen solchen Code-Fehler hat, schauen wir weiter im Stack nach. Der Stack zeigt, dass Greeting!main Greeting!GetCppConGreeting aufruft. In unserem sehr kleinen Codebeispiel könnten wir den Code an dieser Stelle einfach öffnen und den Fehler wahrscheinlich ziemlich leicht finden. Um jedoch die Techniken zu veranschaulichen, die bei größeren, komplexeren Programmen verwendet werden können, werden wir einen neuen Haltepunkt setzen, um ihn weiter zu untersuchen.
Legen Sie den Haltepunkt für den Zugriff auf die Funktion GetCppConGreeting fest
Verwenden Sie das Haltepunkt-Fenster, um den vorhandenen Haltepunkt zu löschen, indem Sie mit der rechten Maustaste auf den vorhandenen Haltepunkt klicken und Entfernen auswählen.
Ermitteln Sie die Adresse der Funktion DisplayGreeting!GetCppConGreeting mit dem Befehl dx.
0:000> dx &DisplayGreeting!GetCppConGreeting &DisplayGreeting!GetCppConGreeting : 0xb61720 [Type: void (__cdecl*)(wchar_t *,unsigned int)] [Type: void __cdecl(wchar_t *,unsigned int)]
Verwenden Sie den Befehl ba, um einen Haltepunkt beim Speicherzugriff zu setzen. Da die Funktion zur Ausführung nur aus dem Speicher gelesen wird, müssen wir einen r – read Haltepunkt setzen.
0:000> ba r4 b61720
Vergewissern Sie sich, dass ein Hardware-Lese-Haltepunkt im Haltepunktfenster aktiv ist.
Da wir uns über die Größe des Begrüßungsstrings wundern, setzen wir ein Überwachungsfenster, um den Wert von sizeof(greeting) anzuzeigen. Wählen Sie im Menüband „View“ die Option Watch aus, und geben Sie sizeof(greeting) ein. Wenn der Wert nicht im Bereich des Überwachungsfensters angezeigt wird – Der Name 'greeting' kann nicht gebunden werden.
Wählen Sie im Menü Time Travel Time travel to start aus, oder verwenden Sie den Befehl
!tt 0
, um zum Start der Ablaufverfolgung zu gelangen.0:000> !tt 0 Setting position to the beginning of the trace Setting position: 15:0 (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 15:0 eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000 eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 ntdll!LdrpInitializeProcess+0x1d1c: 77a266ac 83bdbcfeffff00 cmp dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000
Wählen Sie im Menü Home die Option Go aus, oder verwenden Sie den Befehl
g
, um sich im Code vorwärts zu bewegen, bis der Haltepunkt erreicht ist.0:000> g Breakpoint 2 hit Time Travel Position: 4B:1AD eax=00ddf800 ebx=00fa2000 ecx=00ddf800 edx=00b61046 esi=00b61046 edi=00b61046 eip=00b61721 esp=00ddf7a4 ebp=00ddf864 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 DisplayGreeting!GetCppConGreeting+0x1: 00b61721 8bec mov ebp,esp
Wählen Sie im Menü Home die Option Step Out Back aus, oder verwenden Sie den Befehl
g-u
, um einen Schritt zurückzugehen.0:000> g-u Time Travel Position: 4B:1AA eax=00ddf800 ebx=00fa2000 ecx=00ddf800 edx=00b61046 esi=00b61046 edi=00b61046 eip=00b61917 esp=00ddf7ac ebp=00ddf864 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 DisplayGreeting!main+0x27: 00b61917 e8def7ffff call DisplayGreeting!ILT+245(?GetCppConGreetingYAXPA_WIZ) (00b610fa)
Es sieht so aus, als hätten wir die Ursache dafür gefunden. Das von uns deklarierte Grußarray ist 50 Zeichen lang, während die größeof(greeting), die wir an "GetCppConGreeting" übergeben, 0x64, 100 ist.
Wenn wir das Größenproblem weiter betrachten, stellen wir auch fest, dass die Nachricht 75 Zeichen lang ist und 76 ist, wenn das Ende des Zeichenfolgenzeichens eingeschlossen wird.
HELLO FROM THE WINDBG TEAM. GOOD LUCK IN ALL OF YOUR TIME TRAVEL DEBUGGING!
Eine Möglichkeit, den Code zu korrigieren, bestünde darin, die Größe des Zeichenfelds auf 100 zu erweitern.
std::array <wchar_t, 100> greeting{};
Und wir müssen auch sizeof(greeting) in size(greeting) in dieser Codezeile ändern.
GetCppConGreeting(greeting.data(), size(greeting));
Um diese Korrekturen zu überprüfen, könnten wir den Code neu kompilieren und bestätigen, dass er ohne Fehler läuft.
Setzen eines Haltepunkts über das Quellcodefenster
Eine alternative Möglichkeit, diese Untersuchung durchzuführen, wäre das Setzen eines Haltepunkts durch Anklicken einer beliebigen Codezeile. Wenn Sie zum Beispiel auf die rechte Seite der std:array-Definitionszeile im Quellcodefenster klicken, wird dort ein Haltepunkt gesetzt.
Verwenden Sie im Menü „Time Travel“ den Befehl Time travel to start, um an den Anfang der Ablaufverfolgung zu gelangen.
0:000> !tt 0 Setting position to the beginning of the trace Setting position: 15:0 (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 15:0 eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000 eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 ntdll!LdrpInitializeProcess+0x1d1c: 77a266ac 83bdbcfeffff00 cmp dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000
Klicken Sie im Menüband Home auf Go, um bis zum Haltepunkt zurückzugehen.
Breakpoint 0 hit Time Travel Position: 5B:AF eax=0000000f ebx=00c20000 ecx=00000000 edx=00000000 esi=013a1046 edi=00effa60 eip=013a17c1 esp=00eff970 ebp=00effa60 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 DisplayGreeting!DisplayGreeting+0x41: 013a17c1 8bf4 mov esi,esp
Setzen Sie den Haltepunkt bei Zugriff auf die greeting-Variable
Eine andere Möglichkeit, diese Untersuchung durchzuführen, besteht darin, einen Haltepunkt auf verdächtige Variablen zu setzen und zu untersuchen, welcher Code sie verändert. Um zum Beispiel einen Haltepunkt auf die Variable greeting in der Methode GetCppConGreeting zu setzen, verwenden Sie dieses Verfahren.
In diesem Teil der exemplarischen Vorgehensweise wird davon ausgegangen, dass Sie sich noch an dem Haltepunkt aus dem vorherigen Abschnitt befinden.
Über View und dann Locals. Im Fenster locals ist greeting im aktuellen Kontext verfügbar, so dass wir seinen Speicherort bestimmen können.
Verwenden Sie den Befehl dx, um das Feld greeting zu untersuchen.
0:000> dx &greeting &greeting : ddf800 : { size=50 } [Type: std::array<wchar_t,50> *] [<Raw View>] [Type: std::array<wchar_t,50>] [0] : 3 [Type: wchar_t] [1] : 0 [Type: wchar_t]
In dieser Ablaufverfolgung befindet sich greeting im Speicher unter ddf800.
Verwenden Sie das Haltepunkt-Fenster, um einen vorhandenen Haltepunkt zu löschen, indem Sie mit der rechten Maustaste auf den vorhandenen Haltepunkt klicken und Entfernen auswählen.
Setzen Sie den Haltepunkt mit dem Befehl ba unter Verwendung der Speicheradresse, die wir auf Schreibzugriff überwachen wollen.
ba w4 ddf800
Verwenden Sie im Menü „Time Travel“ den Befehl Time travel to start, um an den Anfang der Ablaufverfolgung zu gelangen.
0:000> !tt 0 Setting position to the beginning of the trace Setting position: 15:0 (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 15:0 eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000 eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 ntdll!LdrpInitializeProcess+0x1d1c: 77a266ac 83bdbcfeffff00 cmp dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000
Wählen Sie im Menü Home Go, um zum ersten Speicherpunkt des Begrüßungsfeldes zu gelangen.
0:000> g- Breakpoint 0 hit Time Travel Position: 5B:9C eax=cccccccc ebx=002b1000 ecx=00000000 edx=68d51a6c esi=013a1046 edi=001bf7d8 eip=013a1735 esp=001bf6b8 ebp=001bf7d8 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 DisplayGreeting!GetCppConGreeting+0x25: 013a1735 c745ec04000000 mov dword ptr [ebp-14h],4 ss:002b:001bf7c4=cccccccc
Alternativ hätten wir bis zum Ende der Ablaufverfolgung gehen und den Code rückwärts durcharbeiten können, um den letzten Punkt in der Ablaufverfolgung zu finden, in den der Array-Speicherplatz geschrieben wurde.
Verwenden Sie die TTD.Memory-Objekte, um den Speicherzugriff anzuzeigen
Eine andere Möglichkeit, um festzustellen, an welchen Stellen im Ablaufverfolgungsspeicher zugegriffen wurde, ist die Verwendung der TTD.Memory-Objekte und des dx-Befehls.
Verwenden Sie den Befehl dx, um das Feld greeting zu untersuchen.
0:000> dx &greeting &greeting : 0xddf800 [Type: std::array<wchar_t,50> *] [+0x000] _Elems : "꽘棶檙瞝???" [Type: wchar_t [50]]
In dieser Ablaufverfolgung befindet sich greeting im Speicher unter ddf800.
Verwenden Sie den Befehl dx, um die vier Bytes im Speicher ab dieser Adresse mit dem Lese-/Schreibzugriff zu betrachten.
0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw") @$cursession.TTD.Memory(0x1bf7d0,0x1bf7d4, "rw") [0x0] [0x1] [0x2] [0x3] [0x4] [0x5] [0x6] [0x7] [0x8] [0x9] [0xa] ...
Klicken Sie auf ein beliebiges Vorkommen, um weitere Informationen zu diesem Speicherzugriff anzuzeigen.
0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5] @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5] EventType : MemoryAccess ThreadId : 0x710 UniqueThreadId : 0x2 TimeStart : 27:3C1 [Time Travel] TimeEnd : 27:3C1 [Time Travel] AccessType : Write IP : 0x6900432f Address : 0xddf800 Size : 0x4 Value : 0xddf818 OverwrittenValue : 0x0 SystemTimeStart : Monday, November 18, 2024 23:01:43.400 SystemTimeEnd : Monday, November 18, 2024 23:01:43.400
Klicken Sie auf [Zeitreise] für TimeStart, um die Ablaufverfolgung zu dem Zeitpunkt zu positionieren.
0:000> dx @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5].TimeStart.SeekTo() @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5].TimeStart.SeekTo() (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 27:3C1 eax=00ddf81c ebx=00fa2000 ecx=00ddf818 edx=ffffffff esi=00000000 edi=00b61046 eip=6900432f esp=00ddf804 ebp=00ddf810 iopl=0 nv up ei pl nz ac po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212 ucrtbased!_register_onexit_function+0xf: 6900432f 51 push ecx
Wenn wir uns für das letzte Auftreten eines Lese-/Schreib-Speicherzugriffs in der Ablaufverfolgung interessieren, können wir auf das letzte Element in der Liste klicken oder die Funktion .Last() an das Ende des dx-Befehls anhängen.
0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw").Last() @$cursession.TTD.Memory(0xddf800,0xddf804, "rw").Last() EventType : MemoryAccess ThreadId : 0x710 UniqueThreadId : 0x2 TimeStart : 53:100E [Time Travel] TimeEnd : 53:100E [Time Travel] AccessType : Read IP : 0x690338e4 Address : 0xddf802 Size : 0x2 Value : 0x45 SystemTimeStart : Monday, November 18, 2024 23:01:43.859 SystemTimeEnd : Monday, November 18, 2024 23:01:43.859
Wir können dann auf [Time Travel] klicken, um zu dieser Position in der Ablaufverfolgung zu gelangen und die Codeausführung an diesem Punkt weiter zu untersuchen, indem wir die zuvor in dieser Übung beschriebenen Techniken anwenden.
Weitere Informationen zu den TTD.Memory-Objekten finden Sie unter TTD.Memory-Objekt.
Zusammenfassung
In diesem sehr kleinen Beispiel konnte das Problem durch die Betrachtung der wenigen Codezeilen festgestellt werden, aber in größeren Programmen können die hier vorgestellten Techniken verwendet werden, um die Zeit zu verkürzen, die zum Auffinden eines Problems erforderlich ist.
Sobald eine Ablaufverfolgung aufgezeichnet wurde, können die Ablaufverfolgungs- und Repro-Schritte freigegeben werden, und das Problem ist auf jedem PC reproduzierbar.
Weitere Informationen
Zeitreise-Debugging – Überblick
Zeitreise-Debugging – Aufzeichnung