Partilhar via


Objetos de depurador nativos em extensões JavaScript

Os objetos nativos do depurador representam várias construções e comportamentos do ambiente do depurador. Os objetos podem ser passados para (ou adquiridos em) extensões JavaScript para manipular o estado do depurador.

Exemplos de objetos do depurador são os seguintes.

  • Sessão
  • Tópicos / Thread
  • Processos / Processo
  • Quadros de pilha / Quadro de pilha
  • Variáveis locais
  • Módulos / Módulo
  • Utilidade
  • Estado
  • Configurações

Por exemplo, o objeto host.namespace.Debugger.Utility.Control.ExecuteCommand pode ser usado para enviar o comando u para o depurador com as seguintes duas linhas de código JavaScript.

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

Este tópico descreve como trabalhar com objetos comuns e fornece informações de referência sobre seus atributos e comportamentos.

Para obter informações gerais sobre como trabalhar com JavaScript, consulte JavaScript Debugger Scripting. Para obter exemplos de JavaScript que usam os objetos do depurador, consulte Scripts de exemplo do depurador JavaScript. Para obter informações sobre como trabalhar com os objetos settings, consulte .settings (set Debug Settings).

Para explorar os objetos disponíveis em uma sessão de depurador, use o comando dx (Display NatVis Expression ). Por exemplo, você pode exibir alguns dos objetos do depurador de nível superior com este comando 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   

Todos os itens listados acima são DML clicáveis e podem ser recursados mais abaixo para visualizar a estrutura do objeto do depurador.

Estendendo o depurador por meio do modelo de dados

O modelo de dados do depurador permite a criação de uma interface para informações sobre aplicativos e drivers no Windows que tem os seguintes atributos.

  • É detetável e organizado - um espaço de nome logicamente estruturado pode ser consultado usando o comando dx.
  • Pode ser consultado usando LINQ- Isso permite a extração e classificação de dados usando uma linguagem de consulta padrão.
  • Pode ser estendido de forma lógica e consistente - Extensível usando técnicas descritas neste tópico com provedores de scripts para depuração, como o Natvis e o JavaScript.

Estendendo um objeto depurador em JavaScript

Além de poder criar um visualizador em JavaScript, as extensões de script também podem modificar os principais conceitos do depurador - sessões, processos, threads, pilhas, quadros de pilha, variáveis locais - e até mesmo publicar-se como pontos de extensão que outras extensões podem consumir.

Esta secção descreve como expandir um conceito fundamental no depurador. As extensões que são construídas para serem compartilhadas devem estar em conformidade com as diretrizes apresentadas em Native Debugger Objects in JavaScript Extensions - Design and Testing Considerations.

Registrando uma extensão

Um script pode registrar o fato de que ele fornece uma extensão através de uma entrada na matriz retornada do método initializeScript.

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

A presença de um objeto host.namedModelParent dentro da matriz retornada indica ao depurador que um determinado objeto protótipo ou classe ES6 (comProcessExtension neste caso) será um modelo de dados pai para o modelo registrado sob o nome Debugger.Models.Process.

Pontos de extensão de objeto do depurador

Os seguintes pontos de extensão do depurador são parte integrante do depurador e estão disponíveis para serem usados por provedores de script, como JavaScript.

Debugger.Models.Sessions: A lista de sessões (destinos) às quais o depurador está ligado

Debugger.Models.Session: Uma sessão individual (destino) à qual o depurador está conectado (modo de utilizador em tempo real, KD, etc...)

Debugger.Models.Processes: A lista de processos dentro de uma sessão

Debugger.Models.Threads: A lista de threads dentro de um processo

Debugger.Models.Thread: Um thread individual dentro de um processo (independentemente do modo de usuário ou kernel)

Debugger.Models.Stack: A pilha de um thread

Debugger.Models.StackFrames: A coleção de frames que compõem uma pilha.

Debugger.Models.StackFrame: Um quadro de pilha individual dentro de uma pilha

Debugger.Models.LocalVariables: As variáveis locais dentro de um frame de pilha

Debugger.Models.Parameters: Os parâmetros para uma chamada dentro de um frame de pilha

Debugger.Models.Module: Um módulo individual dentro do espaço de endereço de um processo

Objetos de modelo de dados adicionais

Além disso, existem alguns objetos de modelo de dados adicionais que são definidos pelo modelo de dados principal.

DataModel.Models.Intrinsic: Um valor intrínseco (ordinais, floats, etc...)

DataModel.Models.String: Uma cadeia de caracteres

DataModel.Models.Array: Uma matriz nativa

DataModel.Models.Guid: Um GUID (Identificador Único Global)

DataModel.Models.Error: Um objeto de erro

