Time Travel Debugging — Enregistrer et rembobiner les bogues qui ne se reproduisent pas dans les applications de longue durée

· Mis à jour le: · · WinDbg, Time Travel Debugging, Débogage, Enquête de bogue, Windows, Développement Windows, .NET, C++, Conseil technique

Historique des révisions (première version, publiée le 2 Sep 2026)
Première publication

« Ça plante une fois par mois, seulement au milieu de la nuit. » « La même opération ne le reproduit jamais sur ma machine. » « On a un dump, mais regarder le lieu du plantage ne nous dit pas pourquoi la valeur est devenue ce qu’elle est. » Parmi les enquêtes de bogues d’applications Windows de longue durée, ce sont les cas qui consomment le plus de temps. Dans un article précédent, « Lire un dump de plantage avec WinDbg + SOS », nous avons examiné comment lire un dump de plantage, qui est une photographie unique. Cet article en est la suite et couvre l’outil pour les situations où une photographie ne suffit pas : Time Travel Debugging (TTD).

TTD est une fonction de WinDbg qui enregistre l’exécution entière d’un processus et permet de la relire plus tard, en avant et en arrière. Au lieu d’essayer encore et encore de reproduire un bogue, vous pouvez « rembobiner » la session du débogueur.1 Les lecteurs visés sont les développeurs et les mainteneurs d’applications Windows, .NET et C++ qui ont déjà fait leur part d’enquête par dump et journaux et ont encore des cas dont ils n’arrivent pas à atteindre la cause. L’environnement requis est Windows 10/11 ou Windows Server 2016 ou ultérieur avec le WinDbg actuel, et l’enregistrement exige des privilèges d’administrateur.12 Le niveau de difficulté est intermédiaire.

Hypothèses de cet article

Élément Contenu
Lecteurs visés Développeurs et mainteneurs d’applications Windows avec des bogues de longue durée ou intermittents dont les dumps et les journaux n’atteignent pas la cause
Connaissances préalables Expérience d’ouvrir un dump dans WinDbg et d’exécuter !analyze -v ou !clrstack. Le contenu de l’article d’analyse SOS est supposé connu
Environnement requis Windows 10/11 ou Windows Server 2016/2019/2022/2025, WinDbg (version actuelle), TTD.exe, privilèges d’administrateur2
Hors périmètre Intégration avec le Snapshot Debugger de Visual Studio Enterprise ; mode noyau (TTD est réservé au mode utilisateur3)

1. L’essentiel d’abord

  • Un dump conserve l’« état » ; TTD conserve le « chemin ». La documentation officielle indique que les dumps ont tendance à manquer l’état et le chemin d’exécution qui ont mené à l’échec.1 Si une photographie du moment du plantage ne vous dit pas la cause, ce qu’il vous faut ensuite n’est pas davantage de photographies, mais un enregistrement.
  • Enregistrer est lourd. Pendant l’enregistrement, le processus cible tourne 5 à 20 fois plus lentement, voire davantage, et le fichier de trace grandit de 5 à 50 Mo par seconde tant que le processus est actif, sans plafond.24 Ce n’est pas un outil que l’on attache inconditionnellement à une application de longue durée.
  • Pour les applications de longue durée, concevez « ce qu’il faut enregistrer ». Le chapitre 5 couvre les quatre points d’entrée que TTD.exe fournit : -ring/-maxFile (ne garder que les N derniers Mo), -module (n’enregistrer que pendant l’exécution de votre propre module), -recordmode Manual (laisser l’application spécifier l’intervalle d’enregistrement) et -monitor (enregistrer chaque lancement).2
  • La lecture s’articule autour de trois choses : les positions (!tt), les événements (dx @$curprocess.TTD.Events) et les requêtes (TTD.Calls / TTD.Memory). Combinez ba (un point d’arrêt sur accès) avec g- (exécution inverse), et le débogueur répond directement « qui a écrit cette valeur en dernier ? ».5
  • Une trace contient le contenu de la mémoire. Elle peut inclure des informations personnelles et confidentielles telles que des chemins de fichiers, des données du Registre et le contenu de la mémoire et des fichiers.1 Concevez le partage et le stockage comme vous le feriez pour un fichier confidentiel.
  • Sachez ce que TTD ne peut pas faire avant de commencer. Il ne peut pas enregistrer le mode noyau, il ne peut pas s’injecter dans les processus protégés (PPL), il ne peut pas se détacher lui-même une fois attaché, et la mémoire ne peut pas être modifiée pendant la lecture.32

Dans le diagramme, un trait continu marque une relation qui vaut toujours et un trait pointillé une relation conditionnelle (les conditions figurent dans l’explication de chaque relation sur la page de détail). La liste complète des relations (32 au total, avec preuve et niveau de certitude) et les définitions des concepts principaux sont rassemblées sur la page de détail de la carte des connaissances (en japonais). Données : JSON-LD / Turtle

2. Là où un dump ne suffit pas

Un dump de plantage est une copie de la mémoire et des registres au moment où le processus a planté (ou a été arrêté). Comme décrit dans l’article d’analyse SOS, !clrstack vous dit où ça a planté et !dumpheap -stat ce qui consomme le tas. Ce qu’il ne peut pas vous dire, c’est ce qui s’est passé sur le chemin vers cet état.

Ce qu'un dump capture et ce que TTD captureUn dump de plantage capture seulement l'état au moment du plantage, pas le chemin qui y a mené. TTD conserve l'exécution complète des instructions du début de l'enregistrement à la fin, de sorte que le chemin est préservé avec l'étatDump de plantage : état au moment du plantageNe montre pas pourquoi la valeur est devenue ce qu'elle estTrace TTD : exécution des instructions de l'intervalle enregistréPeut revenir à la position où la valeur a été écriteC'est cet écart qui étire les enquêtes de longue durée

Figure 1 : Un dump est l’état ; TTD est le chemin. Dans les bogues de longue durée, c’est généralement le second que vous voulez.

Les cas typiques ressemblent à ceci.

  • Un champ d’une structure sur le tas contient une valeur impossible. Le dump montre la valeur corrompue, mais pas qui l’a écrite ni quand.
  • L’emplacement de l’exception est connu, mais pas pourquoi l’argument qui lui a été passé était invalide. En remontant les appelants, la fonction qui a produit la valeur en chemin n’est plus sur la pile.
  • Les handles ou la mémoire grandissent au fil d’un mois. Un dump unique ne peut que dire « ça grandit » ; le chemin d’appels qui l’a fait grandir n’est pas là.
Ce qu'un dump manque dans les cas typiques de longue duréeLa valeur corrompue est capturée mais pas qui l'a écrite, la position de l'exception est capturée mais la fonction qui a produit l'argument n'est plus sur la pile, et la croissance de la ressource est capturée mais pas le chemin qui l'a fait grandir ; les trois types de cas partagent celaChamp corrompuAucun enregistrement de qui l'a écrit ni quandException sur un argument invalideLa fonction qui a produit la valeur n'est plus sur la pileRessource qui grandit sur un moisAucun enregistrement du chemin d'appels qui l'a fait grandirPoint commun : l'état existe, mais pas le chemin

Figure 2 : Les trois cas ont « un état mais pas de chemin ». Davantage de photographies ne comblent pas le chemin.

La documentation de Microsoft compare les forces et les faiblesses des méthodes d’enquête comme suit.1

Méthode Forces Faiblesses
Débogage en direct Interactif, montre le flux d’exécution et permet de changer l’état Arrête le travail de l’utilisateur. Reproduire plusieurs fois demande de l’effort. Souvent inutilisable en production. Difficile de revenir du point d’échec à la cause
Dumps Aucune modification de code au préalable. Faible intrusion, peuvent être collectés sur un déclencheur. Surcoût quasi nul lorsqu’ils ne sont pas utilisés Même avec des instantanés consécutifs, la vue du « temps écoulé » est grossière
Télémétrie et journaux Légers. Liés aux scénarios métier Pas de journaux sur les chemins de code inattendus. Profondeur de données insuffisante, et figée statiquement dans le code
TTD Fort sur les bogues complexes. Aucune modification de code au préalable. Peut être relu hors ligne autant de fois que l’on veut et enregistre tout Surcoût important pendant l’enregistrement. Peut collecter plus de données que nécessaire. Les fichiers deviennent volumineux

Il y a encore une propriété importante que le tutoriel officiel de TTD souligne. Lorsque le débogueur s’arrête au point d’échec, ce point se trouve souvent dans du code de gestion d’erreur plusieurs étapes après la cause réelle.5 Un dump est toujours pris à cette position « plusieurs étapes plus tard ». Avec TTD, vous pouvez reculer de là, instruction par instruction.

L'écart entre le point d'échec et la cause réelleLe point d'échec où un dump est pris se trouve souvent dans la gestion d'erreur plusieurs étapes après la cause réelle, et avec TTD vous pouvez rembobiner depuis ce point instruction par instruction jusqu'à la causeDumpTTDCause réelle (l'instruction qui a corrompu la valeur)Plusieurs étapes en avantPoint d'échec (exception, gestion d'erreur)Figé iciRevenir avec p- / t- / g-

