Freigeben über


Zeitreise-Debugging – Vorstellung der Beispiel-App

Zeitreise-Debugging-Logo mit einer Uhr.

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.

  1. Erfassen Sie eine Zeitreise-Ablaufverfolgung des fehlgeschlagenen Programms.
  2. Verwenden Sie den Befehl dx (Display Debugger Object Model Expression), um das in der Aufzeichnung gespeicherte Ausnahmeereignis zu finden.
  3. Verwenden Sie den Befehl !tt (time travel), um zur Position des Ausnahmeereignisses in der Ablaufverfolgung zu gelangen.
  4. Von diesem Punkt aus geht man einen Schritt zurück, bis der betreffende fehlerhafte Code in den Bereich kommt.
  5. 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.
  6. Ermitteln Sie die Speicheradresse der Variablen mit dem falschen Wert.
  7. Setzen Sie einen Haltepunkt für den Speicherzugriff (ba) an der Adresse der verdächtigen Variablen mit dem Befehl ba (Break on Access).
  8. Verwenden Sie g-, um zum letzten Speicherzugriff der verdächtigen Variablen zurückzugehen.
  9. 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.
  10. 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.
  11. 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

In Abschnitt 1 erstellen Sie den Beispielcode mit Visual Studio.

Erstellen Sie die Beispielanwendung in Visual Studio

  1. 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.

  2. Deaktivieren Sie die Prüfungen des Security Development Lifecycle (SDL).

    Einstellungen des Win32-Anwendungsassistenten in Visual Studio.

  3. Klicken Sie auf Finish.

  4. 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;
    }
    
  5. 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.

  6. Klicken Sie in Visual Studio auf Build>Build Solution.

    Wenn alles gut geht, sollte im Erstellungsfenster eine Meldung erscheinen, dass die Erstellung erfolgreich war.

  7. 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.

  8. Führen Sie die Beispielanwendung mit dem Code-Fehler aus

    Doppelklicken Sie auf die Exe-Datei, um die Beispielanwendung auszuführen.

    Screenshot der Konsole, auf der die Datei DisplayGreeting.exe ausgeführt wird.

    Wenn dieses Dialogfeld angezeigt wird, wählen Sie Close Program aus.

    Screenshot des Dialogfelds mit der Anzeige „DisplayGreeting.exe has stopped working“ (DisplayGreeting.exe funktioniert nicht mehr)

    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

  1. Führen Sie WinDbg als Administrator aus, um Zeitreise-Ablaufverfolgungen aufzeichnen zu können.

  2. Wählen Sie in WinDbg File>Start Debugging>Launch Executable (Advanced) aus.

  3. 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.

    Screenshot von WinDbg mit dem Kontrollkästchen „Record with Time Travel Debugging“ im Bildschirm „Launch Executable (Advanced)“.

  4. Aktivieren Sie das Kontrollkästchen Record with Time Travel Debugging, um eine Ablaufverfolgung aufzuzeichnen, wenn die ausführbare Datei gestartet wird.

  5. Klicken Sie auf Configure und Record, um die Aufzeichnung zu starten.

  6. Wenn das Dialogfeld „Configure recording“ angezeigt wird, klicken Sie auf Record, um die ausführbare Datei zu starten und die Aufzeichnung zu beginnen.

    Screenshot von WinDbg, der das Dialogfeld Configure Recording anzeigt, wobei der Pfad auf temp eingestellt ist.

  7. Der Aufzeichnungsdialog erscheint und zeigt an, dass die Ablaufverfolgung aufgezeichnet wird. Kurze Zeit später stürzt die Anwendung ab.

  8. Klicken Sie auf Close Program, um das Dialogfeld „DisplayGreeting has stopped working“ zu schließen.

    Dialogfeld, das anzeigt, dass die DisplayGreeting-App nicht mehr funktioniert.

  9. Wenn das Programm abstürzt, wird die Ablaufverfolgungsdatei geschlossen und auf die Festplatte geschrieben.

    Screenshot der WinDbg-Ausgabe, die 1/1 Keyframes indiziert anzeigt.

  10. 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.

  1. 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

  1. 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
    
  2. Fügen Sie den Ort Ihres lokalen Codes zum Quellcodepfad hinzu, indem Sie den folgenden Befehl eingeben.

    .srcpath+ C:\MyProjects\DisplayGreeting\DisplayGreeting
    
  3. 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.

  4. 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

  1. 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
    
  2. 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.

  3. 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
    
  4. 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.

  5. 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.

  1. 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.

  2. 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.

    Screenshot von WinDbg, der das Fenster Locals mit Speicher-ASCII-Ausgabe und das Fenster Source Code anzeigt.

  3. Zur weiteren Untersuchung können wir ein Speicherfenster öffnen, um den Inhalt in der Nähe der Basiszeiger-Speicheradresse 0x00effe44 zu betrachten.

  4. Um die zugehörigen ASCII-Zeichen anzuzeigen, wählen Sie im Menüband Speicher Text und dann ASCII aus.

    Screenshot der WinDbg-Vorschau mit Speicher-ASCII-Ausgabe und Quellcode-Fenster.

  5. 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)

