Udostępnij za pomocą


Skryptowanie w debugerze JavaScript

W tym temacie opisano sposób używania języka JavaScript do tworzenia skryptów, które rozumieją obiekty debugera i rozszerzają i dostosowują możliwości debugera.

Omówienie skryptów debugera Języka JavaScript

Dostawcy skryptów łączą język skryptowy z wewnętrznym modelem obiektów debugera. Dostawca skryptów debugera języka JavaScript umożliwia korzystanie z języka JavaScript z debugerem.

Po załadowaniu skryptu JavaScript za pomocą polecenia .scriptload wykonywany jest główny kod skryptu, nazwy, które znajdują się w skrypcie, są łączone w głównej przestrzeni nazw debuggera (dx Debugger), a skrypt pozostaje w pamięci, dopóki nie zostanie usunięty z pamięci i wszystkie odniesienia do jego obiektów zostaną zwolnione. Skrypt może udostępniać nowe funkcje ewaluatorowi wyrażeń debugera, modyfikować model obiektów debugera lub działać jako wizualizator w taki sam sposób, jak wizualizator NatVis.

W tym temacie opisano niektóre czynności, które można wykonać za pomocą skryptów debugera Języka JavaScript.

Te dwa tematy zawierają dodatkowe informacje na temat pracy z językiem JavaScript w debugerze.

Przykładowe skrypty debugera JavaScript

Obiekty natywne w rozszerzeniach języka JavaScript

Wideo dotyczące skryptów w języku JavaScript

Narzędzia Defrag Tools #170 — Andy i Bill demonstrują rozszerzalność języka JavaScript i możliwości skryptów w debugerze.

Dostawca debugera JavaScript

Dostawca języka JavaScript dołączony do debugera w pełni korzysta z najnowszych ulepszeń obiektów i klas ECMAScript6. Aby uzyskać więcej informacji, zobacz ECMAScript 6 — nowe funkcje: Omówienie i porównanie.

JsProvider.dll

JsProvider.dll to dostawca języka JavaScript, który jest ładowany do obsługi skryptów debugera JavaScript.

Wymagania

Skrypty debugera języka JavaScript są przeznaczone do pracy ze wszystkimi obsługiwanymi wersjami systemu Windows.

Ładowanie dostawcy skryptów JavaScript

Przed użyciem dowolnego polecenia skryptu należy załadować dostawcę skryptów. Użyj polecenia .scriptproviders, aby potwierdzić, że dostawca języka JavaScript został załadowany.

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

Polecenia Meta Skryptowania w JavaScript

Następujące polecenia są dostępne do pracy ze skryptami debugera Języka JavaScript.

Wymagania

Przed użyciem dowolnego polecenia skryptu należy załadować dostawcę skryptów. Użyj polecenia .scriptproviders, aby potwierdzić, że dostawca języka JavaScript został załadowany.

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

.scriptproviders (Lista dostawców skryptów)

Polecenie .scriptproviders wyświetli listę wszystkich języków skryptów, które są obecnie zrozumiałe dla debugera i rozszerzenia, w którym są zarejestrowane.

W poniższym przykładzie są ładowani dostawcy języków JavaScript i NatVis.

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

Dowolny plik kończący się na ". NatVis" jest rozumiany jako skrypt NatVis, a każdy plik kończący się na ".js" jest rozumiany jako skrypt JavaScript. Za pomocą polecenia .scriptload można załadować dowolny typ skryptu.

Aby uzyskać więcej informacji, zobacz .scriptproviders (List Script Providers)

.scriptload (załaduj skrypt)

Polecenie .scriptload załaduje skrypt i wykona kod główny skryptu oraz funkcję initializeScript . Jeśli w początkowym obciążeniu i wykonaniu skryptu wystąpią błędy, błędy zostaną wyświetlone w konsoli. Następujące polecenie pokazuje, że TestScript.jszostał pomyślnie załadowany.

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

Wszelkie manipulacje modelu obiektów dokonane przez skrypt będą zachowane, dopóki skrypt nie zostanie odciążony lub uruchomiony ponownie z inną zawartością.

Aby uzyskać więcej informacji, zobacz .scriptload (Załaduj skrypt)