Figure 3 : Un dump est figé au point d’échec. TTD peut remonter du point d’échec à la cause.

3. Comment TTD fonctionne et ce qu’il coûte

3.1 Ce qui est enregistré

TTD injecte un moteur d’enregistrement dans le processus cible et enregistre les instructions exécutées, instruction par instruction. Selon les termes de la documentation officielle, il « encode une trace complète au niveau des instructions à une moyenne de moins d’un octet par instruction » ; en pratique, cela se situe quelque part entre un bit et un octet par instruction. Les programmes qui exécutent peu de sortes de fonctions et traitent peu de données produisent des traces plus petites ; l’inverse en produit de plus grandes.24

Un enregistrement produit deux fichiers.1

Fichier Rôle Taille approximative
.run La trace elle-même. Stocke l’exécution des instructions pendant l’enregistrement Grandit de 5 à 50 Mo par seconde en activité. Ne grandit pas au ralenti4
.idx L’index. Données auxiliaires qui permettent à WinDbg de relire et d’interroger la mémoire efficacement. Créé à l’arrêt de l’enregistrement, et aussi généré automatiquement lorsque WinDbg ouvre le fichier .run 1 à 2 fois la taille de la trace4
Le flux de l'enregistrement TTD à la lectureUn moteur d'enregistrement est injecté dans le processus cible et l'exécution des instructions est enregistrée dans un fichier .run ; lorsque WinDbg ouvre le fichier .run, il crée un index .idx et relit en avant et en arrière à l'aide des positions, des événements et des requêtesProcessus cibleMoteur d'enregistrement injecté (TTDRecordCPU).run (enregistrement de l'exécution des instructions)Ouvrir dans WinDbgGénérer .idx (index)Relire en avant et en arrière avec les positions, les événements et les requêtes

Figure 4 : L’enregistrement se fait avec TTD.exe ou WinDbg ; la lecture se fait avec WinDbg. Partager uniquement le fichier .run suffit.

Le temps à l’intérieur d’un fichier .run s’exprime comme une « position ». Elle prend la forme de deux nombres hexadécimaux séparés par deux-points, tels que 12:0 ou 1A0:12F ; la première moitié est le numéro de séquençage (correspondant à un événement de séquençage) et la seconde moitié le nombre approximatif d’instructions depuis cet événement.6 FFFFFFFFFFFFFFFE:0 signifie la fin de la trace.7 Les positions occupent le premier plan au chapitre 6.

Comment les positions dans une trace s'exprimentUne position est un numéro de séquençage hexadécimal et un nombre de pas séparés par deux-points ; le début est proche de 0, la fin s'exprime comme FFFFFFFFFFFFFFFE:0, et vous pouvez aussi vous déplacer vers une position approximative par pourcentagePosition xx:yy (hexadécimal)xx : numéro de séquençageyy : instructions depuis cet événementLa fin est FFFFFFFFFFFFFFFE:0Les pourcentages tels que !tt 50 fonctionnent aussi

Figure 5 : Une position est « numéro d’événement:nombre d’instructions ». Ce n’est pas l’heure murale, mais elle peut être convertie en heure murale comme le montre le chapitre 6.

3.2 Le coût

La documentation officielle elle-même décrit TTD comme une « technologie invasive ».2 Voici le coût en chiffres.

Élément Contenu
Vitesse Le processus cible tourne 5 à 20 fois plus lentement, voire davantage, pendant l’enregistrement (selon l’application et les options d’enregistrement). Cela peut passer inaperçu dans l’interface, mais c’est perceptible dans les opérations lourdes telles qu’une boîte de dialogue Ouvrir un fichier23
Croissance du fichier 5 à 50 Mo par seconde en activité. Quelques minutes d’enregistrement peuvent atteindre plusieurs Go. Aucun plafond n’est fixé4
Disque saturé Si le disque se remplit pendant l’enregistrement, TTD écrit la dernière page puis attend effectivement de pouvoir écrire à nouveau. WinDbg continue d’afficher la boîte de dialogue d’enregistrement et n’émet ni erreur ni avertissement. Le résultat est une trace incomplète4
Mémoire L’enregistrement ajoute le surcoût des processeurs virtuels à la mémoire du processus cible (par défaut 55 sur x64/ARM64, 32 sur x86). Réduisez-le avec -numVCpu uniquement lorsque la mémoire est insuffisante2
Impossible de se détacher Une fois attaché, TTD ne peut pas se détacher lui-même. Lorsque vous terminez l’enregistrement, fermez l’application ou terminez le processus. Si le processus est essentiel au système, le système d’exploitation doit être redémarré2
Le coût de l'enregistrement et son effet sur les applications de longue duréeLe ralentissement pendant l'enregistrement, la croissance du fichier, l'attente silencieuse lorsque le disque est saturé et l'impossibilité de se détacher après l'attachement sont les quatre coûts qui rendent impossible d'attacher TTD inconditionnellement à une application de longue duréeEnregistrement TTD5 à 20 fois plus lentGrandit de 5 à 50 Mo par seconde, pas de plafondAttend silencieusement lorsque le disque est saturéNe peut pas se détacher après l'attachementL'enregistrement continu inconditionnel n'est pas viable

Figure 6 : N’importe lequel des quatre coûts écarte l’enregistrement continu. C’est pourquoi la « conception d’enregistrement » du chapitre 5 est nécessaire.

3.3 Ce qu’il ne peut pas faire

  • Mode utilisateur uniquement. Il ne peut enregistrer que l’exécution en mode utilisateur d’un processus ; le code qui s’exécute en mode noyau, tel que les pilotes, ne peut pas être débogué.3
  • Processus protégés. TTD ne peut pas s’injecter dans les processus protégés de Windows tels que Protected Process Light (PPL).3
  • La lecture est en lecture seule. Vous pouvez revenir dans le temps, mais vous ne pouvez pas changer l’histoire. Les commandes qui lisent la mémoire fonctionnent ; celles qui la modifient, non.3
  • Incompatibilité avec les logiciels antivirus et de surveillance de la mémoire. Parce que TTD s’accroche au processus, il entre en conflit avec les logiciels qui suivent ou mettent en miroir les appels mémoire du système. Si l’enregistrement produit une erreur qui ressemble à des autorisations insuffisantes, désactivez temporairement ces logiciels pour isoler le problème. Le framework Electron est également un conflit connu ; même si un enregistrement réussit, le processus cible peut se bloquer (interblocage) ou planter.3
  • Les applications UWP ne peuvent pas être lancées et enregistrées (l’attachement à une application UWP déjà en cours d’exécution est possible). Les « processus inhabituels » qui s’exécutent dans une autre session ou un autre contexte de sécurité ne sont actuellement pas pris en charge non plus.8
