JavaScript 拡張機能のネイティブ デバッガー オブジェクト

ネイティブ デバッガー オブジェクトは、デバッガー環境のさまざまなコンストラクトと動作を表します。 オブジェクトを JavaScript 拡張機能に渡したり、取得したりして、デバッガーの状態を操作できます。

デバッガー オブジェクトの例には、次のようなものがあります。

  • セッション
  • Threads/Thread
  • Processes/Process
  • Stack Frames/Stack Frame
  • Local Variables
  • Modules/Module
  • ユーティリティ
  • 都道府県
  • 設定

たとえば、host.namespace.Debugger.Utility.Control.ExecuteCommand オブジェクトを使用すると、次の 2 行の JavaScript コードを使用して u コマンドをデバッガーに送信できます。

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

このトピックでは、共通オブジェクトの操作方法について説明し、その属性と動作に関する参考情報を提供します。

JavaScript の使用に関する一般情報については JavaScript デバッガー スクリプトを参照してください. デバッガー オブジェクトを使用する JavaScript の例については JavaScript デバッガーのサンプル スクリプト」を参照してください。 設定オブジェクトの操作については .settings (デバッグ 設定の設定)」を参照してください。

デバッガー セッションで使用できるオブジェクトを調べるには dx (Display NatVis Expression) コマンドを使用します。 たとえば、この dx コマンドを使用して、最上位レベルのデバッガー オブジェクトの一部を表示できます。

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   

上記の項目はすべてクリック可能な DML であり、さらに下に繰り返してデバッガー オブジェクト構造を表示できます。

データ モデルを使用したデバッガーの拡張

デバッガー データ モデルを使用すると、次の属性を持つ Windows のアプリケーションとドライバーに関する情報へのインターフェイスを作成できます。

  • 検出可能で整理されています。dx コマンドを使用して、論理的に構造化された名前空間を照会できます。
  • LINQ を使用してクエリを実行できます。これにより、標準のクエリ言語を使用してデータを抽出および並べ替えることができます。
  • 論理的かつ一貫した拡張が可能 - Natvis や JavaScript などのデバッガー スクリプト プロバイダーを使用して、このトピックで説明する手法を使用して拡張できます。

JavaScript でのデバッガー オブジェクトの拡張

スクリプト拡張機能は、JavaScript でビジュアライザーを作成できることに加えて、デバッガーの主要な概念 (セッション、プロセス、スレッド、スタック、スタック フレーム、ローカル変数) を変更し、他の拡張機能が使用できる拡張ポイントとして自分自身を発行することもできます。

このセクションでは、デバッガー内で主要な概念を拡張する方法について説明します。 共有用に構築された拡張機能は JavaScript 拡張機能のネイティブ デバッガー オブジェクト - 設計とテストに関する考慮事項に 記載されているガイドラインに準拠している必要があります。

拡張機能の登録

スクリプトは、initializeScript メソッドから返される配列内のエントリを通じて拡張機能を提供するという事実を登録できます。

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

返された配列内に host.namedModelParent オブジェクトが存在することは、指定されたプロトタイプ オブジェクトまたは ES6 クラス (この場合は comProcessExtension) が Debugger.Models.Process という名前で登録されているモデルの親データ モデルであることをデバッガーに示します。

デバッガー オブジェクト拡張ポイント

次のデバッガー拡張ポイントはデバッガーに不可欠であり、JavaScript などのスクリプト プロバイダーで使用できます。

Debugger.Models.Sessions: デバッガーがアタッチされているセッション (ターゲット) の一覧

Debugger.Models.Session: デバッガーがアタッチされている個々のセッション (ターゲット) (ライブ ユーザー モード、KD など)。

Debugger.Models.Processes: セッション内のプロセスの一覧

Debugger.Models.Threads: プロセス内のスレッドの一覧

Debugger.Models.Thread: プロセス内の個々のスレッド (ユーザー モードまたはカーネル モードに関係なく)

Debugger.Models.Stack: スレッドのスタック

Debugger.Models.StackFrames: スタックを構成するフレームのコレクション

Debugger.Models.StackFrame: スタック内の個々のスタック フレーム

Debugger.Models.LocalVariables: スタック フレーム内のローカル変数

Debugger.Models.Parameters: スタック フレーム内の呼び出しのパラメーター

Debugger.Models.Module: プロセスのアドレス空間内の個々のモジュール

その他のデータ モデル オブジェクト

さらに、コア データ モデルによって定義される追加のデータ モデル オブジェクトがいくつかあります。

DataModel.Models.Intrinsic: 組み込み値 (序数、浮動小数点数など)

DataModel.Models.String: 文字列

DataModel.Models.Array: ネイティブ配列

DataModel.Models.Guid: GUID

DataModel.Models.Error: エラー オブジェクト

DataModel.Models.Concepts.Iterable: iterable のすべてのオブジェクトに適用されます

DataModel.Models.Concepts.StringDisplayable: 表示文字列変換を持つすべてのオブジェクトに適用されます

COM デバッガー オブジェクト拡張機能の概要の例