.scriptrun

Polecenie .scriptrun załaduje skrypt, wykona kod główny skryptu, inicjowaniescript i funkcję invokeScript . Jeśli w początkowym obciążeniu i wykonaniu skryptu wystąpią błędy, błędy zostaną wyświetlone w konsoli.

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

Wszystkie manipulacje modelem obiektów debugera wykonane przez skrypt będą zachowane, dopóki skrypt nie zostanie wyładowany lub uruchomiony ponownie z inną zawartością.

Aby uzyskać więcej informacji, zobacz .scriptrun (Uruchom skrypt).

.scriptunload (zwalnianie skryptu)

Polecenie .scriptunload zwalnia załadowany skrypt i wywołuje funkcję uninitializeScript . Użyj następującej składni polecenia, aby odładować skrypt

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

Aby uzyskać więcej informacji, zobacz .scriptunload (Zwalnianie skryptu).

.scriptlist (Lista załadowanych skryptów)

Polecenie .scriptlist wyświetli listę wszystkich skryptów, które zostały załadowane za pośrednictwem pliku scriptload lub polecenia .scriptrun. Jeśli skrypt TestScript został pomyślnie załadowany przy użyciu pliku scriptload, polecenie .scriptlist wyświetli nazwę załadowanego skryptu.

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

Aby uzyskać więcej informacji, zobacz .scriptlist (Lista załadowanych skryptów).

Wprowadzenie do skryptów debugera JavaScript

Przykładowy skrypt HelloWorld

W tej sekcji opisano sposób tworzenia i wykonywania prostego skryptu debugera JavaScript, który wyświetla komunikat Hello World.

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

Użyj edytora tekstów, takiego jak Notatnik, aby utworzyć plik tekstowy o nazwie HelloWorld.js zawierający kod JavaScript pokazany powyżej.

Użyj polecenia .scriptload, aby załadować i wykonać skrypt. Ponieważ użyliśmy nazwy funkcji initializeScript, kod w funkcji jest uruchamiany po załadowaniu skryptu.

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

Po załadowaniu skryptu dodatkowe funkcje są dostępne w debugerze. Użyj polecenia dx (Wyświetl wyrażenie NatVis), aby wyświetlić polecenie Debugger.State.Scripts , aby zobaczyć, że nasz skrypt jest teraz rezydentem.

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

W następnym przykładzie dodamy i wywołamy nazwaną funkcję.

Przykładowy skrypt dodawania dwóch wartości

W tej sekcji opisano sposób tworzenia i wykonywania prostego skryptu debugera JavaScript, który dodaje dane wejściowe i dodaje dwie liczby.

Ten prosty skrypt udostępnia jedną funkcję addTwoValues.

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

Użyj edytora tekstów, takiego jak Notatnik, aby utworzyć plik tekstowy o nazwie FirstSampleFunction.js

Za pomocą polecenia .scriptload załaduj skrypt.

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

Po załadowaniu skryptu dodatkowe funkcje są dostępne w debugerze. Użyj polecenia dx (Wyświetl wyrażenie NatVis), aby wyświetlić polecenie Debugger.State.Scripts , aby zobaczyć, że nasz skrypt jest teraz rezydentem.

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

Możemy wybrać funkcję FirstSampleFunction, aby zobaczyć, jakie funkcje udostępnia.

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

Aby skrypt był nieco wygodniejszy do pracy, przypisz zmienną w debugerze do przechowywania zawartości skryptu przy użyciu polecenia dx.

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

Użyj ewaluatora wyrażeń dx, aby wywołać funkcję addTwoValues.

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

Możesz również użyć wbudowanego aliasu @$scriptContents do pracy ze skryptami. Alias @$scriptContents łączy wszystkie .Content wszystkich załadowanych skryptów.

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

Po zakończeniu pracy ze skryptem użyj polecenia .scriptunload, aby zwolnić skrypt.

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

Automatyzacja poleceń debugera

W tej sekcji opisano sposób tworzenia i wykonywania prostego skryptu debugera JavaScript, który automatyzuje wysyłanie polecenia u (Unassemble). W przykładzie pokazano również, jak zebrać i wyświetlić dane wyjściowe polecenia w pętli.

