Freigeben über


Zeitreise-Debugging – JavaScript-Automatisierung

Zeitreise-Debugging-Logo mit einer Uhr.

Sie können die JavaScript-Automatisierung verwenden, um mit TTD-Ablaufverfolgungen auf verschiedene Weise zu arbeiten, z. B. Befehlsautomatisierung oder das Verwenden von Abfragen zum Suchen von Ereignisdaten aus der Ablaufverfolgungsdatei.

Allgemeine Informationen zur Arbeit mit JavaScript finden Sie unter Debuggerskripts mit JavaScript. Es gibt auch JavaScript-Debugger-Beispielskripte.

JavaScript TTD-Befehlsautomatisierung

Eine Möglichkeit zum Verwenden von JavaScript für die TTD-Automatisierung ist das Senden von Befehlen zum Automatisieren der Arbeit mit Zeitreise-Ablaufverfolgungsdateien.

Orientieren in einer Ablaufverfolgungsdatei

In diesem JavaScript wird gezeigt, wie Sie mithilfe des Befehls !tt zum Anfang einer Zeitreiseablaufverfolgung wechseln.

var dbgControl = host.namespace.Debugger.Utility.Control;  
dbgControl.ExecuteCommand("!tt 0",false);
host.diagnostics.debugLog(">>> Sent command to move to the start of the TTD file \n");

Wir können dies in eine ResetTrace-Funktion umwandeln und als ResetTrace.js speichern, indem wir die JavaScript-Benutzeroberfläche in WinDbg verwenden.

// WinDbg TTD JavaScript ResetTraceCmd Sample

"use strict";

function ResetTraceCmd()
{
    var dbgControl = host.namespace.Debugger.Utility.Control;  
    dbgControl.ExecuteCommand("!tt 0",false);
    host.diagnostics.debugLog(">>> Sent command to move to the start of the TTD file \n");
}

Rufen Sie nach dem Laden einer TTD-Datei in WinDbg die Funktion ResetTraceCmd() mithilfe des DX-Befehls im Debugger-Befehlsfenster auf.

0:000> dx Debugger.State.Scripts.ResetTrace.Contents.ResetTraceCmd()
>>> Sent command to move to the start of the TTD file
Debugger.State.Scripts.ResetTrace.Contents.ResetTrace()

Einschränkungen beim Senden von Befehlen

Aber für alle außer den einfachsten Situationen hat dieser Ansatz, Befehle zu senden, Nachteile. Er basiert auf der Verwendung der Textausgabe. Und das Analysieren dieser Ausgabe führt zu Code, der fehleranfällig und schwer zu verwalten ist. Ein besserer Ansatz besteht darin, die TTD-Objekte direkt zu verwenden.

Das folgende Beispiel zeigt, wie Sie die Objekte direkt verwenden, um dieselbe Aufgabe mithilfe der Objekte direkt abzuschließen.

// WinDbg TTD JavaScript ResetTrace Sample

"use strict";

function ResetTrace()
{
    host.currentProcess.TTD.SetPosition(0);
    host.diagnostics.debugLog(">>> Set position to the start of the TTD file \n");
}

Das Ausführen dieses Codes zeigt, dass wir zum Anfang der Ablaufverfolgungsdatei wechseln können.

0:000> dx Debugger.State.Scripts.ResetTrace.Contents.ResetTrace()
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: F:0
>>> Set position to the start of the TTD file

In diesem Beispiel für die ResetTraceEnd-Funktion, wird die Position auf das Ende der Ablaufverfolgung festgelegt, und die aktuelle und neue Position wird mithilfe des currentThread.TTD-Position-Objekts angezeigt.


// WinDbg TTD JavaScript Sample to Reset Trace using objects directly
// and display current and new position

function ResetTraceEnd()
{
   var PositionOutputStart = host.currentThread.TTD.Position;
   host.diagnostics.debugLog(">>> Current position in trace file:  "+ PositionOutputStart +"\n");
   host.currentProcess.TTD.SetPosition(100);
   var PositionOutputNew = host.currentThread.TTD.Position;
   host.diagnostics.debugLog(">>> New position in trace file:  "+ PositionOutputNew +"\n");
}

