JavaScript 調試程式腳本

本主題描述如何使用 JavaScript 來建立腳本,以瞭解調試程序物件,以及擴充和自定義調試程式的功能。

JavaScript 調試程式腳本概觀

腳本提供者會將腳本語言橋接至調試程式的內部物件模型。 JavaScript 調試程式腳本提供者,允許搭配調試程式使用 JavaScript。

透過 .scriptload 命令載入 JavaScript 時,會執行腳本的根程式代碼、腳本中存在的名稱會橋接至調試程式 (dx Debugger) 的根命名空間,而腳本會保留在記憶體中,直到卸除,並釋放其物件的所有參考為止。 此腳本可為調試程式的表達式評估工具提供新的函式、修改調試程序的物件模型,或可以與 NatVis 可視化檢視相同的方式做為可視化檢視。

本主題描述您可以使用 JavaScript 調試程式腳本執行的一些動作。

這兩個主題提供有關在調試程式中使用JavaScript的其他資訊。

JavaScript 調試程式範例腳本

JavaScript 延伸模組中的原生物件

JavaScript 腳本影片

重組工具 #170 - Andy 和 Bill 示範調試程式中的 JavaScript 擴充性和腳本功能。

調試程式 JavaScript 提供者

調試程式隨附的 JavaScript 提供者會充分利用最新的 ECMAScript6 對象和類別增強功能。 如需詳細資訊,請參閱 ECMAScript 6 — 新功能:概觀和比較

JsProvider.dll

JsProvider.dll是載入以支援 JavaScript 調試程式腳本的 JavaScript 提供者。

需求

JavaScript 調試程式腳本的設計目的是要與所有支援的 Windows 版本搭配使用。

載入 JavaScript 腳本提供者

使用任何 .script 命令之前,必須載入腳本提供者。 使用 .scriptproviders 命令來確認已載入 JavaScript 提供者。

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

JavaScript 腳本中繼命令

下列命令可用來使用 JavaScript 調試程式腳本。

需求

使用任何 .script 命令之前,必須載入腳本提供者。 使用 .scriptproviders 命令來確認已載入 JavaScript 提供者。

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

.scriptproviders (列出腳本提供者)

.scriptproviders 命令會列出調試程式目前瞭解的所有腳本語言,以及它們所註冊的擴充功能。

在下列範例中,會載入 JavaScript 和 NatVis 提供者。

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

任何結尾為 「的檔案」。NatVis 會理解為 NatVis 腳本,而以 “.js” 結尾的任何檔案會理解為 JavaScript 腳本。 任一類型的腳本都可以使用 .scriptload 命令載入。

如需詳細資訊,請參閱 .scriptproviders (列出腳本提供者)

.scriptload (載入腳本)

.scriptload 命令會載入腳本,並執行腳本和 initializeScript 函式的根程序代碼。 如果在初始載入和執行腳本時發生任何錯誤,則會將錯誤顯示至主控台。 下列命令顯示成功載入TestScript.js。

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

腳本所做的任何物件模型操作都會保持原狀,直到腳本後續卸除或以不同內容再次執行為止。

如需詳細資訊,請參閱 .scriptload (載入腳本)

.scriptrun

.scriptrun 命令會載入腳本、執行腳本的根程序代碼、initializeScript 和 invokeScript 函式。 如果在初始載入和執行腳本時發生任何錯誤,則會將錯誤顯示至主控台。

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

腳本所做的任何調試程序物件模型操作都會保留到腳本後續卸除或以不同內容再次執行為止。

如需詳細資訊,請參閱 .scriptrun (執行腳本)。

.scriptunload (Unload Script)

.scriptunload 命令會卸除載入的腳本,並呼叫 uninitializeScript 函式。 使用下列命令語法卸除腳本

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

如需詳細資訊,請參閱 .scriptunload (Unload Script)

.scriptlist (列出載入的腳本)

.scriptlist 命令會列出任何已透過 .scriptload 或 .scriptrun 命令載入的腳本。 如果使用 .scriptload 成功載入 TestScript,.scriptlist 命令會顯示載入的腳本名稱。

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

如需詳細資訊,請參閱 .scriptlist(列出載入的腳本)。

開始使用 JavaScript 調試程式腳本

HelloWorld 範例腳本