DataModel.Models.Concepts.Iterable: Aplicado a todos os objetos que são iteráveis

DataModel.Models.Concepts.StringDisplayable: Aplicado a todos os objetos que têm uma conversão de cadeia de caracteres de exibição

Exemplo de visão geral da extensão de objeto do depurador COM

Vamos considerar um exemplo. Imagine que você deseja criar uma extensão do depurador para exibir informações específicas do COM, como a tabela de interface global (GIT).

No passado, poderia haver uma extensão de depurador existente com vários comandos que fornecem um meio de acessar informações sobre COM. Um comando pode exibir informações centradas no processo (a tabela de interface global, por exemplo). Outro comando pode fornecer informações centradas em threads, como qual código de apartamento está sendo executado. Talvez seja necessário conhecer e carregar uma segunda extensão do debugger para explorar outros aspetos do COM.

Em vez de ter um conjunto de comandos difíceis de descobrir, uma extensão JavaScript pode modificar o conceito do depurador do que é um processo e um thread, para adicionar essas informações de uma forma natural, explorável e compostável com outras extensões do depurador.

Extensão de objeto do depurador em modo de utilizador ou de kernel

O depurador e os objetos do depurador têm comportamento diferente no modo de usuário e kernel. Ao criar seus objetos de modelo do depurador, você precisa decidir em quais ambientes você trabalhará. Como trabalharemos com COM no modo de usuário, criaremos e testaremos esta extensão com no modo de usuário. Em outras situações, poderá criar um script de depuração JavaScript que funcionará na depuração do modo de utilizador e do modo kernel.

Criando um subnamespace

Voltando ao nosso exemplo, podemos definir um protótipo ou classe ES6, comProcessExtension , que contém o conjunto de coisas que queremos adicionar a um objeto de processo.

Importante A intenção com o sub-namespace é criar um paradigma logicamente estruturado e naturalmente explorável. Por exemplo, evite despejar itens não relacionados no mesmo subnamespace. Analise cuidadosamente as informações discutidas em Native Debugger Objects in JavaScript Extensions - Design and Testing Considerations antes de criar um subnamespace.

Neste trecho de código, criamos adicionar um subnamespace chamado 'COM' ao objeto depurador de processo existente.

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

Implementação de namespace

Em seguida, crie o objeto que implementa o COM do subnamespace em um processo.

Importante Pode haver vários processos (se anexados a tais no modo de usuário ou sob KD). Esta extensão não pode assumir que o estado atual do depurador é o que o usuário pretendia. Alguém pode capturar <someProcess>.COM em uma variável e modificá-la, o que pode levar à apresentação de informações do contexto errado do processo. A solução é adicionar código na extensão para que cada instanciação acompanhe o processo ao qual está anexada. Para este exemplo de código, essas informações são passadas através do ponteiro 'this' da propriedade.

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

Lógica de implementação para a tabela de interface global COM

Para separar mais claramente a lógica de implementação da tabela de interface global COM, definiremos uma classe ES6, gipTable, que abstrai a tabela GIP COM e outra, globalObjects, que será a que será retornada pelo getter GlobalObjects() definido no trecho de código da Implementação de Namespace. Todos esses detalhes podem ser ocultos dentro do fechamento de initializeScript para evitar a publicação de qualquer um desses detalhes internos no namespace do depurador.

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

Por fim, use host.namedModelRegistration para registrar a nova funcionalidade COM.

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

Salve o código para GipTableAbstractor.js usando um aplicativo como o bloco de notas.

Aqui estão as informações do processo disponíveis no modo de usuário antes de carregar esta extensão.

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

Carregue a extensão JavaScript.

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

Em seguida, use o comando dx para exibir informações sobre o processo usando o @$curprocess predefinido.

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

Esta tabela também é acessível programaticamente através do cookie GIT.

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]

Alargando os conceitos de objetos do depurador com o LINQ

Além de ser capaz de estender objetos como processo e thread, o JavaScript também pode estender conceitos associados ao modelo de dados. Por exemplo, é possível adicionar um novo método LINQ a cada iterável. Considere uma extensão de exemplo, "DuplicateDataModel", que duplica cada entrada em um N vezes iterável. O código a seguir mostra como isso pode ser implementado.

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

Salve o código para DuplicateDataModel.js usando um aplicativo como o bloco de notas.

Carregue o provedor de scripts JavaScript, se necessário, e carregue a extensão 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'

Use o comando dx para testar a nova função 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) 
…

Ver também

Objetos do depurador nativo em extensões JavaScript - Detalhes do objeto do depurador

Objetos de depurador nativos em extensões JavaScript - Considerações de design e teste

Depuração com Script em JavaScript

Scripts de exemplo do depurador JavaScript