다음을 통해 공유


시간 이동 디버깅 - JavaScript 자동화

시계가 있는 시간 이동 디버깅 로고입니다.

JavaScript 자동화를 사용하여 명령 자동화 또는 쿼리를 사용하여 추적 파일에서 이벤트 데이터를 찾는 등 다양한 방법으로 TTD 추적을 사용할 수 있습니다.

JavaScript 작업에 대한 일반적인 내용은 JavaScript 디버거 스크립팅을 참조 하세요. JavaScript 디버거 예제 스크립트도 있습니다.

JavaScript TTD 명령 자동화

TTD 자동화에 JavaScript를 사용하는 한 가지 방법은 시간 이동 추적 파일 작업을 자동화하는 명령을 보내는 것입니다.

추적 파일에서 이동

이 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 명령은 이 예제 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.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 추적에서 각 이벤트를 찾아 해당 위치로 이동하고 ~ 스레드 상태 및 !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 디버거 예제 스크립트