Script do Depurador JavaScript

Este tópico descreve como usar JavaScript para criar scripts que entendam objetos do depurador e estendem e personalizam os recursos do depurador.

Visão geral do script do depurador JavaScript

Os provedores de script conectam uma linguagem de script ao modelo de objeto interno do depurador. O provedor de script do depurador JavaScript, permite o uso de JavaScript com o depurador.

Quando um JavaScript é carregado usando o comando .scriptload, a função intializeScript e o código raiz do script são executados, os nomes que estão presentes no script são transferidos para o namespace raiz do depurador (dx Debugger) e o script permanece na memória até que seja descarregado e todas as referências a seus objetos sejam liberadas. O script pode fornecer novas funções para o avaliador de expressão do depurador, modificar o modelo de objeto do depurador ou atuar como um visualizador da mesma maneira que um visualizador NatVis.

Este tópico descreve um pouco do que você pode fazer com scripts do depurador JavaScript.

Esses dois tópicos fornecem informações adicionais sobre como trabalhar com JavaScript no depurador.

Scripts de exemplo do depurador JavaScript

Objetos nativos em extensões JavaScript

Vídeo de script JavaScript

Defrag Tools #170 - Andy e Bill demonstram extensibilidade JavaScript e habilidades de script no depurador.

O provedor JavaScript do depurador

O provedor JavaScript incluído no depurador aproveita ao máximo os aprimoramentos mais recentes de objeto e classe ECMAScript6. Para obter mais informações, consulte ECMAScript 6 — Novos recursos: visão geral e comparação.

JsProvider.dll

JsProvider.dll é o provedor JavaScript que é carregado para oferecer suporte ao script do depurador JavaScript.

Requisitos

O JavaScript Debugger Scripting foi projetado para funcionar com todas as versões suportadas do Windows.

Carregando o provedor de script JavaScript

Antes de usar qualquer um dos comandos .script, um provedor de script precisa ser carregado. Use o comando .scriptproviders para confirmar se o provedor JavaScript está carregado.

0:000> .scriptproviders
Available Script Providers:
    NatVis (extension '.NatVis')
    JavaScript (extension '.js')

Metadados de scripts JavaScript

Os comandos a seguir estão disponíveis para trabalhar com scripts do depurador JavaScript.

Requisitos

Antes de usar qualquer um dos comandos .script, um provedor de script precisa ser carregado. Use o comando .scriptproviders para confirmar se o provedor JavaScript está carregado.

0:000> .scriptproviders
Available Script Providers:
    NatVis (extension '.NatVis')
    JavaScript (extension '.js')

.scriptproviders (Listar Provedores de Script)

O comando .scriptproviders listará todas as linguagens de script que são atualmente compreendidas pelo depurador e a extensão sob a qual elas estão registradas.

No exemplo abaixo, os provedores JavaScript e NatVis estão carregados.

0:000> .scriptproviders
Available Script Providers:
    NatVis (extension '.NatVis')
    JavaScript (extension '.js')

Qualquer arquivo que termine em ".NatVis" é entendido como um script NatVis e qualquer arquivo terminado em ".js" é entendido como um script JavaScript. Qualquer tipo de script pode ser carregado com o comando .scriptload.

Para obter mais informações, consulte .scriptproviders (Listar provedores de script)

.scriptload (Carregar Script)

O comando .scriptload carregará um script e executará o código raiz de um script e a função initializeScript . Se houver erros no carregamento inicial e na execução do script, os erros serão exibidos no console. O comando a seguir mostra o carregamento bem-sucedido de TestScript.js.

0:000> .scriptload C:\WinDbg\Scripts\TestScript.js
JavaScript script successfully loaded from 'C:\WinDbg\Scripts\TestScript.js'

Quaisquer manipulações de modelo de objeto feitas pelo script permanecerão no local até que o script seja descarregado subsequentemente ou seja executado novamente com conteúdo diferente.

Para obter mais informações, consulte .scriptload (Carregar Script)

.scriptrun

O comando .scriptrun carregará um script, executará o código raiz do script, o initializeScript e a função invokeScript . Se houver erros no carregamento inicial e na execução do script, os erros serão exibidos no console.

0:000> .scriptrun C:\WinDbg\Scripts\helloWorld.js
JavaScript script successfully loaded from 'C:\WinDbg\Scripts\helloWorld.js'
Hello World!  We are in JavaScript!