本節說明如何建立和執行列印出 Hello World 的簡單 JavaScript 調試程序腳本。

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

使用 記事本 之類的文本編輯器來建立名為 HelloWorld.js 的文本檔,其中包含上面顯示的 JavaScript 程式代碼。

使用 .scriptload 命令來載入和執行腳本。 因為我們使用了函式名稱 initializeScript,因此載入腳本時會執行函式中的程序代碼。

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

載入腳本之後,調試程式會提供額外的功能。 使用 dx (Display NatVis Expression) 命令來顯示 Debugger.State.Scripts,以查看我們的腳本現在是常駐的。

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

在下一個範例中,我們將新增並呼叫具名函式。

新增兩個值範例腳本

本節說明如何建立和執行簡單的 JavaScript 調試程式腳本,以新增輸入並新增兩個數位。

這個簡單的腳本提供單一函式 addTwoValues。

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

使用 記事本 之類的文字編輯器來建立名為 FirstSampleFunction.js 的文字檔

使用 .scriptload 命令來載入腳本。

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

載入腳本之後,調試程式會提供額外的功能。 使用 dx (Display NatVis Expression) 命令來顯示 Debugger.State.Scripts,以查看我們的腳本現在是常駐的。

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

我們可以選取 FirstSampleFunction,以查看它所提供的函式。

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

若要讓腳本更方便使用,請在調試程式中指派變數,以使用 dx 命令來保存腳本的內容。

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

使用 dx 運算式評估工具呼叫 addTwoValues 函式。

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

您也可以使用 @$scriptContents 內建別名來處理腳本。 @$scriptContents別名會合併所有 。載入的所有腳本內容。

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

當您完成使用腳本時,請使用 .scriptunload 命令來卸除腳本。

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

調試程式命令自動化

本節說明如何建立和執行簡單的 JavaScript 調試程式腳本,以自動傳送 u (Unassemble) 命令。 此範例也會示範如何在迴圈中收集和顯示命令輸出。

此腳本提供單一函式 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");

}

使用 記事本 之類的文字編輯器來建立名為 RunCommands.js 的文字檔

使用 .scriptload 命令來載入 RunCommands 腳本。

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

載入腳本之後,調試程式會提供額外的功能。 使用 dx (顯示 NatVis 表達式) 命令來顯示 Debugger.State.Scripts.RunCommands,以查看我們的腳本現在已常駐。

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]

使用 dx 命令呼叫 RunCommands 腳本中的 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

特殊的 JavaScript 調試程式函式

腳本提供者本身所呼叫的 JavaScript 腳本中有數個特殊函式。

initializeScript

當 JavaScript 腳本載入並執行時,它會在腳本中的變數、函式和其他物件之前執行一系列步驟,影響調試程序的物件模型。

  • 腳本會載入記憶體並剖析。
  • 文本中的根程式代碼會執行。
  • 如果腳本有名為 initializeScript 的方法,則會呼叫該方法。
  • initializeScript 的傳回值可用來判斷如何自動修改調試程序的物件模型。
  • 文本中的名稱會橋接至調試程式的命名空間。

如前所述,在執行腳本的根程式代碼之後,會立即呼叫 initializeScript。 其作業是將註冊物件的 JavaScript 陣列傳回給提供者,指出如何修改調試程序的物件模型。

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

invokeScript 方法是主要腳本方法,並在執行 .scriptload 和 .scriptrun 時呼叫。

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

uninitializeScript 方法與 initializeScript 的行為相反。 當腳本解除連結並準備好卸除時,就會呼叫它。 其作業是復原腳本在執行期間命令式執行期間對物件模型所做的任何變更,以及/或終結腳本快取的任何物件。

如果腳本既不對物件模型進行命令式操作,也不需要快取結果,就不需要有 uninitializeScript 方法。 提供者會自動取消對初始化Script 傳回值所指示的物件模型所做的任何變更。 這類變更不需要明確的 uninitializeScript 方法。

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

文本命令呼叫的函式摘要

下表摘要說明文本命令所呼叫的函式

Command .scriptload .scriptrun (執行腳本) .scriptunload (Unload Script)
initializeScript
invokeScript
uninitializeScript

使用此範例程式代碼來查看當腳本載入、執行和卸載時呼叫每個函式的時間。

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

在 JavaScript 中建立調試程式可視化檢視

