Time Travel Debugging - JavaScript オートメーション

Small time travel logo showing clock.

JavaScript オートメーションを使用して、コマンドオートメーションやクエリを使用してトレース ファイルからイベント データを検索するなど、さまざまな方法で TTD トレースを操作できます。

JavaScript の操作に関する一般的な情報については、 JavaScript デバッガー スクリプトを参照してください。 JavaScript デバッガーのサンプル スクリプトもあります。

JavaScript TTD コマンド オートメーション

TTD オートメーションに JavaScript を使用する方法の 1 つは、タイム トラベル トレース ファイルの操作を自動化するコマンドを送信することです。

トレース ファイル内の移動

この JavaScript では、 !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");

これを ResetTrace 関数にし、WinDbg プレビューの JavaScript UI を使用して、ResetTrace.jsとして保存できます。

// WinDbg TTD JavaScript ResetTraceCmd Sample

"use strict";

function ResetTraceCmd()
{
    var dbgControl = host.namespace.Debugger.Utility.Control;  
    dbgControl.ExecuteCommand("!tt 0",false);
    host.diagnostics.debugLog(">>> Sent command to move to the start of the TTD file \n");
}

WinDbg プレビューで TTD ファイルが読み込まれた後、デバッガー コマンド ウィンドウで dx コマンドを使用して、関数 ResetTraceCmd() 関数を呼び出します。

0:000> dx Debugger.State.Scripts.ResetTrace.Contents.ResetTraceCmd()
>>> Sent command to move to the start of the TTD file
Debugger.State.Scripts.ResetTrace.Contents.ResetTrace()

コマンドの送信に関する制限事項

しかし、最も単純な状況を含むすべての場合、コマンドを送信する方法には欠点があります。 テキスト出力の使用に依存しています。 また、その出力を解析すると、コードは脆弱で保守が困難になります。 より良い方法は、TTD オブジェクトを直接使用することです。

次の例では、オブジェクトを直接使用して、オブジェクトを直接使用して同じタスクを完了する方法を示します。

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

このコードを実行すると、トレース ファイルの先頭に移動できることが示されます。

0:000> dx Debugger.State.Scripts.ResetTrace.Contents.ResetTrace()
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: F:0
>>> Set position to the start of the TTD file

この例の ResetTraceEnd 関数では、位置がトレースの末尾に設定され、 currentThread.TTD Position オブジェクトを使用して現在の位置と新しい位置が表示されます。


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

このコードを実行すると、現在の位置と新しい位置が表示されます。

0:000> dx Debugger.State.Scripts.ResetTrace.Contents.ResetTraceEnd()
>>> Current position in trace file:  F:0
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: D3:1
>>> New position in trace file:  D3:1

この拡張サンプルでは、開始位置と終了位置の値を比較して、トレース内の位置が変更されたかどうかを確認します。

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

この実行例では、トレース ファイルの開始時に準備ができたことを示すメッセージが表示されます。

0:000> dx Debugger.State.Scripts.ResetTrace.Contents.ResetTraceEx()
>>> Current position in trace file:  F:0
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: F:0
>>> New position in trace file:  F:0
>>> Position was already set to the start of the TTD file

スクリプトをテストするには、 !tt コマンドを使用してトレース ファイルの途中を移動します。

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

スクリプトを実行すると、位置が TTD トレースの開始に設定されたことを示す適切なメッセージが表示されるようになりました。

0:000> dx Debugger.State.Scripts.ResetTrace.Contents.ResetTraceEx()
>>> Current position in trace file:  71:0
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: F:0
>>> New position in trace file:  F:0
>>> Set position to the start of the TTD file  

タイム トラベル トレース ファイルのインデックス作成

トレース ファイルだけを別の PC にコピーする場合は、インデックスを再作成する必要があります。 詳細については、「 タイム トラベル デバッグ - トレース ファイルの操作」を参照してください。

このコードは、トレース ファイルのインデックス再作成にかかる時間を示す IndexTrace 関数の例を示しています。

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

小さなトレース ファイルからの出力を次に示します。

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

>>> Trace was indexed in 2 ms

try catch ステートメントの追加

インデックス作成の実行時にエラーが発生したかどうかを確認するには、インデックス作成コードを 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");
    }
}

インデックス作成が成功した場合のスクリプト出力を次に示します。

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

>>> Index Return Value: Loaded

>>> Trace was successfully indexed in 1 ms

トレースのインデックスを作成できない場合 (たとえば、トレースがデバッガーに読み込まれていない場合)、catch ループ コードが実行されます。

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

>>> Index Failed!

>>> Index Return Value: undefined

>>> Returned error: TypeError

JavaScript TTD オブジェクト クエリ

JavaScript と TTD のより高度な用途は、タイム トラベル オブジェクトに対してクエリを実行して、トレースで発生した特定の呼び出しまたはイベントを検索することです。 TTD オブジェクトの詳細については、次を参照してください。

タイム トラベル デバッグ オブジェクトの概要

JavaScript 拡張機能のネイティブ デバッガー オブジェクト - デバッガー オブジェクトの詳細

dx コマンドは、デバッガー データ モデルからの情報を表示し、LINQ 構文を使用したクエリをサポートします。 Dx は、オブジェクトのクエリをリアルタイムで実行するのに非常に便利です。 これにより、JavaScript を使用して自動化できる目的のクエリのプロトタイプ作成が可能になります。 dx コマンドはタブ補完を提供します。これは、オブジェクト モデルを探索するときに役立ちます。 LINQ クエリとデバッガー オブジェクトの操作に関する一般的な情報については、「 デバッガー オブジェクトでの LINQ の使用」を参照してください。