Ten skrypt udostępnia jedną funkcję 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");

}

Użyj edytora tekstów, takiego jak Notatnik, aby utworzyć plik tekstowy o nazwie RunCommands.js

Za pomocą polecenia .scriptload załaduj skrypt RunCommands.

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

Po załadowaniu skryptu dodatkowe funkcje są dostępne w debugerze. Użyj polecenia dx (Display NatVis Expression), aby wyświetlić Debugger.State.Scripts.RunCommands i zobaczyć, że nasz skrypt jest teraz rezydentem.

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]

Użyj polecenia dx, aby wywołać funkcję RunCommands w skrypcie 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

Specjalne funkcje debugera JavaScript

Istnieje kilka specjalnych funkcji w skrypcie Języka JavaScript wywoływanym przez samego dostawcę skryptów.

initializeScript

Po załadowaniu i wykonaniu skryptu języka JavaScript następuje szereg kroków, zanim zmienne, funkcje i inne obiekty w skrypcie wpływają na model obiektów debugera.

  • Skrypt jest ładowany do pamięci i analizowany.
  • Kod główny skryptu jest wykonywany.
  • Jeśli skrypt ma metodę o nazwie initializeScript, wywoływana jest ta metoda.
  • Wartość zwracana z initializeScript służy do określania sposobu automatycznego modyfikowania modelu obiektów debugera.
  • Nazwy skryptu są łączone w przestrzeni nazw debugera.

Jak wspomniano, inicjowanie skryptu będzie wywoływane natychmiast po wykonaniu kodu głównego skryptu. Jego zadaniem jest zwrócenie tablicy obiektów rejestracji JavaScript do dostawcy, wskazując jak zmodyfikować model obiektowy debugera.

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

Metoda invokeScript jest podstawową metodą skryptu i jest wywoływana, gdy są uruchamiane .scriptload i .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

Metoda uninitializeScript jest odwrotnością zachowania metody initializeScript. Jest wywoływany, gdy skrypt jest odłączany i przygotowuje się do usunięcia. Jego zadaniem jest cofnięcie wszelkich zmian w modelu obiektów, które skrypt dokonał w sposób imperatywny podczas wykonywania i/lub zniszczenia wszelkich obiektów, które skrypt buforował.

Jeśli skrypt nie wykonuje imperatywnych operacji na modelu obiektów ani nie buforuje wyników, nie musi mieć metody uninitializeScript. Wszelkie zmiany modelu obiektów wykonywane zgodnie ze zwracaną wartością metody initializeScript są automatycznie cofane przez dostawcę. Takie zmiany nie wymagają jawnej metody 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");
}

Podsumowanie funkcji wywoływanych przez polecenia skryptu

Ta tabela zawiera podsumowanie funkcji wywoływanych przez polecenia skryptu

Komenda .scriptload .scriptrun (Uruchom skrypt) .scriptunload (zwalnianie skryptu)
korzeń tak tak
inicjalizacjaSkryptu tak tak
invokeScript tak
dezainicjalizujSkrypt tak

Użyj tego przykładowego kodu, aby zobaczyć, kiedy każda funkcja jest wywoływana, gdy skrypt jest ładowany, wykonywany i zwalniany.

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

Tworzenie wizualizatora debugera w języku JavaScript

Pliki wizualizacji niestandardowych umożliwiają grupowanie i organizowanie danych w strukturze wizualizacji, która lepiej odzwierciedla relacje danych i zawartość. Za pomocą rozszerzeń debugera języka JavaScript można pisać wizualizatory debugera, które działają w sposób bardzo podobny do natVis. Jest to realizowane za pośrednictwem tworzenia prototypowego obiektu JavaScript (lub klasy ES6), który działa jako wizualizator dla danego typu danych. Aby uzyskać więcej informacji na temat natVis i debugera, zobacz dx (Display NatVis Expression).

Przykładowa klasa — Simple1DArray

Rozważmy przykład klasy C++, która reprezentuje tablicę jednowymiarową. Ta klasa ma dwóch członków: m_size, który jest ogólnym rozmiarem tablicy, oraz m_pValues, który jest wskaźnikiem na liczbę liczb całkowitych w pamięci równą polu m_size.