自定義視覺效果檔案可讓您在視覺效果結構中分組及組織數據,以更能反映數據關聯性和內容。 您可以使用 JavaScript 調試程式延伸模組來撰寫調試程式可視化檢視,其運作方式與 NatVis 非常類似。 這可透過撰寫 JavaScript 原型物件(或 ES6 類別)來完成,該物件可作為指定數據類型的可視化檢視。 如需 NatVis 和調試程式的詳細資訊,請參閱 dx (顯示 NatVis 表達式)。

範例類別 - Simple1DArray

請考慮表示單一維度陣列的 C++ 類別範例。 這個類別有兩個成員,m_size這是陣列的整體大小,而m_pValues這是記憶體中數個 int 的指標,等於m_size欄位。

class Simple1DArray
{
private:

    ULONG64 m_size;
    int *m_pValues;
};

我們可以使用 dx 命令來查看預設的數據結構轉譯。

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

JavaScript 可視化檢視

為了可視化此類型,我們需要撰寫原型 (或 ES6) 類別,該類別具有我們想要調試程序顯示的所有欄位和屬性。 我們也需要讓 initializeScript 方法傳回 物件,該物件會告知 JavaScript 提供者將原型連結為指定類型的可視化檢視。

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

將腳本儲存在名為 arrayVisualizer.js 的檔案中。

使用 .load (Load Extension DLL) 命令來載入 JavaScript 提供者。

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

使用 .scriptload 載入數位列可視化檢視腳本。

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

現在,使用 dx 命令時,腳本可視化檢視會顯示數位內容的數據列。

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

此外,此 JavaScript 視覺效果提供 LINQ 功能,例如 Select。

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

影響視覺效果的內容

從 initializeScript 傳回 host.typeSignatureRegistration 物件,建立原生類型的可視化檢視的原型或類別,會將 JavaScript 內的所有屬性和方法新增至原生類型。 此外,適用下列語意:

  • 任何開頭不是以兩個底線 (__) 開頭的名稱,都可以在視覺效果中使用。

  • 屬於標準 JavaScript 物件或 JavaScript 提供者所建立之通訊協定的名稱不會顯示在視覺效果中。

  • 對象可透過 [Symbol.iterator] 的支持進行反覆運算。

  • 對象可透過由數個函式所組成的自定義通訊協議支援來建立索引:getDimensionality、getValueAt 和選擇性 setValueAt。

原生和 JavaScript 物件網橋

JavaScript 與調試程序物件模型之間的網橋是雙向的。 原生物件可以傳遞至 JavaScript,而 JavaScript 物件可以傳遞至調試程式的表達式評估工具。 在此範例中,請考慮在我們的腳本中新增下列方法:

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

這個方法現在可以在上述的LINQ查詢範例中使用。 首先,我們會載入 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

然後,我們可以使用 multiplyBySeven 函式內嵌,如下所示。

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

使用 JavaScript 的條件斷點

您可以在叫用斷點之後,使用 JavaScript 進行補充處理。 例如,腳本可用來檢查其他運行時間值,然後判斷您是否要自動繼續執行程式碼或停止,並執行其他手動偵錯。

如需使用斷點的一般資訊,請參閱 控制斷點的方法。

DebugHandler.js範例斷點處理腳本

此範例會評估記事本的開啟和儲存對話框: 記事本!ShowOpenSaveDialog。 此腳本會評估 pszCaption 變數,以判斷目前的對話方塊是否為 [開啟] 對話框,或是否為 [另存新檔] 對話框。 如果是開啟的對話框,程式代碼執行將會繼續。 如果是另存新檔對話框,程式代碼執行將會停止,而調試程式將會中斷。

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

此命令會在記事本上設定斷點!ShowOpenSaveDialog,每當叫用斷點時,就會執行上述腳本。

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

然後在記事本中選取 [檔案 > 儲存] 選項時,就會執行腳本、不會傳送 g 命令,而且會在程式代碼執行中斷時發生。

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

在 JavaScript 延伸模組中使用 64 位值

本節說明傳入 JavaScript 調試程式延伸模組的 64 位值如何運作。 發生此問題時,JavaScript 只能使用53位來儲存數位。

64 位和 JavaScript 53 位 儲存體