a

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

  1. 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
    
  2. Wählen Sie View und dann Breakpoints aus, um zu überprüfen, ob sie wie vorgesehen eingestellt sind.

    Screenshot des WinDbg-Fensters Breakpoints, das einen einzelnen Haltepunkt anzeigt.

  3. 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
    
  4. 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.

    Screenshot von WinDbg, der das Fenster Locals anzeigt.

  5. 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.

    Screenshot von WinDbg, der das Stack-Fenster anzeigt.

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

  1. 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.

  2. 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)]
    
  3. 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
    
  4. Vergewissern Sie sich, dass ein Hardware-Lese-Haltepunkt im Haltepunktfenster aktiv ist.

    Screenshot des WinDbg-Fensters Breakpoints, das einen einzelnen Hardware-Lesebreakpoint anzeigt.

  5. 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.

    Screenshot von WinDbg, der ein Watch Locals-Fenster anzeigt.

  6. 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
    
  7. 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
    
  8. 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)
    
  9. Es sieht so aus, als hätten wir die Ursache dafür gefunden. Das Array greeting, das wir deklariert haben, ist 50 Zeichen lang, während die Größe sizeof(greeting), die wir an GetCppConGreeting übergeben, 0x64, 100 ist.

    Bildschirmfoto von WinDbg, das den Code DisplayGreeting mit einem Fenster Watch Locals anzeigt, das 0x64 anzeigt.

    Wenn wir uns das Größenproblem genauer ansehen, stellen wir fest, dass die Nachricht 75 Zeichen lang ist, 76 einschließlich des Zeichenkettenendes.

    HELLO FROM THE WINDBG TEAM. GOOD LUCK IN ALL OF YOUR TIME TRAVEL DEBUGGING!
    
  10. 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));
    
  11. 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

  1. 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.

    Screenshot des Quellcodefensters in WinDbg mit einem Haltepunkt, der auf std::array gesetzt wurde.

  2. 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
    
  3. 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.

  1. Über View und dann Locals. Im Fenster locals ist greeting im aktuellen Kontext verfügbar, so dass wir seinen Speicherort bestimmen können.

  2. 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.

  3. 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.

  4. Setzen Sie den Haltepunkt mit dem Befehl ba unter Verwendung der Speicheradresse, die wir auf Schreibzugriff überwachen wollen.

    ba w4 ddf800
    
  5. 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
    
  6. 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.

  1. 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.

  2. 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]           
        ...         
    
  3. 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
    
  4. Klicken Sie auf [Time Travel], um die Ablaufverfolgung an den gewünschten Zeitpunkt zu setzen.

    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
    
  5. 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
    
  6. 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 gemeinsam genutzt werden, so dass das Problem bei Bedarf auf jedem PC reproduziert werden kann.

Weitere Informationen

Zeitreise-Debugging – Überblick

Zeitreise-Debugging – Aufzeichnung

Zeitreise-Debugging – Wiederholung einer Aufzeichnung

Zeitreise-Debugging – Arbeiten mit Ablaufverfolgungsdateien