本主题介绍如何使用 JavaScript 创建脚本来了解调试器对象并扩展和自定义调试器的功能。
JavaScript 调试器脚本概述
脚本提供程序将脚本语言桥接至调试器的内部对象模型。 JavaScript 调试器脚本提供程序允许将 JavaScript 与调试器一起使用。
通过 .scriptload 命令加载 JavaScript 时,将执行脚本的根代码、脚本中存在的名称会桥接到调试器(dx Debugger)的根命名空间中,并且脚本将一直驻留在内存中,直到卸载该脚本并释放对其对象的所有引用。 该脚本可以为调试器的表达式计算器提供新函数、修改调试器的对象模型,也可以像 NatVis 可视化工具那样充当可视化工具。
此主题描述了使用 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 调试器脚本。
- .scriptproviders (列出脚本提供者)
- .scriptload (加载脚本)
- .scriptunload (卸载脚本)
- .scriptrun (运行脚本)
- .scriptlist (列出加载的脚本)
要求
在使用任何 .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 (卸载脚本)
.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 (显示 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 (卸载脚本) |
---|---|---|---|
根 | 是的 | 是的 | |
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,它是一个指向内存中多个整数的指针,数量等于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 (加载扩展 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");
}
}
该命令在 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 位库类型之上。 如果扩展需要(或可能)需要 53 位以上的精度才能传入 64 位值,则应使用以下方法,而不是依赖标准运算符:
“方法名称” | 签名 | 说明 |
---|---|---|
asNumber | .asNumber() | 将 64 位值转换为 JavaScript 数字。 如果精度丢失,将抛出异常 |
convertToNumber | .convertToNumber() | 将 64 位值转换为 JavaScript 数字。 如果精度丢失,将不会抛出异常。 |
getLowPart | .getLowPart() | 将 64 位值的低 32 位转换为 JavaScript 数字 |
getHighPart | .getHighPart() | 将 64 位整数的高 32 位转换为 JavaScript 数值 |
添加 | .add(value) | 将值添加到 64 位值并返回结果 |
相减 | .subtract(value) | 从 64 位值中减去值并返回结果 |
multiply | .multiply(value) | 将 64 位值乘以提供的值并返回结果 |
相割 | .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 (调试 JavaScript) 命令调试 JavaScript 脚本。
注释
若要将 JavaScript 调试与 WinDbg 配合使用,请以管理员身份运行调试器。
使用此示例代码探索调试 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 脚本调试器命令禁用入口事件。
>>> 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 位电脑上的默认目录中安装了该工具包,则它位于以下位置:
C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\winext\JsProvider.d.ts
若要在 VS Code 中使用 IntelliSense 定义文件,请执行以下作:
找到定义文件 - JSProvider.d.ts
将定义文件复制到脚本所在的同一文件夹中。
在您的 JavaScript 脚本文件顶部添加
/// <reference path="JSProvider.d.ts" />
。
在 JavaScript 文件中使用该引用后,除了脚本中的结构外,VS Code 还会在 JSProvider 提供的主机 API 上自动提供 IntelliSense。 例如,键入“host”。 你将看到所有可用的调试器模型 API 的 IntelliSense。
JavaScript 资源
以下是开发 JavaScript 调试扩展时可能有用的 JavaScript 资源。