Das Ausführen dieses Codes zeigt die aktuelle und neue Position an.

0:000> dx Debugger.State.Scripts.ResetTrace.Contents.ResetTraceEnd()
>>> Current position in trace file:  F:0
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: D3:1
>>> New position in trace file:  D3:1

In diesem erweiterten Beispiel werden die Anfangs- und Endpositionswerte verglichen, um festzustellen, ob sich die Position in der Ablaufverfolgung geändert hat.

// WinDbg TTD JavaScript ResetTraceEx Sample

"use strict";

function ResetTraceEx()
{
    const PositionOutputStart = host.currentThread.TTD.Position;
    host.diagnostics.debugLog(">>> Current position in trace file:  "+ PositionOutputStart +"\n");
  
    host.currentProcess.TTD.SetPosition(0);

    const PositionOutputNew = host.currentThread.TTD.Position;
    host.diagnostics.debugLog(">>> New position in trace file:  "+ PositionOutputNew +"\n");

    if (parseInt(PositionOutputStart,16) != parseInt(PositionOutputNew,16))
    {
        host.diagnostics.debugLog(">>> Set position to the start of the TTD file  \n");
    }
    else
    {
        host.diagnostics.debugLog(">>> Position was already set to the start of the TTD file \n");
    }
}

In dieser Beispielausführung wird eine Meldung angezeigt, dass wir uns bereits am Anfang der Ablaufverfolgungsdatei befanden.

0:000> dx Debugger.State.Scripts.ResetTrace.Contents.ResetTraceEx()
>>> Current position in trace file:  F:0
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: F:0
>>> New position in trace file:  F:0
>>> Position was already set to the start of the TTD file

Verwenden Sie zum Testen des Skripts den Befehl !tt, um bis zur Hälfte der Ablaufverfolgungsdatei zu navigieren.

0:000> !tt 50
Setting position to 50% into the trace
Setting position: 71:0

Wenn Sie das Skript ausführen, wird nun die richtige Meldung angezeigt, die angibt, dass die Position auf den Anfang der TTD-Ablaufverfolgung festgelegt wurde.

0:000> dx Debugger.State.Scripts.ResetTrace.Contents.ResetTraceEx()
>>> Current position in trace file:  71:0
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: F:0
>>> New position in trace file:  F:0
>>> Set position to the start of the TTD file  

Indizierung einer Zeitreise-Ablaufverfolgungsdatei

Wenn nur eine Ablaufverfolgungsdatei auf einen anderen PC kopiert wird, muss sie erneut indiziert werden. Weitere Informationen finden Sie unter Zeitreise-Debuggen – Arbeiten mit Ablaufverfolgungsdateien.

Dieser Code zeigt eine Beispiel-IndexTrace-Funktion, die anzeigt, wie lange es dauert, eine Ablaufverfolgungsdatei neu zu indizieren.

function IndexTrace()
{
    var timeS = (new Date()).getTime();
    var output = host.currentProcess.TTD.Index.ForceBuildIndex();
    var timeE = (new Date()).getTime();
    host.diagnostics.debugLog("\n>>> Trace was indexed in " + (timeE - timeS) + " ms\n");
}

Hier ist die Ausgabe aus einer kleinen Ablaufverfolgungsdatei.

0:000> dx Debugger.State.Scripts.TTDUtils.Contents.IndexTrace()

>>> Trace was indexed in 2 ms

Hinzufügen einer try-catch-Anweisung

Um zu überprüfen, ob Fehler beim Ausführen der Indizierung ausgelöst wurden, schließen Sie den Indizierungscode in eine try-catch-Anweisung ein.


function IndexTraceTry()
{
    var timeS = (new Date()).getTime();
    try
    {
         var IndexOutput =  host.currentProcess.TTD.Index.ForceBuildIndex();
         host.diagnostics.debugLog("\n>>> Index Return Value: " + IndexOutput + "\n");
         var timeE = (new Date()).getTime();
         host.diagnostics.debugLog("\n>>> Trace was successfully indexed in " + (timeE - timeS) + " ms\n");
     }

    catch(err)
    {
         host.diagnostics.debugLog("\n>>> Index Failed! \n");
         host.diagnostics.debugLog("\n>>> Index Return Value: " + IndexOutput + "\n");
         host.diagnostics.debugLog("\n>>> Returned error: " + err.name + "\n");
    }
}

