Partager via


Débogage de voyage dans le temps - Automatisation JavaScript

Logo de débogage avec voyage dans le temps mettant en vedette une horloge.

Vous pouvez utiliser l’automatisation JavaScript pour travailler avec des traces TTD de différentes manières, telles que l’automatisation de commandes ou l’utilisation de requêtes pour localiser les données d’événements dans le fichier de trace.

Pour des informations générales sur le travail avec JavaScript, veuillez consulter la section Script du débogueur JavaScript. Il existe également des Exemples de scripts du débogueur JavaScript.

Automatisation des commandes JavaScript TTD

Une façon d’utiliser JavaScript pour l’automatisation TTD est d’envoyer des commandes pour automatiser le travail avec les fichiers de trace de voyage dans le temps.

Se déplacer dans un fichier de trace

Ce JavaScript montre comment se déplacer au début d’une trace de voyage dans le temps en utilisant la commande !tt.

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");

Nous pouvons transformer cela en une fonction ResetTrace et l’enregistrer sous ResetTrace.js, en utilisant l’interface utilisateur JavaScript dans WinDbg.

// 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");
}

Après qu’un fichier TTD soit chargé dans WinDbg, appelez la fonction ResetTraceCmd() en utilisant la commande dx dans la fenêtre de commande du débogueur.

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

Limites de l’envoi de commandes

Mais, sauf pour les situations les plus simples, l’approche consistant à envoyer des commandes présente des inconvénients. Elle repose sur l’utilisation de la sortie de texte. Et l’analyse de cette sortie conduit à un code fragile et difficile à maintenir. Une meilleure approche consiste à utiliser directement les objets TTD.

L’exemple suivant montre comment utiliser directement les objets pour accomplir la même tâche.

// 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");
}

L’exécution de ce code montre que nous sommes capables de nous déplacer au début du fichier de trace.

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

Dans cet exemple de fonction ResetTraceEnd, la position est définie à la fin de la trace et la position actuelle et nouvelle est affichée en utilisant l’objet Position currentThread.TTD.


// 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");
}

L’exécution de ce code affiche la position actuelle et nouvelle.

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

Dans cet exemple étendu, les valeurs de position de départ et de fin sont comparées pour voir si la position dans la trace a changé.

// 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");
    }
}

Dans cet exemple d’exécution, un message est affiché indiquant que nous étions déjà au début du fichier de trace.

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

Pour tester le script, utilisez la commande !tt pour naviguer à mi-chemin dans le fichier de trace.

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

L’exécution du script affiche maintenant le message approprié indiquant que la position a été définie au début de la trace TTD.

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  

Indexation d’un fichier de trace de voyage dans le temps

Si un fichier de trace est simplement copié sur un autre PC, il devra être réindexé. Pour plus d’informations, veuillez consulter la section Débogage Time Travel - Travailler avec les fichiers de trace.

Ce code montre un exemple de fonction IndexTrace qui affiche le temps nécessaire pour réindexer un fichier de trace.

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");
}

Voici la sortie d’un petit fichier de trace.

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

>>> Trace was indexed in 2 ms

Ajout d’une instruction try catch

Pour vérifier si des erreurs ont été levées lors de l’exécution de l’indexation, encadrez le code d’indexation dans une instruction try catch.


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");
    }
}

Voici la sortie du script si l’indexation est réussie.

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

>>> Index Return Value: Loaded

>>> Trace was successfully indexed in 1 ms

Si la trace ne peut pas être indexée, par exemple si la trace n’est pas chargée dans le débogueur, le code de la boucle catch est exécuté.

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

>>> Index Failed!

>>> Index Return Value: undefined

>>> Returned error: TypeError

Requêtes JavaScript TTD Objects

Une utilisation plus avancée de JavaScript et TTD consiste à interroger les objets de voyage dans le temps pour localiser des appels ou événements spécifiques qui se sont produits dans la trace. Pour plus d’informations sur les objets TTD, voir :

