Share via


JavaScript 디버거 스크립팅

이 항목에서는 JavaScript를 사용하여 디버거 개체를 이해하고 디버거의 기능을 확장하고 사용자 지정하는 스크립트를 만드는 방법을 설명합니다.

JavaScript 디버거 스크립팅 개요

스크립트 공급자는 스크립팅 언어를 디버거의 내부 개체 모델에 연결합니다. JavaScript 디버거 스크립팅 공급자를 사용하면 디버거와 함께 JavaScript를 사용할 수 있습니다.

.scriptload 명령을 통해 JavaScript를 로드하면 스크립트의 루트 코드가 실행되고, 스크립트에 있는 이름은 디버거(dx 디버거)의 루트 네임스페이스에 브리지되고 스크립트는 언로드되고 해당 개체에 대한 모든 참조가 해제될 때까지 메모리에 유지됩니다. 스크립트는 디버거의 식 계산자에 새 함수를 제공하거나, 디버거의 개체 모델을 수정하거나, NatVis 시각화 도우미와 거의 동일한 방식으로 시각화 도우미로 작동할 수 있습니다.

이 항목에서는 JavaScript 디버거 스크립팅으로 수행할 수 있는 몇 가지 작업을 설명합니다.

이 두 항목에서는 디버거에서 JavaScript를 사용하는 방법에 대한 추가 정보를 제공합니다.

JavaScript 디버거 예제 스크립트

JavaScript 확장의 네이티브 개체

JavaScript 스크립팅 비디오

조각 모음 도구 #170 - Andy 및 Bill은 디버거에서 JavaScript 확장성 및 스크립팅 기능을 보여 줍니다.

디버거 JavaScript 공급자

디버거에 포함된 JavaScript 공급자는 최신 ECMAScript6 개체 및 클래스 향상 기능을 최대한 활용합니다. 자세한 내용은 ECMAScript 6 - 새로운 기능: 개요 및 비교를 참조하세요.

JsProvider.dll

JsProvider.dll JavaScript 디버거 스크립팅을 지원하기 위해 로드되는 JavaScript 공급자입니다.

요구 사항

JavaScript 디버거 스크립팅은 지원되는 모든 버전의 Windows에서 작동하도록 설계되었습니다.

JavaScript 스크립팅 공급자 로드

.script 명령을 사용하기 전에 스크립팅 공급자를 로드해야 합니다. .scriptproviders 명령을 사용하여 JavaScript 공급자가 로드되어 있는지 확인합니다.

0:000> .scriptproviders
Available Script Providers:
    NatVis (extension '.NatVis')
    JavaScript (extension '.js')

JavaScript 스크립팅 메타 명령

JavaScript 디버거 스크립팅을 사용할 수 있는 명령은 다음과 같습니다.

요구 사항

.script 명령을 사용하기 전에 스크립팅 공급자를 로드해야 합니다. .scriptproviders 명령을 사용하여 JavaScript 공급자가 로드되어 있는지 확인합니다.

0:000> .scriptproviders
Available Script Providers:
    NatVis (extension '.NatVis')
    JavaScript (extension '.js')

.scriptproviders(스크립트 공급자 나열)

.scriptproviders 명령은 디버거에서 현재 인식되는 모든 스크립트 언어와 해당 언어가 등록된 확장을 나열합니다.

아래 예제에서는 JavaScript 및 NatVis 공급자가 로드됩니다.

0:000> .scriptproviders
Available Script Providers:
    NatVis (extension '.NatVis')
    JavaScript (extension '.js')

"로 끝나는 모든 파일입니다. NatVis"는 NatVis 스크립트로 이해되며 ".js"로 끝나는 모든 파일은 JavaScript 스크립트로 이해됩니다. .scriptload 명령을 사용하여 두 유형의 스크립트를 로드할 수 있습니다.

자세한 내용은 .scriptproviders(스크립트 공급자 나열)를 참조 하세요.

.scriptload(스크립트 로드)

.scriptload 명령은 스크립트를 로드하고 스크립트의 루트 코드와 initializeScript 함수를 실행합니다. 스크립트의 초기 로드 및 실행에 오류가 있으면 오류가 콘솔에 표시됩니다. 다음 명령은 TestScript.js 성공적으로 로드하는 방법을 보여줍니다.

0:000> .scriptload C:\WinDbg\Scripts\TestScript.js
JavaScript script successfully loaded from 'C:\WinDbg\Scripts\TestScript.js'

스크립트에서 수행한 개체 모델 조작은 이후에 스크립트가 언로드되거나 다른 콘텐츠로 다시 실행될 때까지 그대로 유지됩니다.

자세한 내용은 .scriptload(스크립트 로드)를 참조하세요 .

.scriptrun

.scriptrun 명령은 스크립트를 로드하고 스크립트의 루트 코드, initializeScript 및 invokeScript 함수를 실행합니다. 스크립트의 초기 로드 및 실행에 오류가 있으면 오류가 콘솔에 표시됩니다.

0:000> .scriptrun C:\WinDbg\Scripts\helloWorld.js
JavaScript script successfully loaded from 'C:\WinDbg\Scripts\helloWorld.js'
Hello World!  We are in JavaScript!

스크립트에서 수행한 모든 디버거 개체 모델 조작은 스크립트가 이후에 언로드되거나 다른 콘텐츠로 다시 실행될 때까지 그대로 유지됩니다.

자세한 내용은 .scriptrun(스크립트 실행)을 참조하세요.

.scriptunload(스크립트 언로드)

.scriptunload 명령은 로드된 스크립트를 언로드하고 uninitializeScript 함수를 호출합니다. 다음 명령 구문을 사용하여 스크립트 언로드

0:000:x86> .scriptunload C:\WinDbg\Scripts\TestScript.js
JavaScript script unloaded from 'C:\WinDbg\Scripts\TestScript.js'

자세한 내용은 .scriptunload(스크립트 언로드)를 참조하세요.

.scriptlist(로드된 스크립트 나열)