class Simple1DArray
{
private:

    ULONG64 m_size;
    int *m_pValues;
};

Możemy użyć polecenia dx, aby przyjrzeć się domyślnemu renderowaniu struktury danych.

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

Wizualizator języka JavaScript

Aby zwizualizować ten typ, musimy utworzyć prototyp (lub klasę ES6), która zawiera wszystkie pola i właściwości, które chcemy pokazać debugerowi. Musimy również sprawić, by metoda initializeScript zwracała obiekt, który instruuje dostawcę JavaScript, aby połączył nasz prototyp jako wizualizator dla danego typu.

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

Zapisz skrypt w pliku o nazwie arrayVisualizer.js.

Użyj polecenia .load (Load Extension DLL), aby załadować dostawcę języka JavaScript.

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

Użyj pliku scriptload, aby załadować skrypt wizualizatora tablicy.

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

Teraz, gdy polecenie dx jest używane wizualizator skryptu wyświetli wiersze zawartości tablicy.

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

Ponadto ta wizualizacja języka JavaScript udostępnia funkcje LINQ, takie jak Select.

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

Co wpływa na wizualizację

Prototyp lub klasa, która zostanie wykonana jako wizualizator dla typu natywnego poprzez zwracanie obiektu host.typeSignatureRegistration z initializeScript, będą mieć wszystkie właściwości i metody JavaScript dodane do typu natywnego. Ponadto mają zastosowanie następujące semantyka:

  • Każda nazwa, która nie zaczyna się od dwóch podkreśleń (__) będzie dostępna w wizualizacji.

  • Nazwy, które są częścią standardowych obiektów JavaScript lub są częścią protokołów tworzonych przez dostawcę języka JavaScript, nie będą wyświetlane w wizualizacji.

  • Obiekt można iterować za pomocą obsługi [Symbol.iterator].

  • Obiekt można indeksować za pomocą obsługi niestandardowego protokołu składającego się z kilku funkcji: getDimensionality, getValueAt i opcjonalnie setValueAt.

Mostek obiektów natywnych i JavaScript

Pomost między językiem JavaScript a modelem obiektowym debugera jest dwukierunkowy. Obiekty natywne można przekazać do JavaScript, a obiekty JavaScript do ewaluatora wyrażeń debugera. W tym przykładzie rozważ dodanie następującej metody w naszym skryscie:

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

Tej metody można teraz użyć w przykładowym zapytaniu LINQ powyżej. Najpierw załadujemy wizualizację języka 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

Następnie możemy użyć funkcji multiplyBySeven bezpośrednio, jak pokazano poniżej.

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

Warunkowe punkty przerwania za pomocą języka JavaScript

Możesz użyć języka JavaScript do wykonania dodatkowego przetwarzania po osiągnięciu punktu przerwania. Na przykład skrypt może służyć do sprawdzania innych wartości czasu wykonywania, a następnie określenia, czy chcesz automatycznie kontynuować wykonywanie kodu, czy zatrzymać i wykonać dodatkowe debugowanie ręczne.

Aby uzyskać ogólne informacje na temat pracy z punktami przerwania, zobacz Metody kontrolowania punktów przerwania.

DebugHandler.js przykładowy skrypt przetwarzania punktu przerwania

W tym przykładzie zostanie ocenione okno dialogowe otwierania i zapisywania Notatnika: notepad!ShowOpenSaveDialog. Ten skrypt oceni zmienną pszCaption, aby określić, czy bieżące okno dialogowe to okno dialogowe "Otwórz" lub czy jest to okno dialogowe "Zapisz jako". Jeśli jest to otwarte okno dialogowe, wykonywanie kodu będzie kontynuowane. Jeśli jest to okno dialogowe 'Zapisz jako,' wykonywanie kodu zostanie zatrzymane, a debuger wejdzie w tryb debugowania.

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

To polecenie ustawia punkt zatrzymania w module Notatnik!ShowOpenSaveDialog i uruchomi powyższy skrypt za każdym razem, gdy ten punkt zatrzymania jest osiągany.

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