Quaisquer manipulações de modelo de objeto do depurador feitas pelo script permanecerão no local até que o script seja descarregado subsequentemente ou seja executado novamente com conteúdo diferente.

Para obter mais informações, consulte .scriptrun (Executar script).

.scriptunload (Descarregar Script)

O comando .scriptunload descarrega um script carregado e chama a função uninitializeScript . Use a sintaxe de comando a seguir para descarregar um script

0:000:x86> .scriptunload C:\WinDbg\Scripts\TestScript.js
JavaScript script unloaded from 'C:\WinDbg\Scripts\TestScript.js'

Para obter mais informações, consulte .scriptunload (Descarregar Script).

.scriptlist (Listar Scripts Carregados)

O comando .scriptlist listará todos os scripts que foram carregados por meio do comando .scriptload ou .scriptrun. Se o TestScript foi carregado com êxito usando .scriptload, o comando .scriptlist exibirá o nome do script carregado.

0:000> .scriptlist
Command Loaded Scripts:
    JavaScript script from 'C:\WinDbg\Scripts\TestScript.js'

Para obter mais informações, consulte .scriptlist (Listar Scripts Carregados).

Introdução ao script do depurador JavaScript

Script de exemplo HelloWorld

Esta seção descreve como criar e executar um script de depurador JavaScript simples que imprime, Hello World.

// WinDbg JavaScript sample
// Prints Hello World
function initializeScript()
{
    host.diagnostics.debugLog("***> Hello World! \n");
}

Use um editor de texto, como o Bloco de Notas, para criar um arquivo de texto chamado HelloWorld.js que contém o código JavaScript mostrado acima.

Use o comando .scriptload para carregar e executar o script. Como usamos o nome da função initializeScript, o código na função é executado quando o script é carregado.

0:000> .scriptload c:\WinDbg\Scripts\HelloWorld.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\HelloWorld.js'
***> Hello World! 

Depois que o script é carregado, a funcionalidade adicional está disponível no depurador. Use o comando dx (Display NatVis Expression) para exibir Debugger.State.Scripts para ver que nosso script agora é residente.

0:000> dx Debugger.State.Scripts
Debugger.State.Scripts                
    HelloWorld 

No próximo exemplo, adicionaremos e chamaremos uma função nomeada.

Adicionando script de exemplo de dois valores

Esta seção descreve como criar e executar um script de depurador JavaScript simples que adiciona entradas e dois números.

Este script simples fornece uma única função, addTwoValues.

// WinDbg JavaScript sample
// Adds two functions
function addTwoValues(a, b)
 {
     return a + b;
 }

Use um editor de texto, como o Bloco de Notas, para criar um arquivo de texto chamado FirstSampleFunction.js

Use o comando .scriptload para carregar o script.

0:000> .scriptload c:\WinDbg\Scripts\FirstSampleFunction.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\FirstSampleFunction.js'

Depois que o script é carregado, a funcionalidade adicional está disponível no depurador. Use o comando dx (Display NatVis Expression) para exibir Debugger.State.Scripts para ver que nosso script agora é residente.

0:000> dx Debugger.State.Scripts
Debugger.State.Scripts                
    FirstSampleFunction    

Podemos selecionar o FirstSampleFunction, para ver quais funções ele fornece.

0:000> dx -r1 -v Debugger.State.Scripts.FirstSampleFunction.Contents
Debugger.State.Scripts.FirstSampleFunction.Contents                 : [object Object]
    host             : [object Object]
    addTwoValues    
 ... 

Para tornar o script um pouco mais conveniente de trabalhar, atribua uma variável no depurador para manter o conteúdo do script usando o comando dx.

0:000> dx @$myScript = Debugger.State.Scripts.FirstSampleFunction.Contents

Use o avaliador de expressão dx para chamar a função addTwoValues.

0:000> dx @$myScript.addTwoValues(10, 41),d
@$myScript.addTwoValues(10, 41),d : 51

Você também pode usar o alias $@scriptContents interno para trabalhar com os scripts. O alias $@scriptContents mescla todos os arquivos . Conteúdo de todos os scripts que são carregados.

0:001> dx @$scriptContents.addTwoValues(10, 40),d
@$scriptContents.addTwoValues(10, 40),d : 50

Quando terminar de trabalhar com o script, use o comando .scriptunload para descarregar o script.