.scriptlist 명령은 .scriptload 또는 .scriptrun 명령을 통해 로드된 모든 스크립트를 나열합니다. .scriptload를 사용하여 TestScript를 성공적으로 로드한 경우 .scriptlist 명령은 로드된 스크립트의 이름을 표시합니다.

0:000> .scriptlist
Command Loaded Scripts:
    JavaScript script from 'C:\WinDbg\Scripts\TestScript.js'

자세한 내용은 .scriptlist(로드된 스크립트 나열)를 참조하세요.

JavaScript 디버거 스크립팅 시작

HelloWorld 예제 스크립트

이 섹션에서는 헬로 월드 출력하는 간단한 JavaScript 디버거 스크립트를 만들고 실행하는 방법을 설명합니다.

// WinDbg JavaScript sample
// Prints Hello World
function initializeScript()
{
    host.diagnostics.debugLog("***> Hello World! \n");
}

메모장 같은 텍스트 편집기를 사용하여 위에 표시된 JavaScript 코드가 포함된 HelloWorld.js 텍스트 파일을 만듭니다.

.scriptload 명령을 사용하여 스크립트를 로드하고 실행합니다. 함수 이름 initializeScript를 사용했으므로 스크립트가 로드될 때 함수의 코드가 실행됩니다.

0:000> .scriptload c:\WinDbg\Scripts\HelloWorld.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\HelloWorld.js'
***> Hello World! 

스크립트가 로드되면 디버거에서 추가 기능을 사용할 수 있습니다. dx(NatVis 식 표시) 명령을 사용하여 Debugger.State.Scripts를 표시하여 스크립트가 현재 상주하고 있는지 확인합니다.

0:000> dx Debugger.State.Scripts
Debugger.State.Scripts                
    HelloWorld 

다음 예제에서는 명명된 함수를 추가하고 호출합니다.

두 값 추가 예제 스크립트

이 섹션에서는 입력을 추가하고 두 개의 숫자를 추가하는 간단한 JavaScript 디버거 스크립트를 만들고 실행하는 방법을 설명합니다.

이 간단한 스크립트는 단일 함수인 addTwoValues를 제공합니다.

// WinDbg JavaScript sample
// Adds two functions
function addTwoValues(a, b)
 {
     return a + b;
 }

메모장 같은 텍스트 편집기를 사용하여 FirstSampleFunction.js이라는 텍스트 파일을 만듭니다.

.scriptload 명령을 사용하여 스크립트를 로드합니다.

0:000> .scriptload c:\WinDbg\Scripts\FirstSampleFunction.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\FirstSampleFunction.js'

스크립트가 로드되면 디버거에서 추가 기능을 사용할 수 있습니다. dx(NatVis 식 표시) 명령을 사용하여 Debugger.State.Scripts를 표시하여 스크립트가 현재 상주하고 있는지 확인합니다.

0:000> dx Debugger.State.Scripts
Debugger.State.Scripts                
    FirstSampleFunction    

FirstSampleFunction선택하여 제공하는 함수를 확인할 수 있습니다.

0:000> dx -r1 -v Debugger.State.Scripts.FirstSampleFunction.Contents
Debugger.State.Scripts.FirstSampleFunction.Contents                 : [object Object]
    host             : [object Object]
    addTwoValues    
 ... 

스크립트를 좀 더 편리하게 작업하려면 dx 명령을 사용하여 스크립트의 내용을 저장할 변수를 디버거에 할당합니다.

0:000> dx @$myScript = Debugger.State.Scripts.FirstSampleFunction.Contents

dx 식 계산기를 사용하여 addTwoValues 함수를 호출합니다.

0:000> dx @$myScript.addTwoValues(10, 41),d
@$myScript.addTwoValues(10, 41),d : 51

별칭으로 작성된 @$scriptContents 사용하여 스크립트를 사용할 수도 있습니다. @$scriptContents 별칭은 모든 별칭을 병합합니다. 로드되는 모든 스크립트의 콘텐츠입니다.

0:001> dx @$scriptContents.addTwoValues(10, 40),d
@$scriptContents.addTwoValues(10, 40),d : 50

스크립트 작업을 마쳤으면 .scriptunload 명령을 사용하여 스크립트를 언로드합니다.

0:000> .scriptunload c:\WinDbg\Scripts\FirstSampleFunction.js
JavaScript script successfully unloaded from 'c:\WinDbg\Scripts\FirstSampleFunction.js'

디버거 명령 자동화

이 섹션에서는 u(Unassemble) 명령의 전송을 자동화하는 간단한 JavaScript 디버거 스크립트를 만들고 실행하는 방법을 설명합니다. 이 샘플에서는 루프에서 명령 출력을 수집하고 표시하는 방법도 보여 집니다.

이 스크립트는 단일 함수인 RunCommands()를 제공합니다.

// WinDbg JavaScript sample
// Shows how to call a debugger command and display results
"use strict";

function RunCommands()
{
var ctl = host.namespace.Debugger.Utility.Control;   
var output = ctl.ExecuteCommand("u");
host.diagnostics.debugLog("***> Displaying command output \n");

for (var line of output)
   {
   host.diagnostics.debugLog("  ", line, "\n");
   }

host.diagnostics.debugLog("***> Exiting RunCommands Function \n");

}

메모장 같은 텍스트 편집기를 사용하여 RunCommands.js이라는 텍스트 파일을 만듭니다.

.scriptload 명령을 사용하여 RunCommands 스크립트를 로드합니다.

0:000> .scriptload c:\WinDbg\Scripts\RunCommands.js 
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\RunCommands.js'

스크립트가 로드되면 디버거에서 추가 기능을 사용할 수 있습니다. dx(NatVis 식 표시) 명령을 사용하여 Debugger.State.Scripts.RunCommands를 표시하여 스크립트가 현재 상주하고 있는지 확인합니다.

