時間移動偵錯 - JavaScript 自動化
您可以使用 JavaScript 自動化,以數種方式使用 TTD 追蹤,例如命令自動化或使用查詢來尋找追蹤檔案中的事件數據。
如需使用 JavaScript 的一般資訊,請參閱 JavaScript 調試程式腳本。 還有 JavaScript 調試程式範例腳本。
JavaScript TTD 命令自動化
使用 JavaScript 進行 TTD 自動化的其中一種方法是傳送命令,以自動化使用時間移動追蹤檔案。
在追蹤檔案中移動
這個 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");
我們可以使用 WinDbg 中的 JavaScript UI,將它設為 ResetTrace 函式,並將其儲存為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 命令呼叫 Function 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
編製時間移動追蹤檔案的索引
如果只將追蹤檔案複製到不同的計算機,則必須重新編製索引。 如需詳細資訊,請參閱 時間移動偵錯 - 使用追蹤檔案。
此程式代碼顯示範例 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 的字串比較。呼叫 物件以尋找呼叫
這個範例命令示範如何使用字串比較來尋找特定呼叫。 在此範例中,查詢會在 CreateFileW 函式的 lpFileName 參數中尋找字串 “OLE”。
dx -r2 @$cursession.TTD.Calls("kernelbase!CreateFileW").Where(x => x.Parameters.lpFileName.ToDisplayString("su").Contains("OLE"))
新增 。Select 語句可列印 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。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 命令呼叫腳本,以顯示核心基底數目的計數!追蹤檔案中的 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");
}
}
在此範例追蹤中,會顯示一個堆疊專案。
0:000> dx Debugger.State.Scripts.MyTraceUtils.Contents.DisplayStack()
>>> Printing stack
>>> Stack Entry -> 0: ntdll!LdrInitializeThunk + 0x21
尋找事件並顯示堆疊
在此程式代碼中,所有例外狀況事件都位於 ,而且迴圈會用來移至每個事件。 然後會使用 TTD 線程物件的 currentThread.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
尋找事件並傳送兩個命令
您可以視需要查詢 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.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