0:000> .scriptunload c:\WinDbg\Scripts\FirstSampleFunction.js
JavaScript script successfully unloaded from 'c:\WinDbg\Scripts\FirstSampleFunction.js'

Automação de Comandos do Depurador

Esta seção descreve como criar e executar um script de depurador JavaScript simples que automatiza o envio do comando u (Desmontar). O exemplo também mostra como coletar e exibir a saída do comando em um loop.

Esse script fornece uma única função, 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");

}

Use um editor de texto, como o Bloco de Notas, para criar um arquivo de texto chamado RunCommands.js

Use o comando .scriptload para carregar o script RunCommands.

0:000> .scriptload c:\WinDbg\Scripts\RunCommands.js 
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\RunCommands.js'

Depois que o script é carregado, a funcionalidade adicional está disponível no depurador. Use o comando dx (Display NatVis Expression) para exibir Debugger.State.Scripts.RunCommands para ver que nosso script agora é residente.

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]

Use o comando dx para chamar a função RunCommands no script RunCommands.

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

Funções especiais do depurador JavaScript

Existem várias funções especiais em um script JavaScript chamado pelo próprio provedor de script.

initializeScript

Quando um script JavaScript é carregado e executado, ele passa por uma série de etapas antes que as variáveis, funções e outros objetos no script afetem o modelo de objeto do depurador.

  • O script é carregado na memória e analisado.
  • O código raiz no script é executado.
  • Se o script tiver um método chamado initializeScript, esse método será chamado.
  • O valor de retorno de initializeScript é usado para determinar como modificar automaticamente o modelo de objeto do depurador.
  • Os nomes no script são vinculados ao namespace do depurador.

Como mencionado, initializeScript será chamado imediatamente após o código raiz do script ser executado. Seu trabalho é retornar uma matriz JavaScript de objetos de registro para o provedor indicando como modificar o modelo de objeto do depurador.

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

O método invokeScript é o método de script primário e é chamado quando .scriptload e .scriptrun são executados.

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

O método uninitializeScript é o oposto comportamental de initializeScript. Ele é chamado quando um script é desvinculado e está se preparando para descarregar. Seu trabalho é desfazer quaisquer alterações no modelo de objeto que o script fez imperativamente durante a execução e/ou destruir quaisquer objetos que o script armazenou em cache.

Se um script não fizer manipulações imperativas no modelo de objeto nem armazenar em cache os resultados, ele não precisará ter um método uninitializeScript. Quaisquer alterações no modelo de objeto executadas conforme indicado pelo valor de retorno de initializeScript são desfeitas automaticamente pelo provedor. Tais alterações não exigem um método uninitializeScript explícito.

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

Resumo das funções chamadas por comandos de script

Esta tabela resume quais funções são chamadas pelos comandos de script

Comando .scriptload .scriptrun (Executar Script) .scriptunload (Descarregar Script)
root sim sim
initializeScript sim sim
invokeScript sim
uninitializeScript sim

Use este código de exemplo para ver quando cada função é chamada à medida que o script é carregado, executado e descarregado.

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

Criando um visualizador de depurador em JavaScript

Os arquivos de visualização personalizados permitem agrupar e organizar dados em uma estrutura de visualização que reflete melhor as relações de dados e o conteúdo. Você pode usar as extensões do depurador JavaScript para escrever visualizadores do depurador que agem de forma muito semelhante ao NatVis. Isso é feito por meio da criação de um objeto protótipo JavaScript (ou uma classe ES6) que atua como visualizador para um determinado tipo de dados. Para obter mais informações sobre o NatVis e o depurador, confira dx (Exibir Expressão NatVis).

Classe de exemplo - Simple1DArray

Considere um exemplo de uma classe C++ que representa uma matriz unidimensional. Essa classe tem dois membros, m_size que é o tamanho geral da matriz e m_pValues que é um ponteiro para um número de ints na memória igual ao campo m_size.

class Simple1DArray
{
private:

    ULONG64 m_size;
    int *m_pValues;
};

Podemos usar o comando dx para examinar a renderização da estrutura de dados padrão.

0:000> dx g_array1D
g_array1D                 [Type: Simple1DArray]
    [+0x000] m_size           : 0x5 [Type: unsigned __int64]
    [+0x008] m_pValues        : 0x8be32449e0 : 0 [Type: int *]

Visualizador JavaScript