Introduction aux objets de débogage de voyage dans le temps

Objets de débogage natifs dans les extensions JavaScript - Détails de l’objet débogueur

La commande dx affiche des informations provenant du modèle de données du débogueur et prend en charge les requêtes utilisant la syntaxe LINQ. Dx est très utile pour interroger les objets en temps réel. Cela permet de prototyper la requête souhaitée qui peut ensuite être automatisée à l’aide de JavaScript. La commande dx fournit une complétion automatique, ce qui peut être utile lors de l’exploration du modèle d’objet. Pour obtenir des informations générales sur le travail avec les requêtes LINQ et les objets de débogage, veuillez consulter la rubrique Utilisation de LINQ avec les objets de débogage.

Cette commande dx compte tous les appels à une certaine API, dans cet exemple GetLastError.

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

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

Cette commande recherche dans toute la trace de voyage dans le temps pour voir quand GetLastError a été appelé.

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]

Comparaisons de chaînes pour l’objet TTD.Calls afin de localiser des appels

Cette commande exemple montre comment utiliser des comparaisons de chaînes pour localiser des appels spécifiques. Dans cet exemple, la requête recherche la chaîne « OLE » dans le paramètre lpFileName de la fonction CreateFileW.

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

Ajoutez une instruction .Select pour imprimer Timestart et la valeur du paramètre lpFileName.

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

Cela génère cette sortie, si un objet TTD.Calls est trouvé contenant l’information cible.

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

Affichage du nombre d’appels dans une trace

Après avoir utilisé la commande dx pour explorer les objets avec lesquels vous souhaitez travailler, vous pouvez automatiser leur utilisation avec JavaScript. Dans cet exemple simple, l’objet TTD.Calls est utilisé pour compter les appels à kernelbase!GetLastError.

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

Enregistrez le script dans un fichier TTDUtils.js et appelez-le en utilisant la commande dx pour afficher un décompte du nombre de kernelbase!GetLastError dans le fichier de trace.


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

Affichage des frames dans une pile

Pour afficher les frames dans une pile, un tableau est utilisé.

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");
    }
}

Dans cet exemple de trace, une seule entrée de pile est affichée.

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

Localisation d’un événement et affichage de la pile

Dans ce code, tous les événements d’exceptions sont localisés et une boucle est utilisée pour se déplacer vers chacun d’eux. Ensuite, le currentThread.ID des TTD Thread Objects est utilisé pour afficher l’ID du thread et currentThread.Stack est utilisé pour afficher tous les frames de la pile.


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");
    }
}

La sortie montre l’emplacement de l’événement d’exception, le TID et les frames de la pile.

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

Localisation d’un événement et envoi de deux commandes

Les requêtes des objets TTD et l’envoi de commandes peuvent être combinés au besoin. Cet exemple localise chaque événement dans la trace TTD de type ThreadCreated, se déplace à cette position, et envoie les commandes ~ Thread Status et !runaway pour afficher l’état du thread.

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);
    }
}

L’exécution du code affiche l’état du thread au moment où l’exception s’est produite.

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

Enchaînement de fonctions utilitaires

Dans cet exemple final, nous pouvons appeler les fonctions utilitaires que nous avons créées précédemment. Tout d’abord, nous indexons la trace en utilisant IndexTraceTry puis appelons ThreadCreateThreadStatus. Nous utilisons ensuite ResetTrace pour nous déplacer au début de la trace et enfin appelons HardwareExceptionDisplayStack.

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

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

}

L’exécution de ce script sur un fichier de trace contenant une exception matérielle génère cette sortie.

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


Voir aussi

Débogage de voyage dans le temps - Vue d’ensemble

Introduction aux objets de débogage de voyage dans le temps

Objets de débogage natifs dans les extensions JavaScript - Détails de l’objet débogueur

Script du débogueur JavaScript

Exemples de scripts du débogueur JavaScript