Ce que TTD ne peut pas enregistrer ni faireLe code en mode noyau, les processus protégés, l'enregistrement au lancement des applications UWP et les processus dans une autre session ou un autre contexte de sécurité ne peuvent pas être enregistrés ; la mémoire ne peut pas être modifiée pendant la lecture ; et les logiciels antivirus et Electron peuvent entrer en conflitLimitations de TTDNe peut pas être enregistréLimitations et conflitsCode en mode noyau (pilotes, etc.)Processus protégés (PPL)Enregistrement au lancement d'UWP (l'attachement est possible)Autres sessions et contextes de sécuritéLa lecture est en lecture seulePeut entrer en conflit avec l'antivirus et Electron

Figure 7 : Les limitations viennent en deux sortes : « ne peut pas enregistrer » et « entre en conflit ». Cette dernière doit être isolée par environnement.

Le dernier point fait trébucher quiconque veut enregistrer un service. La documentation de TTD.exe décrit -attach comme destiné à « l’investigation de services et d’applications de longue durée » et -monitor comme enregistrant « chaque fois qu’un programme ou un service démarre »,2 ce qui ne s’accorde pas simplement avec l’affirmation de la page de dépannage. En pratique, l’approche sûre est de vérifier par environnement dans cet ordre : confirmer que ping.exe ou cmd.exe peuvent être enregistrés dans la même configuration que la production, puis essayer le processus cible.8

4. Enregistrer — L’interface WinDbg et TTD.exe

Il y a deux points d’entrée pour l’enregistrement : l’interface WinDbg, ou TTD.exe en ligne de commande.

4.1 Enregistrer depuis l’interface WinDbg

Exécutez WinDbg en tant qu’administrateur (l’élévation est obligatoire pour TTD1), choisissez File > Start debugging > Launch executable (advanced), spécifiez l’exécutable et cochez Record with Time Travel Debugging. Choisir Configure and Record permet de définir l’emplacement du fichier de trace et Record subset of execution (limiter les modules enregistrés avec une liste séparée par des virgules telle que notepad.exe,kernelbase.dll). Pour un processus déjà en cours d’exécution, choisissez File > Start debugging > Attach to process et cochez de même Record Process with Time Travel Debugging.9

Pendant l’enregistrement, une petite boîte de dialogue avec les boutons « Stop and Debug » et « Cancel » apparaît. Lorsque l’application se termine (ou plante), la trace est fermée et WinDbg l’ouvre automatiquement et construit l’index.5

Le flux de l'enregistrement depuis l'interface WinDbgDans WinDbg démarré en tant qu'administrateur, choisir Launch executable (advanced) ou Attach to process, cocher Record with Time Travel Debugging, définir l'emplacement de sauvegarde et le filtre de module dans Configure and Record, passer par la boîte de dialogue d'enregistrement, et lorsque l'application se termine la trace est fermée et indexée automatiquementDémarrer WinDbg en tant qu'administrateurLaunch executable (advanced) / Attach to processCocher Record with Time Travel DebuggingConfigure and Record : emplacement de sauvegarde, filtre de moduleBoîte de dialogue d'enregistrement (Stop and Debug)L'application se termine, la trace est fermée et indexée automatiquement

Figure 8 : L’enregistrement depuis l’interface prend cinq étapes. Sautez « démarrer en tant qu’administrateur » et vous vous arrêtez à la première boîte de dialogue.

4.2 Enregistrer avec TTD.exe

Lorsque vous devez enregistrer sur un PC où WinDbg ne peut pas être installé, ou pour automatiser l’enregistrement, utilisez TTD.exe seul. Il s’installe via App Installer depuis https://aka.ms/ttd/download, et après l’installation vous pouvez le vérifier avec ttd.exe -help. Pour les environnements hors ligne, Microsoft fournit aussi une procédure officielle (avec un script PowerShell) pour décompresser le bundle MSIX à la main et extraire uniquement les binaires.2 L’enregistrement exige des privilèges d’administrateur et s’exécute normalement depuis une invite de commandes administrateur.2

Il y a trois modes d’enregistrement.2

Les trois modes d'enregistrement de TTD.exelaunch démarre un nouveau processus avec des arguments et l'enregistre, mais il s'exécute avec des privilèges élevés. attach s'attache à un processus en cours par PID. monitor enregistre chaque fois que le programme spécifié démarre, et le lancement se fait avec des privilèges normauxModes d'enregistrement de TTD.exe-launch : lancer et enregistrer-attach : s'attacher à un PID en cours-monitor : enregistrer chaque lancementLancé avec des privilèges d'administrateurConserve les privilèges normauxChemin de lancement normal, adapté à l'automatisation

Figure 9 : Seul -launch peut passer des arguments, mais il s’exécute avec des privilèges élevés. Pour enregistrer un comportement proche de la production, utilisez -attach ou -monitor.

:: Lancer et enregistrer (mode par défaut ; -launch peut être omis)
TTD.exe -out C:\traces MyApp.exe --config prod.json

:: S'attacher à un processus en cours (créer d'abord le répertoire de sortie)
TTD.exe -attach 21440 -out C:\traces\MyApp.run

:: Enregistrer chaque lancement (Ctrl+C termine la surveillance ; -out exige un chemin complet)
TTD.exe -out C:\traces\ -monitor MyApp.exe
  • -launch est le seul mode qui peut passer des arguments, mais le programme est lancé avec les mêmes privilèges (administrateur) que TTD.exe. Pour les applications dont le comportement change avec les privilèges, enregistrez avec -attach ou -monitor pour conserver les privilèges normaux.2
  • -attach exige que le répertoire de sortie existe déjà. Si vous spécifiez un nom de fichier, aucun fichier de ce nom ne doit exister.2
  • -monitor installe un pilote de surveillance des lancements de processus et enregistre chaque fois que le programme spécifié (plusieurs peuvent être indiqués) démarre. Il reste en vigueur jusqu’au redémarrage et s’arrête avec Ctrl+C. Ses avantages sont que vous n’avez pas à assembler le lancement vous-même, que la cible s’exécute avec des privilèges normaux, et qu’il convient à l’automatisation par script. Ajouter -cmdLineFilter "string" n’enregistre que les lancements dont la ligne de commande contient cette chaîne.2
  • -children enregistre aussi les processus enfants, mais chaque processus reçoit son propre fichier .run, et WinDbg ne peut en ouvrir qu’un à la fois.2

Pendant l’enregistrement, une petite interface à deux boutons apparaît : « Tracing Off » (arrêter l’enregistrement et laisser l’application continuer) et « Exit App » (fermer l’application et terminer l’enregistrement). Pour l’automatisation, masquez-la avec -noUI et acceptez le CLUF avec -accepteula.2 Le journal d’enregistrement est conservé dans un fichier .out au même emplacement que le fichier .run, où vous pouvez lire les heures murales de début et de fin de l’enregistrement, la durée de la session d’enregistrement (temps de simulation), s’il s’agissait d’un lancement ou d’un attachement, et la version du système d’exploitation. Lorsqu’un enregistrement échoue, certains messages d’erreur n’apparaissent que dans le fichier .out.2

5. Conception d’enregistrement pour les applications de longue durée

C’est le cœur de l’article. Compte tenu des coûts du chapitre 3, attraper un bogue qui survient à un moment inconnu dans un processus qui tourne pendant des jours exige une conception d’enregistrement. TTD.exe fournit quatre moyens, et vous choisissez selon la nature du symptôme.

Choix de la portée d'enregistrement selon le symptôme d'une application de longue duréeSi vous ne savez pas quand cela se produira, ne gardez que la dernière partie avec un tampon circulaire ; si vous savez quel module est suspect, n'enregistrez que ce module ; si vous pouvez modifier l'application, spécifiez l'intervalle avec l'API d'enregistrement manuel ; s'il n'apparaît qu'au démarrage ou à certains lancements, enregistrez chaque lancement avec le mode surveillanceMoment d'apparition inconnuSeulement au démarrage ou à certains lancementsLe module suspect est clairL'application peut être modifiéeNature du symptôme ?-ring / -maxFile : ne garder que la fin-monitor : enregistrer chaque lancementAjouter -moduleSpécifier l'intervalle avec -recordmode Manual

Figure 10 : Les quatre ne s’excluent pas. Combiner -ring avec -module est la combinaison la plus pratique pour les applications de longue durée.

5.1 Tampon circulaire — Ne garder que les N derniers Mo

Avec -ring, la trace est écrite dans un tampon circulaire de la taille donnée par -maxFile, et le fichier ne grandit jamais au-delà de cette limite. Ce qui reste n’est que la dernière partie de l’enregistrement qui tient dans cette taille.2 L’unité de -maxFile est le Mo ; en mode tampon circulaire, la valeur par défaut est 2 048 Mo, le minimum 1 Mo et le maximum 32 768 Mo (la valeur par défaut pour l’anneau en mémoire d’un processus 32 bits est 256 Mo).2

:: S'attacher à une application de surveillance en cours et ne garder que les 4 Go d'exécution les plus récents
TTD.exe -accepteula -noUI -attach 21440 -ring -maxFile 4096 -out C:\traces\MyApp.run

:: Lorsque le symptôme est détecté, arrêter l'enregistrement (l'application continue)
TTD.exe -stop 21440

-stop accepte un nom de processus, un PID ou all, et arrête cet enregistrement. -wait <seconds> attend que chaque session d’enregistrement sur le système soit terminée (-1 pour indéfiniment), et s’utilise dans les scripts d’automatisation pour imposer l’ordre « arrêter, puis collecter le fichier ».2

Chronologie d'un enregistrement en tampon circulaireL'enregistrement commence à l'attachement, les parties plus anciennes sont poussées hors du tampon circulaire, et lorsque le symptôme apparaît et que stop est émis, seule la portion maxFile la plus récente reste comme traceDémarrer l'enregistrement avec -attach -ringLes intervalles plus anciens sont poussés dehorsLe symptôme apparaîtArrêter l'enregistrement avec -stopSeule la portion maxFile la plus récente resteDimensionner pour que les instants avant le symptôme tiennent dans le tampon

Figure 11 : Le tampon circulaire est un dispositif pour garder « les instants avant le symptôme ». Calculez -maxFile à rebours à partir de « le temps entre le constat du symptôme et l’arrêt, multiplié par la croissance par seconde ».

Il y a deux points de conception.

  1. Rendez le tampon assez grand pour absorber le temps entre le constat et l’arrêt. Dans un processus actif, la trace grandit de 5 à 50 Mo par seconde,4 donc un anneau de 4 Go correspond à une à deux minutes sous charge lourde et un peu plus de dix minutes sous charge légère. Il vous faut un mécanisme qui maintienne le décalage entre la détection du symptôme (une ligne de journal précise, un seuil de compteur, une alerte de la surveillance) et -stop dans cette fenêtre.
  2. Arrêter ne détache pas. -stop arrête l’enregistrement, mais TTD ne se détache pas lui-même du processus cible.2 Terminer complètement l’enregistrement exige de terminer le processus, donc incluez « redémarrer à la prochaine fenêtre de maintenance après la collecte de la trace » dans la procédure d’exploitation.
