Freigeben über


Native Debuggerobjekte in JavaScript-Erweiterungen

Native Debuggerobjekte stellen verschiedene Konstrukte und Verhaltensweisen der Debuggerumgebung dar. Die Objekte können an JavaScript-Erweiterungen übergeben (oder in erworben werden), um den Zustand des Debuggers zu ändern.

Beispiele für Debuggerobjekte sind:

  • Sitzung
  • Threads/Thread
  • Prozesse/Prozess
  • Stapelrahmen/Stapelrahmen
  • Lokale Variablen
  • Module / Modul
  • Hilfsprogramm
  • State
  • Einstellungen

Beispielsweise kann das host.namespace.Debugger.Utility.Control.ExecuteCommand-Objekt verwendet werden, um den Befehl u mit den folgenden zwei Zeilen JavaScript-Code an den Debugger zu senden.

var ctl = host.namespace.Debugger.Utility.Control;   
var outputLines = ctl.ExecuteCommand("u");

In diesem Thema wird beschrieben, wie Sie mit allgemeinen Objekten arbeiten, und es werden Referenzinformationen zu ihren Attributen und Verhaltensweisen bereitgestellt.

Allgemeine Informationen zum Arbeiten mit JavaScript finden Sie unter JavaScript-Debuggerskripting. JavaScript-Beispiele, die die Debuggerobjekte verwenden, finden Sie unter Beispielskripts für JavaScript-Debugger. Informationen zum Arbeiten mit den Einstellungsobjekten finden Sie unter .settings (Festlegen von Debugeinstellungen).

Um die in einer Debuggersitzung verfügbaren Objekte zu untersuchen, verwenden Sie den Befehl dx (NatVis-Ausdruck anzeigen). Mit diesem dx-Befehl können Sie beispielsweise einige der Debuggerobjekte der obersten Ebene anzeigen.

0: kd> dx -r2 Debugger
Debugger                
    Sessions         : [object Object]
        [0x0]            : Remote KD: KdSrv:Server=@{<Local>},Trans=@{NET:Port=50000,Key=1.2.3.4,Target}
    Settings        
        Debug           
        Display         
        EngineInitialization
        Extensions      
        Input           
        Sources         
        Symbols         
        AutoSaveSettings : false
    State           
        DebuggerVariables
        PseudoRegisters 
        Scripts         
        UserVariables   
    Utility         
        Collections     
        Control         
        Objects   

Alle oben aufgeführten Elemente sind anklickbare DML und können weiter unten rekursiert werden, um die Debuggerobjektstruktur anzuzeigen.

Erweitern des Debuggers über das Datenmodell

Das Debuggerdatenmodell ermöglicht das Erstellen einer Schnittstelle mit Informationen zu Anwendungen und Treibern in Windows, die die folgenden Attribute aufweist.

  • Ist auffindbar und organisiert – ein logisch strukturierter Namensraum kann mit dem Dx-Befehl abgefragt werden.
  • Kann mit LINQ abgefragt werden: Dies ermöglicht die Extraktion und Sortierung von Daten mithilfe einer Standardabfragesprache.
  • Kann logisch und konsistent erweitert werden: Erweiterbar mithilfe von Techniken, die in diesem Thema mit Debuggerskriptanbietern wie Natvis und JavaScript beschrieben werden.

Erweitern eines Debuggerobjekts in JavaScript

Neben der Möglichkeit, eine Schnellansicht in JavaScript zu erstellen, können Skripterweiterungen auch die Kernkonzepte des Debuggers – Sitzungen, Prozesse, Threads, Stapel, Stapelrahmen, lokale Variablen – ändern und sogar selbst als Erweiterungspunkte veröffentlichen, die andere Erweiterungen nutzen können.

In diesem Abschnitt wird beschrieben, wie Sie ein Kernkonzept innerhalb des Debuggers erweitern. Erweiterungen, die für die Freigabe erstellt werden, sollten den Richtlinien entsprechen, die unter Native Debugger Objects in JavaScript Extensions – Design and Testing Considerations (Native Debugger-Objekte in JavaScript-Erweiterungen – Überlegungen zum Entwerfen und Testen) beschrieben sind.

Registrieren einer Erweiterung

