Отладка по времени — автоматизация 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 и сохранить ее как ResetTrace.js с помощью пользовательского интерфейса JavaScript в WinDbg.

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

После загрузки TTD-файла в WinDbg вызовите функцию ResetTraceCmd() с помощью команды dx в командном окне отладчика.

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  

Индексирование файла трассировки перемещения по времени

Если только файл трассировки копируется на другой компьютер, его потребуется переиндексировать. Дополнительные сведения см. в разделе Отладка перемещения по времени — работа с файлами трассировки.

В этом коде показан пример функции 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

Запросы объектов TTD JavaScript

Более сложный способ использования 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. Вызывает объект для поиска вызовов

В этом примере команды показано, как использовать сравнение строк для поиска определенных вызовов. В этом примере запрос ищет строку OLE в параметре lpFileNameфункции CreateFileW.

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. Найден объект Calls, содержащий сведения о целевом объекте.

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

Отображение количества вызовов в трассировке

После использования команды dx для изучения объектов, с которыми вы хотите работать, вы можете автоматизировать их использование с помощью JavaScript. В этом простом примере TTD. Объект Calls используется для подсчета вызовов к базе ядра! 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");
    }
}

В этом примере трассировки отображается одна запись стека.

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

Поиск события и отображение стека

В этом коде находятся все события исключений, и для перемещения к каждому из них используется цикл. Затем currentThread.ID объектов потока TTD используется для отображения идентификатора потока, а 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

Поиск события и отправка двух команд

При необходимости можно объединять запросы к объектам TTD и отправку команд. Этот пример находит каждое событие в трассировке TTD типа ThreadCreated, перемещается в эту позицию и отправляет команды ~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.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


См. также:

Отладка по времени — обзор

Общие сведения об объектах отладки по времени

Собственные объекты отладчика в расширениях JavaScript — сведения об объекте отладчика

Скрипты отладчика JavaScript

Примеры скриптов отладчика JavaScript