Arrêt d'un enregistrement en tampon circulaire en coordination avec la surveillanceLorsque le côté surveillance détecte le symptôme par les journaux ou les compteurs, il appelle TTD.exe stop, collecte le fichier .run finalisé et redémarre le processus à la prochaine fenêtre de maintenance pour retirer TTDProcessus cibleTTD.exeSurveillance (journaux, compteurs)Processus cibleTTD.exeSurveillance (journaux, compteurs)Détecter le symptôme-stop PIDArrêter l'enregistrement (le processus continue).run est finaliséCollecter .run, chiffrer et stockerRedémarrer à la prochaine fenêtre de maintenance

Figure 12 : Ce n’est que lorsque le décalage entre la détection et -stop tient dans le tampon que les instants avant le symptôme restent dans la trace. Le redémarrage fait partie de l’exploitation.

5.2 Filtre de module — N’enregistrer que pendant l’exécution de votre propre code

-module <module name> n’enregistre que le module spécifié (l’exécutable lui-même ou une DLL chargée ; plusieurs peuvent être indiqués) et le code que ce module appelle. Le processus cible tourne à pleine vitesse jusqu’à ce que du code du module spécifié s’exécute ; l’enregistrement commence lorsque l’exécution entre dans le module, s’arrête lorsqu’elle le quitte, et le processus revient à pleine vitesse. Parce que l’activation et la désactivation de l’enregistrement sont coûteuses, l’enregistrement reste actif tant que le module spécifié appelle d’autres modules du processus.2

:: N'enregistrer que pendant l'exécution de notre DLL de logique de mesure (combiné avec un anneau)
TTD.exe -accepteula -noUI -attach 21440 -module MeasureCore.dll -ring -maxFile 2048 -out C:\traces\

Une trace ainsi constituée saute simplement les intervalles où l’enregistrement était arrêté, traite « l’instruction suivante » comme la première instruction après la reprise de l’enregistrement, et vous la déboguez sans différence avec une trace du processus entier.2 Dans les applications de longue durée, la boucle idle de l’interface et le traitement interne du framework tendent à représenter la plus grande partie des instructions exécutées, donc restreindre l’enregistrement à votre propre module seul réduit considérablement le surcoût et la taille du fichier.

Comment se comporte l'enregistrement filtré par moduleLe processus cible tourne à pleine vitesse hors du module spécifié ; l'enregistrement commence lorsque le code du module spécifié est entré, continue tant que ce module appelle d'autres modules, et s'arrête lorsque l'exécution quitte le moduleHors du module spécifié : pleine vitesseEntrée dans le module spécifié : l'enregistrement commenceAutres modules qu'il appelle : l'enregistrement continueSortie du module spécifié : l'enregistrement s'arrête

Figure 13 : Parce que les API Win32 et l’environnement d’exécution que votre DLL appelle sont enregistrés aussi, cela suffit pour retracer « ce que notre code a remis au système d’exploitation ».

5.3 Enregistrement manuel — Laisser l’application spécifier l’intervalle

Avec -recordmode Manual, le processus continue de tourner à pleine vitesse même après l’injection de TTD, et l’enregistrement n’a lieu que lorsque le programme appelle l’API d’enregistrement in-process de TTD (la valeur par défaut, Automatic, enregistre dès le moment de l’injection).2 La documentation de l’API et les exemples se trouvent dans le dépôt WinDbg-Samples sur GitHub.10

Si vous pouvez modifier l’application, c’est l’approche la moins gaspilleuse. Vous pouvez intégrer une logique qui démarre l’enregistrement lorsque l’application elle-même sait qu’une anomalie est imminente et s’arrête lorsque les choses reviennent à la normale : « trois tentatives de communication consécutives ont échoué », « l’arriéré de la file a dépassé le seuil », et ainsi de suite. Dans une configuration de processus superviseur comme celle couverte dans l’article sur les Job Objects, le superviseur peut décider de l’intervalle à la place.

5.4 Mode surveillance — Enregistrer chaque lancement

Pour les bogues qui n’apparaissent que juste après le démarrage ou à certains lancements, -monitor convient. Le tableau de comparaison officiel décrit aussi le mode surveillance comme destiné à « attraper les problèmes intermittents et les problèmes de démarrage ».2 Lorsque vous enregistrez le même programme de nombreuses fois, les noms de fichiers séquentiels par défaut (MyApp01.run, MyApp02.run, etc.) deviennent inefficaces parce que les fichiers existants doivent être parcourus, donc utilisez -timestampFilename pour des noms horodatés. Le nombre d’enregistrements simultanés peut être plafonné avec -maxConcurrentRecordings.2

Comment se comporte le mode surveillanceL'option monitor installe un pilote de surveillance des lancements de processus, resserre la cible avec le filtre de ligne de commande chaque fois que le programme spécifié démarre, l'enregistre, crée un fichier de trace distinct par lancement, et continue jusqu'à Ctrl+C ou un redémarrageOuiNonInstaller le pilote de surveillance des lancementsDétecter un lancement du programme spécifiéCorrespond à -cmdLineFilter ?Enregistrer ce lancement (fichier distinct par lancement)Ne pas enregistrerAttendre le prochain lancement (jusqu'à Ctrl+C ou redémarrage)

Figure 14 : Le mode surveillance « guette le lancement ». Il convient aux bogues qui apparaissent à chaque lancement et aux bogues que l’on peut resserrer par les conditions de lancement.

5.5 Où placer le disque

Pour les enregistrements de longue durée, placez les traces sur un volume dédié et incluez la croissance du fichier .run dans votre surveillance. Comme indiqué à la section 3.2, lorsque le disque se remplit, l’enregistrement attend simplement en silence, sans erreur. La solution de contournement officielle est tout aussi primitive : « vérifier l’espace libre dans l’Explorateur » et « vérifier que le fichier .run grandit régulièrement ».4 Sans surveillance de l’espace libre, vous vous retrouvez avec une trace incomplète dans laquelle l’instant que vous vouliez le plus n’a jamais été écrit.

Comment la saturation du disque produit une trace incomplèteLorsque le disque se sature pendant l'enregistrement, TTD écrit la dernière page et attend silencieusement sans erreur ni avertissement, de sorte que le symptôme qui survient ensuite n'est pas enregistré, laissant une trace incomplète qui s'ouvre mais à laquelle manque la partie cruciale. Prévenez-le avec un volume dédié et une surveillance de la croissance de .runprévientLe disque se satureÉcrit la dernière page et attend silencieusementPas d'erreur, pas d'avertissementLe symptôme survient ensuiteTrace incomplète sans le symptômeVolume dédié + surveillance de la croissance de .run

Figure 15 : Une trace qui « s’ouvre mais à laquelle manque la partie cruciale » naît d’un trou dans la surveillance du disque.

6. Relire — Positions, événements et rembobinage

6.1 Ouvrir

Lorsque vous ouvrez un fichier .run dans WinDbg, s’il n’y a pas d’index, !index s’exécute automatiquement et construit le fichier .idx tout en comptant les images clés (positions dans la trace générées automatiquement pour l’indexation ; les traces plus grandes en ont davantage). Plus la trace est grande, plus cela prend de temps.5 L’état de l’index peut être vérifié avec !index -status ; s’il signale autre chose que « Index file loaded », reconstruisez-le avec !index -force. Si cela échoue encore, fermez le débogueur, supprimez le fichier .idx et rouvrez le fichier .run. Reconstruire l’index ne modifie pas le fichier .run, donc aucune donnée n’est perdue.8

Une mise en garde : lorsque l’indexation des traces volumineuses a été améliorée dans TTD 1.11.611, le format d’index a changé, et les traces existantes doivent être réindexées.11 Si vous promenez de vieux fichiers .idx, c’est ici que vous trébuchez.

Vérification et reconstruction de l'indexAprès l'ouverture de la trace, vérifier l'état avec !index -status ; s'il est autre chose que Index file loaded, reconstruire avec !index -force, et si cela échoue encore, fermer le débogueur, supprimer le fichier .idx et rouvrir le fichier .run. La reconstruction ne modifie pas le fichier .runIndex file loadedTout autre choseÉchoueRéussitOuvrir la trace!index -statusPasser à l'analyseReconstruire avec !index -forceFermer, supprimer .idx, rouvrir .run

Figure 16 : Reconstruire l’index ne touche pas le fichier .run. En cas de doute, supprimez-le et rouvrez.

6.2 Se déplacer par position

Passer une position à !tt se déplace à ce point dans le temps.6

!tt 0          ; début de la trace
!tt 50         ; approximativement la position 50 %
!tt 100        ; fin de la trace
!tt 1A0:12F    ; vers la position 1A0:12F