Para visualizar esse tipo, precisamos criar uma classe prototype (ou ES6) que tenha todos os campos e propriedades que queremos que o depurador mostre. Também precisamos que o método initializeScript retorne um objeto que diga ao provedor JavaScript para vincular nosso protótipo como um visualizador para o tipo fornecido.

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

Salve o script em um arquivo chamado arrayVisualizer.js.

Use o comando .load (Carregar DLL da Extensão) para carregar o provedor JavaScript.

0:000> .load C:\ScriptProviders\jsprovider.dll

Use .scriptload para carregar o script do visualizador de matriz.

0:000> .scriptload c:\WinDbg\Scripts\arrayVisualizer.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\arrayVisualizer.js'

Agora, quando o comando dx for usado, o visualizador de script exibirá linhas de conteúdo da matriz.

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

Além disso, essa visualização JavaScript fornece funcionalidade LINQ, como Selecionar.

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

O que afeta a visualização

Um protótipo ou classe que é feito o visualizador para um tipo nativo por meio de um retorno de um objeto host.typeSignatureRegistration de initializeScript terá todas as propriedades e métodos dentro do JavaScript adicionados ao tipo nativo. Além disso, a seguinte semântica se aplica:

  • Qualquer nome que não comece com dois sublinhados (__) estará disponível na visualização.

  • Os nomes que fazem parte de objetos JavaScript padrão ou fazem parte de protocolos criados pelo provedor JavaScript não aparecerão na visualização.

  • Um objeto pode ser tornado iterável através do suporte de [Symbol.iterator].

  • Um objeto pode ser indexado por meio do suporte de um protocolo personalizado que consiste em várias funções: getDimensionality, getValueAt e, opcionalmente, setValueAt.

Ponte de objeto nativa e JavaScript

A ponte entre o JavaScript e o modelo de objeto do depurador é bidirecional. Os objetos nativos podem ser passados para JavaScript e os objetos JavaScript podem ser passados para o avaliador de expressões do Depurador. Como exemplo disso, considere a adição do seguinte método em nosso script:

function multiplyBySeven(val)
{
    return val * 7;
}

Esse método agora pode ser utilizado no exemplo de consulta LINQ acima. Primeiro carregamos a visualização JavaScript.

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

Então podemos usar a função multiplyBySeven embutida como mostrado abaixo.

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

Pontos de interrupção condicionais com JavaScript

Você pode usar JavaScript para fazer processamento suplementar depois que um ponto de interrupção é atingido. Por exemplo, o script pode ser usado para examinar outros valores de tempo de execução e, em seguida, determinar se você deseja continuar automaticamente a execução do código ou parar e fazer depuração manual adicional.

Para obter informações gerais sobre como trabalhar com pontos de interrupção, consulte Métodos de controle de pontos de interrupção.

DebugHandler.js exemplo de script de processamento de ponto de interrupção

Este exemplo avaliará a caixa de diálogo abrir e salvar do bloco de notas: notepad!ShowOpenSaveDialog. Esse script avaliará a variável pszCaption para determinar se a caixa de diálogo atual é uma caixa de diálogo "Abrir" ou se é uma caixa de diálogo "Salvar como". Se for uma caixa de diálogo aberta, a execução do código continuará. Se for uma caixa de diálogo salvar como, a execução do código será interrompida e o depurador será invadido.

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

Este comando define um ponto de interrupção no notepad!ShowOpenSaveDialog, e executará o script acima sempre que esse ponto de interrupção for atingido.

bp notepad!ShowOpenSaveDialog ".scriptrun C:\\WinDbg\\Scripts\\DebugHandler.js"

Em seguida, quando a opção Salvar arquivo > é selecionada no bloco de notas, o script é executado, o comando g não é enviado e ocorre uma quebra na execução do código.

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

Trabalhar com valores de 64 bits em extensões JavaScript

Esta seção descreve como os valores de 64 bits passados para uma extensão do depurador JavaScript se comportam. Esse problema surge porque o JavaScript só tem a capacidade de armazenar números usando 53 bits.

Armazenamento de 64 bits e JavaScript de 53 bits