傳入 JavaScript 的序數位值通常會封送處理為 JavaScript 數位。 問題在於 JavaScript 數位是 64 位雙精確度浮點數。 任何超過53位的序數都會在進入JavaScript時失去精確度。 這會顯示64位指標和其他64位序數值的問題,這些值可能有最高位元組的旗標。 為了解決這個問題,任何64位原生值(無論是從原生程式代碼還是數據模型),輸入JavaScript時會輸入為連結庫類型,而不是JavaScript數位。 此連結庫類型會往返回原生程序代碼,而不會遺失數值精確度。

自動轉換

64 位序數值的連結庫類型支持標準 JavaScript valueOf 轉換。 如果對象用於數學運算或其他需要值轉換的建構中,它會自動轉換成 JavaScript 數位。 如果遺失有效位數(值會利用超過53位的序數精確度),JavaScript提供者將會擲回例外狀況。

請注意,如果您在 JavaScript 中使用位運算符,則會進一步限製為 32 位的序數精確度。

此範例程式代碼會加總兩個數位,並用來測試64位值的轉換。

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

使用 記事本 之類的文本編輯器來建立名為 PlayWith64BitValues.js 的文字檔

使用 .scriptload 命令來載入腳本。

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

若要讓腳本更方便使用,請在調試程式中指派變數,以使用 dx 命令來保存腳本的內容。

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

使用 dx 運算式評估工具呼叫 addTwoValues 函式。

首先,我們將計算 2^53 =9007199254740992 的值(十六進位0x20000000000000)。

首先測試,我們將使用 (2^53) - 2,並查看它傳回正確值的總和。

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

然後,我們將計算 (2^53) -1 =9007199254740991。 這會傳回錯誤,指出轉換程式將會失去有效位數,因此這是可在 JavaScript 程式代碼中搭配 sum 方法使用的最大值。

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

呼叫傳遞 64 位值的數據模型方法。 這裡不會遺失精確度。

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

比較

64 位連結庫類型是 JavaScript 物件,而不是 JavaScript 數位之類的實值型別。 這對比較作業有一些影響。 一般而言,物件上的等號 (==) 表示操作數參考相同的物件,而不是相同的值。 JavaScript 提供者藉由追蹤對 64 位值的即時參考,並針對非收集的 64 位值傳回相同的「不可變」物件,藉以減輕這種情況。 這表示要進行比較,會發生下列情況。

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

使用 記事本 之類的文本編輯器來建立名為 ComparisonWith64BitValues.js 的文字檔

使用 .scriptload 命令來載入腳本。

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

若要讓腳本更方便使用,請在調試程式中指派變數,以使用 dx 命令來保存腳本的內容。

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

首先測試,我們將使用 (2^53) - 2,並查看它傳回預期的值。

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

我們也會嘗試數位 42 做為第一個值,以驗證比較運算元是否正常運作。

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

然後,我們將計算 (2^53) -1 =9007199254740991。 這個值會傳回錯誤,指出轉換程式會失去有效位數,因此這是可與 JavaScript 程式代碼中的比較運算元搭配使用的最大值。

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

維護作業中的精確度

為了允許調試程式延伸模組維持精確度,一組數學函式會投影在64位連結庫類型之上。 如果擴充功能需要超過53位的精確度才能傳入64位值,則應該使用下列方法,而不是依賴標準運算符:

方法名稱 簽章 說明
asNumber .asNumber() 將 64 位值轉換為 JavaScript 數位。 如果遺失有效位數,**擲回例外狀況**
convertToNumber .convertToNumber() 將 64 位值轉換為 JavaScript 數位。 如果遺失有效位數,**NO EXCEPTION IS THROWN**
getLowPart .getLowPart() 將 64 位值的較低 32 位轉換成 JavaScript 數位
getHighPart .getHighPart() 將 64 位值的高 32 位轉換成 JavaScript 數位
add .add(value) 將值新增至64位值,並傳回結果
減去 .subtract(value) 從 64 位值減去值,並傳回結果
multiply .multiply(value) 將 64 位值乘以提供的值,並傳回結果
divide .divide(value) 將 64 位值除以提供的值,並傳回結果
bitwiseAnd .bitwiseAnd(value) 使用提供的值計算 64 位值的位和 ,並傳回結果
bitwiseOr .bitwiseOr(value) 使用提供的值計算 64 位值的位或 ,並傳回結果
bitwiseXor .bitwiseXor(value) 使用提供的值計算 64 位值的位 xor,並傳回結果
bitwiseShiftLeft .bitwiseShiftLeft(value) 將指定數量留下的 64 位值移位,並傳回結果
bitwiseShiftRight .bitwiseShiftRight(value) 依指定數量向右移 64 位值,並傳回結果
toString .toString([radix]) 將 64 位值轉換為預設基數中的顯示字串(或選擇性提供的基數)