Ein Skript kann registrieren, dass es eine Erweiterung über einen Eintrag im Array bereitstellt, der von der initializeScript-Methode zurückgegeben wird.

function initializeScript()
{
    return [new host.namedModelParent(comProcessExtension, "Debugger.Models.Process")];
}

Das Vorhandensein eines host.namedModelParent-Objekts innerhalb des zurückgegebenen Arrays gibt dem Debugger an, dass ein bestimmtes Prototypobjekt oder eine BESTIMMTE ES6-Klasse (in diesem Fall comProcessExtension) ein übergeordnetes Datenmodell für das Modell ist, das unter dem Namen Debugger.Models.Process registriert ist.

Debuggerobjekterweiterungspunkte

Die folgenden Debuggererweiterungspunkte sind integraler Bestandteil des Debuggers und stehen für die Verwendung von Skriptanbietern wie JavaScript zur Verfügung.

Debugger.Models.Sessions: Die Liste der Sitzungen (Ziele), an die der Debugger angefügt ist

Debugger.Models.Session: Eine einzelne Sitzung (Ziel), an die der Debugger angefügt ist (Livebenutzermodus, KD usw.)

Debugger.Models.Processes: Die Liste der Prozesse innerhalb einer Sitzung

Debugger.Models.Threads: Die Liste der Threads innerhalb eines Prozesses

Debugger.Models.Thread: Ein einzelner Thread innerhalb eines Prozesses (unabhängig davon, ob Benutzer- oder Kernelmodus)

Debugger.Models.Stack: Der Stapel eines Threads

Debugger.Models.StackFrames: Die Auflistung von Frames, aus denen ein Stapel besteht

Debugger.Models.StackFrame: Ein einzelner Stapelrahmen innerhalb eines Stapels

Debugger.Models.LocalVariables: Die lokalen Variablen innerhalb eines Stapelrahmens

Debugger.Models.Parameters: Die Parameter für einen Aufruf innerhalb eines Stapelrahmens

Debugger.Models.Module: Ein einzelnes Modul innerhalb des Adressraums eines Prozesses

Zusätzliche Datenmodellobjekte

Darüber hinaus gibt es einige zusätzliche Datenmodellobjekte, die vom Kerndatenmodell definiert werden.

DataModel.Models.Intrinsic: Ein systeminterner Wert (Ordnungszahl, Floats usw.)

DataModel.Models.String: Eine Zeichenfolge

DataModel.Models.Array: Ein natives Array

DataModel.Models.Guid: Eine GUID

DataModel.Models.Error: Ein Fehlerobjekt

DataModel.Models.Concepts.Iterable: Angewendet auf jedes Objekt, das iterierbar ist

DataModel.Models.Concepts.StringDisplayable: Auf jedes Objekt angewendet, das über eine Anzeigezeichenfolgenkonvertierung verfügt

Übersicht über die Com Debugger-Beispielobjekterweiterung

Wir sehen uns hierzu ein Beispiel an. Stellen Sie sich vor, Sie möchten eine Debuggererweiterung erstellen, um COM-spezifische Informationen anzuzeigen, z. B. die globale Schnittstellentabelle (GIT).

In der Vergangenheit gab es möglicherweise eine Debuggererweiterung mit einer Reihe von Befehlen, die einen Zugriff auf Dinge über COM bieten. Ein Befehl kann prozessorientierte Informationen anzeigen (die globale Schnittstellentabelle für instance). Ein anderer Befehl kann threadorientierte Informationen bereitstellen, z. B. darüber, in welchem Apartmentcode ausgeführt wird. Möglicherweise müssen Sie eine zweite Debuggererweiterung kennen und laden, um andere Aspekte von COM zu erkunden.

Anstatt über einen Satz schwer zu ermittelnden Befehlen zu verfügen, kann eine JavaScript-Erweiterung das Konzept des Debuggers für einen Prozess und einen Thread ändern, um diese Informationen auf natürliche Weise hinzuzufügen, die mit anderen Debuggererweiterungen zusammensetzbar ist.

Debuggerobjekterweiterung für Benutzer oder Kernelmodus