Une position est une paire de nombres hexadécimaux, numéro de séquençage:nombre de pas.6 L’objet Position a les propriétés Percent (la fraction de la trace), Sequence et Steps, plus SeekTo(), qui se déplace à cette position, et ToSystemTime(), qui renvoie l’heure murale approximative (UTC).7 Dans les enquêtes de longue durée, ToSystemTime() fait la différence, parce qu’il permet d’apparier les horodatages du journal de l’application avec les positions de la trace. Les résultats de TTD.Calls portent aussi SystemTimeStart / SystemTimeEnd.12

Apparier les positions aux horodatages des journauxEn partant d'un horodatage dans le journal de l'application, suivre l'heure murale approximative de l'objet Position pour identifier la position dans la trace, s'y déplacer avec SeekTo, et lire l'exécution autourJournal de l'application : heure de l'anomalieTrouver une position dont ToSystemTime est procheS'y déplacer avec SeekToLire les appels et les valeurs environnants

Figure 17 : « Que faisait-il juste avant cette ligne de journal ? » se consulte par la correspondance entre positions et heure murale.

Les positions sont aussi utiles pour le partage. Lorsque vous remettez une trace à un collègue, joignez la position !tt x:y et il peut commencer à regarder au même instant. Écrire des plages de positions dans les rapports de bogue est aussi une pratique officiellement recommandée.2

6.3 Rembobiner

Ajouter - aux commandes de pas habituelles se déplace en arrière dans le temps.13

Commande Signification Bouton du ruban
p- Reculer d’une instruction (ou d’une ligne source). Un appel de fonction compte pour un pas Step Over Back
t- Reculer d’une instruction (ou d’une ligne source). Entre dans les appels de fonction Step Into Back
g- Exécuter en inverse. S’arrête lorsqu’un point d’arrêt est atteint, sur un événement, ou au début de la trace Go Back

Les événements qui arrêtent g- sont les mêmes que ceux qui arrêtent un g en avant.13 Autrement dit, posez un ba (point d’arrêt sur accès) ou un bp et exécutez g-, et vous revenez droit à « la dernière position où cette condition valait ». C’est le mouvement de base du chapitre 7.

Choix parmi les commandes inversesp- recule d'un pas en passant par-dessus les appels de fonction, t- recule d'une instruction dans les fonctions, et g- revient droit à un point d'arrêt, un événement ou le début de la trace. Ce qui arrête un g en avant arrête aussi g-p-t-g-Position actuelleReculer d'un pas en passant par-dessus les appelsReculer d'une instruction dans les fonctionsRevenir droit à la prochaine condition d'arrêtba / bp / événement / début de la trace

Figure 18 : Pour regarder soigneusement à proximité, utilisez t- ; pour sauter vers une cause lointaine, utilisez ba + g-.

6.4 Entrer par les événements

Si vous ne savez pas par où commencer la lecture, commencez par la liste des événements. @$curprocess.TTD.Events liste la création et la terminaison de threads, le chargement et le déchargement de modules, et les exceptions comme événements.14

dx -g @$curprocess.TTD.Events
dx @$curprocess.TTD.Events.Where(t => t.Type == "Exception").Select(e => e.Exception)

Un événement d’exception contient la position, le type (Software / Hardware), le code d’exception et le compteur de programme au moment, et cliquer sur le lien [Time Travel] de la sortie se déplace à cette position.15 Le tutoriel officiel suit ce flux : il saute à la position d’une violation d’accès (0xc0000005), soupçonne une corruption de pile parce que le pointeur de pile et le pointeur de base ne s’accordent pas, et recule de trois instructions avec t- pour vérifier les valeurs.5 La fenêtre Timelines de WinDbg visualise les exceptions, les points d’arrêt, les accès mémoire et les appels de fonction sous forme de chronologie, et un double-clic sur une exception émet le même SeekTo().16

6.5 Threads et positions

!positions montre chaque thread actif à la position actuelle avec la position de chaque thread dans la trace.17 Il y a un piège ici. Changer de thread avec ~<number>s ne déplace pas la position dans la trace. La position que le débogueur utilise pour lire la mémoire ne change pas, donc pour regarder la mémoire d’un autre thread « à cet instant », déplacez-vous avec le lien de position dans la sortie de !positions ou avec !tt x:y.13

Le chemin de base d'une lectureOuvrir la trace et construire l'index, se déplacer de la liste des événements à la position de l'exception, reculer pas à pas jusqu'à la cause, et si besoin se déplacer à la position d'un autre thread avec positionsOuvrir la trace et l'indexerTrouver l'exception dans TTD.EventsSe déplacer à la position avec [Time Travel]Reculer avec t- / p- / g-Vérifier les positions des autres threads avec !positions~s ne déplace pas la position de la trace

Figure 19 : « Entrer par un événement, marcher en arrière » est le chemin de base d’une lecture. Changer de thread n’est pas se déplacer en position.

7. Trouver « quand » avec des requêtes — TTD.Calls et TTD.Memory

La vraie force de la lecture est que vous pouvez interroger toute la trace. Les objets de TTD sont exposés via le modèle de données du débogueur (la commande dx), et vous pouvez les filtrer, les trier et les agréger à la manière LINQ.18

7.1 TTD.Calls — Chercher des appels de fonction

@$cursession.TTD.Calls("module!symbol") collecte les appels de la fonction spécifiée dans toute la trace. Les jokers sont autorisés, et chaque appel porte ses positions de début et de fin (TimeStart / TimeEnd), l’identifiant de thread (avec un UniqueThreadId qui n’est jamais réutilisé), les arguments (Parameters[]), la valeur de retour (ReturnValue) et l’adresse de retour (ReturnAddress).12

L’exemple de la documentation officielle est GetLastError. Agrégez les appels dont la valeur de retour est non nulle par code d’erreur, et vous obtenez une liste de quelles erreurs se sont produites combien de fois pendant la trace.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

Pour « d’où MessageBox a-t-il été appelé en dernier ? », prenez le dernier appel avec OrderBy(c => c.TimeStart).Last() et déplacez-vous via le lien [Time Travel] de son TimeStart.18

Construction d'une requête TTD.CallsCollecter les appels par nom de fonction, filtrer par valeur de retour ou arguments, agréger par code d'erreur et analogues, trier par temps, et se déplacer à la position de l'appel voulu via le lien Time TravelTTD.Calls (nom de fonction, jokers)Where : filtrer par valeur de retour ou argumentsGroupBy : agréger par code d'erreur, etc.OrderBy : trier par tempsSe déplacer via [Time Travel] sur TimeStart

Figure 20 : La façon d’utiliser les requêtes est de partir non de « où » mais de « quand, combien de fois, et avec quels arguments ».

Un mot sur les symboles. TTD détermine le nombre et les types des arguments d’une fonction, son type de retour et sa convention d’appel à partir des informations de symboles PDB. Avec des private symbols, vous obtenez le nom de la fonction et les arguments corrects. Avec des public symbols seulement, vous obtenez le nom de la fonction et des arguments par défaut (quatre entiers non signés 64 bits). Un module sans aucun symbole reçoit le nom de fonction UnknownOrMissingSymbols.1218 Pour les sortes de PDB et comment les conserver, voir « Qu’est-ce qu’un PDB ? ».

Disponibilité des symboles et résultats de TTD.CallsAvec des private symbols vous obtenez le nom de la fonction et les arguments corrects ; avec des public symbols seulement le nom de la fonction et les quatre arguments entiers 64 bits par défaut ; sans symboles le nom de fonction devient UnknownOrMissingSymbolsprivate symbolspublic symbolsaucunSymboles pour le module ?Nom de fonction + arguments et valeur de retour correctsNom de fonction + arguments par défaut (4 x entiers 64 bits)UnknownOrMissingSymbols

Figure 21 : Avoir conservé les PDB de vos propres modules détermine directement l’utilité des requêtes.

Calls implique du calcul, donc plus la trace est grande, plus cela prend de temps et plus l’usage du processeur est élevé. Les résultats sont mis en cache en mémoire, donc la deuxième requête et les suivantes pour la même fonction sont plus rapides.12 Lorsqu’une requête ne renvoie rien, il y a quatre causes : la façon dont l’appel est écrit (vérifiez le nom du module avec la commande x ; s’il revient en majuscules, utilisez cela), la DLL cible n’est pas encore chargée à cette position (déplacez-vous vers une position après le chargement et relancez), la fonction est développée en ligne (ne peut pas être suivie), ou le joker est trop large (resserrez-le).18

7.2 TTD.Memory — Chercher des accès mémoire

@$cursession.TTD.Memory(start address, end address, "access type") collecte les accès à la plage mémoire spécifiée dans toute la trace. Les types sont r (lecture), w (écriture), rw, e (exécution), rwe et ec (exécution/changement).19 « Qui a écrit cette variable en dernier ? » se répond en collectant avec "w", en prenant .Last(), et en se déplaçant à cette position.5

