Debuggerskripts mit JavaScript
In diesem Thema wird beschrieben, wie Sie JavaScript zum Erstellen von Skripts verwenden, die Debuggerobjekte verstehen und die Funktionen des Debuggers erweitern und anpassen.
Übersicht über Debuggerskripts mit JavaScript
Skriptanbieter verbinden eine Skriptsprache mit dem internen Objektmodell des Debuggers. Der Anbieter für Debuggerskripts mit JavaScript ermöglicht die Verwendung von JavaScript mit dem Debugger.
Wenn ein JavaScript mit dem Befehl .scriptload geladen wird, wird der Rootcode des Skripts ausgeführt, die im Skript vorhandenen Namen werden in den Root-Namespace des Debuggers (dx Debugger) gebrückt und das Skript bleibt im Speicher bewahrt, bis es entladen wird und alle Referenzen auf seine Objekte freigegeben werden. Das Skript kann neue Funktionen für den Ausdrucksauswerter des Debuggers bereitstellen, das Objektmodell des Debuggers ändern oder als Schnellansicht dienen, ähnlich wie eine NatVis-Schnellansicht.
In diesem Thema wird beschrieben, was Debuggerskripts mit JavaScript leisten können.
Diese beiden Themen enthalten zusätzliche Informationen zur Arbeit mit JavaScript im Debugger.
Beispiele für Debuggerskripts mit JavaScript
Native Objekte in JavaScript-Erweiterungen
Video zu Skripts mit JavaScript
Defrag Tools #170 – Andy und Bill zeigen die JavaScript-Erweiterbarkeit und die Skriptfunktionen im Debugger.
Der Debugger-JavaScript-Anbieter
Der im Debugger enthaltene JavaScript-Anbieter nutzt die neuesten ECMAScript6-Objekt- und Klassenverbesserungen. Weitere Informationen finden Sie unter ECMAScript 6 – Neue Features: Übersicht und Vergleich.
JsProvider.dll
JsProvider.dll ist der JavaScript-Anbieter, der geladen wird, um Debuggerskripts mit JavaScript zu unterstützen.
Anforderungen
Debuggerskripts mit JavaScript sind mit allen unterstützten Versionen von Windows kompatibel.
Laden des JavaScript-Skriptanbieters
Bevor Sie einen .script-Befehl verwenden können, muss ein Skriptanbieter geladen werden. Verwenden Sie den Befehl .scriptproviders, um zu bestätigen, dass der JavaScript-Anbieter geladen ist.
0:000> .scriptproviders
Available Script Providers:
NatVis (extension '.NatVis')
JavaScript (extension '.js')
Skriptmetabefehle für JavaScript
Die folgenden Befehle stehen für die Verwendung von Debuggerskripts mit JavaScript zur Verfügung.
- .scriptproviders (Liste von Skriptanbietern)
- .scriptload (Skript laden)
- .scriptunload (Skript entladen)
- .scriptrun (Skript ausführen)
- .scriptlist (Liste geladener Skripts)
Anforderungen
Bevor Sie einen der .script-Befehle verwenden können, muss ein Scripting Provider geladen werden. Verwenden Sie den Befehl .scriptproviders, um zu bestätigen, dass der JavaScript-Anbieter geladen ist.
0:000> .scriptproviders
Available Script Providers:
NatVis (extension '.NatVis')
JavaScript (extension '.js')
.scriptproviders (Liste von Skriptanbietern)
Der Befehl .scriptproviders listet alle Skriptsprachen auf, die derzeit vom Debugger verstanden werden, sowie die Erweiterung, unter der sie registriert sind.
Im folgenden Beispiel werden die Anbieter JavaScript und NatVis geladen.
0:000> .scriptproviders
Available Script Providers:
NatVis (extension '.NatVis')
JavaScript (extension '.js')
Jede Datei mit der Endung „.NatVis“ wird als NatVis-Skript und jede Datei mit der Endung „.js“ als JavaScript-Skript verstanden. Beide Skripttypen können mit dem Befehl .scriptload geladen werden.
Weitere Informationen finden Sie unter .scriptproviders (Liste von Skriptanbietern)
.scriptload (Skript laden)
Der Befehl .scriptload lädt ein Skript und führt den Rootcode eines Skripts und die Funktion initializeScript aus. Wenn beim anfänglichen Laden und Ausführen des Skripts Fehler auftreten, werden die Fehler in der Konsole angezeigt. Der folgende Befehl zeigt das erfolgreiche Laden von TestScript.js.
0:000> .scriptload C:\WinDbg\Scripts\TestScript.js
JavaScript script successfully loaded from 'C:\WinDbg\Scripts\TestScript.js'
Alle durch das Skript ausgeführten Objektmodellmanipulationen bleiben erhalten, bis das Skript später entladen oder mit anderem Inhalt erneut ausgeführt wird.
Weitere Informationen finden Sie unter .scriptload (Skript laden)
.scriptrun
Der Befehl .scriptrun lädt ein Skript, führt den Rootcode des Skripts und die Funktionen initializeScript und invokeScript aus. Wenn beim anfänglichen Laden und Ausführen des Skripts Fehler auftreten, werden die Fehler in der Konsole angezeigt.
0:000> .scriptrun C:\WinDbg\Scripts\helloWorld.js
JavaScript script successfully loaded from 'C:\WinDbg\Scripts\helloWorld.js'
Hello World! We are in JavaScript!
Alle durch das Skript ausgeführten Debugger-Objektmodellmanipulationen bleiben erhalten, bis das Skript später entladen oder mit anderem Inhalt erneut ausgeführt wird.
Weitere Informationen finden Sie unter .scriptrun (Skript ausführen).
.scriptunload (Skript entladen)
Der Befehl .scriptunload entlädt ein geladenes Skript und ruft die Funktion uninitializeScript auf. Verwenden Sie die folgende Befehlssyntax, um ein Skript zu entladen
0:000:x86> .scriptunload C:\WinDbg\Scripts\TestScript.js
JavaScript script unloaded from 'C:\WinDbg\Scripts\TestScript.js'
Weitere Informationen finden Sie unter .scriptunload (Skript entladen).
.scriptlist (Liste geladener Skripts)
Der Befehl .scriptlist listet alle Skripts auf, die mit den Befehlen .scriptload oder .scriptrun geladen wurden. Wenn das TestScript mit .scriptload erfolgreich geladen wurde, zeigt der Befehl .scriptlist den Namen des geladenen Scripts an.
0:000> .scriptlist
Command Loaded Scripts:
JavaScript script from 'C:\WinDbg\Scripts\TestScript.js'
Weitere Informationen finden Sie unter .scriptlist (Liste geladener Skripts).
Erste Schritte mit Debuggerskripts mit JavaScript
HelloWorld-Beispielskript
In diesem Abschnitt wird beschrieben, wie Sie ein einfaches Debuggerskript mit JavaScript erstellen und ausführen, das „Hallo Welt“ ausdruckt.
// WinDbg JavaScript sample
// Prints Hello World
function initializeScript()
{
host.diagnostics.debugLog("***> Hello World! \n");
}
Erstellen Sie mit einem Text-Editor wie Editor eine Textdatei namens HelloWorld.js, die den oben gezeigten JavaScript-Code enthält.
Verwenden Sie den Befehl .scriptload, um das Skript zu laden und auszuführen. Da wir den Funktionsnamen initializeScript verwendet haben, wird der Code in der Funktion ausgeführt, wenn das Skript geladen wird.
0:000> .scriptload c:\WinDbg\Scripts\HelloWorld.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\HelloWorld.js'
***> Hello World!
Nach dem Laden des Skripts ist die zusätzliche Funktionalität im Debugger verfügbar. Verwenden Sie den Befehl Dx (NatVis-Ausdruck anzeigen),, um Debugger.State.Scripts anzuzeigen und zu überprüfen, ob das Skript jetzt vorhanden ist.
0:000> dx Debugger.State.Scripts
Debugger.State.Scripts
HelloWorld
Im nächsten Beispiel fügen wir eine benannte Funktion hinzu und rufen sie auf.
Beispielskript für das Hinzufügen von zwei Werten
In diesem Abschnitt wird beschrieben, wie Sie ein einfaches Debuggerskript mit JavaScript erstellen und ausführen, das Eingaben annimmt und zwei Zahlen hinzufügt.
Dieses einfache Skript stellt eine einzelne Funktion bereit, addTwoValues.
// WinDbg JavaScript sample
// Adds two functions
function addTwoValues(a, b)
{
return a + b;
}
Erstellen Sie mit einem Text-Editor wie Editor eine Textdatei namens FirstSampleFunction.js.
Verwenden Sie den Befehl .scriptload, um das Skript zu laden.
0:000> .scriptload c:\WinDbg\Scripts\FirstSampleFunction.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\FirstSampleFunction.js'
Nach dem Laden des Skripts ist die zusätzliche Funktionalität im Debugger verfügbar. Verwenden Sie den Befehl Dx (NatVis-Ausdruck anzeigen),, um Debugger.State.Scripts anzuzeigen und zu überprüfen, ob das Skript jetzt vorhanden ist.
0:000> dx Debugger.State.Scripts
Debugger.State.Scripts
FirstSampleFunction
Wir können die FirstSampleFunction auswählen, um zu sehen, welche Funktionen sie bereitstellt.
0:000> dx -r1 -v Debugger.State.Scripts.FirstSampleFunction.Contents
Debugger.State.Scripts.FirstSampleFunction.Contents : [object Object]
host : [object Object]
addTwoValues
...
Damit die Arbeit mit dem Skript etwas bequemer ist, weisen Sie mit dem Befehl dx eine Variable im Debugger zu, die den Inhalt des Skripts enthalten soll.
0:000> dx @$myScript = Debugger.State.Scripts.FirstSampleFunction.Contents
Rufen Sie mit dem dx-Ausdrucksauswerter die Funktion addTwoValues auf.
0:000> dx @$myScript.addTwoValues(10, 41),d
@$myScript.addTwoValues(10, 41),d : 51
Sie können auch den integrierten Alias $@scriptContents verwenden, um mit den Skripts zu arbeiten. Der Alias $@scriptContents führt den gesamten .Content aller geladenen Skripts zusammen.
0:001> dx @$scriptContents.addTwoValues(10, 40),d
@$scriptContents.addTwoValues(10, 40),d : 50
Wenn Sie mit dem Skript fertig sind, verwenden Sie den Befehl .scriptunload, um das Skript zu entladen.
0:000> .scriptunload c:\WinDbg\Scripts\FirstSampleFunction.js
JavaScript script successfully unloaded from 'c:\WinDbg\Scripts\FirstSampleFunction.js'
Debugger-Befehlsautomatisierung
In diesem Abschnitt wird beschrieben, wie Sie ein einfaches Debuggerskript mit JavaScript erstellen und ausführen, um das Senden des Befehls u (Disassemblieren) zu automatisieren. Im Beispiel wird auch gezeigt, wie eine Befehlsausgabe in einer Schleife erfasst und angezeigt wird.
Dieses Skript stellt eine einzelne Funktion bereit, 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");
}
Erstellen Sie mit einem Text-Editor wie Editor eine Textdatei namens RunCommands.js.
Verwenden Sie den Befehl .scriptload, um das Skript RunCommands zu laden und auszuführen.
0:000> .scriptload c:\WinDbg\Scripts\RunCommands.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\RunCommands.js'
Nach dem Laden des Skripts ist die zusätzliche Funktionalität im Debugger verfügbar. Verwenden Sie den Befehl dx (NatVis-Ausdruck anzeigen), um Debugger.State.Scripts.RunCommands anzuzeigen und zu überprüfen, ob das Skript jetzt vorhanden ist.
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]
Rufen Sie mit dem Befehl dx die Funktion RunCommands im RunCommands-Skript auf.
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
Spezielle JavaScript-Debuggerfunktionen
Es gibt mehrere spezielle Funktionen in einem JavaScript-Skript, die vom Skriptanbieter selbst aufgerufen werden.
initializeScript
Wenn ein JavaScript-Skript geladen und ausgeführt wird, durchläuft es eine Reihe von Schritten, bevor sich die Variablen, Funktionen und weiteren Objekte im Skript auf das Objektmodell des Debuggers auswirken.
- Das Skript wird in den Arbeitsspeicher geladen und analysiert.
- Der Rootcode im Skript wird ausgeführt.
- Wenn das Skript über eine Methode mit dem Namen initializeScript verfügt, wird diese Methode aufgerufen.
- Der Rückgabewert aus initializeScript wird verwendet, um festzulegen, wie das Objektmodell des Debuggers automatisch geändert werden soll.
- Die Namen im Skript werden zum Namespace des Debuggers überbrückt.
Wie erwähnt, wird initializeScript unmittelbar nach der Ausführung des Rootcodes des Skripts aufgerufen. Der Auftrag besteht darin, ein JavaScript-Array von Registrierungsobjekten an den Anbieter zurückzugeben und anzuzeigen, wie das Objektmodell des Debuggers geändert werden soll.
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
Die Methode invokeScript ist die primäre Skriptmethode und wird aufgerufen, wenn .scriptload und .scriptrun ausgeführt werden.
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
Die Methode uninitializeScript zeigt das entgegengesetzte Verhalten von initializeScript. Sie wird aufgerufen, wenn die Verknüpfung eines Skripts aufgehoben wird und das Skript bereit zum Entladen ist. Der Auftrag besteht darin, alle Änderungen am Objektmodell rückgängig zu machen, die das Skript während der Ausführung zwingend ausgeführt hat, und/oder alle Objekte zu zerstören, die das Skript zwischengespeichert hat.
Wenn ein Skript weder imperative Manipulationen für das Objektmodell ausführt noch Ergebnisse zwischenspeichert, muss es die Methode uninitializeScript nicht verwenden. Alle Änderungen am Objektmodell, die ausgeführt wurden wie durch den Rückgabewert von initializeScript angegeben, werden automatisch vom Anbieter rückgängig gemacht. Für solche Änderungen ist keine explizite uninitializeScript-Methode erforderlich.
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");
}
Übersicht über die Funktionen, die von Skriptbefehlen aufgerufen werden
In dieser Tabelle finden Sie eine Übersicht der Funktionen, die von den Skriptbefehlen aufgerufen werden.
Get-Help | .scriptload | .scriptrun (Skript ausführen) | .scriptunload (Skript entladen) |
---|---|---|---|
root | ja | ja | |
initializeScript | ja | ja | |
invokeScript | ja | ||
uninitializeScript | ja |
Mit diesem Beispielcode können Sie feststellen, wann die einzelnen Funktionen aufgerufen werden, wenn das Skript geladen, ausgeführt und entladen wird.
// 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");
}
Erstellen einer Debuggerschnellansicht in JavaScript
Mit benutzerdefinierten Visualisierungsdateien können Sie Daten in einer Visualisierungsstruktur gruppieren und organisieren, die Datenbeziehungen und Inhalte besser widerspiegelt. Sie können die JavaScript-Debuggererweiterungen verwenden, um Debuggerschnellansichten zu schreiben, die ähnlich wie NatVis funktionieren. Dies erfolgt mittels der Erstellung eines JavaScript-Prototypobjekts (oder einer ES6-Klasse), das als Schnellansicht für einen bestimmten Datentyp dient. Weitere Informationen zu NatVis und zum Debugger finden Sie unter dx (NatVis-Ausdruck anzeigen).
Beispielklasse – Simple1DArray
Betrachten wir ein Beispiel für eine C++-Klasse, die ein eindimensionales Array darstellt. Diese Klasse besitzt zwei Mitglieder, m_size (die Gesamtgröße des Arrays) und m_pValues (ein Zeiger auf eine Anzahl von Ints im Arbeitsspeicher, die dem feld m_size entsprechen).
class Simple1DArray
{
private:
ULONG64 m_size;
int *m_pValues;
};
Wir können den Befehl dx verwenden, um das Standarddatenstruktur-Rendering zu betrachten.
0:000> dx g_array1D
g_array1D [Type: Simple1DArray]
[+0x000] m_size : 0x5 [Type: unsigned __int64]
[+0x008] m_pValues : 0x8be32449e0 : 0 [Type: int *]
JavaScript-Schnellansicht
Um diesen Typ zu visualisieren, müssen wir eine Prototypklasse (oder ES6) erstellen, die alle Felder und Eigenschaften enthält, die der Debugger anzeigen soll. Außerdem muss die Methode initializeScript ein Objekt zurückgeben, das dem JavaScript-Anbieter mitteilt, unseren Prototyp als Schnellansicht für den angegebenen Typ zu verknüpfen.
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")];
}
Speichern Sie das Skript in einer Datei namens arrayVisualizer.js.
Laden Sie mit dem Befehl .load (Erweiterungs-DLL laden) den JavaScript-Anbieter.
0:000> .load C:\ScriptProviders\jsprovider.dll
Laden Sie mit .scriptload, um das Array-Schnellansichtskript zu laden.
0:000> .scriptload c:\WinDbg\Scripts\arrayVisualizer.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\arrayVisualizer.js'
Wenn jetzt der Befehl dx verwendet wird, zeigt die Skriptschnellansicht Zeilen mit Arrayinhalten an.
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
Darüber hinaus stellt diese JavaScript-Visualisierung LINQ-Funktionen bereit, z. B. 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
Auswirkungen auf die Visualisierung
Ein Prototyp/eine Klasse, die über eine Rückgabe des Objekts host.typeSignatureRegistration aus initializeScript zur Schnellansicht für einen nativen Typ gemacht wird, besitzt alle Eigenschaften und Methoden innerhalb von JavaScript, die dem nativen Typ hinzugefügt werden. Außerdem gilt die folgende Semantik:
Jeder Name, der nicht mit zwei Unterstrichen (__) beginnt, ist in der Visualisierung verfügbar.
Namen, die Teil von JavaScript-Standardobjekten sind oder Teil von Protokollen sind, die vom JavaScript-Anbieter erstellt werden, werden in der Visualisierung nicht angezeigt.
Ein Objekt kann mittels Unterstützung von [Symbol.iterator] iterierbar gemacht werden.
Ein Objekt kann mittels Unterstützung eines angepassten Protokolls indizierbar werden, das aus mehreren Funktionen besteht: getDimensionality, getValueAt und optional setValueAt.
Native und JavaScript-Objektbrücke
Die Brücke zwischen JavaScript und dem Objektmodell des Debuggers ist bidirektional. Native Objekte können an JavaScript übergeben werden. JavaScript-Objekte können an den Ausdrucksauswerter des Debuggers übergeben werden. Betrachten Sie beispielsweise die Hinzufügung der folgenden Methode in unserem Skript:
function multiplyBySeven(val)
{
return val * 7;
}
Diese Methode kann jetzt in der oben gezeigten LINQ-Beispielabfrage verwendet werden. Zuerst wird die JavaScript-Visualisierung geladen.
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
Anschließend kann die multiplyBySeven-Funktion inline verwendet werden, wie unten gezeigt.
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
Bedingte Haltepunkte mit JavaScript
Sie können JavaScript verwenden, um eine zusätzliche Verarbeitung durchzuführen, nachdem ein Haltepunkt erreicht wurde. Beispielsweise kann ein Skript verwendet werden, um weitere Laufzeitwerte zu untersuchen und dann festzustellen, ob Sie die Codeausführung automatisch fortsetzen oder beenden möchten, um ein zusätzliches manuelles Debugging auszuführen.
Allgemeine Informationen zum Arbeiten mit Haltepunkten finden Sie unter Methoden zum Steuern von Haltepunkten.
DebugHandler.js – Beispielskript für die Haltepunktverarbeitung
In diesem Beispiel wird das geöffnete und gespeicherte Dialogfeld des Editors ausgewertet: notepad!ShowOpenSaveDialog. Dieses Skript wertet die Variable pszCaption aus, um zu ermitteln, ob das aktuelle Dialogfeld das Dialogfeld „Öffnen“ ist oder ob es sich um das Dialogfeld „Speichern unter“ handelt. Wenn es sich um das Dialogfeld „Öffnen“ handelt, wird die Codeausführung fortgesetzt. Wenn es sich um das Dialogfeld „Speichern unter“ handelt, wird die Codeausführung beendet, und der Debugger wird unterbrochen.
// 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");
}
}
Mit diesem Befehl wird ein Haltepunkt für notepad!ShowOpenSaveDialog festgelegt, und das oben gezeigte Skript wird jedes Mal ausgeführt, wenn Haltepunkt erreicht wird.
bp notepad!ShowOpenSaveDialog ".scriptrun C:\\WinDbg\\Scripts\\DebugHandler.js"
Wenn anschließend die Option „Datei > speichern“ im Editor ausgewählt wird, wird das Skript ausgeführt, der Befehl g wird nicht gesendet, und die Codeausführung wird unterbrochen.
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
Arbeiten mit 64-Bit-Werten in JavaScript-Erweiterungen
In diesem Abschnitt wird beschrieben, wie sich 64-Bit-Werte verhalten, die in eine JavaScript-Debuggererweiterung übergeben werden. Dieses Problem tritt auf, da JavaScript nur 53-Bit-Zahlen speichern kann.
64-Bit- und JavaScript-53-Bit-Speicher
Ordinalwerte, die in JavaScript übergeben werden, werden in der Regel als JavaScript-Zahlen gemarshallt. Das Problem besteht hier darin, dass JavaScript-Zahlen 64-Bit-Gleitkommawerte mit doppelter Genauigkeit sind. Alle Ordinalwerte oberhalb von 53-Bit würden die Genauigkeit verlieren, wenn sie in JavaScript eingefügt werden. Dies stellt ein Problem für 64-Bit-Zeiger und andere 64-Bit-Ordinalwerte dar, die in den höchsten Bytes Flags aufweisen können. Um dies zu bewältigen, wird jeder native 64-Bit-Wert (ob aus nativem Code oder aus dem Datenmodell) in JavaScript als Bibliothekstyp eingefügt – nicht als JavaScript-Zahl. Dieser Bibliothekstyp wird zurück zum nativen Code wechseln, ohne dass die numerische Genauigkeit verloren geht.
Automatische Konvertierung
Der Bibliothekstyp für 64-Bit-Ordinalwerte unterstützt die JavaScript-Standardkonvertierung valueOf. Wenn das Objekt in einem mathematischen Vorgang oder einem anderen Konstrukt verwendet wird, das eine Wertkonvertierung erfordert, wird es automatisch in eine JavaScript-Zahl konvertiert. Wenn ein Genauigkeitsverlust auftritt (da der Wert eine Ordinalgenauigkeit von mehr als 53-Bit verwendet), löst der JavaScript-Anbieter eine Ausnahme aus.
Beachten Sie, dass bei Verwendung bitweiser Operatoren in JavaScript die Ordinalgenauigkeit auf 32-Bit eingeschränkt ist.
Dieser Beispielcode summiert zwei Zahlen und wird verwendet, um die Konvertierung von 64-Bit-Werten zu testen.
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);
}
Erstellen Sie mit einem Text-Editor wie Editor eine Textdatei namens PlayWith64BitValues.js.
Verwenden Sie den Befehl .scriptload, um das Skript zu laden.
0:000> .scriptload c:\WinDbg\Scripts\PlayWith64BitValues.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\PlayWith64BitValues.js'
Damit die Arbeit mit dem Skript etwas bequemer ist, weisen Sie mit dem Befehl dx eine Variable im Debugger zu, die den Inhalt des Skripts enthalten soll.
0:000> dx @$myScript = Debugger.State.Scripts.PlayWith64BitValues.Contents
Rufen Sie mit dem dx-Ausdrucksauswerter die Funktion addTwoValues auf.
Zunächst berechnen wir den Wert 2^53 = 9007199254740992 (Hex 0x20000000000000).
Zunächst verwenden wir (2^53) - 2 und sehen, dass der richtige Wert für die Summe zurückgegeben wird.
0:000> dx @$myScript.playWith64BitValues(9007199254740990, 9007199254740990)
Sum >> 18014398509481980
Anschließend berechnen wir (2^53) -1 = 9007199254740991. Dies gibt den Fehler zurück, was anzeigt, dass der Konvertierungsprozess die Genauigkeit verliert. Dies ist daher der größte Wert, der mit der Summenmethode im JavaScript-Code verwendet werden kann.
0:000> dx @$myScript.playWith64BitValues(9007199254740990, 9007199254740991)
Error: 64 bit value loses precision on conversion to number
Rufen Sie eine Datenmodellmethode auf, die 64-Bit-Werte übergibt. Hier gibt es keinen Genauigkeitsverlust.
0:001> dx @$myScript.performOp64BitValues( 0x7FFFFFFFFFFFFFFF, 0x7FFFFFFFFFFFFFFF, (x, y) => x + y)
@$myScript.performOp64BitValues( 0x7FFFFFFFFFFFFFFF, 0x7FFFFFFFFFFFFFFF, (x, y) => x + y) : 0xfffffffffffffffe
Vergleich
Der 64-Bit-Bibliothekstyp ist ein JavaScript-Objekt und kein Werttyp, wie z. B. eine JavaScript-Zahl. Dies hat Auswirkungen auf Vergleichsvorgänge. Normalerweise würde die Gleichheit (==) eines Objekts darauf hinweisen, dass Operanden auf dasselbe Objekt und nicht auf denselben Wert verweisen. Der JavaScript-Anbieter reduziert dieses Problem, indem Liveverweise auf 64-Bit-Werte nachverfolgt und dasselbe „unveränderliche“ Objekt für nicht gesammelte 64-Bit-Werte zurückgegeben wird. Dies bedeutet, dass beim Vergleich Folgendes auftreten würde.
// 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");
Erstellen Sie mit einem Text-Editor wie Editor eine Textdatei namens ComparisonWith64BitValues.js.
Verwenden Sie den Befehl .scriptload, um das Skript zu laden.
0:000> .scriptload c:\WinDbg\Scripts\ComparisonWith64BitValues.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\ComparisonWith64BitValues.js'
Damit die Arbeit mit dem Skript etwas bequemer ist, weisen Sie mit dem Befehl dx eine Variable im Debugger zu, die den Inhalt des Skripts enthalten soll.
0:000> dx @$myScript = Debugger.State.Scripts.comparisonWith64BitValues.Contents
Zunächst verwenden wir (2^53) - 2 und sehen, dass dies die erwarteten Werte zurückgibt.
0:001> dx @$myScript.comparisonWith64BitValues(9007199254740990, 9007199254740990)
areEqual >> true
areNotEqual >> false
isEqualTo42 >> false
isLess >> false
Wir können auch die Zahl 42 als ersten Wert testen, um den Vergleichsoperator wie erwartet zu überprüfen.
0:001> dx @$myScript.comparisonWith64BitValues(42, 9007199254740990)
areEqual >> false
areNotEqual >> true
isEqualTo42 >> true
isLess >> true
Anschließend berechnen wir (2^53) -1 = 9007199254740991. Dieser Wert gibt den Fehler zurück, was anzeigt, dass der Konvertierungsprozess die Genauigkeit verliert. Dies ist daher der größte Wert, der mit den Vergleichsoperatoren im JavaScript-Code verwendet werden kann.
0:000> dx @$myScript.playWith64BitValues(9007199254740990, 9007199254740991)
Error: 64 bit value loses precision on conversion to number
Bewahrung der Genauigkeit in Vorgängen
Damit eine Debuggererweiterung die Standardgenauigkeit bewahren kann, wird eine Reihe mathematischer Funktionen auf den 64-Bit-Bibliothekstyp projiziert. Wenn die Erweiterung eine Genauigkeit oberhalb von 53-Bit für eingehende 64-Bit-Werte erfordert (oder möglicherweise erfordert), sollten anstelle der Standardoperatoren die folgenden Methoden verwendet werden:
Methodenname | Signature | Beschreibung |
---|---|---|
asNumber | .asNumber() | Konvertiert den 64-Bit-Wert in eine JavaScript-Zahl. Wenn ein Genauigkeitsverlust auftritt, **WIRD EINE AUSNAHME AUSGELÖST**. |
convertToNumber | .convertToNumber() | Konvertiert den 64-Bit-Wert in eine JavaScript-Zahl. Wenn ein Genauigkeitsverlust auftritt, **WIRD KEINE AUSNAHME AUSGELÖST**. |
getLowPart | .getLowPart() | Konvertiert die niedrigeren 32-Bit-Werte des 64-Bit-Werts in eine JavaScript-Zahl. |
getHighPart | .getHighPart() | Konvertiert die hohen 32-Bit-Werte des 64-Bit-Werts in eine JavaScript-Zahl. |
add | .add(value) | Fügt dem 64-Bit-Wert einen Wert hinzu und gibt das Ergebnis zurück. |
Subtrahieren | .subtract(value) | Subtrahiert einen Wert vom 64-Bit-Wert und gibt das Ergebnis zurück. |
multiply | .multiply(value) | Multipliziert den 64-Bit-Wert mit dem angegebenen Wert und gibt das Ergebnis zurück. |
divide | .divide(value) | Teilt den 64-Bit-Wert durch den angegebenen Wert und gibt das Ergebnis zurück. |
bitwiseAnd | .bitwiseAnd(value) | Berechnet das bitweise „and“ des 64-Bit-Werts mit dem angegebenen Wert und gibt das Ergebnis zurück. |
bitwiseOr | .bitwiseOr(value) | Berechnet das bitweise „or“ des 64-Bit-Werts mit dem angegebenen Wert und gibt das Ergebnis zurück. |
bitwiseXor | .bitwiseXor(value) | Berechnet das bitweise „xor“ des 64-Bit-Werts mit dem angegebenen Wert und gibt das Ergebnis zurück. |
bitwiseShiftLeft | .bitwiseShiftLeft(value) | Verschiebt den 64-Bit-Wert um den angegebenen Betrag nach links und gibt das Ergebnis zurück. |
bitwiseShiftRight | .bitwiseShiftRight(value) | Verschiebt den 64-Bit-Wert um den angegebenen Betrag nach rechts und gibt das Ergebnis zurück. |
toString | .toString([radix]) | Wandelt den 64-Bit-Wert in eine Anzeigezeichenfolge in die Standardbasis um (oder in die optional bereitgestellte Basis). |
Diese Methode ist ebenfalls verfügbar.
Methodenname | Signature | Beschreibung |
---|---|---|
compareTo | .compareTo(value) | Vergleicht den 64-Bit-Wert mit einem anderen 64-Bit-Wert. |
JavaScript-Debugging
In diesem Abschnitt wird beschrieben, wie Sie die Skriptdebuggingfunktionen des Debuggers verwenden. Der Debugger unterstützt das Debuggen von JavaScript-Skripts mithilfe des Befehls .scriptdebug (Debug JavaScript).
Hinweis
Um JavaScript-Debugging mit WinDbg zu verwenden, führen Sie den Debugger als Administrator aus.
Verwenden Sie diesen Beispielcode, um das Debuggen eines JavaScript-Skripts zu untersuchen. Für diese exemplarische Vorgehensweise benennen wir die Datei mit DebuggableSample.js und speichern sie im Verzeichnis 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
//
}
Laden Sie das Beispielskript.
.scriptload C:\MyScripts\DebuggableSample.js
Beginnen Sie mittels des Befehls .scriptdebug mit dem aktiven Debugging des Skripts.
0:000> .scriptdebug C:\MyScripts\DebuggableSample.js
>>> ****** DEBUGGER ENTRY DebuggableSample ******
No active debug event!
>>> Debug [DebuggableSample <No Position>] >
Sobald Sie die Eingabeaufforderung >>> Debug [DebuggableSample <No Position>] >
und eine Aufforderung zur Eingabe sehen, befinden Sie sich im Skript-Debugger.
Zeigen Sie mit dem Befehl .help eine Liste der Befehle in der JavaScript-Debuggingumgebung an.
>>> 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
Verwenden Sie den Skriptdebugger-Befehl sx, um die Liste der Ereignisse anzuzeigen, die abgefangen werden können.
>>> 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
Aktivieren Sie mit dem Skriptdebugger-Befehl sxe „break on entry“, damit das Skript zum Skriptdebugger wechselt, sobald im Skript Code ausgeführt wird.
>>> Debug [DebuggableSample <No Position>] >sxe en
sxe en
Event filter 'en' is now active
Beenden Sie den Skriptdebugger. Wir führen jetzt einen Funktionsaufruf in das Skript aus, was einen Trap zum Debugger ausführen wird.
>>> Debug [DebuggableSample <No Position>] >q
An diesem Punkt befinden Sie sich wieder im normalen Debugger. Führen Sie den folgenden Befehl aus, um das Skript aufzurufen.
dx @$scriptContents.outermost()
Jetzt befinden Sie sich wieder im Skriptdebugger in der ersten Zeile der äußersten JavaScript-Funktion.
>>> ****** SCRIPT BREAK DebuggableSample [BreakIn] ******
Location: line = 73, column = 5
Text: var x = 99
>>> Debug [DebuggableSample 73:5] >
Zusätzlich zur Anzeige des Wechsels in den Debugger erhalten Sie Informationen zu Zeile (73) und Spalte (5), wo der Wechsel stattgefunden hat, sowie zum relevanten Codeausschnitt des Quellcodes: var x = 99.
Lassen Sie uns einige Schritte zu einer anderen Stelle im Skript gehen.
p
t
p
t
p
p
An diesem Punkt sollten Sie sich an der Methode throwAndCatch in Zeile 34 befinden.
...
>>> ****** SCRIPT BREAK DebuggableSample [Step Complete] ******
Location: line = 34, column = 5
Text: var curProc = host.currentProcess
Sie können dies überprüfen, indem Sie eine Stapelüberwachung ausführen.
>>> 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())
Von hier aus können Sie den Wert von Variablen untersuchen.
>>> 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
Lassen Sie uns einen Haltepunkt in der aktuellen Codezeile festlegen und sehen, welche Haltepunkte jetzt festgelegt sind.
>>> 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
Von hier aus deaktivieren wir das Eintragsereignis (en) mithilfe des Skriptdebugger-Befehls sxd.
>>> Debug [DebuggableSample 34:5] >sxd en
sxd en
Event filter 'en' is now inactive
Jetzt lassen wir das Skript bis zum Ende laufen.
>>> Debug [DebuggableSample 34:5] >g
g
This is a fun test
Of the script debugger
foo.a = 99
Caught and returned!
Test
...
Führen Sie die Skriptmethode erneut aus, und beobachten Sie, wie der Haltepunkt erreicht wird.
0:000> dx @$scriptContents.outermost()
inside outer!
>>> ****** SCRIPT BREAK DebuggableSample [Breakpoint 1] ******
Location: line = 34, column = 5
Text: var curProc = host.currentProcess
Zeigen Sie die Aufrufliste an.
>>> 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())
An diesem Punkt möchten wir das Debuggen dieses Skripts beenden. Daher trennen wir es.
>>> Debug [DebuggableSample 34:5] >.detach
.detach
Debugger has been detached from script!
Geben Sie anschließend q ein, um dies zu beenden.
q
This is a fun test
Of the script debugger
foo.a = 99
Caught and returned!
Test
Wenn die Funktion erneut ausgeführt wird, findet kein Wechsel zum Debugger mehr statt.
0:007> dx @$scriptContents.outermost()
inside outer!
This is a fun test
Of the script debugger
foo.a = 99
Caught and returned!
Test
JavaScript in VSCode – Hinzufügen von IntelliSense
Wenn Sie mit den Debugger-Datenmodellobjekten in VSCode arbeiten möchten, können Sie eine Definitionsdatei verwenden, die in den Windows-Entwicklungskits verfügbar ist. Die IntelliSense-Definitionsdatei bietet Unterstützung für alle host.*-Debuggerobjekt-APIs. Wenn Sie das Kit im Standardverzeichnis auf einem 64-Bit-PC installiert haben, befindet es sich hier:
C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\winext\JsProvider.d.ts
So verwenden Sie die IntelliSense-Definitionsdatei in VSCode:
Suchen Sie die Definitionsdatei – JSProvider.d.ts.
Kopieren Sie die Definitionsdatei in denselben Ordner, in dem sich auch Ihr Skript befindet.
Fügen Sie am Anfang der JavaScript-Skriptdatei
/// <reference path="JSProvider.d.ts" />
hinzu.
Mit diesem Verweis in Ihrer JavaScript-Datei stellt VS Code automatisch IntelliSense auf den Host-APIs bereit, die von JSProvider zusätzlich zu den Strukturen in Ihrem Skript bereitgestellt werden. Sie könnten beispielsweise „host.“ eingeben, um IntelliSense für alle verfügbaren Debuggermodell-APIs anzuzeigen.
JavaScript-Ressourcen
Im Folgenden finden Sie JavaScript-Ressourcen, die bei der Entwicklung von JavaScript-Debuggingerweiterungen nützlich sein können.