Der Debugger und die Debuggerobjekte verhalten sich im Benutzer- und Kernelmodus unterschiedlich. Wenn Sie Ihre Debuggermodellobjekte erstellen, müssen Sie entscheiden, in welchen Umgebungen Sie arbeiten. Da wir mit COM im Benutzermodus arbeiten, erstellen und testen wir diese com-Erweiterung im Benutzermodus. In anderen Situationen können Sie möglicherweise einen Debugger javaScript erstellen, der sowohl im Benutzer- als auch im Kernelmodus debuggen kann.

Erstellen eines Unternamespaces

Um zu unserem Beispiel zurückzukehren, können wir einen Prototyp oder eine ES6-Klasse definieren, comProcessExtension , die den Satz von Dingen enthält, die wir einem Prozessobjekt hinzufügen möchten.

Wichtig Die Absicht mit dem Unternamespace besteht darin, ein logisch strukturiertes und natürlich erklärbares Paradigma zu erstellen. Vermeiden Sie beispielsweise das Dumping nicht verwandter Elemente in denselben Unternamespace. Überprüfen Sie sorgfältig die Informationen unter Native Debuggerobjekte in JavaScript-Erweiterungen – Überlegungen zum Entwerfen und Testen , bevor Sie einen Unternamespace erstellen.

In diesem Codeausschnitt wird erstellt, um dem vorhandenen Prozessdebuggerobjekt einen Unternamespace namens "COM" hinzuzufügen.

var comProcessExtension =
{
    //
    // Add a sub-namespace called 'COM' on process.
    //
    get COM()
    {
        //
        // What is 'this' below...?  It's the debugger's process object.  Yes -- this means that there is a cross-language
        // object hierarchy here.  A C++ object implemented in the debugger has a parent model (prototype) which is
        // implemented in JavaScript.
        //
        return new comNamespace(this);
    }
}

Namespaceimplementierung

Erstellen Sie als Nächstes das -Objekt, das den Unternamespace COM für einen Prozess implementiert.

Wichtig Es können mehrere Prozesse vorhanden sein (unabhängig davon, ob sie im Benutzermodus oder unter KD angefügt sind). Diese Erweiterung kann nicht davon ausgehen, dass der aktuelle Zustand des Debuggers dem entspricht, was der Benutzer beabsichtigt hat. Jemand kann someProcess.COM> in einer Variablen erfassen <und ändern, was dazu führen kann, dass Informationen aus dem falschen Prozesskontext angezeigt werden. Die Lösung besteht darin, der Erweiterung Code hinzuzufügen, damit jede Instanziierung nachverfolgt, an welchen Prozess sie angefügt ist. Für dieses Codebeispiel werden diese Informationen über den Zeiger "this" der -Eigenschaft übergeben.

this.__process = process;

class comNamespace
{
    constructor(process)
    {
        //
        // This is an entirely JavaScript object.  Each instantiation of a comNamespace will keep track
        // of what process it is attached to (passed via the ''this'' pointer of the property getter
        // we authored above.
        //
        this.__process = process;
    }
    
    get GlobalObjects()
    {
        return new globalObjects(this.__process);
    }
}

Implementierungslogik für die globale COM-Schnittstellentabelle

Um die Implementierungslogik für die globale COM-Schnittstellentabelle klarer zu trennen, definieren wir eine ES6-Klasse, gipTable , die die COM GIP-Tabelle abstrahiert, und eine andere, globalObjects, die vom GlobalObjects()-Getter zurückgegeben wird, der im oben gezeigten Namespaceimplementierungscode-Snip definiert ist. Alle diese Details können innerhalb des Schließens von initializeScript ausgeblendet werden, um zu vermeiden, dass diese internen Details im Debuggernamespace veröffentlicht werden.

// gipTable:
//
// Internal class which abstracts away the GIP Table.  It iterates objects of the form
// {entry : GIPEntry, cookie : GIT cookie}
//
class gipTable
{
    constructor(gipProcess)
    {
        //
        // Windows 8 through certain builds of Windows 10, it's in CGIPTable::_palloc.  In certain builds
        // of Windows 10 and later, this has been moved to GIPEntry::_palloc.  We need to check which.
        //
        var gipAllocator = undefined;
        try
        {
            gipAllocator = host.getModuleSymbol("combase.dll", "CGIPTable::_palloc", "CPageAllocator", gipProcess)._pgalloc;
        }
        catch(err)
        {
        }

        if (gipAllocator == undefined)
        {
            gipAllocator = host.getModuleSymbol("combase.dll", "GIPEntry::_palloc", "CPageAllocator", gipProcess)._pgalloc;
        }

        this.__data = {
            process : gipProcess,
            allocator : gipAllocator,
            pageList : gipAllocator._pPageListStart,
            pageCount : gipAllocator._cPages,
            entriesPerPage : gipAllocator._cEntriesPerPage,
            bytesPerEntry : gipAllocator._cbPerEntry,
            PAGESHIFT : 16,
            PAGEMASK : 0x0000FFFF,
            SEQNOMASK : 0xFF00
        };
    }