Os valores ordinais passados para o JavaScript são normalmente empacotados como números JavaScript. O problema com isso é que os números JavaScript são valores de ponto flutuante de precisão dupla de 64 bits. Qualquer ordinal acima de 53 bits perderia precisão na entrada no JavaScript. Isso apresenta um problema para ponteiros de 64 bits e outros valores ordinais de 64 bits que podem ter sinalizadores nos bytes mais altos. Para lidar com isso, qualquer valor nativo de 64 bits (seja do código nativo ou do modelo de dados) que entra no JavaScript entra como um tipo de biblioteca - não como um número JavaScript. Esse tipo de biblioteca voltará ao código nativo sem perder a precisão numérica.

Conversão automática

O tipo de biblioteca para valores ordinais de 64 bits oferece suporte ao valor JavaScript padrãoOf conversão. Se o objeto for usado em uma operação matemática ou outra construção que exija conversão de valor, ele será convertido automaticamente em um número JavaScript. Se ocorrer perda de precisão (o valor utiliza mais de 53 bits de precisão ordinal), o provedor JavaScript lançará uma exceção.

Observe que, se você usar operadores bit a bit em JavaScript, estará ainda mais limitado a 32 bits de precisão ordinal.

Este código de exemplo soma dois números e será usado para testar a conversão de valores de 64 bits.

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

Use um editor de texto, como o Bloco de Notas, para criar um arquivo de texto chamado PlayWith64BitValues.js

Use o comando .scriptload para carregar o script.

0:000> .scriptload c:\WinDbg\Scripts\PlayWith64BitValues.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\PlayWith64BitValues.js'

Para tornar o script um pouco mais conveniente de trabalhar, atribua uma variável no depurador para manter o conteúdo do script usando o comando dx.

0:000> dx @$myScript = Debugger.State.Scripts.PlayWith64BitValues.Contents

Use o avaliador de expressão dx para chamar a função addTwoValues.

Primeiro vamos calcular o valor de 2^53 =9007199254740992 (Hex 0x20000000000000).

Primeiro a testar, usaremos (2^53) - 2 e veremos que ele retorna o valor correto para a soma.

0:000> dx @$myScript.playWith64BitValues(9007199254740990, 9007199254740990)
Sum   >> 18014398509481980

Então vamos calcular (2^53) -1 =9007199254740991. Isso retorna o erro indicando que o processo de conversão perderá precisão, portanto, esse é o maior valor que pode ser usado com o método de soma no código JavaScript.

0:000> dx @$myScript.playWith64BitValues(9007199254740990, 9007199254740991)
Error: 64 bit value loses precision on conversion to number

Chame um método de modelo de dados passando valores de 64 bits. Não há perda de precisão aqui.

0:001> dx @$myScript.performOp64BitValues( 0x7FFFFFFFFFFFFFFF,  0x7FFFFFFFFFFFFFFF, (x, y) => x + y)
@$myScript.performOp64BitValues( 0x7FFFFFFFFFFFFFFF,  0x7FFFFFFFFFFFFFFF, (x, y) => x + y) : 0xfffffffffffffffe

Comparação

O tipo de biblioteca de 64 bits é um objeto JavaScript e não um tipo de valor, como um número JavaScript. Isso tem algumas implicações para as operações de comparação. Normalmente, a igualdade (==) em um objeto indicaria que os operandos se referem ao mesmo objeto e não ao mesmo valor. O provedor JavaScript atenua isso rastreando referências em tempo real a valores de 64 bits e retornando o mesmo objeto "imutável" para valores de 64 bits não coletados. Isso significa que, para comparação, ocorreria o seguinte.

// 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");

Use um editor de texto, como o Bloco de Notas, para criar um arquivo de texto chamado ComparisonWith64BitValues.js

Use o comando .scriptload para carregar o script.

0:000> .scriptload c:\WinDbg\Scripts\ComparisonWith64BitValues.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\ComparisonWith64BitValues.js'

Para tornar o script um pouco mais conveniente de trabalhar, atribua uma variável no depurador para manter o conteúdo do script usando o comando dx.

0:000> dx @$myScript = Debugger.State.Scripts.comparisonWith64BitValues.Contents

Primeiro a testar, usaremos (2^53) - 2 e veremos que ele retorna os valores esperados.

0:001> dx @$myScript.comparisonWith64BitValues(9007199254740990, 9007199254740990)
areEqual   >> true
areNotEqual   >> false
isEqualTo42   >> false
isLess   >> false

Também tentaremos o número 42 como o primeiro valor para validar que o operador de comparação está funcionando como deveria.