dx -g @$cursession.TTD.Memory(0x00a4fca0, 0x00a4fca4, "w")
dx @$cursession.TTD.Memory(0x00a4fca0, 0x00a4fca4, "w").Last().TimeStart.SeekTo()

Si vous n’avez besoin de chercher qu’en avant ou en arrière depuis la position actuelle, @$curprocess.TTD.PrevMemoryAccess("w", address, size) / NextMemoryAccess sont plus légers et acceptent plusieurs plages à la fois. La position où un registre a changé se trouve avec @$curthread.TTD.PrevRegisterWrite("rcx").6

7.3 ba + g- — Faire répondre le débogueur « qui l’a corrompu ? »

Condenser la procédure montrée dans le tutoriel officiel en une forme utilisable pour les enquêtes de longue durée donne ce qui suit.5

  1. Se déplacer à la position de l’exception avec TTD.Events
  2. Reculer avec t- et formuler l’hypothèse de quelle variable contient la valeur corrompue
  3. Obtenir l’adresse de cette variable avec dx &variable
  4. Poser un point d’arrêt en écriture avec ba w4 <address>
  5. Exécuter g- pour revenir droit à la position où cette variable a été écrite en dernier
  6. Vérifier si ce point (ou quelques instructions plus tôt) est la cause. Si la valeur écrite venait d’une autre variable, poser ba sur cette variable et exécuter g- à nouveau
  7. Répéter jusqu’à atteindre l’instruction qui l’a corrompue
Retracer l'origine d'une valeur avec ba et g-Poser un point d'arrêt en écriture sur l'adresse de la valeur corrompue et exécuter en inverse, s'arrêter à l'instruction qui l'a écrite en dernier, et si cette valeur venait d'une autre variable répéter les mêmes étapes jusqu'à atteindre l'instruction qui l'a corrompueOuiNonIdentifier l'adresse de la valeur corrompuePoser un point d'arrêt en écriture avec ba wExécuter en inverse avec g-S'arrêter à l'instruction qui l'a écrite en dernierLa valeur venait-elle d'une autre variable ?L'instruction corruptrice = la cause

Figure 22 : Une enquête qui s’arrête à « c’est corrompu » avec un dump avance mécaniquement jusqu’à « qui l’a corrompu » avec TTD.

TTD 1.11.553 et ultérieur ajoutent aussi @$curframe.TTD.VariableHistory(), qui renvoie l’historique des valeurs des variables locales d’une frame. Il montre un tableau des noms de variables et des valeurs que chaque variable a tenues sur quelles plages de positions.11 Dans des situations comme la corruption de pile où vous voulez savoir « depuis quand la valeur est fausse », il est utile pour resserrer où poser ba en premier.

8. Appliquer cela aux bogues de longue durée

Nous appliquons maintenant ces outils aux trois types de cas listés au début.

Types de bogues de longue durée et le point d'entrée TTD pour chacunPour les exceptions intermittentes et la corruption de données, revenir de l'événement avec ba et g- ; pour la croissance des ressources, apparier les appels d'acquisition et de libération avec TTD.Calls ; pour Ne répond pas et les chaînes d'attentes, suivre les positions et les heures murales des appels d'API d'attenteExceptions intermittentes, corruption de donnéesTTD.Events, puis ba + g-Croissance des handles et de la mémoireApparier acquisition et libération avec TTD.CallsNe répond pas, chaînes d'attentes!positions et heures murales des API d'attenteEnregistrer avec -module limité à votre propre DLL

Figure 23 : Le point d’entrée diffère selon le type. Ce qu’ils partagent, c’est de resserrer la portée d’enregistrement avant de commencer à lire.

Type 1 : Exceptions intermittentes et corruption de données. Gardez les instants avant le symptôme avec le tampon circulaire de la section 5.1, sautez à la position de l’exception depuis la liste d’événements de la section 6.4, et retracez l’origine de la valeur avec le ba + g- de la section 7.3. La différence avec une enquête par dump est que l’enquête ne s’arrête pas lorsque vous trouvez « la variable corrompue » ; de là vous continuez mécaniquement en arrière.

Type 2 : Croissance des handles et de la mémoire. C’est le type de cas disséqué dans « Enquête sur les plantages après un fonctionnement de longue durée d’une caméra industrielle - La fuite de handles (partie 1) ». Vous ne pouvez pas enregistrer un mois, donc restreignez l’enregistrement à votre propre DLL avec le -module de la section 5.2, combinez-le avec -ring, et gardez « les quelques minutes pendant lesquelles ça grandit ». Sur la trace, collectez TTD.Calls("kernelbase!CreateFileW") et TTD.Calls("kernelbase!CloseHandle") et appariez la valeur de retour de CreateFileW (la valeur de handle) avec le premier argument de CloseHandle (Parameters[0]). Le ReturnValue de CloseHandle est un drapeau booléen de succès, donc inutile pour l’appariement. Si une valeur de handle reste non fermée, le ReturnAddress (l’appelant) de cet appel CreateFileW vous donne l’appelant qui fuit.12 Cela dit, observer si une fuite existe et de quelle taille elle est est moins coûteux avec Application Verifier ou les compteurs de handles ; la bonne répartition du travail est de faire intervenir TTD au stade où l’on cloue quel chemin fuit. Notez qu’enregistrer avec Application Verifier activé dégrade nettement les performances de lecture à cause de l’usage de la mémoire, donc désactivez-le pendant l’enregistrement.8

Type 3 : « Ne répond pas » et chaînes d’attentes. Comme écrit dans « Ce qu’est vraiment « Ne répond pas » », un blocage est une question de qui attend qui. Une trace ne grandit pas au ralenti,4 donc le temps d’attente d’un thread qui attend dans le noyau n’apparaît pas comme des instructions. Ce qui apparaît, c’est l’appel juste avant d’entrer dans l’attente et les instructions après son retour. Regardez la position de chaque thread avec !positions,17 et alignez « quel thread a commencé à attendre quoi, et quand » à partir du SystemTimeStart / SystemTimeEnd de TTD.Calls("kernelbase!WaitForSingleObject") et analogues ; apparié aux horodatages des journaux, cela permet de reconstruire la chaîne d’attentes.12 Les « bogues dépendants de l’ordre » comme DllMain et le verrou du chargeur ou les réveils parasites des variables de condition sont un domaine où TTD, qui enregistre l’ordre lui-même, convient bien.

9. TTD avec les applications .NET

La documentation officielle de TTD indique que le code managé peut être débogué avec TTD dans WinDbg à l’aide de l’extension SOS (sos.dll) s’exécutant en mode 64 bits.1 Le chargement est le même qu’au chapitre 3 de l’article d’analyse SOS (.loadby sos coreclr ou chargement automatique), et !clrstack et !pe fonctionnent à chaque position de la trace. Le chemin de base est de se déplacer à la position de l’exception avec TTD.Events puis de lire la pile managée avec !clrstack.

Mise en place pour lire une trace TTD d'une application .NETOuvrir la trace TTD dans WinDbg, charger l'extension SOS 64 bits, se déplacer à la position de l'événement d'exception, et lire l'état managé avec !clrstack et !pe. Utiliser TTD.Calls pour les appels aux frontières nativesTrace TTD (.run)WinDbgExtension SOS (64 bits)Se déplacer à la position de l'exception avec TTD.EventsLire avec !clrstack / !peTTD.Calls pour les appels aux frontières natives

Figure 24 : État managé via SOS, recherches d’appels aux frontières natives. Séparez les rôles et vous ne vous perdrez pas.

Deux mises en garde. Premièrement, ce que Microsoft garantit est « SOS en mode 64 bits ». Rien n’est dit des applications .NET compilées pour x86, donc exécutez la cible d’enquête en x64 si vous le pouvez. Deuxièmement, TTD.Calls s’appuie sur les informations de symboles PDB.12 Ne comptez pas chercher par nom des méthodes managées compilées JIT ; l’usage fiable est de suivre les appels aux frontières natives telles que les cibles P/Invoke dans des DLL natives, COM et les API Win32. Les problèmes de tas managé comme ceux de « Distinguer l’attente du GC d’une fuite mémoire en .NET » se poursuivent d’abord avec dotnet-counters / dotnet-gcdump / !gcroot sur un dump, et TTD intervient au stade où une frontière native est impliquée.

10. Choisir parmi dumps, journaux, ETW et TTD

TTD n’est pas une panacée, et il ne remplace pas les outils existants. Voici sa place face aux outils couverts dans nos articles.