    *[Symbol.iterator]()
    {
        for (var pageNum = 0; pageNum < this.__data.pageCount; ++pageNum)
        {
            var page = this.__data.pageList[pageNum];
            for (var entryNum = 0; entryNum < this.__data.entriesPerPage; ++entryNum)
            {
                var entryAddress = page.address.add(this.__data.bytesPerEntry * entryNum);
                var gipEntry = host.createPointerObject(entryAddress, "combase.dll", "GIPEntry *", this.__data.process);
                if (gipEntry.cUsage != -1 && gipEntry.dwType != 0)
                {
                    yield {entry : gipEntry, cookie : (gipEntry.dwSeqNo | (pageNum << this.__data.PAGESHIFT) | entryNum)};
                }
            }
        }
    }

    entryFromCookie(cookie)
    {
        var sequenceNo = (cookie & this.__data.SEQNOMASK);
        cookie = cookie & ~sequenceNo;
        var pageNum = (cookie >> this.__data.PAGESHIFT);
        if (pageNum < this.__data.pageCount)
        {
            var page = this.__data.pageList[pageNum];
            var entryNum = (cookie & this.__data.PAGEMASK);
            if (entryNum < this.__data.entriesPerPage)
            {
                var entryAddress = page.address.add(this.__data.bytesPerEntry * entryNum);
                var gipEntry = host.createPointerObject(entryAddress, "combase.dll", "GIPEntry *", this.__data.process);
                if (gipEntry.cUsage != -1 && gipEntry.dwType != 0 && gipEntry.dwSeqNo == sequenceNo)
                {
                    return {entry : gipEntry, cookie : (gipEntry.dwSeqNo | (pageNum << this.__data.PAGESHIFT) | entryNum)};
                }
            }
        }

        //
        // If this exception flows back to C/C++, it will be a failed HRESULT (according to the type of error -- here E_BOUNDS)
        // with the message being encapsulated by an error object.
        //
        throw new RangeError("Unable to find specified value");
    }
}
// globalObjects:
//
// The class which presents how we want the GIP table to look to the data model.  It iterates the actual objects
// in the GIP table indexed by their cookie.
//
class globalObjects
{
    constructor(process)
    {
        this.__gipTable = new gipTable(process);
    }

    *[Symbol.iterator]()
    {
        for (var gipCombo of this.__gipTable)
        {
            yield new host.indexedValue(gipCombo.entry.pUnk, [gipCombo.cookie]);
        }
    }

    getDimensionality()
    {
        return 1;
    }

    getValueAt(cookie)
    {
        return this.__gipTable.entryFromCookie(cookie).entry.pUnk;
    }
}

Verwenden Sie zuletzt host.namedModelRegistration, um die neue COM-Funktionalität zu registrieren.

function initializeScript()
{
    return [new host.namedModelParent(comProcessExtension, "Debugger.Models.Process"),
            new host.namedModelRegistration(comNamespace, "Debugger.Models.ComProcess")];
}

Speichern Sie den Code mithilfe einer Anwendung wie Editor in GipTableAbstractor.js.

Hier sind die Prozessinformationen, die im Benutzermodus verfügbar sind, bevor diese Erweiterung geladen wird.

0:000:x86> dx @$curprocess
@$curprocess                 : DataBinding.exe
    Name             : DataBinding.exe
    Id               : 0x1b9c
    Threads         
    Modules  

Laden Sie die JavaScript-Erweiterung.

0:000:x86> .scriptload C:\JSExtensions\GipTableAbstractor.js
JavaScript script successfully loaded from 'C:\JSExtensions\GipTableAbstractor.js'