Następnie po wybraniu opcji Zapisz plik > w Notatniku skrypt jest uruchamiany, polecenie g nie jest wysyłane, a następuje przerwa w wykonywaniu kodu.

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

Praca z wartościami 64-bitowymi w rozszerzeniach języka JavaScript

W tej sekcji opisano zachowanie wartości 64-bitowych przekazywanych do rozszerzenia debugera Języka JavaScript. Ten problem występuje, ponieważ JavaScript może przechowywać liczby używając 53 bitów.

64-bitowe przechowywanie i 53-bitowe przechowywanie w JavaScript

Wartości porządkowe przekazywane do JavaScript są zwykle przekształcane jako liczby JavaScript. Problem polega na tym, że liczby języka JavaScript to 64-bitowe wartości zmiennoprzecinkowe o podwójnej precyzji. Każda porządkowa liczba ponad 53-bitowa utraciłaby precyzję podczas wprowadzania do języka JavaScript. Jest to problem z 64-bitowymi wskaźnikami i innymi 64-bitowymi wartościami porządkowymi, które mogą mieć flagi w najwyższych bajtach. Aby rozwiązać ten problem, każda 64-bitowa wartość natywna (pochodząca z kodu natywnego lub modelu danych), która trafia do JavaScript, jest traktowana jako typ biblioteczny, a nie jako liczba JavaScript. Ten typ biblioteki powróci do kodu natywnego bez utraty dokładności liczbowej.

Automatyczna konwersja

Typ biblioteki dla 64-bitowych wartości porządkowych obsługuje standardową konwersję valueOf w JavaScript. Jeśli obiekt jest używany w operacji matematycznej lub innej konstrukcji, która wymaga konwersji wartości, zostanie automatycznie przekonwertowana na liczbę JavaScript. Jeśli wystąpi utrata precyzji (wartość wykorzystuje więcej niż 53-bitową precyzję porządkową), dostawca języka JavaScript zgłosi wyjątek.

Należy pamiętać, że w przypadku używania operatorów bitowych w języku JavaScript jesteś dodatkowo ograniczony do 32-bitowej precyzji liczbowej.

Ten przykładowy kod sumuje dwie liczby i będzie używany do testowania konwersji wartości 64-bitowych.

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

Użyj edytora tekstów, takiego jak Notatnik, aby utworzyć plik tekstowy o nazwie PlayWith64BitValues.js

Za pomocą polecenia .scriptload załaduj skrypt.

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

Aby skrypt był nieco wygodniejszy do pracy, przypisz zmienną w debugerze do przechowywania zawartości skryptu przy użyciu polecenia dx.

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

Użyj ewaluatora wyrażeń dx, aby wywołać funkcję addTwoValues.

Najpierw obliczymy wartość 2^53 = 9007199254740992 (szesnastkowo 0x20000000000000).

Najpierw do przetestowania użyjemy wartości (2^53) — 2 i zobaczymy, że zwraca poprawną wartość dla sumy.

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

Następnie obliczymy (2^53) -1 =9007199254740991. Zwraca komunikat o błędzie wskazujący, że proces konwersji utraci precyzję, więc jest to największą wartością, która może być używana w metodą sumowania w kodzie JavaScript.

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

Wywołaj metodę modelu danych przekazującą wartości 64-bitowe. Nie ma tutaj żadnej utraty precyzji.

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

Porównanie

Typ biblioteki 64-bitowej jest obiektem JavaScript, a nie typem wartości, takim jak liczba języka JavaScript. Ma to pewne konsekwencje dla operacji porównania. Zwykle równość (==) na obiekcie wskazuje, że operandy odwołują się do tego samego obiektu, a nie tej samej wartości. Dostawca języka JavaScript ogranicza ten problem, śledząc dynamiczne referencje do wartości 64-bitowych i zwracając ten sam "niezmienny" obiekt dla 64-bitowych wartości, które nie zostały zebrane. Oznacza to, że dla porównania wystąpią następujące elementy.

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

Użyj edytora tekstów, takiego jak Notatnik, aby utworzyć plik tekstowy o nazwie ComparisonWith64BitValues.js

Za pomocą polecenia .scriptload załaduj skrypt.

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