Ce que vous voulez savoir Outil à utiliser en premier Quand TTD intervient
L’état au moment du plantage Dump de plantage (WER LocalDumps / ProcDump) Lorsque le dump montre « c’est corrompu » mais pas qui l’a corrompu
Quel fichier ou quelle clé de Registre a échoué Process Monitor Lorsque vous avez aussi besoin de comment les arguments passés à l’API en échec ont été construits
Performances de tout le PC sur une longue durée WPR/WPA, PerfView (ETW) Lorsque c’est un problème de correction plutôt que de performance et que vous avez besoin de l’ordre au niveau des instructions
La séquence d’événements au niveau métier Les journaux de l’application Lorsque cela s’est produit sur un chemin de code sans journalisation (TTD enregistre tout sans modification de code préalable1)
Qui a écrit cette valeur, l’ordre des appels TTD
Choix d'une méthode d'enquêteD'abord saisir l'état avec des dumps et des journaux légers, enquêter sur les API en échec avec ProcMon et les performances avec ETW, et ne passer à TTD que lorsque vous avez encore besoin de qui a passé quoi et quandOuiNonSymptômeSaisir l'état avec dumps et journaux (léger)API en échec : ProcMonPerformances : WPR/WPA, PerfViewBesoin de qui, quand, et ce qui a été passé ?Concevoir la portée d'enregistrement et enregistrer avec TTDRéglé avec des outils légers

Figure 25 : Saisissez d’abord le « résultat » avec des outils légers, et sortez TTD seulement pour les cas où le « chemin » s’avère nécessaire.

L’ordre est « le plus léger d’abord ». Les dumps et les journaux ne coûtent presque rien à collecter, donc gardez-les en place en permanence ; sortez TTD, après la conception du chapitre 5, pour les cas où la lecture du dump a montré que le chemin est nécessaire. Compte tenu du surcoût de TTD et du fait que les traces contiennent des informations confidentielles, il n’y a aucune raison d’inverser cet ordre.

11. Notes d’exploitation

  • Traitez les traces comme des fichiers confidentiels. Un enregistrement contient le contenu de la mémoire et peut inclure des informations personnelles et liées à la sécurité telles que des chemins de fichiers, des données du Registre et le contenu de la mémoire et des fichiers.12 Lorsque vous enregistrez dans un environnement client, comprenez à l’avance ce qui peut être inclus (chaînes de connexion, jetons, données clients) et décidez d’un canal de transfert chiffré, d’un emplacement de stockage et d’une durée de conservation.
  • Ne partagez que le fichier .run. Le fichier .idx est à peu près aussi grand que le fichier .run et est généré automatiquement lorsque WinDbg l’ouvre. Les fichiers .run se compressent bien. Lorsque vous signalez un bogue dans TTD lui-même, joignez aussi le fichier .out.2
  • Gardez les versions alignées. TTD continue d’être mis à jour avec WinDbg ; 1.11.611 inclut une correction des plantages d’enregistrement dans les programmes qui utilisent AVX/AVX512 et un changement de format d’index.11 Mélanger une ancienne version du côté enregistrement ou du côté lecture signifie réindexer ou réenregistrer.
  • Vérifiez -replayCpuSupport lorsque vous relisez sur un processeur différent. La valeur par défaut favorise la portabilité, et MostConservative est fourni pour les cas où le processeur d’enregistrement et le processeur de lecture diffèrent (par exemple relire une trace Intel sur arm64). Inversement, si vous savez que le processeur de lecture est égal ou meilleur, vous pouvez choisir un enregistrement plus petit et plus rapide.2
  • Windows Server peut aussi être enregistré. TTD.exe prend en charge Windows Server 2016/2019/2022/2025.2
  • Isoler un échec d’enregistrement. Essayez d’abord si ping.exe ou cmd.exe peuvent être enregistrés ; sinon, soupçonnez un conflit avec un logiciel invasif tel qu’un antivirus ou une virtualisation d’applications.8
Procédure de traitement des tracesParce qu'une trace enregistrée contient le contenu de la mémoire, comprendre quelles informations elle peut contenir, compresser uniquement le fichier .run et le remettre par un canal chiffré, décider de l'emplacement de stockage et de la durée de conservation, et du côté analyse l'ouvrir avec la même version de WinDbg et construire l'indexEnregistrement terminé (.run / .idx / .out)Comprendre quelles informations elle peut contenirCompresser uniquement .run, chiffrer et remettreDécider de l'emplacement de stockage et de la durée de conservationOuvrir avec la même version de WinDbg et générer .idx

Figure 26 : Remettre une trace, c’est remettre un fichier confidentiel. Décidez de la procédure avant d’enregistrer.

12. Résumé

  • Un dump est l’« état » ; TTD est le « chemin ». Dans les cas qui ont besoin de l’origine d’une valeur corrompue ou de l’ordre des appels, TTD transforme l’enquête en travail mécanique
  • L’enregistrement est 5 à 20 fois plus lent, grandit de 5 à 50 Mo par seconde, et ne peut pas se détacher. Pour les applications de longue durée, concevez d’abord la portée d’enregistrement avec -ring/-maxFile, -module, -recordmode Manual et -monitor
  • La lecture est « entrer par un événement, marcher en arrière » : TTD.Events, puis [Time Travel], puis t-/g-, et ba + g- pour faire répondre le débogueur « qui l’a corrompu »
  • TTD.Calls et TTD.Memory sont des requêtes sur toute la trace. Appariez aux horodatages des journaux avec ToSystemTime() et SystemTimeStart
  • Pour .NET, lisez l’état avec SOS 64 bits et utilisez TTD.Calls aux frontières natives
  • Une trace est un fichier confidentiel. Ne partagez que le fichier .run, et décidez d’abord du canal et du stockage

Les cas où un dump a été collecté mais la cause est hors de portée, ou où le travail est bloqué parce que le bogue ne se reproduit pas, se règlent assez souvent avec TTD une fois que la portée d’enregistrement peut être conçue. Nous pouvons aussi prendre en charge tout, de la conception d’enregistrement à l’analyse du fichier .run ; n’hésitez pas à nous contacter avec vos dumps et vos journaux.

Articles associés

Domaines de conseil associés

KomuraSoft LLC prend en charge l’enquête de cause racine des bogues d’applications Windows qui n’apparaissent qu’après une longue durée de fonctionnement ou de façon intermittente, en combinant dumps de plantage, journaux et traces TTD ; la mise en place d’un dispositif d’enquête incluant la conception de la portée d’enregistrement ; et l’isolement des pannes qui impliquent des frontières natives (COM, P/Invoke, SDK de périphériques). Contactez-nous dès le stade « nous avons un dump mais n’arrivons pas à la cause ».