この dx コマンドは、この例 の GetLastError で、特定の API へのすべての呼び出しをカウントします。

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

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

このコマンドは、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]

TTD の文字列比較。呼び出しオブジェクトを呼び出して呼び出しを検索する

このコマンド例は、文字列比較を使用して特定の呼び出しを検索する方法を示しています。 この例では、CreateFileW 関数lpFileName パラメーターで文字列 "OLE" を検索します。

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

を追加します。Timestart と 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 })

これにより、TTD の場合、この出力が生成 されます。ターゲット情報を含む呼び出し オブジェクトが見つかります。

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

トレース内の呼び出しの数を表示する

dx コマンドを使用して、操作するオブジェクトを探索したら、JavaScript での使用を自動化できます。 この簡単な例では、 TTD です。呼び出し オブジェクトは、カーネルベースへの呼び出しをカウントするために使用されます 。GetLastError

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

スクリプトをTTDUtils.js ファイルに保存し、dx コマンドを使用して呼び出してカーネルベースの数を表示します。トレース ファイル内の GetLastError。


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

スタック内のフレームの表示

スタック内のフレームを表示するには、配列が使用されます。

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

このサンプル トレースでは、1 つのスタック エントリが表示されます。

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

イベントの検索とスタックの表示

このコードでは、すべての例外イベントが配置され、ループを使用して各イベントに移動します。 その後、 TTD スレッド オブジェクト の currentThread.ID を使用してスレッド ID を表示し、currentThread.Stack を使用してスタック内のすべてのフレームを表示します。


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

出力には、例外イベントの場所、TID、およびスタック フレームが表示されます。

0:000> dx Debugger.State.Scripts.TTDUtils.Contents.HardwareExceptionDisplayStack()
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: 91:0
>>> The Thread ID (TID) is : 5260
>>> Printing stack
>>> Stack Entry -> 0:  0x540020
>>> Stack Entry -> 1:  0x4d0049
>>> Stack Entry -> 2:  DisplayGreeting!__CheckForDebuggerJustMyCode + 0x16d
>>> Stack Entry -> 3:  DisplayGreeting!mainCRTStartup + 0x8
>>> Stack Entry -> 4:  KERNEL32!BaseThreadInitThunk + 0x19
>>> Stack Entry -> 5:  ntdll!__RtlUserThreadStart + 0x2f
>>> Stack Entry -> 6:  ntdll!_RtlUserThreadStart + 0x1b

イベントの検索と 2 つのコマンドの送信

TTD オブジェクトのクエリとコマンドの送信は、必要に応じて組み合わせることができます。 この例では、ThreadCreated 型の TTD トレース内の各イベントを検索し、その位置に移動し、 ~ スレッドの状態!runaway コマンドを送信してスレッドの状態を表示します。

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

コードを実行すると、例外が発生した時点のスレッドの状態が表示されます。

0:000> dx Debugger.State.Scripts.TTDUtils.Contents.ThreadCreateThreadStatus()
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: F:0
.  0  Id: 948.148c Suspend: 4096 Teb: 00a33000 Unfrozen
User Mode Time
  Thread       Time
    0:148c     0 days 0:00:00.000
Kernel Mode Time
  Thread       Time
    0:148c     0 days 0:00:00.000
Elapsed Time
  Thread       Time
    0:148c     3474 days 2:27:43.000

ユーティリティ関数を連結する

この最後のサンプルでは、前に作成したユーティリティ関数を呼び出すことができます。 まず、 IndexTraceTry を使用してトレースのインデックスを作成し、 ThreadCreateThreadStatus を呼び出します。 次に 、ResetTrace を使用してトレースの先頭に移動し、最後に HardwareExceptionDisplayStack を呼び出します。

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

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

}

ハードウェア例外を含むトレース ファイルでこのスクリプトを実行すると、この出力が生成されます。

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

>>> Index Return Value: Loaded

>>> Trace was successfully indexed in 0 ms
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: F:0
.  0  Id: 948.148c Suspend: 4096 Teb: 00a33000 Unfrozen
User Mode Time
  Thread       Time
    0:148c     0 days 0:00:00.000
Kernel Mode Time
  Thread       Time
    0:148c     0 days 0:00:00.000
Elapsed Time
  Thread       Time
    0:148c     3474 days 2:27:43.000
>>> Printing stack
>>> Stack Entry -> 0:  ntdll!LdrInitializeThunk
>>> Current position in trace file:  F:0
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: F:0
>>> New position in trace file:  F:0
(948.148c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: 91:0
>>> The Thread ID (TID) is : 5260
>>> Printing stack
>>> Stack Entry -> 0:  0x540020
>>> Stack Entry -> 1:  0x4d0049
>>> Stack Entry -> 2:  DisplayGreeting!__CheckForDebuggerJustMyCode + 0x16d
>>> Stack Entry -> 3:  DisplayGreeting!mainCRTStartup + 0x8
>>> Stack Entry -> 4:  KERNEL32!BaseThreadInitThunk + 0x19
>>> Stack Entry -> 5:  ntdll!__RtlUserThreadStart + 0x2f
>>> Stack Entry -> 6:  ntdll!_RtlUserThreadStart + 0x1b


参照

Time Travel Debugging - 概要

タイム トラベル デバッグ オブジェクトの概要

JavaScript 拡張機能のネイティブ デバッガー オブジェクト - デバッガー オブジェクトの詳細

JavaScript デバッガー スクリプト

JavaScript デバッガーのスクリプト例