Verwenden Sie dann den dx-Befehl, um Informationen zum Prozess mithilfe des vordefinierten @$curprocess anzuzeigen.

0:000:x86> dx @$curprocess
@$curprocess                 : DataBinding.exe
    Name             : DataBinding.exe
    Id               : 0x1b9c
    Threads         
    Modules         
    COM              : [object Object]
0:000:x86> dx @$curprocess.COM
@$curprocess.COM                 : [object Object]
    GlobalObjects    : [object Object]
0:000:x86> dx @$curprocess.COM.GlobalObjects
@$curprocess.COM.GlobalObjects                 : [object Object]
    [0x100]          : 0x12f4fb0 [Type: IUnknown *]
    [0x201]          : 0x37cfc50 [Type: IUnknown *]
    [0x302]          : 0x37ea910 [Type: IUnknown *]
    [0x403]          : 0x37fcfe0 [Type: IUnknown *]
    [0x504]          : 0x12fe1d0 [Type: IUnknown *]
    [0x605]          : 0x59f04e8 [Type: IUnknown *]
    [0x706]          : 0x59f0eb8 [Type: IUnknown *]
    [0x807]          : 0x59f5550 [Type: IUnknown *]
    [0x908]          : 0x12fe340 [Type: IUnknown *]
    [0xa09]          : 0x5afcb58 [Type: IUnknown *]

Auf diese Tabelle kann auch programmgesteuert über das GIT-Cookie zugegriffen werden.

0:000:x86> dx @$curprocess.COM.GlobalObjects[0xa09]
@$curprocess.COM.GlobalObjects[0xa09]                 : 0x5afcb58 [Type: IUnknown *]
    [+0x00c] __abi_reference_count [Type: __abi_FTMWeakRefData]
    [+0x014] __capture        [Type: Platform::Details::__abi_CapturePtr]

Erweitern von Debuggerobjektkonzepten mit LINQ

Neben der Möglichkeit, Objekte wie Prozess und Thread zu erweitern, kann JavaScript auch Konzepte erweitern, die dem Datenmodell zugeordnet sind. Beispielsweise ist es möglich, jedem Iterablen eine neue LINQ-Methode hinzuzufügen. Betrachten Sie die Beispielerweiterung "DuplicateDataModel", die jeden Eintrag in einem iterierbaren N-Mal dupliziert. Der folgende Code zeigt, wie dies implementiert werden kann.

function initializeScript()
{
    var newLinqMethod =
    {
        Duplicate : function *(n)
        {
            for (var val of this)
            {
                for (var i = 0; i < n; ++i)
                {
                    yield val;
                }
            };
        }
    };

    return [new host.namedModelParent(newLinqMethod, "DataModel.Models.Concepts.Iterable")];
}

Speichern Sie den Code mithilfe einer Anwendung wie Editor in DuplicateDataModel.js.

Laden Sie bei Bedarf den JavaScript-Skriptanbieter, und laden Sie dann die DuplicateDataModel.js-Erweiterung.

0:000:x86> !load jsprovider.dll
0:000:x86> .scriptload C:\JSExtensions\DuplicateDataModel.js
JavaScript script successfully loaded from 'C:\JSExtensions\DuplicateDataModel.js'

Verwenden Sie den dx-Befehl, um die neue Duplikatfunktion zu testen.

0: kd> dx -r1 Debugger.Sessions.First().Processes.First().Threads.Duplicate(2),d
Debugger.Sessions.First().Processes.First().Threads.Duplicate(2),d                 : [object Generator]
    [0]              : nt!DbgBreakPointWithStatus (fffff800`9696ca60) 
    [1]              : nt!DbgBreakPointWithStatus (fffff800`9696ca60) 
    [2]              : intelppm!MWaitIdle+0x18 (fffff805`0e351348) 
    [3]              : intelppm!MWaitIdle+0x18 (fffff805`0e351348) 
…

Weitere Informationen

Native Debuggerobjekte in JavaScript-Erweiterungen – Debuggerobjektdetails

Native Debuggerobjekte in JavaScript-Erweiterungen – Überlegungen zum Entwerfen und Testen

Skripterstellung für JavaScript-Debugger

Beispielskripts für den JavaScript-Debugger