0:000>dx -r3 Debugger.State.Scripts.RunCommands
Debugger.State.Scripts.RunCommands                
    Contents         : [object Object]
        host             : [object Object]
            diagnostics      : [object Object]
            namespace       
            currentSession   : Live user mode: <Local>
            currentProcess   : notepad.exe
            currentThread    : ntdll!DbgUiRemoteBreakin (00007ffd`87f2f440) 
            memory           : [object Object]

dx 명령을 사용하여 RunCommands 스크립트에서 RunCommands 함수를 호출합니다.

0:000> dx Debugger.State.Scripts.RunCommands.Contents.RunCommands()
  ***> Displaying command output
  ntdll!ExpInterlockedPopEntrySListEnd+0x17 [d:\rs1\minkernel\ntos\rtl\amd64\slist.asm @ 196]:
  00007ffd`87f06e67 cc              int     3
  00007ffd`87f06e68 cc              int     3
  00007ffd`87f06e69 0f1f8000000000  nop     dword ptr [rax]
  ntdll!RtlpInterlockedPushEntrySList [d:\rs1\minkernel\ntos\rtl\amd64\slist.asm @ 229]:
  00007ffd`87f06e70 0f0d09          prefetchw [rcx]
  00007ffd`87f06e73 53              push    rbx
  00007ffd`87f06e74 4c8bd1          mov     r10,rcx
  00007ffd`87f06e77 488bca          mov     rcx,rdx
  00007ffd`87f06e7a 4c8bda          mov     r11,rdx
***> Exiting RunCommands Function

특수 JavaScript 디버거 함수

스크립트 공급자 자체에서 호출하는 JavaScript 스크립트에는 몇 가지 특수 함수가 있습니다.

initializeScript

JavaScript 스크립트가 로드되고 실행되면 스크립트의 변수, 함수 및 기타 개체가 디버거의 개체 모델에 영향을 미치기 전에 일련의 단계를 거닐게 됩니다.

  • 스크립트는 메모리에 로드되고 구문 분석됩니다.
  • 스크립트의 루트 코드가 실행됩니다.
  • 스크립트에 initializeScript라는 메서드가 있는 경우 해당 메서드가 호출됩니다.
  • initializeScript의 반환 값은 디버거의 개체 모델을 자동으로 수정하는 방법을 결정하는 데 사용됩니다.
  • 스크립트의 이름은 디버거의 네임스페이스에 브리지됩니다.

멘션 스크립트의 루트 코드가 실행된 직후 initializeScript가 호출됩니다. 이 작업은 등록 개체의 JavaScript 배열을 디버거의 개체 모델을 수정하는 방법을 나타내는 공급자에 반환하는 것입니다.

function initializeScript()
{
    // Add code here that you want to run every time the script is loaded. 
    // We will just send a message to indicate that function was called.
    host.diagnostics.debugLog("***> initializeScript was called\n");
}

invokeScript

invokeScript 메서드는 기본 스크립트 메서드이며 .scriptload 및 .scriptrun을 실행할 때 호출됩니다.

function invokeScript()
{
    // Add code here that you want to run every time the script is executed. 
    // We will just send a message to indicate that function was called.
    host.diagnostics.debugLog("***> invokeScript was called\n");
}

uninitializeScript

uninitializeScript 메서드는 initializeScript의 동작과 반대입니다. 스크립트가 연결 해제되고 언로드할 준비가 되면 호출됩니다. 이 작업은 스크립트가 실행하는 동안 명령적으로 수행한 개체 모델의 변경 내용을 실행 취소하거나 스크립트가 캐시한 개체를 삭제하는 것입니다.

스크립트가 개체 모델에 대한 명령적 조작을 수행하거나 결과를 캐시하지 않는 경우 uninitializeScript 메서드가 필요하지 않습니다. initializeScript의 반환 값으로 표시된 대로 수행되는 개체 모델에 대한 변경 내용은 공급자가 자동으로 취소합니다. 이러한 변경에는 명시적 uninitializeScript 메서드가 필요하지 않습니다.

function uninitializeScript()
{
    // Add code here that you want to run every time the script is unloaded. 
    // We will just send a message to indicate that function was called.
    host.diagnostics.debugLog("***> uninitialize was called\n");
}

스크립트 명령에 의해 호출되는 함수 요약

이 표에는 스크립트 명령에서 호출되는 함수가 요약되어 있습니다.

명령 .scriptload .scriptrun(스크립트 실행) .scriptunload(스크립트 언로드)
root
initializeScript
invokeScript
uninitializeScript

이 샘플 코드를 사용하여 스크립트가 로드, 실행 및 언로드될 때 각 함수가 호출되는 시기를 확인합니다.

// Root of Script
host.diagnostics.debugLog("***>; Code at the very top (root) of the script is always run \n");


function initializeScript()
{
    // Add code here that you want to run every time the script is loaded. 
    // We will just send a message to indicate that function was called.
    host.diagnostics.debugLog("***>; initializeScript was called \n");
}

function invokeScript()
{
    // Add code here that you want to run every time the script is executed. 
    // We will just send a message to indicate that function was called.
    host.diagnostics.debugLog("***>; invokeScript was called \n");
}


function uninitializeScript()
{
    // Add code here that you want to run every time the script is unloaded. 
    // We will just send a message to indicate that function was called.
    host.diagnostics.debugLog("***>; uninitialize was called\n");
}


function main()
{
    // main is just another function name in JavaScript
    // main is not called by .scriptload or .scriptrun  
    host.diagnostics.debugLog("***>; main was called \n");
}

JavaScript에서 디버거 시각화 도우미 만들기

사용자 지정 시각화 파일을 사용하면 데이터 관계 및 콘텐츠를 더 잘 반영하는 시각화 구조로 데이터를 그룹화하고 구성할 수 있습니다. JavaScript 디버거 확장을 사용하여 NatVis와 매우 유사한 방식으로 작동하는 디버거 시각화 도우미를 작성할 수 있습니다. 이 작업은 지정된 데이터 형식의 시각화 도우미 역할을 하는 JavaScript 프로토타입 개체(또는 ES6 클래스)를 작성하여 수행됩니다. NatVis 및 디버거에 대한 자세한 내용은 dx(NatVis 식 표시)를 참조하세요.

예제 클래스 - Simple1DArray

단일 차원 배열을 나타내는 C++ 클래스의 예를 생각해 보세요. 이 클래스에는 배열의 전체 크기인 m_size m_size 필드와 같은 메모리의 여러 ints에 대한 포인터인 m_pValues 두 개의 멤버가 있습니다.

class Simple1DArray
{
private:

    ULONG64 m_size;
    int *m_pValues;
};

dx 명령을 사용하여 기본 데이터 구조 렌더링을 살펴볼 수 있습니다.

0:000> dx g_array1D
g_array1D                 [Type: Simple1DArray]
    [+0x000] m_size           : 0x5 [Type: unsigned __int64]
    [+0x008] m_pValues        : 0x8be32449e0 : 0 [Type: int *]

JavaScript 시각화 도우미

이 형식을 시각화하려면 디버거가 표시할 모든 필드와 속성이 있는 프로토타입(또는 ES6) 클래스를 작성해야 합니다. 또한 initializeScript 메서드가 지정된 형식에 대한 시각화 도우미로 프로토타입을 연결하도록 JavaScript 공급자에게 지시하는 개체를 반환하도록 해야 합니다.

function initializeScript()
{
    //
    // Define a visualizer class for the object.
    //
    class myVisualizer
    {
        //
        // Create an ES6 generator function which yields back all the values in the array.
        //
        *[Symbol.iterator]()
        {
            var size = this.m_size;
            var ptr = this.m_pValues;
            for (var i = 0; i < size; ++i)
            {
                yield ptr.dereference();

                //
                // Note that the .add(1) method here is effectively doing pointer arithmetic on
                // the underlying pointer.  It is moving forward by the size of 1 object.
                //
                ptr = ptr.add(1);
            }
        }
    }

    return [new host.typeSignatureRegistration(myVisualizer, "Simple1DArray")];
}

스크립트를 arrayVisualizer.js 파일에 저장합니다.

.load(Load Extension DLL) 명령을 사용하여 JavaScript 공급자를 로드합니다.

0:000> .load C:\ScriptProviders\jsprovider.dll

.scriptload를 사용하여 배열 시각화 도우미 스크립트를 로드합니다.

0:000> .scriptload c:\WinDbg\Scripts\arrayVisualizer.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\arrayVisualizer.js'

이제 dx 명령을 사용하면 스크립트 시각화 도우미가 배열 콘텐츠의 행을 표시합니다.

0:000> dx g_array1D
g_array1D                 : [object Object] [Type: Simple1DArray]
    [<Raw View>]     [Type: Simple1DArray]
    [0x0]            : 0x0
    [0x1]            : 0x1
    [0x2]            : 0x2
    [0x3]            : 0x3
    [0x4]            : 0x4

또한 이 JavaScript 시각화는 Select와 같은 LINQ 기능을 제공합니다.

0:000> dx g_array1D.Select(n => n * 3),d
g_array1D.Select(n => n * 3),d                
    [0]              : 0
    [1]              : 3
    [2]              : 6
    [3]              : 9
    [4]              : 12

시각화에 미치는 영향

initializeScript에서 host.typeSignatureRegistration 개체의 반환을 통해 네이티브 형식에 대한 시각화 도우미로 만든 프로토타입 또는 클래스에는 JavaScript 내의 모든 속성과 메서드가 네이티브 형식에 추가됩니다. 또한 다음 의미 체계가 적용됩니다.

  • 두 개의 밑줄(__)로 시작하지 않는 모든 이름은 시각화에서 사용할 수 있습니다.

  • 표준 JavaScript 개체의 일부이거나 JavaScript 공급자가 만드는 프로토콜의 일부인 이름은 시각화에 표시되지 않습니다.

  • [Symbol.iterator]의 지원을 통해 개체를 반복할 수 있습니다.

  • 개체는 getDimensionality, getValueAt 및 선택적으로 setValueAt 등의 여러 함수로 구성된 사용자 지정 프로토콜을 지원하여 인덱싱할 수 있습니다.

네이티브 및 JavaScript 개체 브리지

JavaScript와 디버거의 개체 모델 간의 브리지는 양방향입니다. 네이티브 개체는 JavaScript로 전달될 수 있으며 JavaScript 개체는 디버거의 식 계산기에 전달될 수 있습니다. 예를 들어 스크립트에 다음 메서드를 추가하는 것이 좋습니다.

function multiplyBySeven(val)
{
    return val * 7;
}

이제 이 메서드를 위의 LINQ 쿼리 예제에서 활용할 수 있습니다. 먼저 JavaScript 시각화를 로드합니다.

0:000> .scriptload c:\WinDbg\Scripts\arrayVisualizer2.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\arrayVisualizer2.js'

0:000> dx @$myScript = Debugger.State.Scripts.arrayVisualizer2.Contents

그런 다음 아래와 같이 multiplyBySeven 함수 인라인을 사용할 수 있습니다.

0:000> dx g_array1D.Select(@$myScript.multiplyBySeven),d
g_array1D.Select(@$myScript.multiplyBySeven),d                
    [0]              : 0
    [1]              : 7
    [2]              : 14
    [3]              : 21
    [4]              : 28

JavaScript를 사용하는 조건부 중단점

중단점이 적중된 후 JavaScript를 사용하여 추가 처리를 수행할 수 있습니다. 예를 들어 스크립트를 사용하여 다른 런타임 값을 검사한 다음 자동으로 코드 실행을 계속할지 아니면 중지하고 추가 수동 디버깅을 수행할지 결정할 수 있습니다.

중단점 작업에 대한 일반적인 내용은 중단점 제어 방법을 참조 하세요.

DebugHandler.js 예제 중단점 처리 스크립트

다음은 메모장 열기 및 저장 대화 상자인 메모장을 평가하는 예제입니다. ShowOpenSaveDialog. 이 스크립트는 pszCaption 변수를 평가하여 현재 대화 상자가 "열기" 대화 상자인지 또는 "다른 이름으로 저장" 대화 상자인지 확인합니다. 열려 있는 대화 상자인 경우 코드 실행이 계속됩니다. 저장 대화 상자인 경우 코드 실행이 중지되고 디버거가 중단됩니다.

 // Use JavaScript strict mode 
"use strict";

// Define the invokeScript method to handle breakpoints

 function invokeScript()
 {
    var ctl = host.namespace.Debugger.Utility.Control;

    //Get the address of my string
    var address = host.evaluateExpression("pszCaption");

    // The open and save dialogs use the same function
    // When we hit the open dialog, continue.
    // When we hit the save dialog, break.
    if (host.memory.readWideString(address) == "Open") {
        // host.diagnostics.debugLog("We're opening, let's continue!\n");
        ctl.ExecuteCommand("gc");
    }
    else
    {
        //host.diagnostics.debugLog("We're saving, let's break!\n");
    }
  }

이 명령은 메모장에서 중단점을 설정합니다. ShowOpenSaveDialog는 중단점이 적중될 때마다 위의 스크립트를 실행합니다.

bp notepad!ShowOpenSaveDialog ".scriptrun C:\\WinDbg\\Scripts\\DebugHandler.js"

그런 다음 메모장에서 파일 > 저장 옵션을 선택하면 스크립트가 실행되고 g 명령이 전송되지 않으며 코드 실행 중단이 발생합니다.

JavaScript script successfully loaded from 'C:\WinDbg\Scripts\DebugHandler.js'
notepad!ShowOpenSaveDialog:
00007ff6`f9761884 48895c2408      mov     qword ptr [rsp+8],rbx ss:000000db`d2a9f2f0=0000021985fe2060

JavaScript 확장에서 64비트 값 작업

이 섹션에서는 JavaScript 디버거 확장에 전달된 64비트 값이 어떻게 동작하는지 설명합니다. 이 문제는 JavaScript에서 53비트를 사용하여 숫자를 저장하는 기능만 있기 때문에 발생합니다.

64비트 및 JavaScript 53비트 스토리지

JavaScript에 전달된 서수 값은 일반적으로 JavaScript 숫자로 마샬링됩니다. 이 문제는 JavaScript 숫자가 64비트 배정밀도 부동 소수점 값이라는 점입니다. 53비트를 넘는 서수는 JavaScript에 진입할 때 전체 자릿수를 잃게 됩니다. 64비트 포인터 및 플래그가 가장 높은 바이트일 수 있는 기타 64비트 서수 값에 문제가 발생합니다. 이를 처리하기 위해 JavaScript를 입력하는 64비트 네이티브 값(네이티브 코드 또는 데이터 모델)은 JavaScript 번호가 아닌 라이브러리 형식으로 입력됩니다. 이 라이브러리 형식은 숫자 정밀도를 잃지 않고 네이티브 코드로 다시 왕복합니다.

자동 변환

64비트 서수 값에 대한 라이브러리 형식은 표준 JavaScript valueOf 변환을 지원합니다. 값 변환이 필요한 수학 연산 또는 기타 구문에 개체를 사용하는 경우 자동으로 JavaScript 숫자로 변환됩니다. 정밀도 손실이 발생하는 경우(값이 53비트 이상의 서수 정밀도를 활용함) JavaScript 공급자는 예외를 throw합니다.

JavaScript에서 비트 연산자를 사용하는 경우 32비트 서수 정밀도로 추가로 제한됩니다.

이 샘플 코드는 두 숫자를 합산하고 64비트 값의 변환을 테스트하는 데 사용됩니다.

function playWith64BitValues(a64, b64)
{
    // Sum two numbers to demonstrate 64-bit behavior.
    //
    // Imagine a64==100, b64==1000
    // The below would result in sum==1100 as a JavaScript number.  No exception is thrown.  The values auto-convert.
    //
    // Imagine a64==2^56, b64=1
    // The below will **Throw an Exception**.  Conversion to numeric results in loss of precision!
    //
    var sum = a64 + b64;
    host.diagnostics.debugLog("Sum   >> ", sum, "\n");

}

function performOp64BitValues(a64, b64, op)
{
    //
    // Call a data model method passing 64-bit value.  There is no loss of precision here.  This round trips perfectly.
    // For example:
    //  0:000> dx @$myScript.playWith64BitValues(0x4444444444444444ull, 0x3333333333333333ull, (x, y) => x + y)
    //  @$myScript.playWith64BitValues(0x4444444444444444ull, 0x3333333333333333ull, (x, y) => x + y) : 0x7777777777777777
    //
    return op(a64, b64);
}

메모장 같은 텍스트 편집기를 사용하여 PlayWith64BitValues.js이라는 텍스트 파일을 만듭니다.

.scriptload 명령을 사용하여 스크립트를 로드합니다.

0:000> .scriptload c:\WinDbg\Scripts\PlayWith64BitValues.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\PlayWith64BitValues.js'

스크립트를 좀 더 편리하게 작업하려면 dx 명령을 사용하여 스크립트의 내용을 저장할 변수를 디버거에 할당합니다.

0:000> dx @$myScript = Debugger.State.Scripts.PlayWith64BitValues.Contents

dx 식 계산기를 사용하여 addTwoValues 함수를 호출합니다.

먼저 2^53 =9007199254740992(16진수 0x20000000000000)의 값을 계산합니다.

먼저 테스트하려면 (2^53) - 2를 사용하고 합계에 대한 올바른 값을 반환하는 것을 확인합니다.

0:000> dx @$myScript.playWith64BitValues(9007199254740990, 9007199254740990)
Sum   >> 18014398509481980

그런 다음 (2^53) -1 =9007199254740991 계산합니다. 변환 프로세스의 전체 자릿수가 손실됨을 나타내는 오류를 반환하므로 JavaScript 코드에서 sum 메서드와 함께 사용할 수 있는 가장 큰 값입니다.

0:000> dx @$myScript.playWith64BitValues(9007199254740990, 9007199254740991)
Error: 64 bit value loses precision on conversion to number

64비트 값을 전달하는 데이터 모델 메서드를 호출합니다. 여기서는 정밀도의 손실이 없습니다.

0:001> dx @$myScript.performOp64BitValues( 0x7FFFFFFFFFFFFFFF,  0x7FFFFFFFFFFFFFFF, (x, y) => x + y)
@$myScript.performOp64BitValues( 0x7FFFFFFFFFFFFFFF,  0x7FFFFFFFFFFFFFFF, (x, y) => x + y) : 0xfffffffffffffffe

비교

64비트 라이브러리 형식은 JavaScript 개체이며 JavaScript 번호와 같은 값 형식이 아닙니다. 비교 작업에는 몇 가지 의미가 있습니다. 일반적으로 개체의 같음(==)은 피연산자가 동일한 값이 아닌 동일한 개체를 참조함을 나타냅니다. JavaScript 공급자는 64비트 값에 대한 라이브 참조를 추적하고 수집되지 않은 64비트 값에 대해 동일한 "변경할 수 없는" 개체를 반환하여 이를 완화합니다. 즉, 비교를 위해 다음이 발생합니다.

// Comparison with 64 Bit Values

function comparisonWith64BitValues(a64, b64)
{
    //
    // No auto-conversion occurs here.  This is an *EFFECTIVE* value comparison.  This works with ordinals with above 53-bits of precision.
    //
    var areEqual = (a64 == b64);
    host.diagnostics.debugLog("areEqual   >> ", areEqual, "\n");
    var areNotEqual = (a64 != b64);
    host.diagnostics.debugLog("areNotEqual   >> ", areNotEqual, "\n");

    //
    // Auto-conversion occurs here.  This will throw if a64 does not pack into a JavaScript number with no loss of precision.
    //
    var isEqualTo42 = (a64 == 42);
    host.diagnostics.debugLog("isEqualTo42   >> ", isEqualTo42, "\n");
    var isLess = (a64 < b64);
    host.diagnostics.debugLog("isLess   >> ", isLess, "\n");

메모장 같은 텍스트 편집기를 사용하여 ComparisonWith64BitValues.js이라는 텍스트 파일을 만듭니다.

.scriptload 명령을 사용하여 스크립트를 로드합니다.

0:000> .scriptload c:\WinDbg\Scripts\ComparisonWith64BitValues.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\ComparisonWith64BitValues.js'

스크립트를 좀 더 편리하게 작업하려면 dx 명령을 사용하여 스크립트의 내용을 저장할 변수를 디버거에 할당합니다.

0:000> dx @$myScript = Debugger.State.Scripts.comparisonWith64BitValues.Contents

먼저 테스트하려면 (2^53) - 2를 사용하고 예상 값을 반환하는 것을 확인합니다.

0:001> dx @$myScript.comparisonWith64BitValues(9007199254740990, 9007199254740990)
areEqual   >> true
areNotEqual   >> false
isEqualTo42   >> false
isLess   >> false

또한 첫 번째 값으로 숫자 42를 시도하여 비교 연산자가 정상적으로 작동하는지 확인합니다.

0:001> dx @$myScript.comparisonWith64BitValues(42, 9007199254740990)
areEqual   >> false
areNotEqual   >> true
isEqualTo42   >> true
isLess   >> true

그런 다음 (2^53) -1 =9007199254740991 계산합니다. 이 값은 변환 프로세스의 정밀도가 손실됨을 나타내는 오류를 반환하므로 JavaScript 코드의 비교 연산자와 함께 사용할 수 있는 가장 큰 값입니다.

0:000> dx @$myScript.playWith64BitValues(9007199254740990, 9007199254740991)
Error: 64 bit value loses precision on conversion to number

작업에서 전체 자릿수 유지 관리

디버거 확장이 정밀도를 기본 수 있도록 64비트 라이브러리 형식 위에 수학 함수 집합이 프로젝션됩니다. 확장에 들어오는 64비트 값에 대해 53비트 이상의 정밀도가 필요한 경우 표준 연산자를 사용하는 대신 다음 메서드를 사용해야 합니다.

메서드 이름 서명 설명
asNumber .asNumber() 64비트 값을 JavaScript 숫자로 변환합니다. 정밀도 손실이 발생하면 **AN EXCEPTION IS THROW**입니다.
convertToNumber .convertToNumber() 64비트 값을 JavaScript 숫자로 변환합니다. 정밀도 손실이 발생하면 **예외가 throw되지 않음**
getLowPart .getLowPart() 64비트 값의 하위 32비트를 JavaScript 숫자로 변환합니다.
getHighPart .getHighPart() 64비트 값의 상위 32비트를 JavaScript 숫자로 변환합니다.
add .add(value) 64비트 값에 값을 추가하고 결과를 반환합니다.
subtract .subtract(value) 64비트 값에서 값을 빼고 결과를 반환합니다.
multiply .multiply(value) 64비트 값을 제공된 값으로 곱하고 결과를 반환합니다.
divide .divide(value) 64비트 값을 제공된 값으로 나누고 결과를 반환합니다.
bitwiseAnd .bitwiseAnd(value) 제공된 값을 사용하여 비트 및 64비트 값을 계산하고 결과를 반환합니다.
bitwiseOr .bitwiseOr(value) 제공된 값을 사용하여 비트 또는 64비트 값을 계산하고 결과를 반환합니다.
bitwiseXor .bitwiseXor(value) 제공된 값을 사용하여 64비트 값의 비트 xor를 계산하고 결과를 반환합니다.
bitwiseShiftLeft .bitwiseShiftLeft(value) 지정된 크기로 남은 64비트 값을 이동하고 결과를 반환합니다.
bitwiseShiftRight .bitwiseShiftRight(value) 64비트 값을 지정된 양만큼 오른쪽으로 이동하고 결과를 반환합니다.
toString .toString([radix]) 64비트 값을 기본 radix(또는 선택적으로 제공된 radix)의 표시 문자열로 변환합니다.

이 메서드도 사용할 수 있습니다.

메서드 이름 서명 설명
compareTo .compareTo(value) 64비트 값을 다른 64비트 값과 비교합니다.

JavaScript 디버깅

이 섹션에서는 디버거의 스크립트 디버깅 기능을 사용하는 방법을 설명합니다. 디버거는 .scriptdebug(JavaScript 디버그) 명령을 사용하여 JavaScript 스크립트를 디버깅하는 통합 지원을 제공합니다.

참고 항목

WinDbg에서 JavaScript 디버깅을 사용하려면 디버거를 관리istrator로 실행합니다.

이 샘플 코드를 사용하여 JavaScript 디버깅을 탐색합니다. 이 연습에서는 이름을 DebuggableSample.js C:\MyScripts 디렉터리에 저장합니다.

"use strict";

class myObj
{
    toString()
    {
        var x = undefined[42];
        host.diagnostics.debugLog("BOO!\n");
    }
}

class iterObj
{
    *[Symbol.iterator]()
    {
        throw new Error("Oopsies!");
    }
}

function foo()
{
    return new myObj();
}

function iter()
{
    return new iterObj();
}

function throwAndCatch()
{
    var outer = undefined;
    var someObj = {a : 99, b : {c : 32, d: "Hello World"} };
    var curProc = host.currentProcess;
    var curThread = host.currentThread;

    try
    {
        var x = undefined[42];
    } catch(e) 
    {
        outer = e;
    }

    host.diagnostics.debugLog("This is a fun test\n");
    host.diagnostics.debugLog("Of the script debugger\n");
    var foo = {a : 99, b : 72};
    host.diagnostics.debugLog("foo.a = ", foo.a, "\n");

    return outer;
}

function throwUnhandled()
{
    var proc = host.currentProcess;
    var thread = host.currentThread;
    host.diagnostics.debugLog("Hello...  About to throw an exception!\n");
    throw new Error("Oh me oh my!  This is an unhandled exception!\n");
    host.diagnostics.debugLog("Oh...  this will never be hit!\n");
    return proc;
}

function outer()
{
    host.diagnostics.debugLog("inside outer!\n");
    var foo = throwAndCatch();
    host.diagnostics.debugLog("Caught and returned!\n");
    return foo;
}

function outermost()
{
    var x = 99;
    var result = outer();
    var y = 32;
    host.diagnostics.debugLog("Test\n");
    return result;
}

function initializeScript()
{
    //
    // Return an array of registration objects to modify the object model of the debugger
    // See the following for more details:
    //
    //     https://aka.ms/JsDbgExt
    //
}

샘플 스크립트를 로드합니다.

.scriptload C:\MyScripts\DebuggableSample.js

.scriptdebug 명령을 사용하여 스크립트를 적극적으로 디버깅하기 시작합니다.

0:000> .scriptdebug C:\MyScripts\DebuggableSample.js
>>> ****** DEBUGGER ENTRY DebuggableSample ******
           No active debug event!

>>> Debug [DebuggableSample <No Position>] >

프롬프트 >>> Debug [DebuggableSample <No Position>] > 와 입력 요청이 표시되면 스크립트 디버거 내에 있습니다.

.help 명령을 사용하여 JavaScript 디버깅 환경에서 명령 목록을 표시합니다.

>>> Debug [DebuggableSample <No Position>] >.help
Script Debugger Commands (*NOTE* IDs are **PER SCRIPT**):
    ? .................................. Get help
    ? <expr>  .......................... Evaluate expression <expr> and display result
    ?? <expr>  ......................... Evaluate expression <expr> and display result
    |  ................................. List available scripts
    |<scriptid>s  ...................... Switch context to the given script
    bc \<bpid\>  ......................... Clear breakpoint by specified \<bpid\>
    bd \<bpid\>  ......................... Disable breakpoint by specified \<bpid\>
    be \<bpid\>  ......................... Enable breakpoint by specified \<bpid\>
    bl  ................................ List breakpoints
    bp <line>:<column>  ................ Set breakpoint at the specified line and column
    bp <function-name>  ................ Set breakpoint at the (global) function specified by the given name
    bpc  ............................... Set breakpoint at current location
    dv  ................................ Display local variables of current frame
    g  ................................. Continue script
    gu   ............................... Step out
    k  ................................. Get stack trace
    p  ................................. Step over
    q  ................................. Exit script debugger (resume execution)
    sx  ................................ Display available events/exceptions to break on
    sxe <event>  ....................... Enable break on <event>
    sxd <event>  ....................... Disable break on <event>
    t  ................................. Step in
    .attach <scriptId>  ................ Attach debugger to the script specified by <scriptId>
    .detach [<scriptId>]  .............. Detach debugger from the script specified by <scriptId>
    .frame <index>  .................... Switch to frame number <index>
    .f+  ............................... Switch to next stack frame
    .f-  ............................... Switch to previous stack frame
    .help  ............................. Get help

sx 스크립트 디버거 명령을 사용하여 트래핑할 수 있는 이벤트 목록을 확인합니다.

>>> Debug [DebuggableSample <No Position>] >sx              
sx                                                          
    ab  [   inactive] .... Break on script abort            
    eh  [   inactive] .... Break on any thrown exception    
    en  [   inactive] .... Break on entry to the script     
    uh  [     active] .... Break on unhandled exception     

sxe 스크립트 디버거 명령을 사용하여 스크립트가 스크립트 디버거로 트래핑되도록 항목 중단을 켜면 해당 내의 모든 코드가 실행되는 즉시 스크립트 디버거로 트래핑됩니다.

>>> Debug [DebuggableSample <No Position>] >sxe en          
sxe en                                                      
Event filter 'en' is now active                             

스크립트 디버거를 종료하면 디버거에 트래핑되는 스크립트에 함수 호출을 만듭니다.

>>> Debug [DebuggableSample <No Position>] >q

이 시점에서 일반 디버거로 돌아갑니다. 다음 명령을 실행하여 스크립트를 호출합니다.

dx @$scriptContents.outermost()

이제 스크립트 디버거로 돌아가서 가장 바깥쪽 JavaScript 함수의 첫 번째 줄에서 중단되었습니다.

>>> ****** SCRIPT BREAK DebuggableSample [BreakIn] ******   
           Location: line = 73, column = 5                  
           Text: var x = 99                                 

>>> Debug [DebuggableSample 73:5] >                         

디버거에 대한 중단을 확인하는 것 외에도 중단이 발생한 줄(73) 및 열(5)과 소스 코드의 관련 코드 조각( var x = 99)에 대한 정보를 얻을 수 있습니다.

몇 번 단계를 수행하고 스크립트의 다른 위치로 이동해 보겠습니다.

    p
    t
    p
    t
    p
    p

이 시점에서 34줄의 throwAndCatch 메서드로 나뉘어야 합니다.

...
>>> ****** SCRIPT BREAK DebuggableSample [Step Complete] ******                       
           Location: line = 34, column = 5                                            
           Text: var curProc = host.currentProcess                                    

스택 추적을 실행하여 이를 확인할 수 있습니다.

>>> Debug [DebuggableSample 34:5] >k                                                  
k                                                                                     
    ##  Function                         Pos    Source Snippet                        
-> [00] throwAndCatch                    034:05 (var curProc = host.currentProcess)   
   [01] outer                            066:05 (var foo = throwAndCatch())           
   [02] outermost                        074:05 (var result = outer())                

여기에서 변수 값을 조사할 수 있습니다.

>>> Debug [DebuggableSample 34:5] >??someObj                
??someObj                                                   
someObj          : {...}                                    
    __proto__        : {...}                                
    a                : 0x63                                 
    b                : {...}                                
>>> Debug [DebuggableSample 34:5] >??someObj.b              
??someObj.b                                                 
someObj.b        : {...}                                    
    __proto__        : {...}                                
    c                : 0x20                                 
    d                : Hello World                          

현재 코드 줄에 중단점을 설정하고 이제 설정된 중단점을 살펴보겠습니다.

>>> Debug [DebuggableSample 34:5] >bpc                      
bpc                                                         
Breakpoint 1 set at 34:5                                    
>>> Debug [DebuggableSample 34:5] >bl                       
bl                                                          
      Id State    Pos                                       
       1 enabled  34:5                                      

여기서는 sxd 스크립트 디버거 명령을 사용하여 entry(en) 이벤트를 사용하지 않도록 설정합니다.

>>> Debug [DebuggableSample 34:5] >sxd en                                                                              
sxd en                                                                                                                 
Event filter 'en' is now inactive                                                                                      

그런 다음 스크립트가 끝까지 계속 진행되도록 합니다.

>>> Debug [DebuggableSample 34:5] >g                                                                                   
g                                                                                                                      
This is a fun test                                                                                                     
Of the script debugger                                                                                                 
foo.a = 99                                                                                                             
Caught and returned!                                                                                                   
Test                                                                                                                   
...

스크립트 메서드를 다시 실행하고 중단점이 적중되는 것을 확인합니다.

0:000> dx @$scriptContents.outermost()                                                
inside outer!                                                                         
>>> ****** SCRIPT BREAK DebuggableSample [Breakpoint 1] ******                        
           Location: line = 34, column = 5                                            
           Text: var curProc = host.currentProcess                                    

호출 스택을 표시합니다.

>>> Debug [DebuggableSample 34:5] >k                                                  
k                                                                                     
    ##  Function                         Pos    Source Snippet                        
-> [00] throwAndCatch                    034:05 (var curProc = host.currentProcess)   
   [01] outer                            066:05 (var foo = throwAndCatch())           
   [02] outermost                        074:05 (var result = outer())                

이 시점에서 이 스크립트의 디버깅을 중지하려고 하므로 이 스크립트에서 분리합니다.

>>> Debug [DebuggableSample 34:5] >.detach                  
.detach                                                     
Debugger has been detached from script!                     

그런 다음 종료할 q를 입력합니다.

q                                                           
This is a fun test                                          
Of the script debugger                                      
foo.a = 99                                                  
Caught and returned!                                        
Test                                                        

함수를 다시 실행하면 더 이상 디버거에 침입하지 않습니다.

0:007> dx @$scriptContents.outermost()
inside outer!
This is a fun test
Of the script debugger
foo.a = 99
Caught and returned!
Test

VSCode의 JavaScript - IntelliSense 추가

VSCode에서 디버거 데이터 모델 개체를 사용하려는 경우 Windows 개발 키트에서 사용할 수 있는 정의 파일을 사용할 수 있습니다. IntelliSense 정의 파일은 모든 호스트.* 디버거 개체 API를 지원합니다. 64비트 PC의 기본 디렉터리에 키트를 설치한 경우 다음 위치에 있습니다.

C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\winext\JsProvider.d.ts

VSCode에서 IntelliSense 정의 파일을 사용하려면 다음을 수행합니다.

  1. 정의 파일 찾기 - JSProvider.d.ts

  2. 정의 파일을 스크립트와 동일한 폴더에 복사합니다.

  3. JavaScript 스크립트 파일의 맨 위에 추가 /// <reference path="JSProvider.d.ts" /> 합니다.

JavaScript 파일에서 해당 참조를 사용하면 VS Code는 스크립트의 구조 외에도 JSProvider에서 제공하는 호스트 API에 대한 IntelliSense를 자동으로 제공합니다. 예를 들어 "host"를 입력합니다. 사용 가능한 모든 디버거 모델 API에 대한 IntelliSense가 표시됩니다.

JavaScript 리소스

다음은 JavaScript 디버깅 확장을 개발할 때 유용할 수 있는 JavaScript 리소스입니다.

참고 항목

JavaScript 디버거 예제 스크립트

JavaScript 확장의 네이티브 개체