0:001> dx @$myScript.comparisonWith64BitValues(42, 9007199254740990)
areEqual   >> false
areNotEqual   >> true
isEqualTo42   >> true
isLess   >> true

Então vamos calcular (2^53) -1 =9007199254740991. Este valor retorna o erro indicando que o processo de conversão perderá precisão, portanto, esse é o maior valor que pode ser usado com os operadores de comparação no código JavaScript.

0:000> dx @$myScript.playWith64BitValues(9007199254740990, 9007199254740991)
Error: 64 bit value loses precision on conversion to number

Mantendo a precisão nas operações

Para permitir que uma extensão do depurador mantenha a precisão, um conjunto de funções matemáticas é projetado sobre o tipo de biblioteca de 64 bits. Se a extensão precisar (ou possivelmente) precisar de precisão acima de 53 bits para valores de entrada de 64 bits, os seguintes métodos devem ser utilizados em vez de depender de operadores padrão:

Nome do Método Signature Descrição
asNumber .asNúmero() Converte o valor de 64 bits em um número JavaScript. Se ocorrer perda de precisão, **UMA EXCEÇÃO É LANÇADA**
convertToNumber .convertToNumber() Converte o valor de 64 bits em um número JavaScript. Se ocorrer perda de precisão, **NENHUMA EXCEÇÃO É LANÇADA**
getLowPart .getLowPart() Converte os 32 bits mais baixos do valor de 64 bits em um número JavaScript
getHighPart .getHighPart() Converte os 32 bits superiores do valor de 64 bits em um número JavaScript
add .add(value) Adiciona um valor ao valor de 64 bits e retorna o resultado
subtrair .subtract(value) Subtrai um valor do valor de 64 bits e retorna o resultado
multiply .multiply(valor) Multiplica o valor de 64 bits pelo valor fornecido e retorna o resultado
divide .divide(value) Divide o valor de 64 bits pelo valor fornecido e retorna o resultado
bitwiseAnd .bitwiseAnd(valor) Calcula o bit a bit e do valor de 64 bits com o valor fornecido e retorna o resultado
bitwiseOr .bitwiseOr(value) Calcula o bit a bit ou do valor de 64 bits com o valor fornecido e retorna o resultado
bitwiseXor .bitwiseXor(value) Calcula o bit a bit x ou do valor de 64 bits com o valor fornecido e retorna o resultado
bitwiseShiftLeft .bitwiseShiftLeft(value) Desloca o valor de 64 bits deixado pela quantidade fornecida e retorna o resultado
bitwiseShiftRight .bitwiseShiftRight(value) Muda o valor de 64 bits para a direita pelo valor fornecido e retorna o resultado
toString .toString([radix]) Converte o valor de 64 bits em uma cadeia de caracteres de exibição no radix padrão (ou no radix fornecido opcionalmente)

Esse método também está disponível.

Nome do Método Signature Descrição
compareTo .compareTo(value) Compara o valor de 64 bits com outro valor de 64 bits.

Depuração de JavaScript

Esta seção descreve como usar os recursos de depuração de script do depurador. O depurador tem suporte integrado para depuração de scripts JavaScript usando o comando .scriptdebug (Debug JavaScript).

Observação

Para usar a depuração JavaScript com WinDbg, execute o depurador como Administrador.

Use este código de exemplo para explorar a depuração de um JavaScript. Para este passo a passo, vamos nomeá-lo DebuggableSample.js e salvá-lo no diretório 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
    //
}

Carregue o script de exemplo.

.scriptload C:\MyScripts\DebuggableSample.js

Inicie a depuração ativa do script, usando o comando .scriptdebug.

0:000> .scriptdebug C:\MyScripts\DebuggableSample.js
>>> ****** DEBUGGER ENTRY DebuggableSample ******
           No active debug event!

>>> Debug [DebuggableSample <No Position>] >

Depois de ver o prompt >>> Debug [DebuggableSample <No Position>] > e uma solicitação de entrada, você estará dentro do depurador de script.

Use o comando .help para exibir uma lista de comandos no ambiente de depuração JavaScript.

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

Use o comando do depurador de script sx para ver a lista de eventos que podem ser interceptados.

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

Use o comando do depurador de script sxe para ativar a interrupção na entrada para que o script seja interceptado no depurador de script assim que qualquer código dentro dele for executado.

>>> Debug [DebuggableSample <No Position>] >sxe en          
sxe en                                                      
Event filter 'en' is now active                             