您也可以使用這個方法。

方法名稱 簽章 說明
compareTo .compareTo(value) 比較64位值與另一個64位值。

JavaScript 偵錯

本節說明如何使用調試程序的腳本偵錯功能。 調試程式已整合支援使用 .scriptdebug (Debug JavaScript) 命令偵錯 JavaScript 腳本。

注意

若要搭配 WinDbg 使用 JavaScript 偵錯,請以 管理員 istrator 身分執行調試程式。

使用此範例程式代碼來探索 JavaScript 偵錯。 在本逐步解說中,我們會將它命名為DebuggableSample.js,並將它儲存在 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
    //
}

載入範例腳本。

.scriptload C:\MyScripts\DebuggableSample.js

使用 .scriptdebug 命令開始主動偵錯腳本。

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

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

當您看到提示 >>> Debug [DebuggableSample <No Position>] > 和輸入要求之後,您就會在腳本調試程式內。

使用 .help 命令,在 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

使用 sx 文稿調試程式命令來查看我們可以設陷的事件清單。

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

使用 sxe 文稿調試程式命令來開啟中斷專案,讓腳本會在腳本調試程式內執行任何程式代碼時立即陷入腳本調試程式。

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

結束腳本調試程序,我們將對腳本進行函式呼叫,以攔截調試程式。

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

此時,您會回到一般調試程式。 執行下列命令以呼叫腳本。

dx @$scriptContents.outermost()

現在,您會回到腳本調試程式,並在最外層 JavaScript 函式的第一行中斷。

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

>>> Debug [DebuggableSample 73:5] >                         

除了看到中斷進入調試程式之外,您還可以取得中斷發生所在行 (73) 和數據行 (5) 的資訊,以及原始程式碼的相關代碼段: var x = 99

讓我們逐步執行幾次,並移至腳本中的另一個位置。

    p
    t
    p
    t
    p
    p

此時,您應該在行 34 上將 throwAndCatch 方法分成 。

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

您可以藉由執行堆疊追蹤來確認這一點。

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

您可以從這裏調查變數的值。

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

讓我們在目前的程式代碼行上設定斷點,並查看現在設定的斷點。

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

從這裡,我們將使用 sxd 腳本調試程式命令來停用專案 (en) 事件。

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

然後,只要去,讓腳本繼續結束。

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

再次執行腳本方法,並監看叫我們的斷點。

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

顯示呼叫堆疊。

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

此時,我們想要停止偵錯此腳本,因此我們會中斷連結。

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

然後輸入 q 以結束。

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

再次執行函式將不再中斷至調試程式。

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

VSCode 中的 JavaScript - 新增 IntelliSense

如果您想要在 VSCode 中使用除錯程式資料模型物件,您可以使用 Windows 開發工具套件中可用的定義檔。 IntelliSense 定義檔提供所有 host.* 調試程式物件 API 的支援。 如果您在 64 位電腦上的預設目錄中安裝套件,其位於:

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

若要在 VSCode 中使用 IntelliSense 定義檔:

  1. 找出定義檔案 - JSProvider.d.ts

  2. 將定義檔複製到與文稿相同的資料夾。

  3. 將 新增 /// <reference path="JSProvider.d.ts" /> 至 JavaScript 腳本檔案頂端。

使用 JavaScript 檔案中的該參考,除了腳本中的結構之外,VS Code 還會在 JSProvider 所提供的主機 API 上自動提供 IntelliSense。 例如,輸入 “host”。 您會看到所有可用調試程式模型 API 的 IntelliSense。

JavaScript 資源

以下是開發 JavaScript 偵錯延伸模組時可能很有用的 JavaScript 資源。

另請參閱

JavaScript 調試程式範例腳本

JavaScript 延伸模組中的原生物件