Aby skrypt był nieco wygodniejszy do pracy, przypisz zmienną w debugerze do przechowywania zawartości skryptu przy użyciu polecenia dx.

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

Najpierw do przetestowania użyjemy wartości (2^53) — 2 i zobaczymy, że zwraca oczekiwane wartości.

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

Spróbujemy również liczbę 42 jako pierwszą wartość, aby sprawdzić, czy operator porównania działa tak, jak powinien.

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

Następnie obliczymy (2^53) -1 =9007199254740991. Ta wartość zwraca błąd wskazujący, że proces konwersji utraci precyzję, więc jest to największa wartość, która może być używana z operatorami porównania w kodzie JavaScript.

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

Utrzymywanie precyzji w operacjach

Aby umożliwić rozszerzenie debugera w celu zachowania dokładności, zestaw funkcji matematycznych jest przewidywany na podstawie typu biblioteki 64-bitowej. Jeśli rozszerzenie wymaga (lub być może) precyzji powyżej 53-bitowych dla przychodzących wartości 64-bitowych, należy użyć następujących metod zamiast polegać na operatorach standardowych:

Nazwa metody Podpis Opis
jakoLiczba .asNumber() Konwertuje wartość 64-bitową na liczbę języka JavaScript. Jeśli wystąpi utrata dokładności, **WYJĄTEK JEST ZGŁASZANY**
convertToNumber .convertToNumber() Konwertuje wartość 64-bitową na liczbę języka JavaScript. Jeśli wystąpi utrata dokładności, **NIE ZGŁOSZONO WYJĄTKU**
pobierzDolnąCzęść .getLowPart() Konwertuje dolne 32 bity wartości 64-bitowej na liczbę w JavaScript.
uzyskajWysokąCzęść .getHighPart() Konwertuje wysokie 32 bity wartości 64-bitowej na liczbę języka JavaScript
dodawać .add(value) Dodaje wartość do wartości 64-bitowej i zwraca wynik
odejmować .subtract(wartość) Odejmuje wartość z 64-bitowej wartości i zwraca wynik
mnożyć .multiply(wartość) Mnoży wartość 64-bitową przez podaną wartość i zwraca wynik
podzielić .divide(value) Dzieli wartość 64-bitową przez podaną wartość i zwraca wynik
bitoweAnd .bitwiseAnd(wartość) Oblicza bitową i 64-bitową wartość z podaną wartością i zwraca wynik
BitwiseOr .bitwiseOr(wartość) Oblicza bitową lub 64-bitową wartość z podaną wartością i zwraca wynik
operacja bitowa XOR .bitwiseXor(wartość) Oblicza xor bitowy 64-bitowej wartości z podaną wartością i zwraca wynik
bitwiseShiftLeft .bitwiseShiftLeft(wartość) Przesuwa 64-bitową wartość w lewo o podaną liczbę i zwraca wynik
bitwiseShiftRight .bitwiseShiftRight(wartość) Przesuwa wartość 64-bitową w prawo o daną kwotę i zwraca wynik
toString .toString([radix]) Konwertuje wartość 64-bitową na ciąg znaków do wyświetlenia w domyślnym systemie liczbowym (lub w opcjonalnie podanym systemie liczbowym)

Ta metoda jest również dostępna.

Nazwa metody Podpis Opis
compareTo .compareTo(wartość) Porównuje wartość 64-bitową z inną wartością 64-bitową.

Debugowanie w języku JavaScript

W tej sekcji opisano sposób używania możliwości debugowania skryptów debugera. Debuger ma zintegrowaną obsługę debugowania skryptów JavaScript przy użyciu polecenia .scriptdebug (Debugowanie języka JavaScript).

Uwaga / Notatka

Aby użyć debugowania języka JavaScript z usługą WinDbg, uruchom debuger jako administrator.

Ten przykładowy kod służy do eksplorowania debugowania kodu JavaScript. W tym przewodniku nadamy mu nazwę DebuggableSample.js i zapiszemy ją w katalogu 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
    //
}

Załaduj przykładowy skrypt.

.scriptload C:\MyScripts\DebuggableSample.js

