JavaScript 扩展中的本机调试器对象

本机调试器对象表示调试器环境的各种构造和行为。 对象可以传递到 (中,或者在) JavaScript 扩展中获取,以操作调试器的状态。

示例调试器对象包括以下内容。

  • 会话
  • 线程/线程
  • 流程/流程
  • 堆栈帧/堆栈帧
  • 局部变量
  • 模块/模块
  • 实用工具
  • 状态
  • 设置

例如,host.namespace.Debugger.Utility.Control.ExecuteCommand 对象可用于使用以下两行 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:应用于可迭代的每个对象

DataModel.Models.Concepts.StringDisplayable:应用于具有显示字符串转换的每个对象

示例 COM 调试器对象扩展概述

我们分析一个示例。 假设你想要创建一个调试器扩展来显示特定于 COM 的信息,例如全局接口表 (GIT) 。

过去,可能存在具有多个命令的现有调试器扩展,这些命令提供了访问有关 COM 的内容的方法。 一个命令可能会 (全局接口表(例如) )显示以进程为中心的信息。 另一个命令可能会提供以线程为中心的信息,例如在哪个单元代码中执行。 可能需要了解并加载第二个调试器扩展,以探索 COM 的其他方面。

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 全局接口表的实现逻辑,我们将定义一个 ES6 类 gipTable (用于提取 COM GIP 表)和另一个 globalObjects,后者将从上面所示的命名空间实现代码截图中定义的 GlobalObjects () getter 中返回。 所有这些详细信息都可以隐藏在 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 方法添加到每个可迭代方法。 考虑一个示例扩展“DuplicateDataModel”,它将可迭代 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 调试器示例脚本