Références

  1. Microsoft Learn, Time Travel Debugging - Overview. Sur le fait que TTD enregistre l’exécution d’un processus et la relit en avant et en arrière, que les dumps ont tendance à manquer l’état et le chemin d’exécution qui ont mené à l’échec, que l’enregistrement exige des privilèges d’administrateur, que les enregistrements peuvent contenir des informations personnelles et liées à la sécurité, le tableau comparatif des méthodes d’enquête, les rôles de .run/.idx, et le débogage du code managé avec l’extension SOS en mode 64 bits.  2 3 4 5 6 7 8 9 10

  2. Microsoft Learn, Time Travel Debugging - TTD.exe command line utility. Sur le ralentissement de 5 à 20 fois ou davantage, l’impossibilité de se détacher après l’attachement, la prise en charge de Windows Server 2016 à 2025, l’installation et le déploiement hors ligne, les trois modes -launch/-attach/-monitor, les options -out/-noUI/-accepteula/-stop/-wait/-tracingOff/-children/-cmdLineFilter/-timestampFilename/-ring/-maxFile/-maxConcurrentRecordings/-numVCpu/-replayCpuSupport/-module/-recordmode, la lecture du fichier .out, et les conseils de partage des 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

  3. Microsoft Learn, Time Travel Debugging - Overview - Things to look out for. Sur l’incompatibilité avec les logiciels antivirus et de surveillance de la mémoire et Electron, le mode utilisateur uniquement, le fait que la lecture est en lecture seule, l’impossibilité de s’injecter dans les processus protégés (PPL), et l’impact de performance d’environ 10 à 20 fois pendant l’enregistrement.  2 3 4 5 6 7

  4. Microsoft Learn, Time Travel Debugging - Working with Trace Files. Sur les facteurs de taille de trace (un bit à un octet par instruction), la croissance de 5 à 50 Mo par seconde en activité et nulle au ralenti, l’absence de plafond de taille maximale, le fait que l’index fait 1 à 2 fois la trace, et le comportement de l’enregistrement et de l’indexation lorsque le disque se sature, ainsi que la solution de contournement.  2 3 4 5 6 7 8 9

  5. Microsoft Learn, Time Travel Debugging - Sample App Walkthrough. Sur la procédure générale de se déplacer à la position d’un événement d’exception et de revenir avec ba et g- à la position où une valeur invalide a été écrite en dernier, le fait que le point d’échec se trouve souvent dans la gestion d’erreur plusieurs étapes après la cause réelle, le fait que la trace est fermée lors d’un plantage et que WinDbg l’indexe automatiquement, et l’usage de TTD.Memory et .Last() 2 3 4 5 6 7

  6. Microsoft Learn, !tt (time travel). Sur la spécification des positions pour !tt (pourcentage ou xx:yy), la signification des deux composantes d’une position (numéro de séquençage et nombre de pas), et TTD.PrevRegisterWrite/PrevMemoryAccess/NextMemoryAccess 2 3 4

  7. Microsoft Learn, TTD Position Objects. Sur Percent/Sequence/Steps de l’objet Position, SeekTo(), le fait que ToSystemTime() renvoie l’heure murale approximative (UTC), et le fait que FFFFFFFFFFFFFFFE:0 désigne la fin de la trace.  2

  8. Microsoft Learn, Time Travel Debugging - Troubleshooting. Sur le fait que l’élévation est requise, que l’enregistrement au lancement des applications UWP n’est pas pris en charge, que les « processus inhabituels » dans une autre session ou un autre contexte de sécurité sont hors périmètre, l’isolement avec ping.exe/cmd.exe, le ralentissement de la lecture lorsque Application Verifier est utilisé en même temps, et la reconstruction de l’index avec !index -status/!index -force 2 3 4 5

  9. Microsoft Learn, Time Travel Debugging - Record a trace. Sur l’enregistrement depuis Launch executable (advanced) / Attach to process dans l’interface WinDbg, la case Record with Time Travel Debugging, le réglage de l’emplacement de sauvegarde avec Configure and Record, et la limitation des modules avec Record subset of execution

  10. Microsoft, WinDbg-Samples - TTD in-process recording API (GitHub). Sur la documentation de l’API d’enregistrement in-process qui, combinée à -recordmode Manual, laisse le programme contrôler le début et l’arrêt de l’enregistrement. 

  11. Microsoft Learn, Time travel debugging release notes. Sur la correction d’enregistrement pour les programmes qui utilisent AVX/AVX512 et le changement de format d’index (réindexation requise) dans 1.11.611, et @$curframe.TTD.VariableHistory() ajouté dans 1.11.553.  2 3

  12. Microsoft Learn, TTD Calls Objects. Sur les arguments de TTD.Calls, les propriétés ThreadId/UniqueThreadId/Function/ReturnValue/ReturnAddress/Parameters[]/TimeStart/TimeEnd/SystemTimeStart/SystemTimeEnd, les valeurs par défaut lorsque les informations de symboles PDB manquent (quatre arguments entiers non signés 64 bits, UnknownOrMissingSymbols), et le fait que le calcul prend du temps et que les résultats sont mis en cache.  2 3 4 5 6 7

  13. Microsoft Learn, Time Travel Debugging - Replay a trace. Sur l’exécution inverse avec p-/t-/g-, le fait que g- s’arrête sur les mêmes événements que l’exécution en avant, !positions, et le fait que ~s ne change pas la position dans la trace.  2 3

  14. Microsoft Learn, TTD Event Objects. Sur les types d’événements (ThreadCreated/ThreadTerminated/ModuleLoaded/ModuleUnloaded/Exception) et les objets enfants Position, Module, Thread et Exception. 

  15. Microsoft Learn, TTD Exception Objects. Sur Type (Software/Hardware), ProgramCounter, Code, Flags et Position de l’objet exception. 

  16. Microsoft Learn, WinDbg: Timelines. Sur le fait que la fenêtre Timelines visualise les exceptions, les points d’arrêt, les accès mémoire et les appels de fonction, et qu’un double-clic sur une exception émet Position.SeekTo()

  17. Microsoft Learn, !positions. Sur l’affichage de chaque thread actif et de la position de chaque thread dans la trace.  2

  18. Microsoft Learn, Introduction to Time Travel Debugging objects. Sur les objets @$curprocess.TTD / @$cursession.TTD, les requêtes avec OrderBy/Where/Select/GroupBy, les exemples d’agrégation des erreurs GetLastError et de recherche du dernier appel à MessageBoxW, la signification de UnknownOrMissingSymbols, et les quatre raisons pour lesquelles Calls ne renvoie rien.  2 3 4 5

  19. Microsoft Learn, TTD Memory Objects. Sur les types d’accès de TTD.Memory (r/w/rw/e/rwe/ec) et le déplacement vers une position via le lien [Time Travel] dans les résultats. 

Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.

Ces pages replacent le sujet dans un contexte plus large de services et de décisions.

Cet article est directement lié aux services suivants.

Questions fréquentes

Questions souvent posées lors d’une consultation sur le sujet de cet article.

Quelle est la différence entre Time Travel Debugging (TTD) et un dump de plantage ?
Un dump de plantage est une photographie de la mémoire au moment du plantage : il montre l'état à cet instant, mais pas le chemin qui y a mené. Une trace TTD est un enregistrement complet de l'exécution des instructions du processus, que l'on peut ensuite relire en avant et en arrière, de sorte que vous pouvez rembobiner et vérifier directement qui a écrit en dernier dans une variable ou ce qui a été appelé juste avant une exception. La documentation officielle de Microsoft indique aussi que les dumps ont tendance à manquer l'état et le chemin d'exécution qui ont mené à l'échec. Le prix est que le processus tourne 5 à 20 fois plus lentement pendant l'enregistrement, et que le fichier de trace grandit d'environ 5 à 50 Mo par seconde tant que le processus est actif.
Puis-je laisser TTD attaché à une application de production qui tourne pendant des jours ?
Pas tel quel. TTD ralentit considérablement le processus pendant l'enregistrement, la trace grandit de 5 à 50 Mo par seconde dans un processus actif, et il n'y a pas de plafond sur la taille du fichier. L'utiliser sur une application de longue durée suppose une conception d'enregistrement : le tampon circulaire de TTD.exe (-ring) avec -maxFile pour ne garder que les N derniers Mo, -module pour n'enregistrer que pendant l'exécution de votre propre module, ou -recordmode Manual pour laisser l'application choisir l'intervalle d'enregistrement. De plus, une fois attaché, TTD ne peut pas se détacher lui-même, il faut donc décider à l'avance comment vous terminerez l'enregistrement, ce qui signifie terminer (redémarrer) le processus.
Peut-on enregistrer des services et des processus dans d'autres sessions ?
La documentation de TTD.exe décrit -attach comme destiné à l'investigation de services et d'applications de longue durée, et -monitor comme enregistrant chaque fois qu'un programme ou un service démarre. D'un autre côté, la page de dépannage indique que les processus inhabituels qui s'exécutent dans une autre session ou un autre contexte de sécurité ne sont actuellement pas pris en charge pour l'enregistrement. Que cela fonctionne réellement dépend de l'environnement, donc enregistrez d'abord un processus simple tel que ping.exe ou cmd.exe dans la même configuration que la production, essayez ensuite le processus cible, et seulement alors intégrez-le à l'exploitation.
Peut-on utiliser TTD sur des traces d'applications .NET ?
Oui. La documentation officielle indique que l'extension SOS (sos.dll) s'exécutant en mode 64 bits peut être utilisée sur une trace TTD dans WinDbg pour déboguer du code managé. Le schéma de base consiste à exécuter des commandes SOS telles que !clrstack et !pe à chaque position de la trace, à se déplacer jusqu'à la position d'un événement d'exception, puis à lire la pile managée. La requête TTD.Calls, qui cherche des appels par nom de symbole, s'appuie sur les informations de symboles PDB, donc dans les applications .NET l'usage fiable est de suivre les appels aux frontières natives telles que P/Invoke, COM et les API Win32.
Est-il prudent d'envoyer un fichier de trace (.run) à une autre entreprise ou hors de l'organisation ?
Pas tel quel. Un enregistrement TTD contient le contenu de la mémoire du processus, et la documentation officielle indique explicitement qu'il peut inclure des informations personnelles ou confidentielles telles que des chemins de fichiers, des données du Registre et le contenu de la mémoire et des fichiers. Si vous en envoyez un, comprenez d'abord ce qui a été enregistré (chaînes de connexion, jetons, données clients, etc.), puis décidez d'un canal de transfert chiffré et d'un emplacement de stockage. Partager uniquement le fichier .run suffit ; le fichier d'index (.idx) est généré automatiquement lorsque WinDbg l'ouvre.
TTD.Calls ne renvoie rien lorsque je cherche une fonction. Pourquoi ?
Il y a quatre causes principales. Premièrement, les symboles : les fonctions d'un module sans PDB s'appellent UnknownOrMissingSymbols, et le nom du module peut être en majuscules, donc vérifiez le nom de symbole réel avec la commande x. Deuxièmement, la DLL cible peut ne pas encore être chargée à cette position ; déplacez-vous vers une position après le chargement de la DLL et interrogez à nouveau. Troisièmement, si la fonction est développée en ligne, le moteur de requête ne peut pas la suivre. Quatrièmement, le joker peut être trop large et correspondre à trop de fonctions ; resserrez le motif.

Profil de l’auteur

Page de présentation de l’auteur de l’article.

Go Komura

Représentant de KomuraSoft LLC

Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.

Retour au blog