例を挙げて考えてみましょう。 グローバル インターフェイス テーブル (GIT) など、COM 固有の情報を表示するデバッガー拡張機能を作成するとします。

以前は、COM に関する情報にアクセスする手段を提供する多数のコマンドを含む既存のデバッガー拡張機能が存在する可能性があります。 1 つのコマンドで、プロセス中心の情報 (たとえばグローバル インターフェイス テーブル) が表示される場合があります。 別のコマンドでは、どのようなアパートメント コード内で実行されているかなどのスレッド中心の情報が提供される場合があります。 COM の他の側面を調べるには、2 つ目のデバッガー拡張機能について知り、読み込む必要がある場合があります。

JavaScript 拡張機能では、コマンドの検出が困難なセットを用意する代わりに、プロセスとスレッドの内容に関するデバッガーの概念を変更して、この情報を自然で探索可能で、他のデバッガー拡張機能と共に構成可能な方法で追加できます。

ユーザー モードまたはカーネル モードデバッガー オブジェクト拡張機能

デバッガーとデバッガー オブジェクトは、ユーザー モードとカーネル モードで動作が異なります。 デバッガー モデル オブジェクトを作成するときは、作業する環境を決定する必要があります。 ユーザー モードで COM を操作するため、この com 拡張機能をユーザー モードで作成してテストします。 その他の状況では、ユーザー モードとカーネル モードの両方のデバッグで動作するデバッガー JavaScript を作成できる場合があります。

サブ名前空間の作成

この例に戻ると、プロセス オブジェクトに追加する一連のものを含むプロトタイプまたは ES6 クラス comProcessExtension を定義できます。

重要 : サブ名前空間の目的は、論理的に構造化された自然に探索可能なパラダイムを作成することです。 たとえば、関連のない項目を同じサブ名前空間にダンプしないようにします。 サブ名前空間を作成する前に、「 JavaScript 拡張機能のネイティブ デバッガー オブジェクト - 設計とテストに関する考慮事項 」で説明されている情報を慎重に確認してください。

このコード スニペットでは、"COM" というサブ名前空間を既存のプロセス デバッガー オブジェクトに追加します。

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

名前空間の実装

次に、プロセスにサブ名前空間 COM を実装するオブジェクトを作成します。

重要 : 複数のプロセスが存在する可能性があります (ユーザー モードまたは KD のどちらにアタッチされているかに関係なく)。 この拡張機能では、デバッガーの現在の状態がユーザーの意図したとおりであると想定することはできません。 だれかが <someProcess.COM> を変数にキャプチャして変更できるため、間違ったプロセス コンテキストから情報が表示される可能性があります。 解決策は、拡張機能にコードを追加して、各インスタンス化がアタッチされているプロセスを追跡することです。 このコード サンプルでは、この情報はプロパティの 'this' ポインターを介して渡されます。

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

COM グローバル インターフェイス テーブルの実装ロジック

COM グローバル インターフェイス テーブルの実装ロジックをより明確に分離するために、COM GIP テーブルを抽象化する 1 つの ES6 クラス gipTable と、もう 1 つの globalObjectsを定義します。 これは、上記の名前空間実装コード部分で定義された GlobalObjects() ゲッターから返されるものです。 これらの詳細はすべて initializeScript のクロージャ内で非表示にして、これらの内部の詳細をデバッガー名前空間に発行しないようにすることができます。

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

最後に、host.namedModelRegistration を使用して、新しい COM 機能を登録します。

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

メモ帳などのアプリケーションを使用してGipTableAbstractor.jsするコードを保存します。

この拡張機能を読み込む前に、ユーザー モードで使用できるプロセス情報を次に示します。

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

JavaScript 拡張機能を読み込みます。

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

次に、dx コマンドを使用して、定義済みの $@curprocess を使用してプロセスに関する情報を表示します。

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

このテーブルには、GIT Cookie を使用してプログラムからアクセスすることもできます。

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]

LINQ を使用したデバッガー オブジェクトの概念の拡張

JavaScript では、プロセスやスレッドなどのオブジェクトを拡張できるだけでなく、データ モデルに関連付けられている概念も拡張できます。 たとえば、新しい LINQ メソッドをすべての iterable に追加できます。 "DuplicateDataModel" という拡張機能の例を考えてみます。この拡張機能は、Iterable N 回のすべてのエントリを複製します。 次のコードは、これを実装する方法を示しています。

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

メモ帳などのアプリケーションを使用して、コードを DuplicateDataModel.js に保存します。

必要に応じて JavaScript スクリプト プロバイダーを読み込み、DuplicateDataModel.js拡張機能を読み込みます。

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

dx コマンドを使用して、新しい Duplicate 関数をテストします。

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

関連項目

JavaScript 拡張機能のネイティブ デバッガー オブジェクト - デバッガー オブジェクトの詳細

JavaScript拡張機能のネイティブ デバッガー オブジェクト - 設計とテストに関する考慮事項

JavaScript デバッガー スクリプト

JavaScript デバッガーのスクリプト例