Dies ist die Skriptausgabe, wenn die Indizierung erfolgreich ist.

0:000> dx Debugger.State.Scripts.TTDUtils.Contents.IndexTraceTry()

>>> Index Return Value: Loaded

>>> Trace was successfully indexed in 1 ms

Wenn die Ablaufverfolgung nicht indiziert werden kann, z. B. wenn die Ablaufverfolgung nicht im Debugger geladen wird, wird der Catch-Schleifencode ausgeführt.

0:007> dx Debugger.State.Scripts.TTDUtils.Contents.IndexTraceTry()

>>> Index Failed!

>>> Index Return Value: undefined

>>> Returned error: TypeError

JavaScript TTD-Objektabfragen

Eine erweiterte Verwendung von JavaScript und TTD besteht darin, die Zeitreiseobjekte abzufragen, um bestimmte Aufrufe oder Ereignisse zu finden, die in der Ablaufverfolgung aufgetreten sind. Weitere Informationen zu TDD-Objekten finden Sie unter:

Einführung in Objekte zum Debuggen von Zeitreisen

Native Debugger-Objekte in JavaScript-Erweiterungen – Details zum Debugger-Objekt

Der Befehl dx zeigt Informationen aus dem Debuggerdatenmodell an und unterstützt Abfragen mithilfe der LINQ-Syntax. Dx ist sehr nützlich, um die Objekte in Echtzeit abzufragen. Dies ermöglicht die Prototyperstellung der gewünschten Abfrage, die dann mithilfe von JavaScript automatisiert werden kann. Der dx-Befehl stellt die Vervollständigung mit der TAB-TASTE bereit, was beim Untersuchen des Objektmodells hilfreich sein kann. Allgemeine Informationen zur Arbeit mit LINQ-Abfragen und Debugger-Objekten finden Sie unter Verwendung von LINQ mit den Debugger-Objekten.

Dieser dx-Befehl zählt alle Aufrufe einer bestimmten API, in diesem Beispiel GetLastError.

0:000> dx @$cursession.TTD.Calls("kernelbase!GetLastError").Count()

@$cursession.TTD.Calls("kernelbase! GetLastError").Count() : 0x12

Dieser Befehl sucht in der gesamten Zeitreiseablaufverfolgung, um zu sehen, wann GetLastError aufgerufen wurde.

0:000> dx @$cursession.TTD.Calls("kernelbase!GetLastError").Where(c => c.ReturnValue != 0)

@$cursession.TTD.Calls("kernelbase!GetLastError").Where(c => c.ReturnValue != 0)
    [0x0]
    [0x1]
    [0x2]
    [0x3]

Zeichenfolgenvergleiche für TTD.Calls-Objekt, zum Auffinden von Aufrufen

In diesem Beispielbefehl wird gezeigt, wie Sie Zeichenfolgenvergleiche verwenden, um bestimmte Aufrufe zu finden. In diesem Beispiel sucht die Abfrage im lpFileName-Parameter der CreateFileW-Funktion nach der Zeichenfolge „OLE“.

dx -r2 @$cursession.TTD.Calls("kernelbase!CreateFileW").Where(x => x.Parameters.lpFileName.ToDisplayString("su").Contains("OLE"))

Fügen Sie eine .Select-Anweisung zum Drucken von Timestart und dem Wert des lpFileName-Parameters hinzu.

dx -r2 @$cursession.TTD.Calls("kernelbase!CreateFileW").Where(x => x.Parameters.lpFileName.ToDisplayString("su").Contains("OLE")).Select(x => new { TimeStart = x.TimeStart, lpFileName = x.Parameters.lpFileName })

Dies generiert diese Ausgabe, wenn ein TDD.Calls-Objekt gefunden wird, das die Zielinformationen enthält.

    [0x0]
        TimeStart        : 6E37:590
        lpFileName       : 0x346a78be90 : "C:\WINDOWS\SYSTEM32\OLEACCRC.DLL" [Type: wchar_t *]

