共用方式為


JavaScript 調試程式腳本

本主題說明如何使用 JavaScript 來建立腳本,以瞭解偵錯工具物件,以及擴充和自訂偵錯工具的功能。

JavaScript 偵錯工具指令碼概觀

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

透過 .scriptload 命令載入 JavaScript 時,會執行腳本的根程式碼,腳本中存在的名稱會橋接至偵錯工具的根命名空間 (dx 偵錯器),而且腳本會常駐記憶體中,直到卸載並釋放其物件的所有參考為止。 腳本可以為偵錯工具的運算式評估器提供新的函式、修改偵錯工具的物件模型,或可以作為視覺效果,其方式與 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 命令將載入腳本,執行腳本的根代碼、 initializeScriptinvokeScript 函數。 如果指令碼的初始載入和執行有任何錯誤,錯誤將顯示在主控台上。

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 (卸載腳本)

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

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

如需詳細資訊,請參閱 .scriptunload (卸載指令碼)。

.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 (顯示 NatVis 運算式) 命令來顯示 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 (顯示 NatVis 運算式) 命令來顯示 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 指令碼中有數個由指令碼提供者本身呼叫的特殊函數。

初始化腳本

當 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 方法。 提供者會自動復原 initializeScript 傳回值所指出的物件模型所執行的任何變更。 這類變更不需要明確的 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");
}

指令碼呼叫的函式摘要

下表摘要說明指令碼指令會呼叫哪些函式

指令 .scriptload .scriptrun(運行腳本) .scriptunload (卸載腳本)
是的 是的
初始化腳本 是的 是的
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是記憶體中等於 m_size 欄位的 int 數量的指標。

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 (載入延伸模組 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 物件可以傳遞至 Debugger 的運算式評估器。 例如,請考慮在腳本中新增下列方法:

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

此指令在 notepad!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 位數值會引起問題,這些值可能在最高位元組中具有旗標。 為了處理這個問題,任何進入 JavaScript 的 64 位元原生值(無論是來自原生程式碼還是資料模型)都會以函式庫類型的形式輸入,而不是作為 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 位程式庫類型之上。 如果延伸模組需要 (或可能) 傳入 64 位元值的精確度高於 53 位元,則應使用下列方法,而不是依賴標準運算子:

方法名稱 簽章 說明
作為編號 .asNumber() 將 64 位元值轉換為 JavaScript 數字。 如果發生精確度損失,則會拋出例外**
convertToNumber .convertToNumber() 將 64 位元值轉換為 JavaScript 數字。 如果發生精確度損失,則不會拋出任何例外。
getLowPart .getLowPart() 將 64 位值的低 32 位轉換為 JavaScript 數字
getHighPart .getHighPart() 將 64 位值的高 32 位轉換為 JavaScript 數字
新增 .add(值) 將值新增至 64 位元值,並傳回結果
差集 .subtract(值) 從 64 位元值中減去一個值並傳回結果
.multiply(值) 將 64 位元值乘以提供的值,並傳回結果
分割 .divide(值) 將 64 位元值除以提供的值,並傳回結果
位元和 (bitwise AND) .bitwiseAnd(值) 使用提供的值計算 64 位值的位元 和,並傳回結果
bitwise或 .bitwiseOr(值) 使用提供的值計算 64 位元運算,並傳回結果。
bitwiseX或 .bitwiseXor(值) 使用提供的值計算 64 位值的位元 xor,並傳回結果
位元左移 .bitwiseShiftLeft(值) 將 64 位元值移至指定數量,並傳回結果
位元運算向右移位 .bitwiseShiftRight(值) 將 64 位元值向右移動給定的量並傳回結果
toString .toString([基數]) 將 64 位元值轉換為預設基數 (或選擇性提供的基數) 中的顯示字串

此方法也可用。

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

JavaScript 偵錯

本節說明如何使用偵錯工具的腳本偵錯功能。 偵錯工具已整合支援使用 .scriptdebug (偵錯 JavaScript) 命令偵錯 JavaScript 腳本。

備註

若要搭配 WinDbg 使用 JavaScript 偵錯,請以系統管理員身分執行偵錯工具。

使用此範例程式碼來探索偵錯 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 script debugger 命令來查看我們可以捕捉的事件清單。

>>> 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 指令碼偵錯工具命令來開啟 break on entry ,讓指令碼會在指令碼偵錯工具內的任何程式碼執行時立即陷印到指令碼偵錯工具中。

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

VS Code 中的 JavaScript - 新增 IntelliSense

如果您想要在 VS Code 中使用偵錯工具資料模型物件,您可以使用 Windows 開發套件中可用的定義檔。 IntelliSense 定義檔案提供所有 host.* 偵錯工具物件 API 的支援。 如果您將套件安裝在 64 位 PC 上的預設目錄中,則它位於以下位置:

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

若要在 VS Code 中使用 IntelliSense 定義檔案:

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

  2. 將定義檔案複製到與指令碼相同的資料夾。

  3. /// <reference path="JSProvider.d.ts" /> 新增到 JavaScript 指令碼檔案的頂端。

透過 JavaScript 檔案中的該參考,除了指令碼中的結構之外,VS Code 還會自動在 JSProvider 提供的主機 API 上為您提供 IntelliSense。 例如,輸入「主機」。 然後,您會看到 IntelliSense 的所有可用偵錯工具模型 API。

JavaScript 資源

以下是 JavaScript 資源,在您開發 JavaScript 偵錯擴充功能時可能很有用。

另請參閱

JavaScript 偵錯工具範例腳本

JavaScript 擴充功能中的原生物件