时间旅行调试 - 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");

我们可以将其转换为 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  

为时间旅行跟踪文件编制索引

如果只是将跟踪文件复制到另一台电脑,则需要重新编制索引。 有关详细信息,请参阅按时间顺序查看调试 - 使用跟踪文件

此代码显示了一个示例 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 Object 的字符串比较以查找调用

此示例命令演示如何使用字符串比较来查找特定调用。 在本例中,查询在 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.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");
    }
}

在此示例跟踪中,显示了一个堆栈条目。

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

查找事件并发送两个命令

可以根据需要将查询 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

另请参阅

时间旅行调试 - 概述

时间旅行调试对象简介

JavaScript 扩展中的本机调试器对象 - 调试器对象详细信息

JavaScript 调试器脚本

JavaScript 调试器示例脚本