Saia do depurador de script e faremos uma chamada de função no script que irá interceptar no depurador.

>>> Debug [DebuggableSample <No Position>] >q

Neste ponto, você está de volta ao depurador normal. Execute o seguinte comando para chamar o script.

dx @$scriptContents.outermost()

Agora, você está de volta no depurador de script e quebrado na primeira linha da função JavaScript mais externa.

>>> ****** SCRIPT BREAK DebuggableSample [BreakIn] ******   
           Location: line = 73, column = 5                  
           Text: var x = 99                                 

>>> Debug [DebuggableSample 73:5] >                         

Além de ver a quebra no depurador, você obtém informações sobre a linha (73) e a coluna (5) onde a quebra ocorreu, bem como o trecho relevante do código-fonte: var x = 99.

Vamos pisar algumas vezes e chegar a outro lugar no roteiro.

    p
    t
    p
    t
    p
    p

Neste ponto, você deve ser quebrado no método throwAndCatch na linha 34.

...
>>> ****** SCRIPT BREAK DebuggableSample [Step Complete] ******                       
           Location: line = 34, column = 5                                            
           Text: var curProc = host.currentProcess                                    

Você pode verificar isso executando um rastreamento de pilha.

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

A partir daqui, você pode investigar o valor das variáveis.

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

Vamos definir um ponto de interrupção na linha de código atual e ver quais pontos de interrupção estão definidos agora.

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

A partir daqui, desativaremos o evento entry (en) usando o comando sxd script debugger.

>>> Debug [DebuggableSample 34:5] >sxd en                                                                              
sxd en                                                                                                                 
Event filter 'en' is now inactive                                                                                      

E depois é só ir e deixar o roteiro continuar até o fim.

>>> Debug [DebuggableSample 34:5] >g                                                                                   
g                                                                                                                      
This is a fun test                                                                                                     
Of the script debugger                                                                                                 
foo.a = 99                                                                                                             
Caught and returned!                                                                                                   
Test                                                                                                                   
...

Execute o método de script novamente e veja nosso ponto de interrupção ser atingido.

0:000> dx @$scriptContents.outermost()                                                
inside outer!                                                                         
>>> ****** SCRIPT BREAK DebuggableSample [Breakpoint 1] ******                        
           Location: line = 34, column = 5                                            
           Text: var curProc = host.currentProcess                                    

Exibir a pilha de chamadas.

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

Neste ponto, queremos parar de depurar esse script, então nos desvinculamos dele.

>>> Debug [DebuggableSample 34:5] >.detach                  
.detach                                                     
Debugger has been detached from script!                     

E então digite q para sair.

q                                                           
This is a fun test                                          
Of the script debugger                                      
foo.a = 99                                                  
Caught and returned!                                        
Test                                                        

Executar a função novamente não invadirá mais o depurador.

0:007> dx @$scriptContents.outermost()
inside outer!
This is a fun test
Of the script debugger
foo.a = 99
Caught and returned!
Test

JavaScript no VSCode - Adicionando IntelliSense

Se você gostaria de trabalhar com os objetos de modelo de dados do depurador no VSCode, você pode usar um arquivo de definição que está disponível nos kits de desenvolvimento do Windows. O arquivo de definição do IntelliSense fornece suporte para todas as APIs de objeto do depurador do host.*. Se você instalou o kit no diretório padrão em um PC de 64 bits, ele está localizado aqui:

C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\winext\JsProvider.d.ts

Para usar o arquivo de definição do IntelliSense no VSCode:

  1. Localize o arquivo de definição - JSProvider.d.ts

  2. Copie o arquivo de definição para a mesma pasta que o script.

  3. Adicione /// <reference path="JSProvider.d.ts" /> à parte superior do arquivo de script JavaScript.

Com essa referência em seu arquivo JavaScript, o VS Code fornecerá automaticamente o IntelliSense nas APIs de host fornecidas pelo JSProvider, além das estruturas em seu script. Por exemplo, digite “host.” e você verá o IntelliSense para todas as APIs de modelo de depurador disponíveis.

Recursos do JavaScript

A seguir estão os recursos JavaScript que podem ser úteis à medida que você desenvolve extensões de depuração JavaScript.

Consulte também

Scripts de exemplo do depurador JavaScript

Objetos nativos em extensões JavaScript