Anzeigen der Anzahl von Anrufen in einer Ablaufverfolgung

Nachdem Sie den dx-Befehl zum Erkunden von Objekten verwendet haben, mit denen Sie arbeiten möchten, können Sie deren Verwendung mit JavaScript automatisieren. In diesem einfachen Beispiel wird das TTD.Calls-Objekt verwendet, um Aufrufe von kernelbase!GetLastError zu zählen.

function CountLastErrorCalls()
{
    var LastErrorCalls = host.currentSession.TTD.Calls("kernelbase!GetLastError");
    host.diagnostics.debugLog(">>> GetLastError calls in this TTD recording: " +  LastErrorCalls.Count() +" \n");
}

Speichern Sie das Skript in einer TTDUtils.js Datei, und rufen Sie es mithilfe des dx-Befehls auf, um die Anzahl von kernelbase!GetLastError in der Ablaufverfolgungsdatei anzuzeigen.


0:000> dx Debugger.State.Scripts.TTDUtils.Contents.CountLastErrorCalls()
>>> GetLastError calls in this TTD recording: 18

Anzeigen der Frames in einem Stapel

Um die Frames in einem Stapel anzuzeigen, wird ein Array verwendet.

function DisplayStack()
{
// Create an array of stack frames in the current thread
const Frames = Array.from(host.currentThread.Stack.Frames);
host.diagnostics.debugLog(">>> Printing stack \n");
// Print out all of the frame entries in the array
for(const [Idx, Frame] of Frames.entries())
    {
        host.diagnostics.debugLog(">>> Stack Entry -> " + Idx + ":  "+ Frame + " \n");
    }
}

In dieser Beispielablaufverfolgung wird der eine Stapeleintrag angezeigt.

0:000> dx Debugger.State.Scripts.TTDUtils.Contents.DisplayStack()
>>> Printing stack
>>> Stack Entry -> 0:  ntdll!LdrInitializeThunk + 0x21

Suchen eines Ereignisses und Anzeigen des Stapels

In diesem Code befinden sich alle Ausnahmenereignisse, und eine Schleife wird verwendet, um zu den einzelnen zu wechseln. Anschließend wird die currentThread.ID der TTD-Threadobjekte verwendet, um die Thread-ID anzuzeigen und currentThread.Stack, um alle Frames im Stapel anzuzeigen.


function HardwareExceptionDisplayStack()
{
var exceptionEvents = host.currentProcess.TTD.Events.Where(t => t.Type == "Exception");
    for (var curEvent of exceptionEvents)
    {
        // Move to the current event position
        curEvent.Position.SeekTo();
        host.diagnostics.debugLog(">>> The Thread ID (TID) is : " + host.currentThread.Id + "\n");
        // Create an array of stack frames in the current thread
        const Frames = Array.from(host.currentThread.Stack.Frames);
        host.diagnostics.debugLog(">>> Printing stack \n");
        // Print out all of the frame entries in the array
        for(const [Idx, Frame] of Frames.entries()) {
            host.diagnostics.debugLog(">>> Stack Entry -> " + Idx + ":  "+ Frame + " \n");
        }
    host.diagnostics.debugLog("\n");
    }
}

Die Ausgabe zeigt die Position des Ausnahmeereignisses, die TID und die Stapelframes an.

0:000> dx Debugger.State.Scripts.TTDUtils.Contents.HardwareExceptionDisplayStack()
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: 91:0
>>> The Thread ID (TID) is : 5260
>>> Printing stack
>>> Stack Entry -> 0:  0x540020
>>> Stack Entry -> 1:  0x4d0049
>>> Stack Entry -> 2:  DisplayGreeting!__CheckForDebuggerJustMyCode + 0x16d
>>> Stack Entry -> 3:  DisplayGreeting!mainCRTStartup + 0x8
>>> Stack Entry -> 4:  KERNEL32!BaseThreadInitThunk + 0x19
>>> Stack Entry -> 5:  ntdll!__RtlUserThreadStart + 0x2f
>>> Stack Entry -> 6:  ntdll!_RtlUserThreadStart + 0x1b

Suchen eines Ereignisses und Senden von zwei Befehlen

