Débogage de voyage dans le temps - Automatisation JavaScript
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 en faire une fonction ResetTrace et l’enregistrer en tant que MyTraceUtils.js, à l’aide de l’interface utilisateur JavaScript dans WinDbg.
// My Trace Utils
// WinDbg TTD JavaScript MyTraceUtilsCmd Sample
"use strict";
function MyTraceUtilsCmd()
{
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.MyTraceUtils.Contents.ResetTraceCmd()
>>> Sent command to move to the start of the TTD file
Debugger.State.Scripts.MyTraceUtils.Contents.ResetTraceCmd()
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.
// My Trace Utils
// 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.MyTraceUtils.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.MyTraceUtils.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.MyTraceUtils.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.MyTraceUtils.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.MyTraceUtils.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.MyTraceUtils.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.MyTraceUtils.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.MyTraceUtils.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.MyTraceUtils.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.MyTraceUtils.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.MyTraceUtils.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.MyTraceUtils.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