Time Travel Debugging — Langlaufende Fehler, die sich nicht reproduzieren, aufzeichnen und zurückspulen
· Aktualisiert am: · Go Komura · WinDbg, Time Travel Debugging, Debugging, Fehleruntersuchung, Windows, Windows-Entwicklung, .NET, C++, Technische Beratung
Änderungsverlauf (Erstfassung, veröffentlicht am 2. Sep 2026)
- Erstveröffentlichung
„Es stürzt einmal im Monat ab, nur mitten in der Nacht.“ „Derselbe Vorgang reproduziert es auf meinem Rechner nie.“ „Wir haben einen Dump, aber der Absturzort sagt uns nicht, warum der Wert so geworden ist.“ Unter den Fehleruntersuchungen langlaufender Windows-Apps sind das die Fälle, die am meisten Zeit fressen. In einem früheren Artikel, „Absturz-Dumps mit WinDbg + SOS lesen“, haben wir betrachtet, wie man einen Absturz-Dump liest, der eine einzelne Fotografie ist. Dieser Artikel setzt dort an und behandelt das Werkzeug für die Situationen, in denen eine Fotografie nicht reicht: Time Travel Debugging (TTD).
TTD ist eine WinDbg-Funktion, die die gesamte Ausführung eines Prozesses aufzeichnet und sie später vorwärts und rückwärts wiedergeben lässt. Statt immer wieder zu versuchen, einen Fehler zu reproduzieren, können Sie die Debuggersitzung „zurückspulen“.1 Die vorgesehenen Leser sind Entwickler und Betreuer von Windows-, .NET- und C++-Anwendungen, die Dump- und Log-Untersuchungen bereits hinter sich haben und trotzdem Fälle haben, deren Ursache sie nicht erreichen. Die Voraussetzungsumgebung ist Windows 10/11 oder Windows Server 2016 oder höher mit dem aktuellen WinDbg, und die Aufzeichnung erfordert Administratorrechte.12 Der Schwierigkeitsgrad ist Mittelstufe.
Voraussetzungen dieses Artikels
| Punkt | Inhalt |
|---|---|
| Vorgesehene Leser | Entwickler und Betreuer von Windows-Apps mit langlaufenden oder intermittierenden Fehlern, deren Ursache Dumps und Logs nicht erreichen |
| Vorausgesetztes Wissen | Erfahrung, einen Dump in WinDbg zu öffnen und !analyze -v oder !clrstack auszuführen. Der Inhalt des SOS-Analyseartikels wird vorausgesetzt |
| Voraussetzungsumgebung | Windows 10/11 oder Windows Server 2016/2019/2022/2025, WinDbg (aktuelle Version), TTD.exe, Administratorrechte2 |
| Nicht behandelt | Integration mit dem Visual Studio Enterprise Snapshot Debugger; Kernelmodus (TTD ist nur Benutzermodus3) |
1. Zuerst die Kurzfassung
- Ein Dump bewahrt den „Zustand“; TTD bewahrt den „Pfad“. Die offizielle Dokumentation stellt fest, dass Dumps den Zustand und den Ausführungspfad, die zum Fehler geführt haben, oft verfehlen.1 Wenn eine Fotografie des Absturzmoments die Ursache nicht sagt, brauchen Sie als Nächstes nicht mehr Fotografien, sondern eine Aufzeichnung.
- Aufzeichnen ist schwer. Während der Aufzeichnung läuft der Zielprozess 5- bis 20-mal langsamer oder schlimmer, und die Trace-Datei wächst bei aktivem Prozess um 5 bis 50 MB pro Sekunde, ohne Obergrenze.24 Es ist kein Werkzeug, das Sie bedingungslos an eine langlaufende App hängen.
- Für langlaufende Apps entwerfen Sie, „was aufgezeichnet wird“. Kapitel 5 behandelt die vier Einstiege, die TTD.exe bereitstellt:
-ring/-maxFile(nur die letzten N MB behalten),-module(nur aufzeichnen, während das eigene Modul ausgeführt wird),-recordmode Manual(die App legt das Aufzeichnungsintervall fest) und-monitor(jeden Start aufzeichnen).2 - Die Wiedergabe dreht sich um drei Dinge: Positionen (
!tt), Ereignisse (dx @$curprocess.TTD.Events) und Abfragen (TTD.Calls/TTD.Memory). Kombinieren Sieba(einen Break-on-Access-Haltepunkt) mitg-(Rückwärtsausführung), und der Debugger beantwortet direkt „wer hat diesen Wert zuletzt geschrieben?“.5 - Eine Trace enthält den Inhalt des Speichers. Sie kann personenbezogene und vertrauliche Informationen enthalten, etwa Dateipfade, Registrierungsdaten und den Inhalt von Speicher und Dateien.1 Entwerfen Sie Weitergabe und Speicherung so, wie Sie es für eine vertrauliche Datei tun würden.
- Wissen Sie vorher, was TTD nicht kann. Es kann den Kernelmodus nicht aufzeichnen, es kann sich nicht in geschützte Prozesse (PPL) injizieren, es kann sich nach dem Anhängen nicht selbst lösen, und der Speicher lässt sich während der Wiedergabe nicht ändern.32
In der Abbildung kennzeichnet eine durchgezogene Linie eine stets geltende Beziehung und eine gestrichelte Linie eine bedingte (die Bedingungen stehen bei jeder Beziehung auf der Detailseite). Die vollständige Liste der Beziehungen (32 insgesamt, mit Beleg und Sicherheitsgrad) und die Definitionen der wichtigsten Konzepte sind auf der Detailseite der Wissenskarte (auf Japanisch) zusammengestellt. Daten: JSON-LD / Turtle
2. Wo ein Dump nicht ausreicht
Ein Absturz-Dump ist eine Kopie von Speicher und Registern im Moment, in dem der Prozess abgestürzt ist (oder angehalten wurde). Wie im SOS-Analyseartikel beschrieben, sagt !clrstack, wo es abgestürzt ist, und !dumpheap -stat, was den Heap verbraucht. Was er nicht sagen kann, ist was auf dem Weg zu diesem Zustand geschehen ist.
flowchart TB
accTitle: Was ein Dump erfasst und was TTD erfasst
accDescr: Ein Absturz-Dump erfasst nur den Zustand im Moment des Absturzes, nicht den Weg, der dorthin geführt hat. TTD behält die gesamte Befehlsausführung vom Beginn der Aufzeichnung bis zum Ende, sodass der Pfad zusammen mit dem Zustand erhalten bleibt
dump["Absturz-Dump: Zustand im Moment des Absturzes"] --> q1["Zeigt nicht, warum der Wert so geworden ist"]
ttd["TTD-Trace: Befehlsausführung des aufgezeichneten Intervalls"] --> q2["Kann zur Position zurückgehen, an der der Wert geschrieben wurde"]
q1 -.-> gap["Diese Lücke ist es, die langlaufende Untersuchungen in die Länge zieht"]
Abbildung 1: Ein Dump ist Zustand; TTD ist der Pfad. Bei langlaufenden Fehlern ist in der Regel Letzteres das, was Sie wollen.
Typische Fälle sehen so aus.
- Ein Feld einer Struktur auf dem Heap hält einen unmöglichen Wert. Der Dump zeigt den zerstörten Wert, aber nicht, wer ihn geschrieben hat oder wann.
- Der Ort der Ausnahme ist bekannt, aber nicht, warum das übergebene Argument ungültig war. Geht man die Aufrufer weiter hinauf, steht die Funktion, die den Wert unterwegs erzeugt hat, nicht mehr auf dem Stapel.
- Handles oder Speicher wachsen im Lauf eines Monats. Ein einzelner Dump kann nur sagen „es wächst“; der Aufrufpfad, der es hat wachsen lassen, ist nicht da.
flowchart TB
accTitle: Was ein Dump in typischen Langzeitfällen verfehlt
accDescr: Der zerstörte Wert ist erfasst, aber nicht wer ihn geschrieben hat, die Ausnahmeposition ist erfasst, aber die Funktion, die das Argument erzeugt hat, steht nicht mehr auf dem Stapel, und das Ressourcenwachstum ist erfasst, aber nicht der Pfad, der es hat wachsen lassen; die drei Fallarten teilen das
c1["Zerstörtes Feld"] --> m1["Keine Aufzeichnung, wer es wann geschrieben hat"]
c2["Ausnahme bei ungültigem Argument"] --> m2["Funktion, die den Wert erzeugt hat, steht nicht mehr auf dem Stapel"]
c3["Ressource, die über einen Monat wächst"] --> m3["Keine Aufzeichnung des Aufrufpfads, der sie hat wachsen lassen"]
m1 --> same["Gemeinsamer Punkt: Zustand vorhanden, aber kein Pfad"]
m2 --> same
m3 --> same
Abbildung 2: Alle drei Fälle haben „Zustand, aber keinen Pfad“. Mehr Fotografien füllen den Pfad nicht.
Die Dokumentation von Microsoft vergleicht Stärken und Schwächen der Untersuchungsmethoden wie folgt.1
| Methode | Stärken | Schwächen |
|---|---|---|
| Live-Debugging | Interaktiv, zeigt den Ausführungsfluss und lässt den Zustand ändern | Hält die Arbeit des Benutzers an. Wiederholtes Reproduzieren kostet Aufwand. In der Produktion oft unbrauchbar. Vom Fehlerpunkt zur Ursache zurückzugehen ist schwer |
| Dumps | Keine Codeänderungen im Voraus nötig. Geringe Aufdringlichkeit, lassen sich bei einem Auslöser erfassen. Nahezu kein Aufwand, wenn sie nicht genutzt werden | Selbst mit aufeinanderfolgenden Schnappschüssen ist die Sicht auf „vergangene Zeit“ grob |
| Telemetrie und Logs | Leichtgewichtig. An Geschäftsszenarien gebunden | Keine Logs auf unerwarteten Codepfaden. Unzureichende Datentiefe, und statisch im Code verankert |
| TTD | Stark bei komplexen Fehlern. Keine Codeänderungen im Voraus nötig. Lässt sich offline beliebig oft wiedergeben und zeichnet alles auf | Großer Aufwand während der Aufzeichnung. Kann mehr Daten erfassen als nötig. Dateien werden groß |
Es gibt noch eine wichtige Eigenschaft, die der offizielle TTD-Walkthrough hervorhebt. Wenn der Debugger am Fehlerpunkt anhält, liegt dieser Punkt oft in Fehlerbehandlungscode mehrere Schritte hinter der eigentlichen Ursache.5 Ein Dump wird immer an dieser Position „mehrere Schritte später“ genommen. Mit TTD können Sie von dort Befehl für Befehl zurückgehen.
flowchart TB
accTitle: Die Lücke zwischen Fehlerpunkt und eigentlicher Ursache
accDescr: Der Fehlerpunkt, an dem ein Dump genommen wird, liegt oft in der Fehlerbehandlung mehrere Schritte hinter der eigentlichen Ursache, und mit TTD können Sie von diesem Punkt Befehl für Befehl zur Ursache zurückspulen
cause["Eigentliche Ursache (die Anweisung, die den Wert zerstört hat)"] --> steps["Mehrere Schritte vorwärts"]
steps --> fail["Fehlerpunkt (Ausnahme, Fehlerbehandlung)"]
fail -->|"Dump"| photo["Hier eingefroren"]
fail -->|"TTD"| back["Mit p- / t- / g- zurückgehen"]
back --> cause
Abbildung 3: Ein Dump ist am Fehlerpunkt eingefroren. TTD kann vom Fehlerpunkt zur Ursache zurückgehen.
3. Wie TTD funktioniert und was es kostet
3.1 Was aufgezeichnet wird
TTD injiziert eine Aufzeichnungsengine in den Zielprozess und zeichnet die ausgeführten Anweisungen Anweisung für Anweisung auf. In den Worten der offiziellen Dokumentation „kodiert sie eine vollständige Trace auf Anweisungsebene mit durchschnittlich weniger als einem Byte pro Anweisung“; in der Praxis landet sie irgendwo zwischen einem Bit und einem Byte pro Anweisung. Programme, die wenige Arten von Funktionen ausführen und wenig Daten verarbeiten, erzeugen kleinere Traces; das Gegenteil erzeugt größere.24
Eine Aufzeichnung erzeugt zwei Dateien.1
| Datei | Rolle | Ungefähre Größe |
|---|---|---|
.run |
Die Trace selbst. Speichert die Befehlsausführung während der Aufzeichnung | Wächst bei Aktivität um 5 bis 50 MB pro Sekunde. Wächst im Leerlauf nicht4 |
.idx |
Der Index. Hilfsdaten, mit denen WinDbg effizient wiedergeben und Speicher abfragen kann. Wird erzeugt, wenn die Aufzeichnung stoppt, und auch automatisch, wenn WinDbg die .run-Datei öffnet |
1- bis 2-mal so groß wie die Trace4 |
flowchart TB
accTitle: Der Ablauf von der TTD-Aufzeichnung zur Wiedergabe
accDescr: Eine Aufzeichnungsengine wird in den Zielprozess injiziert und die Befehlsausführung in einer .run-Datei aufgezeichnet; wenn WinDbg die .run-Datei öffnet, erzeugt sie einen .idx-Index und gibt mit Positionen, Ereignissen und Abfragen vorwärts und rückwärts wieder
proc["Zielprozess"] --> inj["Aufzeichnungsengine injiziert (TTDRecordCPU)"]
inj --> run[".run (Aufzeichnung der Befehlsausführung)"]
run --> open["In WinDbg öffnen"]
open --> idx[".idx (Index) erzeugen"]
idx --> play["Mit Positionen, Ereignissen und Abfragen vorwärts und rückwärts wiedergeben"]
Abbildung 4: Aufgezeichnet wird mit TTD.exe oder WinDbg; gelesen wird mit WinDbg. Es reicht, nur die .run-Datei zu teilen.
Die Zeit in einer .run-Datei wird als „Position“ ausgedrückt. Sie hat die Form zweier hexadezimaler Zahlen, getrennt durch einen Doppelpunkt, etwa 12:0 oder 1A0:12F; die erste Hälfte ist die Sequenznummer (entsprechend einem Sequenzereignis) und die zweite Hälfte die ungefähre Anzahl der Anweisungen seit diesem Ereignis.6 FFFFFFFFFFFFFFFE:0 bedeutet das Ende der Trace.7 Positionen stehen in Kapitel 6 im Mittelpunkt.
flowchart TB
accTitle: Wie Positionen in einer Trace ausgedrückt werden
accDescr: Eine Position ist eine hexadezimale Sequenznummer und Schrittzahl, getrennt durch einen Doppelpunkt; der Anfang liegt nahe 0, das Ende wird als FFFFFFFFFFFFFFFE:0 ausgedrückt, und Sie können auch zu einer ungefähren Position per Prozent gehen
pos["Position xx:yy (hexadezimal)"] --> seq["xx: Sequenznummer"]
pos --> step["yy: Anweisungen seit diesem Ereignis"]
seq --> tail["Das Ende ist FFFFFFFFFFFFFFFE:0"]
step --> pct["Prozentangaben wie !tt 50 funktionieren ebenfalls"]
Abbildung 5: Eine Position ist „Ereignisnummer:Anweisungsanzahl“. Sie ist keine Wanduhrzeit, lässt sich aber, wie in Kapitel 6 gezeigt, in Wanduhrzeit umrechnen.
3.2 Die Kosten
Die offizielle Dokumentation selbst beschreibt TTD als „invasive Technologie“.2 Hier sind die Kosten in Zahlen.
| Punkt | Inhalt |
|---|---|
| Geschwindigkeit | Der Zielprozess läuft während der Aufzeichnung 5- bis 20-mal langsamer oder schlimmer (abhängig von der App und den Aufzeichnungsoptionen). In der Oberfläche kann es unbemerkt bleiben, bei schweren Vorgängen wie einem Datei-öffnen-Dialog ist es spürbar23 |
| Dateiwachstum | 5 bis 50 MB pro Sekunde bei Aktivität. Einige Minuten Aufzeichnung können mehrere GB erreichen. Es ist keine Obergrenze gesetzt4 |
| Festplatte voll | Wenn die Festplatte während der Aufzeichnung voll wird, schreibt TTD die letzte Seite und wartet dann faktisch, bis es wieder schreiben kann. WinDbg zeigt weiter den Aufzeichnungsdialog und gibt weder einen Fehler noch eine Warnung aus. Das Ergebnis ist eine unvollständige Trace4 |
| Speicher | Die Aufzeichnung fügt dem Speicher des Zielprozesses den Aufwand virtueller CPUs hinzu (standardmäßig 55 auf x64/ARM64, 32 auf x86). Verringern Sie ihn mit -numVCpu nur, wenn der Speicher knapp ist2 |
| Lässt sich nicht lösen | Einmal angehängt, kann TTD sich nicht selbst lösen. Wenn Sie die Aufzeichnung beenden, schließen Sie die App oder beenden Sie den Prozess. Ist der Prozess für das System unverzichtbar, muss das Betriebssystem neu gestartet werden2 |
flowchart TB
accTitle: Die Kosten der Aufzeichnung und ihre Wirkung auf langlaufende Apps
accDescr: Die Verlangsamung während der Aufzeichnung, das Dateiwachstum, das stille Warten bei voller Festplatte und die Unmöglichkeit, sich nach dem Anhängen zu lösen, sind die vier Kosten, die es unmöglich machen, TTD bedingungslos an eine langlaufende App zu hängen
cost["TTD-Aufzeichnung"] --> slow["5- bis 20-mal langsamer"]
cost --> grow["Wächst 5 bis 50 MB pro Sekunde, keine Obergrenze"]
cost --> disk["Wartet still, wenn die Festplatte voll ist"]
cost --> stuck["Lässt sich nach dem Anhängen nicht lösen"]
slow --> no["Bedingungslose Daueraufzeichnung ist nicht tragfähig"]
grow --> no
disk --> no
stuck --> no
Abbildung 6: Jede einzelne der vier Kosten schließt Daueraufzeichnung aus. Deshalb braucht es den „Aufzeichnungsentwurf“ von Kapitel 5.
3.3 Was es nicht kann
- Nur Benutzermodus. Es kann nur die Benutzermodus-Ausführung eines Prozesses aufzeichnen; Code, der im Kernelmodus läuft, etwa Treiber, lässt sich nicht debuggen.3
- Geschützte Prozesse. TTD kann sich nicht in geschützte Windows-Prozesse wie Protected Process Light (PPL) injizieren.3
- Wiedergabe ist schreibgeschützt. Sie können in der Zeit zurückgehen, aber die Geschichte nicht ändern. Befehle, die Speicher lesen, funktionieren; Befehle, die ihn ändern, nicht.3
- Unverträglichkeit mit Antivirus- und Speicherüberwachungssoftware. Weil TTD in den Prozess einhakt, kollidiert es mit Software, die Systemspeicheraufrufe verfolgt oder spiegelt. Wenn die Aufzeichnung einen Fehler erzeugt, der wie unzureichende Rechte aussieht, deaktivieren Sie solche Software vorübergehend, um das Problem einzugrenzen. Das Electron-Framework ist ebenfalls ein bekannter Konflikt; selbst wenn eine Aufzeichnung gelingt, kann der Zielprozess in einem Deadlock hängen oder abstürzen.3
- UWP-Apps lassen sich nicht starten und aufzeichnen (Anhängen an eine bereits laufende UWP-App ist möglich). „Ungewöhnliche Prozesse“, die in einer anderen Sitzung oder einem anderen Sicherheitskontext laufen, werden derzeit ebenfalls nicht unterstützt.8
flowchart TB
accTitle: Was TTD nicht aufzeichnen oder tun kann
accDescr: Kernelmodus-Code, geschützte Prozesse, Startaufzeichnung von UWP-Apps und Prozesse in einer anderen Sitzung oder einem anderen Sicherheitskontext lassen sich nicht aufzeichnen; der Speicher lässt sich während der Wiedergabe nicht ändern; und Antivirussoftware und Electron können kollidieren
no["Einschränkungen von TTD"] --> g1["Lässt sich nicht aufzeichnen"]
no --> g2["Einschränkungen und Konflikte"]
g1 --> k["Kernelmodus-Code (Treiber usw.)"]
k --> ppl["Geschützte Prozesse (PPL)"]
ppl --> uwp["Startaufzeichnung von UWP (Anhängen ist möglich)"]
uwp --> sess["Andere Sitzungen und Sicherheitskontexte"]
g2 --> ro["Wiedergabe ist schreibgeschützt"]
ro --> av["Kann mit Antivirus und Electron kollidieren"]
Abbildung 7: Die Einschränkungen kommen in zwei Arten: „lässt sich nicht aufzeichnen“ und „kollidiert“. Letzteres muss je Umgebung eingegrenzt werden.
Der letzte Punkt stolpert jeden, der einen Dienst aufzeichnen will. Die Dokumentation von TTD.exe beschreibt -attach als für die „Untersuchung von Diensten und langlaufenden Apps“ gedacht und -monitor als Aufzeichnung „jedes Mal, wenn ein Programm oder Dienst startet“,2 was sich nicht einfach mit der Aussage auf der Seite zur Fehlerbehebung deckt. In der Praxis ist der sichere Weg, je Umgebung in dieser Reihenfolge zu prüfen: bestätigen, dass ping.exe oder cmd.exe in derselben Konfiguration wie in der Produktion aufgezeichnet werden können, dann den Zielprozess versuchen.8
4. Aufzeichnen — Die WinDbg-Oberfläche und TTD.exe
Es gibt zwei Einstiege für die Aufzeichnung: die WinDbg-Oberfläche oder das Kommandozeilen-TTD.exe.
4.1 Aufzeichnen aus der WinDbg-Oberfläche
Starten Sie WinDbg als Administrator (eine Erhöhung ist für TTD verpflichtend1), wählen Sie File > Start debugging > Launch executable (advanced), geben Sie die ausführbare Datei an und aktivieren Sie Record with Time Travel Debugging. Mit Configure and Record können Sie den Speicherort der Trace-Datei und Record subset of execution festlegen (die aufgezeichneten Module mit einer kommagetrennten Liste wie notepad.exe,kernelbase.dll einschränken). Für einen bereits laufenden Prozess wählen Sie File > Start debugging > Attach to process und aktivieren ebenso Record Process with Time Travel Debugging.9
Während der Aufzeichnung erscheint ein kleiner Dialog mit den Schaltflächen „Stop and Debug“ und „Cancel“. Wenn die App beendet (oder abstürzt), wird die Trace geschlossen, und WinDbg öffnet sie automatisch und baut den Index.5
flowchart TB
accTitle: Der Ablauf der Aufzeichnung aus der WinDbg-Oberfläche
accDescr: In als Administrator gestartetem WinDbg Launch executable (advanced) oder Attach to process wählen, Record with Time Travel Debugging aktivieren, Speicherort und Modulfilter in Configure and Record festlegen, den Aufzeichnungsdialog durchlaufen, und wenn die App beendet, wird die Trace geschlossen und automatisch indiziert
adm["WinDbg als Administrator starten"] --> pick["Launch executable (advanced) / Attach to process"]
pick --> chk["Record with Time Travel Debugging aktivieren"]
chk --> cfg["Configure and Record: Speicherort, Modulfilter"]
cfg --> rec["Aufzeichnungsdialog (Stop and Debug)"]
rec --> fin["App beendet, Trace wird geschlossen und automatisch indiziert"]
Abbildung 8: Die Aufzeichnung aus der Oberfläche hat fünf Schritte. Überspringen Sie „als Administrator starten“, bleiben Sie am ersten Dialog stehen.
4.2 Aufzeichnen mit TTD.exe
Wenn Sie auf einem PC aufzeichnen müssen, auf dem WinDbg nicht installiert werden kann, oder um die Aufzeichnung zu automatisieren, verwenden Sie TTD.exe allein. Es wird über den App Installer von https://aka.ms/ttd/download installiert, und nach der Installation können Sie es mit ttd.exe -help prüfen. Für Offline-Umgebungen stellt Microsoft außerdem ein offizielles Verfahren (mit einem PowerShell-Skript) bereit, um das MSIX-Bundle von Hand zu entpacken und nur die Binärdateien zu extrahieren.2 Die Aufzeichnung erfordert Administratorrechte und wird normalerweise von einer Administrator-Eingabeaufforderung aus ausgeführt.2
Es gibt drei Aufzeichnungsmodi.2
flowchart TB
accTitle: Die drei Aufzeichnungsmodi von TTD.exe
accDescr: launch startet einen neuen Prozess mit Argumenten und zeichnet ihn auf, läuft aber mit erhöhten Rechten. attach hängt an einen laufenden Prozess per PID. monitor zeichnet jedes Mal auf, wenn das angegebene Programm startet, und der Start geschieht mit normalen Rechten
m["Aufzeichnungsmodi von TTD.exe"] --> l["-launch: starten und aufzeichnen"]
m --> a["-attach: an eine laufende PID anhängen"]
m --> mo["-monitor: jeden Start aufzeichnen"]
l -.-> lp["Wird mit Administratorrechten gestartet"]
a -.-> ap["Behält normale Rechte"]
mo -.-> mp["Normaler Startpfad, geeignet für Automatisierung"]
Abbildung 9: Nur -launch kann Argumente übergeben, läuft aber mit erhöhten Rechten. Um Verhalten nahe der Produktion aufzuzeichnen, verwenden Sie -attach oder -monitor.
:: Starten und aufzeichnen (Standardmodus; -launch kann weggelassen werden)
TTD.exe -out C:\traces MyApp.exe --config prod.json
:: An einen laufenden Prozess anhängen (Ausgabeverzeichnis vorher anlegen)
TTD.exe -attach 21440 -out C:\traces\MyApp.run
:: Jeden Start aufzeichnen (Ctrl+C beendet die Überwachung; -out erfordert einen vollständigen Pfad)
TTD.exe -out C:\traces\ -monitor MyApp.exe
-launchist der einzige Modus, der Argumente übergeben kann, aber das Programm wird mit denselben (Administrator-)Rechten wie TTD.exe gestartet. Bei Apps, deren Verhalten sich mit den Rechten ändert, zeichnen Sie mit-attachoder-monitorauf, um normale Rechte zu behalten.2-attachsetzt voraus, dass das Ausgabeverzeichnis bereits existiert. Wenn Sie einen Dateinamen angeben, darf keine Datei dieses Namens existieren.2-monitorinstalliert einen Treiber zur Überwachung von Prozessstarts und zeichnet jedes Mal auf, wenn das angegebene Programm (mehrere können angegeben werden) startet. Es bleibt bis zum Neustart wirksam und wird mit Ctrl+C gestoppt. Die Vorteile sind, dass Sie den Start nicht selbst zusammenbauen müssen, das Ziel mit normalen Rechten läuft und es sich für skriptgesteuerte Automatisierung eignet. Mit-cmdLineFilter "string"werden nur Starts aufgezeichnet, deren Kommandozeile diese Zeichenfolge enthält.2-childrenzeichnet auch Kindprozesse auf, aber jeder Prozess erhält eine eigene.run-Datei, und WinDbg kann jeweils nur eine öffnen.2
Während der Aufzeichnung erscheint eine kleine Oberfläche mit zwei Schaltflächen: „Tracing Off“ (Aufzeichnung stoppen und die App weiterlaufen lassen) und „Exit App“ (die App schließen und die Aufzeichnung beenden). Für Automatisierung blenden Sie sie mit -noUI aus und akzeptieren die EULA mit -accepteula.2 Das Aufzeichnungsprotokoll wird in einer .out-Datei am selben Ort wie die .run-Datei gehalten, wo Sie die Wanduhr-Start- und -Endzeiten der Aufzeichnung, die Länge der Aufzeichnungssitzung (Simulationszeit), ob es ein Start oder ein Anhängen war, und die Betriebssystemversion lesen können. Wenn eine Aufzeichnung fehlschlägt, erscheinen manche Fehlermeldungen nur in der .out-Datei.2
5. Aufzeichnungsentwurf für langlaufende Apps
Das ist das Herzstück des Artikels. Angesichts der Kosten in Kapitel 3 braucht das Fangen eines Fehlers, der zu einem unbekannten Zeitpunkt in einem Prozess auftritt, der tagelang läuft, einen Aufzeichnungsentwurf. TTD.exe stellt vier Mittel bereit, und Sie wählen nach der Art des Symptoms.
flowchart TB
accTitle: Wahl des Aufzeichnungsumfangs nach dem Symptom einer langlaufenden App
accDescr: Wenn Sie nicht wissen, wann es auftritt, behalten Sie nur den letzten Teil mit einem Ringpuffer; wenn Sie wissen, welches Modul verdächtig ist, zeichnen Sie nur dieses Modul auf; wenn Sie die App ändern können, legen Sie das Intervall mit der API für manuelle Aufzeichnung fest; wenn es nur beim Start oder bei bestimmten Starts erscheint, zeichnen Sie jeden Start mit dem Überwachungsmodus auf
q{"Art des Symptoms?"} -->|"Unbekannt, wann es auftritt"| ring["-ring / -maxFile: nur das Ende behalten"]
q -->|"Nur beim Start oder bei bestimmten Starts"| mon["-monitor: jeden Start aufzeichnen"]
ring -->|"Verdächtiges Modul ist klar"| mod["-module hinzufügen"]
ring -->|"App kann geändert werden"| man["Intervall mit -recordmode Manual festlegen"]
Abbildung 10: Die vier schließen einander nicht aus. Die Kombination von -ring mit -module ist die praktischste Kombination für langlaufende Apps.
5.1 Ringpuffer — Nur die letzten N MB behalten
Mit -ring wird die Trace in einen Ringpuffer der mit -maxFile angegebenen Größe geschrieben, und die Datei wächst nie über diese Grenze hinaus. Was bleibt, ist nur der letzte Teil der Aufzeichnung, der in diese Größe passt.2 Die Einheit von -maxFile ist MB; im Ringpuffermodus ist der Standard 2048 MB, das Minimum 1 MB und das Maximum 32768 MB (der Standard für den In-Memory-Ring eines 32-Bit-Prozesses ist 256 MB).2
:: An eine laufende Überwachungs-App anhängen und nur die letzten 4 GB Ausführung behalten
TTD.exe -accepteula -noUI -attach 21440 -ring -maxFile 4096 -out C:\traces\MyApp.run
:: Wenn das Symptom erkannt wird, die Aufzeichnung stoppen (die App läuft weiter)
TTD.exe -stop 21440
-stop akzeptiert einen Prozessnamen, eine PID oder all und stoppt diese Aufzeichnung. -wait <seconds> wartet, bis jede Aufzeichnungssitzung auf dem System beendet ist (-1 für unbegrenzt), und wird in Automatisierungsskripten verwendet, um die Reihenfolge „stoppen, dann die Datei einsammeln“ durchzusetzen.2
flowchart TB
accTitle: Zeitlinie einer Ringpuffer-Aufzeichnung
accDescr: Die Aufzeichnung beginnt beim Anhängen, ältere Teile werden aus dem Ringpuffer verdrängt, und wenn das Symptom erscheint und stop ausgegeben wird, bleibt nur der jüngste maxFile-Anteil als Trace
s["Aufzeichnung mit -attach -ring starten"] --> old["Ältere Intervalle werden verdrängt"]
old --> sym["Symptom erscheint"]
sym --> stop["Aufzeichnung mit -stop stoppen"]
stop --> keep["Nur der jüngste maxFile-Anteil bleibt"]
keep -.-> note["So dimensionieren, dass die Momente vor dem Symptom in den Puffer passen"]
Abbildung 11: Der Ringpuffer ist eine Vorrichtung, um „die Momente vor dem Symptom“ zu behalten. Rechnen Sie -maxFile rückwärts aus „Zeit vom Bemerken des Symptoms bis zum Stoppen, multipliziert mit Wachstum pro Sekunde“.
Es gibt zwei Entwurfspunkte.
- Machen Sie den Puffer groß genug, um die Zeit vom Bemerken bis zum Stoppen aufzunehmen. In einem aktiven Prozess wächst die Trace um 5 bis 50 MB pro Sekunde,4 sodass ein 4-GB-Ring unter Last einer bis zwei Minuten und unter leichter Last etwas über zehn Minuten entspricht. Sie brauchen einen Mechanismus, der die Verzögerung zwischen dem Erkennen des Symptoms (eine bestimmte Logzeile, ein Zählerschwellenwert, ein Alarm der Überwachung) und
-stopin diesem Fenster hält. - Stoppen löst nicht.
-stopstoppt die Aufzeichnung, aber TTD löst sich nicht selbst vom Zielprozess.2 Die Aufzeichnung vollständig zu beenden erfordert das Beenden des Prozesses, also nehmen Sie „Neustart im nächsten Wartungsfenster nach dem Einsammeln der Trace“ in das Betriebsverfahren auf.
sequenceDiagram
accTitle: Stoppen einer Ringpuffer-Aufzeichnung in Abstimmung mit der Überwachung
accDescr: Wenn die Überwachungsseite das Symptom über Logs oder Zähler erkennt, ruft sie TTD.exe stop auf, sammelt die abgeschlossene .run-Datei ein und startet den Prozess im nächsten Wartungsfenster neu, um TTD zu entfernen
participant W as Überwachung (Logs, Zähler)
participant T as TTD.exe
participant P as Zielprozess
W->>W: Symptom erkennen
W->>T: -stop PID
T->>P: Aufzeichnung stoppen (Prozess läuft weiter)
T-->>W: .run ist abgeschlossen
W->>W: .run einsammeln, verschlüsseln und speichern
W->>P: Im nächsten Wartungsfenster neu starten
Abbildung 12: Nur wenn die Verzögerung zwischen Erkennung und -stop in den Puffer passt, bleiben die Momente vor dem Symptom in der Trace. Der Neustart ist Teil des Betriebs.
5.2 Modulfilter — Nur aufzeichnen, während der eigene Code läuft
-module <module name> zeichnet nur das angegebene Modul (die ausführbare Datei selbst oder eine geladene DLL; mehrere können angegeben werden) und den Code, den dieses Modul aufruft, auf. Der Zielprozess läuft mit voller Geschwindigkeit, bis Code im angegebenen Modul ausgeführt wird; die Aufzeichnung beginnt, wenn die Ausführung ins Modul eintritt, stoppt, wenn sie es verlässt, und der Prozess kehrt zur vollen Geschwindigkeit zurück. Weil das Ein- und Ausschalten der Aufzeichnung teuer ist, bleibt die Aufzeichnung an, während das angegebene Modul andere Module im Prozess aufruft.2
:: Nur aufzeichnen, während unsere Messlogik-DLL läuft (kombiniert mit einem Ring)
TTD.exe -accepteula -noUI -attach 21440 -module MeasureCore.dll -ring -maxFile 2048 -out C:\traces\
Eine so erzeugte Trace überspringt einfach die Intervalle, in denen die Aufzeichnung aus war, behandelt „die nächste Anweisung“ als die erste Anweisung nach Wiederaufnahme der Aufzeichnung, und Sie debuggen sie nicht anders als eine Trace des ganzen Prozesses.2 In langlaufenden Apps machen die UI-Leerlaufschleife und die interne Verarbeitung des Frameworks tendenziell den Großteil der ausgeführten Anweisungen aus, sodass die Beschränkung der Aufzeichnung auf das eigene Modul allein Aufwand und Dateigröße erheblich senkt.
flowchart TB
accTitle: Wie modulgefilterte Aufzeichnung sich verhält
accDescr: Der Zielprozess läuft außerhalb des angegebenen Moduls mit voller Geschwindigkeit; die Aufzeichnung beginnt, wenn Code im angegebenen Modul betreten wird, läuft weiter, während dieses Modul andere Module aufruft, und stoppt, wenn die Ausführung das Modul verlässt
out1["Außerhalb des angegebenen Moduls: volle Geschwindigkeit"] --> in1["Eintritt ins angegebene Modul: Aufzeichnung beginnt"]
in1 --> callee["Andere Module, die es aufruft: Aufzeichnung läuft weiter"]
callee --> out2["Verlassen des angegebenen Moduls: Aufzeichnung stoppt"]
out2 --> out1
Abbildung 13: Weil die Win32-APIs und die Laufzeit, die Ihre DLL aufruft, ebenfalls aufgezeichnet werden, reicht das, um zu verfolgen, „was unser Code dem Betriebssystem übergeben hat“.
5.3 Manuelle Aufzeichnung — Die App legt das Intervall fest
Mit -recordmode Manual läuft der Prozess auch nach der Injektion von TTD mit voller Geschwindigkeit weiter, und die Aufzeichnung geschieht nur, wenn das Programm die In-Prozess-Aufzeichnungs-API von TTD aufruft (der Standard, Automatic, zeichnet ab dem Moment der Injektion auf).2 Die API-Dokumentation und Beispiele liegen im WinDbg-Samples-Repository auf GitHub.10
Wenn Sie die App ändern können, ist das der sparsamste Ansatz. Sie können Logik einbauen, die die Aufzeichnung startet, wenn die App selbst weiß, dass eine Anomalie unmittelbar bevorsteht, und stoppt, wenn sich die Lage normalisiert: „drei aufeinanderfolgende Kommunikationsversuche sind fehlgeschlagen“, „der Rückstau der Warteschlange hat den Schwellenwert überschritten“ und so weiter. In einer Supervisor-Prozess-Konfiguration wie der im Job-Object-Artikel behandelten kann der Supervisor das Intervall stattdessen entscheiden.
5.4 Überwachungsmodus — Jeden Start aufzeichnen
Für Fehler, die nur unmittelbar nach dem Start oder bei bestimmten Starts erscheinen, passt -monitor. Die offizielle Vergleichstabelle beschreibt den Überwachungsmodus ebenfalls als für das „Fangen intermittierender Probleme und Startprobleme“ gedacht.2 Wenn Sie dasselbe Programm viele Male aufzeichnen, werden die standardmäßigen fortlaufenden Dateinamen (MyApp01.run, MyApp02.run und so weiter) ineffizient, weil vorhandene Dateien durchsucht werden müssen; verwenden Sie daher -timestampFilename für Namen mit Zeitstempel. Die Zahl gleichzeitiger Aufzeichnungen lässt sich mit -maxConcurrentRecordings deckeln.2
flowchart TB
accTitle: Wie der Überwachungsmodus sich verhält
accDescr: Die Option monitor installiert einen Treiber zur Überwachung von Prozessstarts, grenzt das Ziel mit dem Kommandozeilenfilter jedes Mal ein, wenn das angegebene Programm startet, zeichnet es auf, erzeugt eine eigene Trace-Datei je Start und läuft weiter bis Ctrl+C oder einem Neustart
drv["Treiber zur Startüberwachung installieren"] --> launch["Start des angegebenen Programms erkennen"]
launch --> filt{"Stimmt mit -cmdLineFilter überein?"}
filt -->|"Ja"| rec["Diesen Start aufzeichnen (eigene Datei je Start)"]
filt -->|"Nein"| skip["Nicht aufzeichnen"]
rec --> next["Auf den nächsten Start warten (bis Ctrl+C oder Neustart)"]
skip --> next
Abbildung 14: Der Überwachungsmodus „lauert auf den Start“. Er passt zu Fehlern, die bei jedem Start erscheinen, und zu Fehlern, die sich nach Startbedingungen eingrenzen lassen.
5.5 Wohin mit der Festplatte
Für langlaufende Aufzeichnungen legen Sie die Traces auf ein eigenes Volume und nehmen das Wachstum der .run-Datei in Ihre Überwachung auf. Wie in Abschnitt 3.2 vermerkt, wartet die Aufzeichnung bei voller Festplatte einfach still, ohne Fehler. Der offizielle Workaround ist ebenso primitiv: „freien Speicherplatz im Explorer prüfen“ und „prüfen, dass die .run-Datei regelmäßig wächst“.4 Ohne Überwachung des freien Speichers enden Sie mit einer unvollständigen Trace, in der der Moment, den Sie am meisten wollten, nie geschrieben wurde.
flowchart TB
accTitle: Wie eine volle Festplatte eine unvollständige Trace erzeugt
accDescr: Wenn die Festplatte während der Aufzeichnung voll wird, schreibt TTD die letzte Seite und wartet still ohne Fehler und ohne Warnung, sodass das danach auftretende Symptom nicht aufgezeichnet wird und eine unvollständige Trace bleibt, die sich öffnet, der aber der entscheidende Teil fehlt. Verhindern Sie das mit einem eigenen Volume und Überwachung des .run-Wachstums
full["Festplatte wird voll"] --> wait["Schreibt die letzte Seite und wartet still"]
wait --> none["Kein Fehler, keine Warnung"]
none --> sym["Symptom tritt danach auf"]
sym --> inc["Unvollständige Trace ohne das Symptom"]
guard["Eigenes Volume + Überwachung des .run-Wachstums"] -.->|"verhindert"| full
Abbildung 15: Eine Trace, die „sich öffnet, der aber der entscheidende Teil fehlt“, entsteht aus einer Lücke in der Festplattenüberwachung.
6. Wiedergeben — Positionen, Ereignisse und Zurückspulen
6.1 Öffnen
Wenn Sie eine .run-Datei in WinDbg öffnen und es keinen Index gibt, läuft !index automatisch und baut die .idx-Datei, während es Keyframes zählt (Positionen in der Trace, die automatisch für die Indizierung erzeugt werden; größere Traces haben mehr davon). Je größer die Trace, desto länger dauert es.5 Der Zustand des Index lässt sich mit !index -status prüfen; wenn etwas anderes als „Index file loaded“ gemeldet wird, bauen Sie ihn mit !index -force neu. Wenn das immer noch fehlschlägt, schließen Sie den Debugger, löschen Sie die .idx-Datei und öffnen Sie die .run-Datei erneut. Das Neubauen des Index ändert die .run-Datei nicht, sodass keine Daten verloren gehen.8
Ein Vorbehalt: Als die Indizierung großer Traces in TTD 1.11.611 verbessert wurde, hat sich das Indexformat geändert, und vorhandene Traces müssen neu indiziert werden.11 Wenn Sie alte .idx-Dateien mitführen, stolpern Sie hier.
flowchart TB
accTitle: Prüfen und Neubauen des Index
accDescr: Nach dem Öffnen der Trace den Zustand mit !index -status prüfen; wenn er etwas anderes als Index file loaded ist, mit !index -force neu bauen, und wenn das immer noch fehlschlägt, den Debugger schließen, die .idx-Datei löschen und die .run-Datei erneut öffnen. Das Neubauen ändert die .run-Datei nicht
open["Die Trace öffnen"] --> st["!index -status"]
st -->|"Index file loaded"| ok["Zur Analyse übergehen"]
st -->|"Alles andere"| force["Mit !index -force neu bauen"]
force -->|"Schlägt fehl"| del["Schließen, .idx löschen, .run erneut öffnen"]
del --> ok
force -->|"Gelingt"| ok
Abbildung 16: Das Neubauen des Index berührt die .run-Datei nicht. Im Zweifel löschen und erneut öffnen.
6.2 Nach Position gehen
Wenn Sie !tt eine Position übergeben, geht es zu diesem Zeitpunkt.6
!tt 0 ; Anfang der Trace
!tt 50 ; ungefähr die 50%-Position
!tt 100 ; Ende der Trace
!tt 1A0:12F ; zur Position 1A0:12F
Eine Position ist ein Paar hexadezimaler Zahlen, Sequenznummer:Schrittzahl.6 Das Position-Objekt hat die Eigenschaften Percent (der Anteil der Trace), Sequence und Steps, dazu SeekTo(), das zu dieser Position geht, und ToSystemTime(), das die ungefähre Wanduhrzeit (UTC) zurückgibt.7 In langlaufenden Untersuchungen macht ToSystemTime() den Unterschied, weil es die Zeitstempel im App-Log mit Positionen in der Trace abgleichen lässt. Ergebnisse von TTD.Calls tragen auch SystemTimeStart / SystemTimeEnd.12
flowchart TB
accTitle: Positionen mit Log-Zeitstempeln abgleichen
accDescr: Ausgehend von einem Zeitstempel im App-Log der ungefähren Wanduhrzeit des Position-Objekts folgen, die Position in der Trace identifizieren, mit SeekTo dorthin gehen und die Ausführung in der Umgebung lesen
log["App-Log: Zeitpunkt der Anomalie"] --> match["Eine Position finden, deren ToSystemTime nahe liegt"]
match --> seek["Mit SeekTo dorthin gehen"]
seek --> read["Die umliegenden Aufrufe und Werte lesen"]
Abbildung 17: „Was hat es unmittelbar vor dieser Logzeile getan?“ lässt sich über die Entsprechung zwischen Positionen und Wanduhrzeit nachschlagen.
Positionen sind auch zum Teilen nützlich. Wenn Sie eine Trace an einen Kollegen übergeben, hängen Sie die Position !tt x:y an, und er kann vom selben Zeitpunkt aus zu lesen beginnen. Das Schreiben von Positionsbereichen in Fehlerberichten ist ebenfalls eine offiziell empfohlene Praxis.2
6.3 Zurückspulen
Ein angehängtes - an den üblichen Schrittbefehlen bewegt sich in der Zeit rückwärts.13
| Befehl | Bedeutung | Schaltfläche im Menüband |
|---|---|---|
p- |
Eine Anweisung (oder eine Quellzeile) zurück. Ein Funktionsaufruf zählt als ein Schritt | Step Over Back |
t- |
Eine Anweisung (oder eine Quellzeile) zurück. Tritt in Funktionsaufrufe ein | Step Into Back |
g- |
Rückwärts ausführen. Stoppt, wenn ein Haltepunkt getroffen wird, bei einem Ereignis oder am Anfang der Trace | Go Back |
Die Ereignisse, die g- stoppen, sind dieselben, die ein vorwärts gerichtetes g stoppen.13 Anders gesagt: Setzen Sie einen ba (Break-on-Access-Haltepunkt) oder bp und führen Sie g- aus, und Sie gehen geradewegs zurück zur „letzten Position, an der diese Bedingung galt“. Das ist der Grundzug von Kapitel 7.
flowchart TB
accTitle: Wahl unter den Rückwärtsbefehlen
accDescr: p- geht einen Schritt über Funktionsaufrufe hinweg zurück, t- geht eine Anweisung in Funktionen hinein zurück, und g- geht geradewegs zu einem Haltepunkt, einem Ereignis oder dem Anfang der Trace zurück. Was ein vorwärts gerichtetes g stoppt, stoppt auch g-
cur["Aktuelle Position"] -->|"p-"| over["Einen Schritt über Aufrufe hinweg zurück"]
cur -->|"t-"| into["Eine Anweisung in Funktionen hinein zurück"]
cur -->|"g-"| run["Geradewegs zur nächsten Stoppbedingung zurück"]
run -.-> stop["ba / bp / Ereignis / Anfang der Trace"]
Abbildung 18: Um die Nähe sorgfältig zu betrachten, verwenden Sie t-; um zu einer fernen Ursache zu springen, ba + g-.
6.4 Über Ereignisse einsteigen
Wenn Sie unsicher sind, wo Sie zu lesen beginnen sollen, beginnen Sie mit der Ereignisliste. @$curprocess.TTD.Events listet Thread-Erzeugung und -Beendigung, Modulladen und -entladen sowie Ausnahmen als Ereignisse auf.14
dx -g @$curprocess.TTD.Events
dx @$curprocess.TTD.Events.Where(t => t.Type == "Exception").Select(e => e.Exception)
Ein Ausnahmeereignis enthält die Position, den Typ (Software / Hardware), den Ausnahmecode und den Programmzähler zu dem Zeitpunkt, und ein Klick auf den Link [Time Travel] in der Ausgabe geht zu dieser Position.15 Der offizielle Walkthrough folgt diesem Ablauf: Er springt zur Position einer Zugriffsverletzung (0xc0000005), vermutet Stapelzerstörung, weil Stapelzeiger und Basiszeiger nicht übereinstimmen, und geht mit t- drei Anweisungen zurück, um die Werte zu prüfen.5 Das Fenster Timelines von WinDbg visualisiert Ausnahmen, Haltepunkte, Speicherzugriffe und Funktionsaufrufe als Zeitlinie, und ein Doppelklick auf eine Ausnahme löst dasselbe SeekTo() aus.16
6.5 Threads und Positionen
!positions zeigt jeden aktiven Thread an der aktuellen Position zusammen mit der Position jedes Threads in der Trace.17 Hier gibt es eine Falle. Das Umschalten von Threads mit ~<number>s bewegt die Position in der Trace nicht. Die Position, die der Debugger zum Lesen des Speichers verwendet, ändert sich nicht; um den Speicher eines anderen Threads „zu diesem Zeitpunkt“ zu betrachten, gehen Sie mit dem Positionslink in der Ausgabe von !positions oder mit !tt x:y dorthin.13
flowchart TB
accTitle: Der Grundweg durch eine Wiedergabe
accDescr: Die Trace öffnen und den Index bauen, von der Ereignisliste zur Position der Ausnahme gehen, schrittweise rückwärts zur Ursache gehen und bei Bedarf mit positions zur Position eines anderen Threads gehen
open["Die Trace öffnen und indizieren"] --> ev["Die Ausnahme in TTD.Events finden"]
ev --> seek["Mit [Time Travel] zur Position gehen"]
seek --> back["Mit t- / p- / g- zurückgehen"]
back --> th["Positionen anderer Threads mit !positions prüfen"]
th -.-> caution["~s bewegt die Trace-Position nicht"]
Abbildung 19: „Über ein Ereignis einsteigen, rückwärts gehen“ ist der Grundweg durch eine Wiedergabe. Threads umzuschalten ist nicht, Positionen zu bewegen.
7. „Wann“ mit Abfragen finden — TTD.Calls und TTD.Memory
Die eigentliche Stärke der Wiedergabe ist, dass Sie die ganze Trace abfragen können. Die Objekte von TTD werden über das Debugger-Datenmodell (den Befehl dx) bereitgestellt, und Sie können sie LINQ-artig filtern, sortieren und aggregieren.18
7.1 TTD.Calls — Nach Funktionsaufrufen suchen
@$cursession.TTD.Calls("module!symbol") sammelt die Aufrufe der angegebenen Funktion aus der ganzen Trace. Platzhalter sind erlaubt, und jeder Aufruf trägt seine Start- und Endpositionen (TimeStart / TimeEnd), die Thread-ID (mit einer UniqueThreadId, die nie wiederverwendet wird), die Argumente (Parameters[]), den Rückgabewert (ReturnValue) und die Rücksprungadresse (ReturnAddress).12
Das Beispiel in der offiziellen Dokumentation ist GetLastError. Aggregieren Sie die Aufrufe, deren Rückgabewert ungleich null ist, nach Fehlercode, und Sie erhalten eine Liste, welche Fehler wie oft während der Trace aufgetreten sind.18
dx -g @$cursession.TTD.Calls("kernelbase!GetLastError").Where(x => x.ReturnValue != 0).GroupBy(x => x.ReturnValue).Select(x => new { ErrorNumber = x.First().ReturnValue, ErrorCount = x.Count() }).OrderByDescending(p => p.ErrorCount),d
Für „wo wurde MessageBox zuletzt aufgerufen?“ nehmen Sie den letzten Aufruf mit OrderBy(c => c.TimeStart).Last() und gehen über den Link [Time Travel] seines TimeStart dorthin.18
flowchart TB
accTitle: Aufbau einer TTD.Calls-Abfrage
accDescr: Aufrufe nach Funktionsnamen sammeln, nach Rückgabewert oder Argumenten filtern, nach Fehlercode und dergleichen aggregieren, nach Zeit sortieren und über den Time-Travel-Link zur Position des gewünschten Aufrufs gehen
calls["TTD.Calls (Funktionsname, Platzhalter)"] --> where["Where: nach Rückgabewert oder Argumenten filtern"]
where --> group["GroupBy: nach Fehlercode usw. aggregieren"]
group --> order["OrderBy: nach Zeit sortieren"]
order --> jump["Über [Time Travel] auf TimeStart gehen"]
Abbildung 20: Die Art, Abfragen zu nutzen, ist, nicht von „wo“ auszugehen, sondern von „wann, wie oft und mit welchen Argumenten“.
Ein Wort zu Symbolen. TTD bestimmt Anzahl und Typen der Argumente einer Funktion, ihren Rückgabetyp und ihre Aufrufkonvention aus den PDB-Symbolinformationen. Mit private symbols erhalten Sie den Funktionsnamen und die korrekten Argumente. Nur mit public symbols erhalten Sie den Funktionsnamen und Standardargumente (vier 64-Bit-Ganzzahlen ohne Vorzeichen). Ein Modul ganz ohne Symbole erhält den Funktionsnamen UnknownOrMissingSymbols.1218 Zu den Arten von PDB und wie man sie aufbewahrt, siehe „Was ist eine PDB?“.
flowchart TB
accTitle: Verfügbarkeit von Symbolen und Ergebnisse von TTD.Calls
accDescr: Mit private symbols erhalten Sie den Funktionsnamen und die korrekten Argumente; nur mit public symbols den Funktionsnamen und die vier Standard-64-Bit-Ganzzahlargumente; ohne Symbole wird der Funktionsname UnknownOrMissingSymbols
sym{"Symbole für das Modul?"} -->|"private symbols"| full["Funktionsname + korrekte Argumente und Rückgabewert"]
sym -->|"public symbols"| pub["Funktionsname + Standardargumente (4 x 64-Bit-Ganzzahlen)"]
sym -->|"keine"| unk["UnknownOrMissingSymbols"]
Abbildung 21: Ob Sie die PDBs Ihrer eigenen Module aufbewahrt haben, bestimmt unmittelbar, wie nützlich die Abfragen sind.
Calls bedeutet Berechnung, daher dauert es bei größerer Trace länger und die CPU-Auslastung steigt. Ergebnisse werden im Speicher zwischengespeichert, sodass die zweite und spätere Abfrage derselben Funktion schneller ist.12 Wenn eine Abfrage nichts liefert, gibt es vier Ursachen: wie der Aufruf geschrieben ist (prüfen Sie den Modulnamen mit dem Befehl x; kommt er in Großbuchstaben zurück, verwenden Sie das), die Zieldll ist an dieser Position noch nicht geladen (gehen Sie zu einer Position nach dem Laden und führen Sie sie erneut aus), die Funktion ist inline expandiert (lässt sich nicht verfolgen), oder der Platzhalter ist zu weit (engen Sie ihn ein).18
7.2 TTD.Memory — Nach Speicherzugriffen suchen
@$cursession.TTD.Memory(start address, end address, "access type") sammelt Zugriffe auf den angegebenen Speicherbereich aus der ganzen Trace. Die Typen sind r (Lesen), w (Schreiben), rw, e (Ausführen), rwe und ec (Ausführen/Ändern).19 „Wer hat diese Variable zuletzt geschrieben?“ beantworten Sie, indem Sie mit "w" sammeln, .Last() nehmen und zu dieser Position gehen.5
dx -g @$cursession.TTD.Memory(0x00a4fca0, 0x00a4fca4, "w")
dx @$cursession.TTD.Memory(0x00a4fca0, 0x00a4fca4, "w").Last().TimeStart.SeekTo()
Wenn Sie nur vorwärts oder rückwärts von der aktuellen Position suchen müssen, sind @$curprocess.TTD.PrevMemoryAccess("w", address, size) / NextMemoryAccess leichter und akzeptieren mehrere Bereiche auf einmal. Die Position, an der sich ein Register geändert hat, lässt sich mit @$curthread.TTD.PrevRegisterWrite("rcx") finden.6
7.3 ba + g- — Den Debugger „wer hat es zerstört?“ beantworten lassen
Verdichtet man das im offiziellen Walkthrough gezeigte Verfahren zu einer für langlaufende Untersuchungen nutzbaren Form, ergibt sich Folgendes.5
- Mit
TTD.Eventszur Position der Ausnahme gehen - Mit
t-zurückgehen und hypothetisieren, welche Variable den zerstörten Wert hält - Die Adresse dieser Variable mit
dx &variableholen - Einen Schreibhaltepunkt mit
ba w4 <address>setzen g-ausführen, um geradewegs zur Position zu gehen, an der diese Variable zuletzt geschrieben wurde- Prüfen, ob dieser Punkt (oder einige Anweisungen früher) die Ursache ist. Wenn der geschriebene Wert von einer anderen Variable stammte,
baauf diese Variable setzen undg-erneut ausführen - Wiederholen, bis Sie die Anweisung erreichen, die es zerstört hat
flowchart TB
accTitle: Den Ursprung eines Werts mit ba und g- verfolgen
accDescr: Einen Schreibhaltepunkt auf die Adresse des zerstörten Werts setzen und rückwärts ausführen, an der Anweisung anhalten, die ihn zuletzt geschrieben hat, und wenn dieser Wert von einer anderen Variable stammte, dieselben Schritte wiederholen, bis Sie die Anweisung erreichen, die ihn zerstört hat
bad["Die Adresse des zerstörten Werts identifizieren"] --> ba["Einen Schreibhaltepunkt mit ba w setzen"]
ba --> gb["Mit g- rückwärts ausführen"]
gb --> writer["An der Anweisung anhalten, die ihn zuletzt geschrieben hat"]
writer --> q{"Stammte der Wert von einer anderen Variable?"}
q -->|"Ja"| bad
q -->|"Nein"| found["Die zerstörende Anweisung = die Ursache"]
Abbildung 22: Eine Untersuchung, die mit einem Dump bei „es ist zerstört“ endet, geht mit TTD mechanisch zu „wer hat es zerstört“ weiter.
TTD 1.11.553 und später fügen außerdem @$curframe.TTD.VariableHistory() hinzu, das die Geschichte der lokalen Variablenwerte eines Frames zurückgibt. Es zeigt eine Tabelle der Variablennamen und, welche Werte jede Variable über welche Positionsbereiche gehalten hat.11 In Situationen wie Stapelzerstörung, in denen Sie wissen wollen, „seit wann der Wert falsch ist“, ist es nützlich, um einzugrenzen, wo Sie ba zuerst setzen.
8. Anwendung auf langlaufende Fehler
Nun wenden wir diese Werkzeuge auf die drei am Anfang aufgeführten Fallarten an.
flowchart TB
accTitle: Arten langlaufender Fehler und der TTD-Einstieg für jede
accDescr: Bei intermittierenden Ausnahmen und Datenzerstörung vom Ereignis mit ba und g- zurückgehen; bei Ressourcenwachstum Beschaffungs- und Freigabeaufrufe mit TTD.Calls abgleichen; bei Keine Rückmeldung und Warteketten Positionen und Wanduhrzeiten von Warte-API-Aufrufen verfolgen
t1["Intermittierende Ausnahmen, Datenzerstörung"] --> a1["TTD.Events, dann ba + g-"]
t2["Handle- und Speicherwachstum"] --> a2["Beschaffung und Freigabe mit TTD.Calls abgleichen"]
t3["Keine Rückmeldung, Warteketten"] --> a3["!positions und Wanduhrzeiten von Warte-APIs"]
a2 -.-> pre["Mit -module auf die eigene DLL beschränkt aufzeichnen"]
Abbildung 23: Der Einstieg unterscheidet sich nach Art. Gemeinsam ist, den Aufzeichnungsumfang einzugrenzen, bevor Sie zu lesen beginnen.
Art 1: Intermittierende Ausnahmen und Datenzerstörung. Behalten Sie die Momente vor dem Symptom mit dem Ringpuffer von Abschnitt 5.1, springen Sie von der Ereignisliste von Abschnitt 6.4 zur Position der Ausnahme und verfolgen Sie den Ursprung des Werts mit dem ba + g- von Abschnitt 7.3. Der Unterschied zu einer Dump-Untersuchung ist, dass die Untersuchung nicht endet, wenn Sie „die zerstörte Variable“ gefunden haben; von dort gehen Sie mechanisch rückwärts weiter.
Art 2: Handle- und Speicherwachstum. Das ist die Art von Fall, die in „Untersuchung eines Absturzes einer Industriekamera-Anwendung nach Langzeitbetrieb - Der Handle-Leak (Teil 1)“ seziert wird. Sie können keinen Monat aufzeichnen, also beschränken Sie die Aufzeichnung mit dem -module von Abschnitt 5.2 auf Ihre eigene DLL, kombinieren Sie sie mit -ring und behalten Sie „die wenigen Minuten, während es wächst“. Auf der Trace sammeln Sie TTD.Calls("kernelbase!CreateFileW") und TTD.Calls("kernelbase!CloseHandle") und gleichen den Rückgabewert von CreateFileW (den Handle-Wert) mit dem ersten Argument von CloseHandle (Parameters[0]) ab. Der ReturnValue von CloseHandle ist ein boolesches Erfolgsflag, also für den Abgleich nutzlos. Bleibt ein Handle-Wert ungeschlossen, gibt die ReturnAddress (der Aufrufer) dieses CreateFileW-Aufrufs Ihnen den Aufrufer, der leakt.12 Gleichwohl ist das Beobachten, ob ein Leak existiert und wie groß es ist, mit Application Verifier oder Handle-Zählern günstiger; die richtige Arbeitsteilung ist, TTD in der Phase einzusetzen, in der Sie festnageln, welcher Pfad leakt. Beachten Sie, dass eine Aufzeichnung mit aktiviertem Application Verifier die Wiedergabeleistung wegen der Speicherverwendung spürbar verschlechtert; deaktivieren Sie es daher während der Aufzeichnung.8
Art 3: „Keine Rückmeldung“ und Warteketten. Wie in „Was „Keine Rückmeldung“ wirklich ist“ geschrieben, ist ein Hänger eine Frage, wer auf wen wartet. Eine Trace wächst im Leerlauf nicht,4 sodass die Wartezeit eines Threads, der im Kernel wartet, nicht als Anweisungen erscheint. Was erscheint, ist der Aufruf unmittelbar vor dem Eintritt in das Warten und die Anweisungen, nachdem er zurückgekehrt ist. Betrachten Sie die Position jedes Threads mit !positions,17 und reihen Sie „welcher Thread hat wann worauf zu warten begonnen“ aus dem SystemTimeStart / SystemTimeEnd von TTD.Calls("kernelbase!WaitForSingleObject") und dergleichen auf; abgeglichen mit den Log-Zeitstempeln lässt sich so die Wartekette rekonstruieren.12 „Reihenfolgeabhängige Fehler“ wie DllMain und die Ladersperre oder Scheinwecken von Bedingungsvariablen sind ein Bereich, in dem TTD, das die Reihenfolge selbst aufzeichnet, gut passt.
9. TTD mit .NET-Apps
Die offizielle TTD-Dokumentation stellt fest, dass verwalteter Code mit TTD in WinDbg unter Verwendung der SOS-Erweiterung (sos.dll) im 64-Bit-Modus debuggt werden kann.1 Das Laden ist dasselbe wie in Kapitel 3 des SOS-Analyseartikels (.loadby sos coreclr oder automatisches Laden), und !clrstack und !pe funktionieren an jeder Position der Trace. Der Grundweg ist, mit TTD.Events zur Position der Ausnahme zu gehen und dann den verwalteten Stapel mit !clrstack zu lesen.
flowchart TB
accTitle: Einrichtung zum Lesen einer TTD-Trace einer .NET-App
accDescr: Die TTD-Trace in WinDbg öffnen, die 64-Bit-SOS-Erweiterung laden, zur Position des Ausnahmeereignisses gehen und den verwalteten Zustand mit !clrstack und !pe lesen. TTD.Calls für Aufrufe über native Grenzen verwenden
run["TTD-Trace (.run)"] --> wd["WinDbg"]
wd --> sos["SOS-Erweiterung (64-Bit)"]
sos --> ev["Mit TTD.Events zur Ausnahmeposition gehen"]
ev --> clr["Mit !clrstack / !pe lesen"]
wd -.-> calls["TTD.Calls für Aufrufe über native Grenzen"]
Abbildung 24: Verwalteter Zustand über SOS, Aufrufsuchen an nativen Grenzen. Trennen Sie die Rollen, und Sie verirren sich nicht.
Zwei Vorbehalte. Erstens: Was Microsoft zusichert, ist „SOS im 64-Bit-Modus“. Über für x86 gebaute .NET-Apps steht nichts, also führen Sie das Untersuchungsziel wenn möglich als x64 aus. Zweitens stützt sich TTD.Calls auf PDB-Symbolinformationen.12 Verlassen Sie sich nicht darauf, JIT-kompilierte verwaltete Methoden nach Namen zu suchen; die zuverlässige Nutzung ist, Aufrufe über native Grenzen wie P/Invoke-Ziele in nativen DLLs, COM und Win32-APIs zu verfolgen. Probleme des verwalteten Heaps wie in „GC-Wartezeit von einem Speicherleck in .NET unterscheiden“ werden zuerst mit dotnet-counters / dotnet-gcdump / !gcroot an einem Dump verfolgt, und TTD kommt in der Phase, in der eine native Grenze beteiligt ist.
10. Wahl zwischen Dumps, Logs, ETW und TTD
TTD ist kein Allheilmittel und ersetzt die vorhandenen Werkzeuge nicht. Hier steht es neben den in unseren Artikeln behandelten Werkzeugen.
| Was Sie wissen wollen | Zuerst zu verwendendes Werkzeug | Wann TTD dazukommt |
|---|---|---|
| Der Zustand im Moment des Absturzes | Absturz-Dump (WER LocalDumps / ProcDump) | Wenn der Dump „es ist zerstört“ zeigt, aber nicht, wer es zerstört hat |
| Welche Datei oder welcher Registrierungsschlüssel fehlgeschlagen ist | Process Monitor | Wenn Sie auch brauchen, wie die an die fehlschlagende API übergebenen Argumente gebaut wurden |
| Leistungsaufnahme des ganzen PCs über lange Dauer | WPR/WPA, PerfView (ETW) | Wenn es ein Korrektheitsproblem statt Leistung ist und Sie die Reihenfolge auf Anweisungsebene brauchen |
| Die geschäftliche Ereignisfolge | Die Logs der App | Wenn es auf einem Codepfad ohne Logging geschehen ist (TTD zeichnet alles ohne vorherige Codeänderungen auf1) |
| Wer diesen Wert geschrieben hat, die Reihenfolge der Aufrufe | TTD | — |
flowchart TB
accTitle: Wahl einer Untersuchungsmethode
accDescr: Zuerst den Zustand mit leichten Dumps und Logs erfassen, fehlschlagende APIs mit ProcMon und Leistung mit ETW untersuchen, und nur dann zu TTD übergehen, wenn Sie noch brauchen, wer was wann übergeben hat
start["Symptom"] --> light["Den Zustand mit Dumps und Logs erfassen (leicht)"]
light --> api["Fehlschlagende APIs: ProcMon"]
light --> perf["Leistung: WPR/WPA, PerfView"]
light --> need{"Brauchen Sie wer, wann und was übergeben wurde?"}
need -->|"Ja"| ttd["Den Aufzeichnungsumfang entwerfen und mit TTD aufzeichnen"]
need -->|"Nein"| done["Mit leichten Werkzeugen erledigt"]
Abbildung 25: Erfassen Sie zuerst das „Ergebnis“ mit leichten Werkzeugen, und holen Sie TTD nur für die Fälle heraus, in denen sich der „Pfad“ als nötig erweist.
Die Reihenfolge ist „zuerst das Leichteste“. Dumps und Logs kosten fast nichts zu erfassen, also halten Sie sie dauerhaft bereit; holen Sie TTD nach dem Entwurf von Kapitel 5 für die Fälle heraus, in denen das Lesen des Dumps gezeigt hat, dass der Pfad nötig ist. Angesichts des Aufwands von TTD und der Tatsache, dass Traces vertrauliche Informationen enthalten, gibt es keinen Grund, diese Reihenfolge umzukehren.
11. Betriebliche Hinweise
- Behandeln Sie Traces als vertrauliche Dateien. Eine Aufzeichnung enthält Speicherinhalte und kann personenbezogene und sicherheitsbezogene Informationen enthalten, etwa Dateipfade, Registrierungsdaten und den Inhalt von Speicher und Dateien.12 Wenn Sie in einer Kundenumgebung aufzeichnen, verstehen Sie im Voraus, was enthalten sein kann (Verbindungszeichenfolgen, Token, Kundendaten), und legen Sie einen verschlüsselten Übertragungsweg, einen Speicherort und eine Aufbewahrungsfrist fest.
- Teilen Sie nur die
.run-Datei. Die.idx-Datei ist etwa so groß wie die.run-Datei und wird automatisch erzeugt, wenn WinDbg sie öffnet..run-Dateien komprimieren sich gut. Wenn Sie einen Fehler in TTD selbst melden, hängen Sie auch die.out-Datei an.2 - Halten Sie Versionen abgestimmt. TTD wird zusammen mit WinDbg weiter aktualisiert; 1.11.611 enthält eine Korrektur für Aufzeichnungsabstürze in Programmen, die AVX/AVX512 verwenden, und eine Änderung des Indexformats.11 Eine alte Version auf der Aufzeichnungs- oder der Wiedergabeseite zu mischen bedeutet Neuindizierung oder Neuaufzeichnung.
- Prüfen Sie
-replayCpuSupport, wenn Sie auf einer anderen CPU wiedergeben. Der Standard bevorzugt Übertragbarkeit, undMostConservativeist für Fälle vorgesehen, in denen sich die Aufzeichnungs-CPU und die Wiedergabe-CPU unterscheiden (etwa die Wiedergabe einer Intel-Trace auf arm64). Umgekehrt können Sie, wenn Sie wissen, dass die Wiedergabe-CPU gleich oder besser ist, eine kleinere, schnellere Aufzeichnung wählen.2 - Windows Server lässt sich ebenfalls aufzeichnen. TTD.exe unterstützt Windows Server 2016/2019/2022/2025.2
- Eine fehlschlagende Aufzeichnung eingrenzen. Versuchen Sie zuerst, ob
ping.exeodercmd.exeaufgezeichnet werden können; wenn nicht, verdächtigen Sie einen Konflikt mit aufdringlicher Software wie Antivirus oder Anwendungsvirtualisierung.8
flowchart TB
accTitle: Verfahren für den Umgang mit Traces
accDescr: Weil eine aufgezeichnete Trace Speicherinhalte enthält, verstehen, welche Informationen sie enthalten kann, nur die .run-Datei komprimieren und über einen verschlüsselten Kanal übergeben, Speicherort und Aufbewahrungsfrist festlegen und auf der Analyseseite mit derselben WinDbg-Version öffnen und den Index bauen
rec["Aufzeichnung abgeschlossen (.run / .idx / .out)"] --> know["Verstehen, welche Informationen sie enthalten kann"]
know --> share["Nur .run komprimieren, verschlüsseln und übergeben"]
share --> keep["Speicherort und Aufbewahrungsfrist festlegen"]
keep --> open["Mit derselben WinDbg-Version öffnen und .idx erzeugen"]
Abbildung 26: Eine Trace zu übergeben heißt, eine vertrauliche Datei zu übergeben. Legen Sie das Verfahren fest, bevor Sie aufzeichnen.
12. Zusammenfassung
- Ein Dump ist „Zustand“; TTD ist der „Pfad“. In Fällen, die den Ursprung eines zerstörten Werts oder die Reihenfolge von Aufrufen brauchen, macht TTD die Untersuchung zur mechanischen Arbeit
- Die Aufzeichnung ist 5- bis 20-mal langsamer, wächst 5 bis 50 MB pro Sekunde und lässt sich nicht lösen. Für langlaufende Apps entwerfen Sie zuerst den Aufzeichnungsumfang mit
-ring/-maxFile,-module,-recordmode Manualund-monitor - Die Wiedergabe ist „über ein Ereignis einsteigen, rückwärts gehen“:
TTD.Events, dann[Time Travel], dannt-/g-, undba+g-, damit der Debugger „wer hat es zerstört?“ beantwortet TTD.CallsundTTD.Memorysind Abfragen über die ganze Trace. Gleichen Sie mitToSystemTime()undSystemTimeStartgegen Log-Zeitstempel ab- Für .NET lesen Sie den Zustand mit 64-Bit-SOS und verwenden Sie
TTD.Callsan nativen Grenzen - Eine Trace ist eine vertrauliche Datei. Teilen Sie nur die
.run-Datei und legen Sie Kanal und Speicherung zuerst fest
Fälle, in denen ein Dump erfasst wurde, die Ursache aber außer Reichweite ist, oder in denen die Arbeit stockt, weil der Fehler sich nicht reproduziert, werden recht oft mit TTD erledigt, sobald der Aufzeichnungsumfang entworfen werden kann. Wir können auch alles vom Aufzeichnungsentwurf bis zur Analyse der .run-Datei übernehmen; wenden Sie sich gern mit Ihren Dumps und Logs an uns.
Weiterführende Artikel
- Absturz-Dumps mit WinDbg + SOS lesen — Ein praxistauglicher Leitfaden zur Analyse nach der Sammlung
- Einführung in das Sammeln von Windows-Absturzabbildern - WER/ProcDump/WinDbg
- Was ist eine PDB (Program Database)? — Debugging-Informationen, Symbole und Source Link verstehen
- Untersuchung eines Absturzes einer Industriekamera-Anwendung nach Langzeitbetrieb - Der Handle-Leak (Teil 1)
- Was „Keine Rückmeldung“ wirklich ist — Wie Windows entscheidet, dass eine App hängt, und wie Sie Apps entwerfen, die das nicht tun
- Process Monitor (ProcMon) Praxisleitfaden — „Konfiguration wird nicht gelesen“ und ACCESS DENIED in 10 Minuten identifizieren
- WPR/WPA in der Praxis — Einstieg in die systemweite Leistungsuntersuchung für „der ganze PC ist langsam“
- Mit PerfView und dotnet-trace die Ursache von „langsam“ finden — Praxis-Einstieg in die .NET-Performanceanalyse
- Was nach dem Tod des Elternprozesses bleibt — Kindprozesse mit Job Objects an der Leine halten
Zugehörige Beratungsfelder
KomuraSoft LLC übernimmt Ursachenuntersuchungen von Windows-App-Fehlern, die erst nach langem Lauf oder intermittierend erscheinen, unter Kombination von Absturz-Dumps, Logs und TTD-Traces; den Aufbau einer Untersuchungsaufstellung einschließlich des Entwurfs des Aufzeichnungsumfangs; und die Eingrenzung von Störungen, die native Grenzen (COM, P/Invoke, Geräte-SDKs) einbeziehen. Wenden Sie sich schon in der Phase „wir haben einen Dump, erreichen die Ursache aber nicht“ an uns.
- Fehleruntersuchung und Ursachenanalyse
- Technische Beratung und Design-Review
- Windows-App-Entwicklung
- Kontakt
Quellen
-
Microsoft Learn, Time Travel Debugging - Overview. Dazu, dass TTD die Ausführung eines Prozesses aufzeichnet und sie vorwärts und rückwärts wiedergeben lässt, dass Dumps den Zustand und den Ausführungspfad, die zum Fehler geführt haben, oft verfehlen, dass die Aufzeichnung Administratorrechte erfordert, dass Aufzeichnungen personenbezogene und sicherheitsbezogene Informationen enthalten können, zur Vergleichstabelle der Untersuchungsmethoden, zu den Rollen von
.run/.idxund zum Debuggen verwalteten Codes mit der SOS-Erweiterung im 64-Bit-Modus. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 -
Microsoft Learn, Time Travel Debugging - TTD.exe command line utility. Zur 5- bis 20-fachen oder stärkeren Verlangsamung, dazu, dass es sich nach dem Anhängen nicht selbst lösen kann, zur Unterstützung von Windows Server 2016 bis 2025, zu Installation und Offline-Bereitstellung, zu den drei Modi
-launch/-attach/-monitor, zu den Optionen-out/-noUI/-accepteula/-stop/-wait/-tracingOff/-children/-cmdLineFilter/-timestampFilename/-ring/-maxFile/-maxConcurrentRecordings/-numVCpu/-replayCpuSupport/-module/-recordmode, zum Lesen der.out-Datei und zu Ratschlägen zum Teilen von Traces. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19 ↩20 ↩21 ↩22 ↩23 ↩24 ↩25 ↩26 ↩27 ↩28 ↩29 ↩30 ↩31 ↩32 ↩33 ↩34 -
Microsoft Learn, Time Travel Debugging - Overview - Things to look out for. Zur Unverträglichkeit mit Antivirus- und Speicherüberwachungssoftware und Electron, dazu, dass nur der Benutzermodus möglich ist, dass die Wiedergabe schreibgeschützt ist, dass eine Injektion in geschützte Prozesse (PPL) nicht möglich ist, und zur etwa 10- bis 20-fachen Leistungsauswirkung während der Aufzeichnung. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, Time Travel Debugging - Working with Trace Files. Zu den Faktoren der Trace-Größe (ein Bit bis ein Byte pro Anweisung), zum Wachstum von 5 bis 50 MB pro Sekunde bei Aktivität und keinem im Leerlauf, zum Fehlen einer maximalen Größenobergrenze, dazu, dass der Index 1- bis 2-mal so groß wie die Trace ist, und zum Verhalten von Aufzeichnung und Indizierung bei voller Festplatte samt Workaround. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Microsoft Learn, Time Travel Debugging - Sample App Walkthrough. Zum allgemeinen Verfahren, zur Position eines Ausnahmeereignisses zu gehen und mit
baundg-zur Position zurückzugehen, an der ein ungültiger Wert zuletzt geschrieben wurde, dazu, dass der Fehlerpunkt oft in der Fehlerbehandlung mehrere Schritte hinter der eigentlichen Ursache liegt, dazu, dass die Trace bei einem Absturz geschlossen und von WinDbg automatisch indiziert wird, und zur Nutzung vonTTD.Memoryund.Last(). ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 -
Microsoft Learn, !tt (time travel). Zur Positionsangabe für
!tt(Prozent oderxx:yy), zur Bedeutung der zwei Bestandteile einer Position (Sequenznummer und Schrittzahl) und zuTTD.PrevRegisterWrite/PrevMemoryAccess/NextMemoryAccess. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, TTD Position Objects. Zu den Eigenschaften
Percent/Sequence/Stepsdes Position-Objekts, zuSeekTo(), dazu, dassToSystemTime()die ungefähre Wanduhrzeit (UTC) zurückgibt, und dazu, dassFFFFFFFFFFFFFFFE:0das Ende der Trace bezeichnet. ↩ ↩2 -
Microsoft Learn, Time Travel Debugging - Troubleshooting. Dazu, dass eine Erhöhung erforderlich ist, dass die Startaufzeichnung von UWP-Apps nicht unterstützt wird, dass „ungewöhnliche Prozesse“ in einer anderen Sitzung oder einem anderen Sicherheitskontext außerhalb des Rahmens liegen, zur Eingrenzung mit
ping.exe/cmd.exe, dazu, dass die Wiedergabe langsamer wird, wenn Application Verifier gleichzeitig verwendet wird, und zum Neubauen des Index mit!index -status/!index -force. ↩ ↩2 ↩3 ↩4 ↩5 -
Microsoft Learn, Time Travel Debugging - Record a trace. Zur Aufzeichnung aus Launch executable (advanced) / Attach to process in der WinDbg-Oberfläche, zum Kontrollkästchen Record with Time Travel Debugging, zum Festlegen des Speicherorts mit Configure and Record und zum Einschränken von Modulen mit Record subset of execution. ↩
-
Microsoft, WinDbg-Samples - TTD in-process recording API (GitHub). Zur Dokumentation der In-Prozess-Aufzeichnungs-API, die zusammen mit
-recordmode Manualdem Programm die Steuerung von Start und Stopp der Aufzeichnung überlässt. ↩ -
Microsoft Learn, Time travel debugging release notes. Zur Aufzeichnungskorrektur für Programme, die AVX/AVX512 verwenden, und zur Änderung des Indexformats (Neuindizierung erforderlich) in 1.11.611 sowie zu
@$curframe.TTD.VariableHistory(), hinzugefügt in 1.11.553. ↩ ↩2 ↩3 -
Microsoft Learn, TTD Calls Objects. Zu den Argumenten von
TTD.Calls, zu den EigenschaftenThreadId/UniqueThreadId/Function/ReturnValue/ReturnAddress/Parameters[]/TimeStart/TimeEnd/SystemTimeStart/SystemTimeEnd, zu den Standardwerten, wenn PDB-Symbolinformationen fehlen (vier 64-Bit-Ganzzahlargumente ohne Vorzeichen,UnknownOrMissingSymbols), und dazu, dass die Berechnung Zeit braucht und Ergebnisse zwischengespeichert werden. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 -
Microsoft Learn, Time Travel Debugging - Replay a trace. Zur Rückwärtsausführung mit
p-/t-/g-, dazu, dassg-bei denselben Ereignissen stoppt wie die Vorwärtsausführung, zu!positionsund dazu, dass~sdie Position in der Trace nicht ändert. ↩ ↩2 ↩3 -
Microsoft Learn, TTD Event Objects. Zu den Ereignistypen (ThreadCreated/ThreadTerminated/ModuleLoaded/ModuleUnloaded/Exception) und den Kindobjekten Position, Module, Thread und Exception. ↩
-
Microsoft Learn, TTD Exception Objects. Zu
Type(Software/Hardware),ProgramCounter,Code,FlagsundPositiondes Ausnahmeobjekts. ↩ -
Microsoft Learn, WinDbg: Timelines. Dazu, dass das Fenster Timelines Ausnahmen, Haltepunkte, Speicherzugriffe und Funktionsaufrufe visualisiert und ein Doppelklick auf eine Ausnahme
Position.SeekTo()auslöst. ↩ -
Microsoft Learn, !positions. Zur Anzeige jedes aktiven Threads und der Position jedes Threads in der Trace. ↩ ↩2
-
Microsoft Learn, Introduction to Time Travel Debugging objects. Zu den Objekten
@$curprocess.TTD/@$cursession.TTD, zu Abfragen mitOrderBy/Where/Select/GroupBy, zu den Beispielen,GetLastError-Fehler zu aggregieren und den letzten Aufruf vonMessageBoxWzu finden, zur Bedeutung vonUnknownOrMissingSymbolsund zu den vier Gründen, warumCallsnichts liefert. ↩ ↩2 ↩3 ↩4 ↩5 -
Microsoft Learn, TTD Memory Objects. Zu den Zugriffstypen von
TTD.Memory(r/w/rw/e/rwe/ec) und zum Gehen zu einer Position über den Link[Time Travel]in den Ergebnissen. ↩
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
Absturz-Dumps mit WinDbg + SOS lesen — Ein praxistauglicher Leitfaden zur Analyse nach der Sammlung
Erklärt, wie man einen gesammelten Windows-Absturz-Dump tatsächlich mit WinDbg und der SOS-Erweiterung liest. Behandelt die Konfiguration...
Warum Argumente zerbrechen — Die Regeln der Windows-Kommandozeilenargumente
Windows übergibt CreateProcess eine einzige Zeichenfolge, die der Empfänger zerlegt. Behandelt die Regeln von CommandLineToArgvW, CRT und...
Ende der Wartung von Windows-Druckertreibern — Wie Geschäftsanwendungen Bericht- und Etikettendruck vorbereiten
Microsoft stellt v3/v4-Druckertreiber schrittweise ein. Was Windows protected print mode entfernt und wie Geschäftsanwendungen Bericht- u...
DllMain und die Ladersperre — Der wahre Grund, warum man sagt, in der DLL-Initialisierung nichts zu tun
Warum Sie aus DllMain weder LoadLibrary aufrufen noch mit Threads synchronisieren dürfen. Anhand von Primärquellen erklärt dieser Artikel...
Process Monitor (ProcMon) Praxisleitfaden — „Konfiguration wird nicht gelesen“ und ACCESS DENIED in 10 Minuten identifizieren
„Ich habe die Konfigurationsdatei korrigiert, aber die Änderung wird nicht übernommen“ – „Bis gestern lief es noch, aber heute startet es...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
Fehleranalyse und Langzeitprobleme
Sporadische Fehler, Kommunikationsdiagnose, Langzeitabstürze und Tests von Fehlerpfaden.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Windows-App-Entwicklung
Geschäftsanwendungen, Geräteintegration und Kommunikationstools von den Anforderungen bis zur Umsetzung.
Fehleruntersuchung und Ursachenanalyse
Wir untersuchen schwer reproduzierbare Fehler, Langzeitprobleme, Lecks und Kommunikationsabbrüche.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- Worin unterscheiden sich Time Travel Debugging (TTD) und ein Absturz-Dump?
- Ein Absturz-Dump ist eine Fotografie des Speichers im Moment des Absturzes: Er zeigt den Zustand in diesem Augenblick, aber nicht den Weg, der dorthin geführt hat. Eine TTD-Trace ist eine vollständige Aufzeichnung der Befehlsausführung des Prozesses, die sich später vorwärts und rückwärts wiedergeben lässt, sodass Sie zurückspulen und direkt prüfen können, wer eine Variable zuletzt geschrieben hat oder was unmittelbar vor einer Ausnahme aufgerufen wurde. Die offizielle Dokumentation von Microsoft stellt außerdem fest, dass Dumps den Zustand und den Ausführungspfad, die zum Fehler geführt haben, oft verfehlen. Der Preis ist, dass der Prozess während der Aufzeichnung 5- bis 20-mal langsamer läuft und die Trace-Datei bei aktivem Prozess um etwa 5 bis 50 MB pro Sekunde wächst.
- Kann ich TTD an einer Produktions-App belassen, die tagelang läuft?
- Nicht so, wie sie ist. TTD verlangsamt den Prozess während der Aufzeichnung erheblich, die Trace wächst in einem aktiven Prozess um 5 bis 50 MB pro Sekunde, und es gibt keine Obergrenze für die Dateigröße. Der Einsatz an einer langlaufenden App setzt einen Aufzeichnungsentwurf voraus: den Ringpuffer von TTD.exe (-ring) mit -maxFile, um nur die letzten N MB zu behalten, -module, um nur aufzuzeichnen, während das eigene Modul läuft, oder -recordmode Manual, damit die App das Aufzeichnungsintervall wählt. Außerdem kann TTD sich nach dem Anhängen nicht selbst lösen, sodass Sie im Voraus festlegen müssen, wie Sie die Aufzeichnung beenden, was die Beendigung (den Neustart) des Prozesses bedeutet.
- Lassen sich Dienste und Prozesse in anderen Sitzungen aufzeichnen?
- Die Dokumentation von TTD.exe beschreibt -attach als für die Untersuchung von Diensten und langlaufenden Apps gedacht und -monitor als Aufzeichnung bei jedem Start eines Programms oder Dienstes. Andererseits stellt die Seite zur Fehlerbehebung fest, dass ungewöhnliche Prozesse, die in einer anderen Sitzung oder einem anderen Sicherheitskontext laufen, derzeit nicht für die Aufzeichnung unterstützt werden. Ob es tatsächlich funktioniert, hängt von der Umgebung ab; zeichnen Sie daher zuerst einen einfachen Prozess wie ping.exe oder cmd.exe in derselben Konfiguration wie in der Produktion auf, versuchen Sie dann den Zielprozess und bauen Sie es erst danach in den Betrieb ein.
- Lässt sich TTD auf Traces von .NET-Apps verwenden?
- Ja. Die offizielle Dokumentation stellt fest, dass die SOS-Erweiterung (sos.dll) im 64-Bit-Modus auf einer TTD-Trace in WinDbg verwendet werden kann, um verwalteten Code zu debuggen. Das Grundmuster ist, SOS-Befehle wie !clrstack und !pe an jeder Position der Trace auszuführen, zur Position eines Ausnahmeereignisses zu gehen und dann den verwalteten Stapel zu lesen. Die TTD.Calls-Abfrage, die Aufrufe nach Symbolnamen sucht, stützt sich auf PDB-Symbolinformationen; in .NET-Apps ist die zuverlässige Nutzung daher, Aufrufe über native Grenzen wie P/Invoke, COM und Win32-APIs zu verfolgen.
- Ist es unbedenklich, eine Trace-Datei (.run) an ein anderes Unternehmen oder nach außen zu senden?
- Nicht so, wie sie ist. Eine TTD-Aufzeichnung enthält die Speicherinhalte des Prozesses, und die offizielle Dokumentation stellt ausdrücklich fest, dass sie personenbezogene oder vertrauliche Informationen enthalten kann, etwa Dateipfade, Registrierungsdaten und den Inhalt von Speicher und Dateien. Wenn Sie eine senden, verstehen Sie zuerst, was aufgezeichnet wurde (Verbindungszeichenfolgen, Token, Kundendaten und so weiter), und legen Sie dann einen verschlüsselten Übertragungsweg und einen Speicherort fest. Es reicht, nur die .run-Datei zu teilen; die Indexdatei (.idx) wird automatisch erzeugt, wenn WinDbg sie öffnet.
- TTD.Calls liefert nichts, wenn ich nach einer Funktion suche. Warum?
- Es gibt vier Hauptursachen. Erstens Symbole: Funktionen in einem Modul ohne PDB heißen UnknownOrMissingSymbols, und der Modulname kann in Großbuchstaben stehen; prüfen Sie den tatsächlichen Symbolnamen mit dem Befehl x. Zweitens ist die Zieldll an dieser Position möglicherweise noch nicht geladen; gehen Sie zu einer Position nach dem Laden der DLL und fragen Sie erneut ab. Drittens kann die Abfrage-Engine die Funktion nicht verfolgen, wenn sie inline expandiert ist. Viertens ist der Platzhalter vielleicht zu weit und trifft zu viele Funktionen; engen Sie das Muster ein.
Autorenprofil
Profilseite des Artikelautors.
Go Komura
Geschäftsführer von KomuraSoft LLC
Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.