시간 이동 디버깅 - 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 Preview의 JavaScript UI를 사용하여 ResetTrace.js 저장할 수 있습니다.
// 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");
}
WinDbg 미리 보기에서 TTD 파일을 로드한 후 디버거 명령 창에서 dx 명령을 사용하여 ResetTraceCmd() 함수를 호출합니다.
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
시간 이동 추적 파일 인덱싱
추적 파일만 다른 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.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
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.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
이벤트 찾기 및 스택 표시
이 코드에서는 모든 예외 이벤트가 있으며 루프를 사용하여 각 이벤트로 이동합니다. 그런 다음 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.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 개체를 쿼리하고 명령을 보낼 수 있습니다. 다음은 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.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