Ladění cestování v čase – JavaScriptová automatizace

logo pro ladění časových cest s hodinami.

Automatizaci JavaScriptu můžete použít k práci s trasování TTD mnoha způsoby, jako je automatizace příkazů nebo použití dotazů k vyhledání dat událostí z trasovacího souboru.

Obecné informace o práci s JavaScriptem viz Skriptování ladicího programu JavaScript. K dispozici jsou také ukázkové skripty ladicího programu JavaScriptu.

Automatizace příkazů TTD v JavaScriptu

Jedním ze způsobů, jak používat JavaScript pro automatizaci TTD, je odesílat příkazy pro automatizaci práce se soubory trasování času.

Pohyb v souboru se stopami

Tento JavaScript ukazuje, jak přejít na začátek trasování cesty času pomocí příkazu !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");

Můžeme to udělat do funkce ResetTrace a uložit ji jako MyTraceUtils.jspomocí javascriptového uživatelského rozhraní v 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");
}

Po načtení souboru TTD ve WinDbg zavolejte funkci ResetTraceCmd() pomocí příkazu dx v příkazovém okně ladicího programu.

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

Omezení odesílání příkazů

Ale pro všechny kromě nejjednodušších situací má přístup k odesílání příkazů nevýhody. Spoléhá na použití textového výstupu. Analýza výstupu vede k kódu, který je křehký a obtížně se udržuje. Lepším přístupem je přímé použití objektů TTD.

Následující příklad ukazuje, jak přímo použít objekty k dokončení stejné úlohy.

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

Spuštění tohoto kódu ukazuje, že se můžeme přesunout na začátek trasovacího souboru.

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

V tomto příkladu ResetTraceEnd funkce je pozice nastavena na konec trasování a aktuální a nová pozice se zobrazí pomocí currentThread.TTD Position objektu.


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

Spuštěním tohoto kódu se zobrazí aktuální a nová pozice.

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

V tomto rozšířeném vzorku se hodnoty počáteční a koncové pozice porovnávají, aby se zjistilo, jestli se pozice ve stopě změnila.

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

V tomto příkladu spuštění se zobrazí zpráva, že jsme byli všichni připraveni na začátku trasovacího souboru.

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

Pro otestování skriptu použijte příkaz !tt k navigaci do poloviny trasovacího souboru.

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

Spuštěním skriptu se zobrazí správná zpráva, která indikuje, že pozice byla nastavena na začátek trasování 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  

Indexování souboru časové stopy

Pokud se do jiného počítače zkopíruje jenom trasovací soubor, bude potřeba ho znovu indexovat. Další informace naleznete v tématu Ladění časového cestování – práce s trasovacími soubory.

Tento kód ukazuje ukázkovou funkci IndexTrace, která zobrazuje, jak dlouho trvá opětovné indexování trasovací soubor.

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

Tady je výstup z malého trasovacího souboru.

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

>>> Trace was indexed in 2 ms

Přidání příkazu try catch

Pokud chcete zkontrolovat, jestli při spuštění indexování došlo k chybám, uzavřete kód indexování do příkazu 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");
    }
}

Tady je výstup skriptu, pokud je indexování úspěšné.

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

>>> Index Return Value: Loaded

>>> Trace was successfully indexed in 1 ms

Pokud nelze sledování indexovat, například pokud není načteno do debuggovacího programu, spustí se kód smyčky 'catch'.

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

>>> Index Failed!

>>> Index Return Value: undefined

>>> Returned error: TypeError

Dotazy na objekty TTD v JavaScriptu

Pokročilejším použitím JavaScriptu a TTD je dotazování na objekty časového cestování za účelem vyhledání konkrétních volání nebo událostí, ke kterým došlo v trasování. Další informace o objektech TTD najdete tady:

Úvod do ladění objektů časového cestování

Nativní objekty ladicího programu v rozšířeních JavaScriptu – Podrobnosti o objektu ladicího programu

Příkaz dx zobrazí informace z datového modelu ladicího programu a podporuje dotazy pomocí syntaxe LINQ. Dx je velmi užitečný k dotazování objektů v reálném čase. To umožňuje vytváření prototypů požadovaného dotazu, které je pak možné automatizovat pomocí JavaScriptu. Příkaz dx poskytuje dokončování tabulátoru, které může být užitečné při zkoumání objektového modelu. Obecné informace o práci s dotazy LINQ a objekty ladicího programu naleznete v tématu Použití LINQ S objekty ladicího programu.

Tento příkaz dx spočítá všechna volání určitého rozhraní API v tomto příkladu GetLastError.

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

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

Tento příkaz prohledává celou časovou stopu cestování, aby zjistil, kdy byla volána funkce GetLastError.

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]

Porovnání řetězců pro objekt TTD.Calls pro vyhledání volání

Tento ukázkový příkaz ukazuje, jak pomocí porovnání řetězců vyhledat konkrétní volání. V tomto příkladu dotaz hledá řetězec "OLE" v parametru lpFileNamefunkce CreateFileW.

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

Přidejte prohlášení Select k vytištění Timestart a hodnoty parametru 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 })

Toto vygeneruje tento výstup, pokud je nalezen objekt TTD.Calls, který obsahuje cílové informace.

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

Zobrazení počtu volání ve sledu

Po použití příkazu dx k prozkoumání objektů, se kterými chcete pracovat, můžete jejich použití automatizovat pomocí JavaScriptu. V tomto jednoduchém příkladu se používá objekt TTD.Calls k počítání volání kernelbase!GetLastError.

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

Uložte skript do souboru TTDUtils.js a vyvolejte ho pomocí příkazu dx pro zobrazení počtu výskytů kernelbase!GetLastError v trasovacím souboru.


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

Zobrazení rámců ve stohu

K zobrazení rámců v zásobníku se použije pole.

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

V tomto ukázkovém trasování je zobrazena jedna položka zásobníku.

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

Vyhledání události a zobrazení zásobníku

V tomto kódu se nacházejí všechny události výjimek a smyčka se používá k přechodu na každý z nich. Pak se ID aktuálního vlákna (currentThread.ID) objektu vlákna TTD použije k zobrazení ID vlákna a currentThread.Stack se použije k zobrazení všech rámců v zásobníku.


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

Výstup ukazuje umístění události výjimky, TID a rámce zásobníku.

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

Vyhledání události a odeslání dvou příkazů

Dotazování objektů TTD a odesílání příkazů je možné podle potřeby kombinovat. Tento příklad vyhledá každou událost v trasování TTD typu ThreadCreated, přesune se na tuto pozici a odešle ~ Stav vlákna a !runaway příkazy k zobrazení stavu vlákna.

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

Spuštění kódu zobrazí stav vlákna v okamžiku, kdy došlo k výjimce.

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

Řetězení užitečných funkcí pohromadě

V této poslední ukázce můžeme použít pomocné funkce, které jsme vytvořili dříve. Nejprve indexujeme trasování pomocí IndexTraceTry a potom zavoláme ThreadCreateThreadStatus. Pak použijeme ResetTrace k přechodu na začátek trasování a nakonec zavoláme HardwareExceptionDisplayStack.

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

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

}

Spuštění tohoto skriptu v trasovacím souboru, který obsahuje výjimku hardwaru, vygeneruje tento výstup.

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

Viz také

Ladění časové – přehled

Úvod k objektům ladění pro cestování časem

Nativní objekty ladicího programu v rozšířeních JavaScriptu – Podrobnosti o objektu ladicího programu

Skriptování ladicího programu JavaScriptu

Ukázkové skripty ladicího programu JavaScriptu