Compartilhar via


Depuração de viagem no tempo - Automação JavaScript

Logotipo de depuração da viagem no tempo com um relógio.

Você pode usar a automação do JavaScript para trabalhar com rastreamentos TTD de várias maneiras, como automação de comando. Também pode usar consultas para localizar dados de eventos do arquivo de rastreamento.

Para obter informações gerais sobre como trabalhar com JavaScript, consulte Script do Depurador JavaScript. Há também Scripts de exemplo do depurador JavaScript.

Automação de comandos TTD do JavaScript

Uma maneira de usar o JavaScript para automação TTD é enviar comandos para automatizar o trabalho com arquivos de rastreamento de viagem no tempo.

Mover em um arquivo de rastreamento

Este JavaScript mostra como mover para o início de um rastreamento de viagem no tempo usando o comando !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");

Podemos transformar isso em uma função ResetTrace e salvá-la como MyTraceUtils.js, usando a interface do usuário JavaScript no 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");
}

Depois que um arquivo TTD for carregado no WinDbg, chame a função ResetTraceCmd() usando o comando dx na janela de comando do depurador.

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

Limitações para envio de comandos

Mas para todas as situações, exceto as mais simples, a abordagem de envio de comandos tem desvantagens. Ela se baseia no uso de saída do texto. E analisar essa saída gera um código frágil e difícil de manter. Uma abordagem melhor é usar os objetos TTD diretamente.

O exemplo a seguir mostra como usar os objetos diretamente para concluir a mesma tarefa usando os objetos diretamente.

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

A execução desse código mostra que podemos passar para o início do arquivo de rastreamento.

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

Neste exemplo de função ResetTraceEnd, a posição é definida como o final do rastreamento, enquanto a posição atual e nova são exibidas usando o objeto de posição 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");
}

A execução desse código exibe as posições atual e nova.

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

Neste exemplo expandido, os valores de posição inicial e final são comparados para ver se a posição no rastreamento foi alterada.

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

Neste exemplo de execução, é exibida uma mensagem informando que já poderíamos iniciar o arquivo de rastreamento.

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

Para testar o script, use o comando !tt para navegar até a metade do arquivo de rastreamento.

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

A execução do script agora exibe a mensagem apropriada que indica que a posição foi definida para o início do rastreamento 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  

Indexar um arquivo de rastreamento de viagem no tempo

Se apenas um arquivo de rastreamento for copiado para outro computador, ele precisará ser reindexado. Para obter mais informações, consulte Depuração de viagem no tempo – Trabalhar com arquivos de rastreamento.

Este código mostra um exemplo de função IndexTrace que exibe quanto tempo leva para reindexar um arquivo de rastreamento.

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

Aqui está a saída de um pequeno arquivo de rastreamento.

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

>>> Trace was indexed in 2 ms

Adicionar uma instrução try catch

Para verificar se os erros foram gerados quando a indexação foi executada, coloque o código de indexação em uma instrução 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");
    }
}

Aqui está a saída do script se a indexação for bem-sucedida.

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

>>> Index Return Value: Loaded

>>> Trace was successfully indexed in 1 ms

Se o rastreamento não for indexado, por exemplo, se ele não for carregado no depurador, o código do loop catch será executado.

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

>>> Index Failed!

>>> Index Return Value: undefined

>>> Returned error: TypeError

Consultas de objetos JavaScript TTD

Um uso mais avançado de JavaScript e TTD é consultar os objetos de viagem no tempo para localizar chamadas ou eventos específicos que ocorreram no rastreamento. Para obter mais informações sobre os objetos TTD, consulte:

Introdução à Depuração de objetos de viagem no tempo

Objetos nativos do depurador em extensões JavaScript - Detalhes do objeto do depurador

O comando dx exibe informações do modelo de dados do depurador e possibilita consultas usando a sintaxe LINQ. O Dx é muito útil para consultar os objetos em tempo real. Ele permite a prototipagem da consulta desejada que pode ser automatizada usando o JavaScript. O comando dx fornece o preenchimento de tabulação, o que pode ser útil ao explorar o modelo de objeto. Para obter informações gerais sobre como trabalhar com consultas LINQ e objetos do depurador, consulte Usando LINQ com os objetos do depurador.

Este comando dx contabiliza todas as chamadas para determinada API. Neste exemplo, é GetLastError.

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

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

Esse comando explora todo o rastreamento de viagem no tempo para ver quando GetLastError foi chamado.

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]

Comparações de strings para o objeto TTD.Calls localizar chamadas

Este comando de exemplo mostra como usar comparações de strings para localizar chamadas específicas. Neste exemplo, a consulta procura a string "OLE" no parâmetro lpFileName da função CreateFileW.

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

Adicione uma instrução .Select para imprimir Timestart e o valor do parâmetro 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 })

Isso gera essa saída, se um objeto TTD.Calls for encontrado contendo as informações de destino.

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

Exibir o número de chamadas em um rastreamento

Depois de usar o comando dx para explorar os objetos com os quais deseja trabalhar, você pode automatizar seu uso com o JavaScript. Neste exemplo simples, o objeto TTD.Calls é usado para contabilizar chamadas para kernelbase!GetLastError.

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

Salve o script em um arquivo TTDUtils.js e chame-o usando o comando dx para exibir uma contagem do número de kernelbase!GetLastError no arquivo de rastreamento.


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

Exibir os quadros em uma pilha

Para exibir os quadros em uma pilha, uma matriz é usada.

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

Neste rastreamento de exemplo, a entrada de uma pilha é exibida.

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

Localizar um evento e exibir a pilha

Neste código, todos os eventos de exceção estão localizados e um loop é usado para mover para cada um. O currentThread.ID dos Objetos de Thread TTD então é usado para exibir a ID do thread, enquanto currentThread.Stack é usado para exibir todos os quadros na pilha.


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

A saída mostra o local do evento de exceção, o TID e os quadros de pilha.

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

Localizar um evento e enviar dois comandos

A consulta de objetos TTD e o envio de comandos podem ser combinados conforme necessário. Este exemplo localiza cada evento no rastreamento TTD do tipo ThreadCreated, move-se para essa posição e envia os comandos ~ Thread Status e !runaway para exibir o status do 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);
    }
}

A execução do código exibe o status do thread no momento em que a exceção ocorreu.

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

Encadear funções de utilitário

Neste último exemplo, podemos chamar as funções utilitárias que criamos anteriormente. Primeiro, indexamos o rastreamento usando IndexTraceTry e, em seguida, chamamos ThreadCreateThreadStatus. Em seguida, usamos ResetTrace para ir ao início do rastreamento e, por último, chamamos HardwareExceptionDisplayStack.

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

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

}

A execução desse script em um arquivo de rastreamento que contém uma exceção de hardware gera essa saída.

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

Confira também

Depuração de viagem no tempo – Visão geral

Introdução à Depuração de objetos de viagem no tempo

Objetos nativos do depurador em extensões JavaScript - Detalhes do objeto do depurador

Script do Depurador JavaScript

Scripts de exemplo do depurador JavaScript