Rozpocznij aktywne debugowanie skryptu przy użyciu polecenia .scriptdebug .

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

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

Po wyświetleniu monitu >>> Debug [DebuggableSample <No Position>] > i żądania wprowadzenia danych wejściowych znajdujesz się wewnątrz debugera skryptu.

Użyj polecenia .help , aby wyświetlić listę poleceń w środowisku debugowania Języka 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

Użyj polecenia debugera skryptu sx, aby wyświetlić listę zdarzeń, które możemy przechwytywać.

>>> 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     

Użyj polecenia debugera skryptu sxe, aby włączyć przełamanie przy wejściu, tak aby skrypt wszedł do debugera skryptu, gdy tylko jakikolwiek kod w nim zostanie wykonany.

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

Zamknij debuger skryptu i wykonamy wywołanie funkcji w skrypcie, które zostanie wychwycone w debugerze.

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

W tym momencie wracasz do normalnego debugera. Wykonaj następujące polecenie, aby wywołać skrypt.

dx @$scriptContents.outermost()

Teraz wróciłeś do debugera skryptu i zatrzymałeś się na pierwszej linii najbardziej zewnętrznej funkcji w JavaScripcie.

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

>>> Debug [DebuggableSample 73:5] >                         

Oprócz możliwości zobaczenia przełamania w debuggerze, uzyskujesz informacje na wierszu (73) i w kolumnie (5), w których nastąpiło przerwanie, a także odpowiedni fragment kodu źródłowego: var x = 99.

Zróbmy kilka kroków i przejdźmy do innego miejsca w skrypcie.

    p
    t
    p
    t
    p
    p

W tym momencie powinieneś znaleźć się w metodzie throwAndCatch na wierszu 34.

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

Możesz to sprawdzić, wykonując ślad stosu.

>>> 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())                

W tym miejscu możesz zbadać wartość zmiennych.

>>> 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                          

Ustawmy punkt przerwania w bieżącym wierszu kodu i zobaczmy, jakie punkty przerwania są teraz ustawione.

>>> 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                                      

Teraz wyłączymy zdarzenie wejściowe (en) przy użyciu polecenia debugera skryptu sxd.

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

A potem po prostu przejdź i pozwól skryptowi przejść do końca.

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

Ponownie uruchom skrypt i zaobserwuj, jak nasz punkt przerwania zostaje osiągnięty.

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

Wyświetl stos wywołań.

>>> 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())                

W tym momencie chcemy zatrzymać debugowanie tego skryptu, więc odłączymy go od niego.

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

Następnie wpisz q, aby zakończyć.

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

Ponowne wykonanie funkcji nie spowoduje włamania się do debugera.

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

JavaScript w programie VS Code — dodawanie funkcji IntelliSense

Jeśli chcesz pracować z obiektami modelu danych debugera w programie VS Code, możesz użyć pliku definicji dostępnego w zestawach deweloperskich systemu Windows. pl-PL: Plik definicji IntelliSense zapewnia obsługę wszystkich interfejsów API obiektów debugera host.*. Jeśli zestaw został zainstalowany w katalogu domyślnym na komputerze 64-bitowym, znajduje się tutaj:

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

Aby użyć pliku definicji funkcji IntelliSense w programie VS Code:

  1. Lokalizowanie pliku definicji — JSProvider.d.ts

  2. Skopiuj plik definicji do tego samego folderu co skrypt.

  3. Dodaj /// <reference path="JSProvider.d.ts" /> do góry pliku skryptu JavaScript.

Wraz z tym odwołaniem w pliku JavaScript program VS Code automatycznie udostępni funkcję IntelliSense w interfejsach API hosta udostępnianych przez jsProvider oprócz struktur w skrypcie. Na przykład wpisz "host". Będziesz mógł zobaczyć funkcję IntelliSense dla wszystkich dostępnych API modelu debugowania.

Zasoby języka JavaScript

Poniżej przedstawiono zasoby języka JavaScript, które mogą być przydatne podczas tworzenia rozszerzeń debugowania języka JavaScript.

Zobacz również

Przykładowe skrypty debugera JavaScript

Obiekty natywne w rozszerzeniach języka JavaScript