Time Travel Debugging - JavaScript オートメーション
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 を使用してMyTraceUtils.jsとして保存できます。
// 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");
}
WinDbg に TTD ファイルが読み込まれた後、デバッガー コマンド ウィンドウで dx コマンドを使用して、ResetTraceCmd() 関数を呼び出します。
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()
コマンドの送信に関する制限事項
しかし、最も単純な状況以外では、このコマンドを送信するアプローチには欠点があります。 この方法は、テキスト出力の使用に依存します。 その出力を解析すると、コードが不安定で維持が困難になります。 より良い方法は、TTD オブジェクトを直接使用することです。
次の例は、オブジェクトを直接使用して同じタスクを完了する方法を示しています。
// 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");
}
このコードを実行すると、トレース ファイルの先頭に移動できることがわかります。
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
この例の 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.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
この展開された例では、開始位置と終了位置の値を比較して、トレース内の位置が変更されたかどうかを確認します。
// 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.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
スクリプトをテストするため、!tt コマンドを使用してトレース ファイルの半分の位置まで移動します。
0:000> !tt 50
Setting position to 50% into the trace
Setting position: 71:0
スクリプトを実行すると、位置が 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
タイム トラベル トレース ファイルのインデックス作成
トレース ファイルだけを別の 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.MyTraceUtils.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.MyTraceUtils.Contents.IndexTraceTry()
>>> Index Return Value: Loaded
>>> Trace was successfully indexed in 1 ms
トレースのインデックスを作成できない場合 (たとえば、トレースがデバッガーに読み込まれていない場合)、catch ループ コードが実行されます。
0:007> dx Debugger.State.Scripts.MyTraceUtils.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 コマンドは、特定の API へのすべての呼び出しをカウントします (この例では GetLastError)。
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.Calls オブジェクトで呼び出しを検索するための文字列比較
このコマンド例は、文字列比較を使用して特定の呼び出しを検索する方法を示しています。 この例では、クエリは CreateFileW 関数の lpFileName パラメーターで文字列 "OLE" を検索します。
dx -r2 @$cursession.TTD.Calls("kernelbase!CreateFileW").Where(x => x.Parameters.lpFileName.ToDisplayString("su").Contains("OLE"))
Timestart と lpFileName パラメーターの値を出力する .Select ステートメントを追加します。
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.Calls オブジェクトが見つかった場合、次の出力が生成されます。
[0x0]
TimeStart : 6E37:590
lpFileName : 0x346a78be90 : "C:\WINDOWS\SYSTEM32\OLEACCRC.DLL" [Type: wchar_t *]
トレース内の呼び出しの数の表示
dx コマンドを使用して操作するオブジェクトを探索したら、JavaScript でオブジェクトの使用を自動化できます。 このシンプルな例では、TTD.Calls オブジェクトを使用して、kernelbase!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 コマンドを使用してこれを呼び出すと、トレース ファイル内の kernelbase!GetLastError の数が表示されます。
0:000> dx Debugger.State.Scripts.MyTraceUtils.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.MyTraceUtils.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.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
イベントの検索と 2 つのコマンドの送信
TTD オブジェクトのクエリとコマンドの送信は、必要に応じて組み合わせることができます。 次の例は、ThreadCreated 型の TTD トレース内の各イベントを検索してその位置に移動し、~ Thread Status コマンドと !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.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
ユーティリティ関数の連結
この最後のサンプルでは、前に作成したユーティリティ関数を呼び出すことができます。 まず、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.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