Das Abfragen von TTD-Objekten und das Senden von Befehlen können bei Bedarf kombiniert werden. In diesem Beispiel wird jedes Ereignis in der TTD-Ablaufverfolgung des Typs ThreadCreated gesucht, an diese Position verschoben und der ~ Threadstatus und die !runaway-Befehle gesendet, um den Threadstatus anzuzeigen.

function ThreadCreateThreadStatus()
{
var threadEvents = host.currentProcess.TTD.Events.Where(t => t.Type == "ThreadCreated");
    for (var curEvent of threadEvents)
    {
        // Move to the current event position
       curEvent.Position.SeekTo();
        // Display Information about threads
       host.namespace.Debugger.Utility.Control.ExecuteCommand("~", false);
       host.namespace.Debugger.Utility.Control.ExecuteCommand("!runaway 7", false);
    }
}

Beim Ausführen des Codes, wird der Threadstatus zu dem Zeitpunkt angezeigt, zu dem die Ausnahme aufgetreten ist.

0:000> dx Debugger.State.Scripts.TTDUtils.Contents.ThreadCreateThreadStatus()
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: F:0
.  0  Id: 948.148c Suspend: 4096 Teb: 00a33000 Unfrozen
User Mode Time
  Thread       Time
    0:148c     0 days 0:00:00.000
Kernel Mode Time
  Thread       Time
    0:148c     0 days 0:00:00.000
Elapsed Time
  Thread       Time
    0:148c     3474 days 2:27:43.000

Verketten von Hilfsfunktionen

In diesem letzten Beispiel können wir die zuvor erstellten Hilfsfunktionen aufrufen. Zuerst indizieren wir die Ablaufverfolgung mithilfe von IndexTraceTry und rufen dann ThreadCreateThreadStatus auf. Anschließend verwenden wir ResetTrace, um zum Anfang der Ablaufverfolgung zu wechseln und zuletzt HardwareExceptionDisplayStack aufzurufen.

function ProcessTTDFiles()
{
    try
    {
    IndexTraceTry()
    ThreadCreateThreadStatus()
    ResetTrace()
    HardwareExceptionDisplayStack()
    }

    catch(err)
    {
         host.diagnostics.debugLog("\n >>> Processing of TTD file failed \n");
    }

}

Wenn Sie dieses Skript in einer Ablaufverfolgungsdatei ausführen, die eine Hardwareausnahme enthält, wird diese Ausgabe generiert.

0:000> dx Debugger.State.Scripts.TTDUtils.Contents.ProcessTTDFiles()

>>> Index Return Value: Loaded

>>> Trace was successfully indexed in 0 ms
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: F:0
.  0  Id: 948.148c Suspend: 4096 Teb: 00a33000 Unfrozen
User Mode Time
  Thread       Time
    0:148c     0 days 0:00:00.000
Kernel Mode Time
  Thread       Time
    0:148c     0 days 0:00:00.000
Elapsed Time
  Thread       Time
    0:148c     3474 days 2:27:43.000
>>> Printing stack
>>> Stack Entry -> 0:  ntdll!LdrInitializeThunk
>>> Current position in trace file:  F:0
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: F:0
>>> New position in trace file:  F:0
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: 91:0
>>> The Thread ID (TID) is : 5260
>>> Printing stack
>>> Stack Entry -> 0:  0x540020
>>> Stack Entry -> 1:  0x4d0049
>>> Stack Entry -> 2:  DisplayGreeting!__CheckForDebuggerJustMyCode + 0x16d
>>> Stack Entry -> 3:  DisplayGreeting!mainCRTStartup + 0x8
>>> Stack Entry -> 4:  KERNEL32!BaseThreadInitThunk + 0x19
>>> Stack Entry -> 5:  ntdll!__RtlUserThreadStart + 0x2f
>>> Stack Entry -> 6:  ntdll!_RtlUserThreadStart + 0x1b


Weitere Informationen

Zeitreise-Debugging – Überblick

Einführung in Objekte zum Debuggen von Zeitreisen

Native Debugger-Objekte in JavaScript-Erweiterungen – Details zum Debugger-Objekt

Skripting mit JavaScript-Debugger

Beispiele für Debuggerskripts mit JavaScript