本主题介绍有关在 JavaScript 扩展中使用本机调试器对象的其他详细信息。
本机调试器对象表示调试器环境的各种构造和行为。 可以将对象传递到 JavaScript 扩展中或在其中获取,以操控调试器的状态。
有关调试器对象 JavaScript 扩展的信息,请参阅 JavaScript 扩展中的本机调试器对象。
有关使用 JavaScript 的一般信息,请参阅 JavaScript 调试器脚本。
调试器团队在 GitHub 上托管了一个用于 JavaScript 脚本和扩展的存储库。https://github.com/Microsoft/WinDbg-Samples
JavaScript 扩展中的调试器对象
传递原生对象
可以通过多种方式在 JavaScript 扩展中传入或获取调试器对象。
- 它们可以传递给 JavaScript 函数或方法
- 它们可以是 JavaScript 原型的实例对象(例如可视化工具)
- 可以从旨在创建本机调试器对象的主机方法返回它们
- 它们可以从旨在创建调试器本机对象的主机方法中返回。
传递给 JavaScript 扩展的调试器对象具有本部分中介绍的一组功能。
- 属性访问
- 预期名称
- 与本机调试器对象相关的特殊类型
- 其他属性
属性访问
虽然 JavaScript 提供程序本身放置的对象上有一些属性,但进入 JavaScript 的本机对象上的大多数属性由数据模型提供。 这意味着,对于属性访问——object.propertyName 或 object[propertyName],将发生以下情况。
- 如果 propertyName 是 JavaScript 提供程序投射到对象上的属性名称,则首先解析为该属性;否则
- 如果propertyName是数据模型(另一个可视化工具)投影到对象上的键的名称,它将其次解析为此名称,否则
- 如果 propertyName 是本地对象字段的名称,它将在第三顺位解析为该名称;否则
- 如果对象是指针,将解引用指针,并且上述流程将继续(解引用对象的投影属性后跟着一个键,然后是本机字段)
JavaScript 中属性访问的常用方式 -- object.propertyName 和 object[propertyName] -- 会访问对象的内置字段,就像调试器中的 "dx" 命令一样。
预期名称
以下属性(和方法)投影到进入 JavaScript 的本机对象上。
方法 | 签名 | DESCRIPTION |
---|---|---|
主机上下文 | 资产 | 返回一个对象,该对象表示对象所在的上下文(地址空间、调试目标等...) |
targetLocation | 资产 | 返回一个对象,该对象是对象在地址空间中的抽象(虚拟地址、寄存器、子寄存器等...) |
targetSize | 资产 | 返回对象的大小(有效: sizeof(<TYPE OF OBJECT>) |
添加父模型 | .addParentModel(object) | 将新的父模型(类似于 JavaScript 原型,但在数据模型端)添加到对象 |
移除父模型 | .removeParentModel(object) | 从对象中删除给定的父模型 |
运行时类型化对象 | 资产 | 对对象进行分析,并尝试将其转换为最派生的运行时类型。 |
目标类型 | 资产 | JavaScript 扩展可以直接访问基础语言的类型系统。 此访问通过类型对象的概念来表示。 有关详细信息,请参阅 JavaScript 扩展中的本机调试器对象 - 类型对象 |
如果对象是指针,则以下属性(和方法)投影到进入 JavaScript 的指针上:
属性名称 | 签名 | DESCRIPTION |
---|---|---|
添加 | .add(value) | 在指针和指定值之间执行指针数学加法 |
地址 | 资产 | 以 64 位序号对象的形式返回指针的地址(库类型) |
取消引用 | .dereference() | 取消引用指针并返回基础对象 |
isNull | 资产 | 返回指针值是否为 nullptr (0) |
与本机调试器对象相关的特殊类型
位置对象
从本机对象的 targetLocation 属性返回的位置对象包含以下属性(和方法)。
属性名称 | 签名 | DESCRIPTION |
---|---|---|
添加 | .add(value) | 向位置添加一个绝对的字节偏移量。 |
相减 | .subtract(value) | 从当前位置中减去一个绝对字节偏移量。 |
其他属性
可迭代性
任何被数据模型理解为可迭代的对象(无论是本机数组,还是通过可视化工具(如 NatVis 或其它)被使能为可迭代)都会有一个迭代器函数(通过 ES6 标准的 Symbol.iterator 索引)附加在其上。 这意味着可以在 JavaScript 中迭代原生对象,如下所示。
function iterateNative(nativeObject)
{
for (var val of nativeObject)
{
//
// val will contain each element iterated from the native object. This would be each element of an array,
// each element of an STL structure which is made iterable through NatVis, each element of a data structure
// which has a JavaScript iterator accessible via [Symbol.iterator], or each element of something
// which is made iterable via support of IIterableConcept in C/C++.
//
}
}
可索引性
能够通过序数在单一维度上进行索引的对象(例如:原生数组)将可以通过标准属性访问运算符 -- object[index] 在 JavaScript 中进行索引。 如果对象可按名称编制索引,或者可在多个维度中编制索引,则 getValueAt 和 setValueAt 方法将投影到对象上,以便 JavaScript 代码可以利用索引器。
function indexNative(nativeArray)
{
var first = nativeArray[0];
}
字符串转换
任何支持通过 IStringDisplayableConcept 或 NatVis DisplayString 元素进行显示字符串转换的本机对象,都可以通过标准的 JavaScript toString 方法来访问该字符串转换。
function stringifyNative(nativeObject)
{
var myString = nativeObject.toString();
}
创建本机调试器对象
如前所述,JavaScript 脚本可以通过多种方式之一传递到 JavaScript 来访问本机对象,也可以通过对主机库的调用创建它们。 使用以下函数创建本机调试器对象。
方法 | 签名 | DESCRIPTION |
---|---|---|
host.getModuleSymbol |
getModuleSymbol(moduleName, symbolName, [contextInheritor]) getModuleSymbol(moduleName, symbolName, [typeName], [contextInheritor]) |
返回特定模块中全局符号的对象。 模块名称和符号名称是字符串。 如果提供了可选的 contextInheritor 参数,则模块和符号将会在与传递对象相同的上下文(地址空间,调试目标)中进行查找。 如果未提供参数,模块和符号将在调试器的当前上下文中查找。 不是一次性测试脚本的 JavaScript 扩展应始终提供显式上下文。 如果提供了可选的 typeName 参数,则假定该符号为传递的类型,并且符号(s)中指示的类型将被忽略。 请注意,任何预期操作模块公共符号的调用方应始终提供显式类型名称。 |
host.getModuleContainingSymbol |
getModuleContainingSymbol(location, [contextInheritor]) | 返回包含给定地址的符号(例如:函数或数据)。 请注意,仅当包含给定地址的模块有 专用 符号时,此作才起作用。 如果提供了可选的 contextInheritor 参数,则模块和符号将会在传递对象的相同上下文(地址空间,调试目标)中进行查找。 如果未提供参数,模块和符号将在调试器的当前上下文中查找。 不是一次性测试脚本的 JavaScript 扩展应始终提供显式上下文。 |
host.createPointerObject |
createPointerObject(address, moduleName, typeName, [contextInheritor]) |
在指定的地址或位置创建指针对象。 模块名称和类型名称是字符串。 如果提供了可选参数contextInheritor,那么模块和符号将在与传递对象相同的上下文(地址空间、调试目标)中进行查找。 如果未提供参数,模块和符号将在调试器的当前上下文中查找。 不是一次性测试脚本的 JavaScript 扩展应始终提供显式上下文。 |
host.createTypedObject |
createTypedObject(location, moduleName, typeName, [contextInheritor]) |
创建一个对象,该对象表示位于调试目标的地址空间中指定位置的本机类型对象。 模块名称和类型名称是字符串。 如果提供了可选的 contextInheritor 参数,则模块和符号将在与传递对象相同的上下文(地址空间、调试目标)中进行查找。 如果未提供参数,模块和符号将在调试器的当前上下文中查找。 不是一次性测试脚本的 JavaScript 扩展应始终提供显式上下文。 |
JavaScript 扩展的主机 API(应用程序接口)
JavaScript 提供程序将一个名为主机的对象插入到它加载的每个脚本的全局命名空间中。 此对象提供对脚本的关键功能的访问权限,以及访问调试器的命名空间。 分两个阶段进行设置。
阶段 1:在执行任何脚本之前,主机对象仅包含脚本初始化自身和注册其扩展点(作为生成者和使用者)所需的最小功能集。 根代码和初始化代码不打算操纵调试目标的状态或执行复杂操作,因此,在 initializeScript 方法返回之前,主机不会完全初始化。
阶段 2:当 initializeScript 返回后,主机对象将被填充用于操作调试目标状态所需的一切内容。
主机对象级别
一些关键功能片段直接位于主机对象下。 其余部分属于子命名空间。 命名空间包括以下内容。
Namespace | DESCRIPTION |
---|---|
诊断 | 帮助诊断和调试脚本代码的功能 |
内存 | 在调试目标中启用内存读取和写入的功能 |
根级别
直接在主机对象中,可以找到以下属性、方法和构造函数。
名称 | 签名 | 阶段当前 | DESCRIPTION |
---|---|---|---|
createPointerObject | createPointerObject(address,moduleName,typeName,[contextInheritor]) |
2 | 在指定的地址或位置创建指针对象。 模块名称和类型名称是字符串。 可选 contextInheritor 参数与 getModuleSymbol 一样工作。 |
createTypedObject | createTypedObject(location, moduleName, typeName, [contextInheritor]) |
2 | 创建一个对象,该对象表示位于指定位置的调试目标的地址空间中的本机类型对象。 模块名称和类型名称是字符串。 可选 contextInheritor 参数与 getModuleSymbol 一样工作。 |
currentProcess | 资产 |
2 | 返回表示调试器的当前进程的对象 |
当前会话 | 资产 |
2 | 返回当前会话中正在被调试的目标、转储等内容的对象,该对象代表了调试器的状态。 |
当前线程 | 资产 |
2 | 返回表示调试器的当前线程的对象 |
evaluateExpression | evaluateExpression(表达式,[contextInheritor]) |
2 | 这会调用调试主机以仅使用调试目标的语言来计算表达式。 如果提供了可选的 contextInheritor 参数,将在参数的上下文(例如地址空间和调试目标)中计算表达式;否则,它将在调试器的当前上下文中进行评估 |
evaluateExpressionInContext | evaluateExpressionInContext(context, expression) |
2 | 这会调用调试主机以仅使用调试目标的语言来计算表达式。 上下文参数表示用于评估的隐式“this”指针。 表达式将在由 上下文 参数指示的上下文(例如:地址空间和调试目标)中进行计算。 |
获取模块符号 (getModuleSymbol) | getModuleSymbol(moduleName, symbolName, [contextInheritor]) |
2 | 返回特定模块中全局符号的对象。 模块名称和符号名称是字符串。 如果提供了可选的 contextInheritor 参数;则模块和符号将在与传递的对象相同的上下文(地址空间、调试目标)中查找。 如果未提供参数,模块和符号将在调试器的当前上下文中查找。 不是一次性脚本的 JavaScript 扩展应始终提供显式上下文 |
getNamedModel | getNamedModel(modelName) |
2 | 返回针对给定名称注册的数据模型。 请注意,针对尚未注册的名称调用此名称是完全合法的。 这样做将为该名称创建一个存根,并且对该存根的操作将在注册时反映到实际对象上。 |
索引值 | new indexedValue(value,indices) |
2 | 为了将一组默认索引分配给迭代值,JavaScript 迭代器可以返回一个对象构造函数。 索引集必须表示为 JavaScript 数组。 |
Int64 | new Int64(value, [highValue]) |
1 | 这构造了一个库 Int64 类型。 单参数版本将接受任何无需转换即可打包到 Int64 中的值,并将其放入其中。 如果提供了可选的第二个参数,则第一个参数的转换将打包到较低的 32 位,第二个参数的转换将打包到前 32 位。 |
namedModelParent | new namedModelParent(object, name) |
1 | 要放置在从 initializeScript 返回的数组中的对象的构造函数,它表示使用 JavaScript 原型或 ES6 类作为具有给定名称的数据模型的数据模型父扩展 |
命名模型注册 | new namedModelRegistration(object,name) |
1 | 要放置在从 initializeScript 返回的数组中的对象的构造函数,这表示通过已知名称将 JavaScript 原型或 ES6 类注册为数据模型,以便其他扩展可以查找和扩展 |
命名空间 | 资产 |
2 | 提供对调试器根命名空间的直接访问权限。 例如,可以通过 host.namespace.Debugger.Sessions.First()访问第一个调试目标的进程列表。使用此属性的进程 |
registerNamedModel | registerNamedModel(object, modelName) |
2 | 这会将 JavaScript 原型或 ES6 类注册为给定名称下的数据模型。 这样的注册允许其他脚本或调试器扩展定位和扩展原型或类。 请注意,脚本应倾向于从其 initializeScript 方法返回 namedModelRegistration 对象,而不是强制执行此作。 任何必须进行更改的脚本都必须具有 initializeScript 方法才能清理。 |
registerExtensionForTypeSignature | registerExtensionForTypeSignature(object, typeSignature) |
2 | 这会将 JavaScript 原型或 ES6 类注册为指定本机类型的扩展数据模型,根据提供的类型签名。 请注意,脚本应倾向于从其 initializeScript 方法返回 typeSignatureExtension 对象,而不是强制执行此作。 任何必须进行更改的脚本都必须具有 initializeScript 方法才能清理。 |
注册类型签名的原型 | registerPrototypeForTypeSignature(object, typeSignature) |
2 | 这会将 JavaScript 原型或 ES6 类注册为由提供的类型签名定义的本机类型的标准数据模型(例如:可视化工具)。 请注意,脚本应倾向于从其 initializeScript 方法返回 typeSignatureRegistration 对象,而不是强制执行此作。 任何必须进行更改的脚本都必须具有 uninitializeScript方法才能清理。 |
parseInt64 | parseInt64(string, [radix]) |
1 | 此方法的行为类似于标准的 JavaScript parseInt 方法,所不同的是,它返回一个库的 Int64 类型。 如果提供了基数,则分析将按指示在 2、8、10 或 16 进制中进行。 |
typeSignatureExtension | new typeSignatureExtension(object, typeSignature, [moduleName], [minVersion], [maxVersion]) |
1 | 要放置在从 initializeScript 返回的数组中的对象的构造函数,这表示 JavaScript 原型或 ES6 类通过类型签名描述的本机类型的扩展。 这种注册会将字段添加到调试器可视化的任何与签名匹配的类型中,而不是完全接管该可视化。 可选的模块名称和版本可以限制注册。 版本指定为“1.2.3.4”样式字符串。 |
类型签名注册 | new typeSignatureRegistration(object,typeSignature,[moduleName],[minVersion],[maxVersion]) |
1 | 要放置在从 initializeScript 返回的数组中的对象的构造函数,这表示针对本机类型签名的 JavaScript 原型或 ES6 类的规范注册。 这种注册会“接管”调试器对任何与签名匹配的类型的可视化,而不仅仅是扩展它。 可选的模块名称和版本可以限制注册。 版本指定为“1.2.3.4”字符串格式。 |
注销命名模型 | unregisterNamedModel(modelName) |
2 | 这会通过给定的名称,将数据模型的注册取消,从而撤消由 registerNamedModel 执行的所有操作。 |
取消注册类型签名的扩展 | unregisterExtensionForTypeSignature(object, typeSignature, [moduleName], [minVersion], [maxVersion]) |
2 | 这会取消注册 JavaScript 原型或 ES6 类,使其不再根据提供的类型签名作为本机类型的扩展数据模型。 它是 registerExtensionForTypeSignature 方法的逻辑撤销。 请注意,脚本应倾向于从其 initializeScript 方法返回 typeSignatureExtension 对象,而不是强制执行此作。 任何必须进行更改的脚本都必须具有 initializeScript 方法才能清理。 可选的模块名称和版本可以限制注册。 版本指定为“1.2.3.4”样式字符串。 |
注销类型签名的原型 | unregisterPrototypeForTypeSignature(object, typeSignature, [moduleName], [minVersion], [maxVersion]) |
2 | 这将取消注册 JavaScript 原型或 ES6 类作为指定类型签名所提供的原生类型的标准数据模型(例如:可视化工具)。 它是 registerPrototypeForTypeSignature 的逻辑撤消。 请注意,脚本应倾向于从其 initializeScript 方法返回 typeSignatureRegistration 对象,而不是强制执行此作。 任何必须进行更改的脚本都必须具有 uninitializeScript 方法才能清理。 可选的模块名称和版本可以限制注册。 版本指定为“1.2.3.4”样式字符串。 |
诊断功能
主机对象的诊断子命名空间包含以下项。
名称 | 签名 | 阶段当前 | DESCRIPTION |
---|---|---|---|
调试日志 | debugLog(object...) | 1 | 这为脚本扩展提供 printf 样式调试。 目前,debugLog 的输出将路由到调试器的输出控制台。 在以后的某个时间点,计划提供此输出的路由灵活性。 注意:这不应用作将用户输出打印到控制台的方法。 它将来可能不会路由到那里。 |
内存功能
主机对象的内存子命名空间包含以下项。
名称 | 签名 | 阶段当前 | DESCRIPTION |
---|---|---|---|
readMemoryValues | readMemoryValues(location, numElements, [elementSize], [isSigned], [contextInheritor]) |
2 | 这会从调试目标的地址空间中读取值的原始数组,并将类型化数组置于此内存视图的顶部。 提供的位置可以是地址(64 位值)、位置对象或本机指针。 数组的大小由 numElements 参数指示。 数组的每个元素的大小(和类型)由可选的 elementSize 和 isSigned 参数提供。 如果未提供此类参数,则默认值为字节(无符号/1 字节)。 如果提供了可选的 contextInheritor 参数,则会在上下文(例如:地址空间和调试目标)中读取由该参数指示的内存;否则,将从调试器的当前上下文中读取它。 请注意,对 8、16 和 32 位值使用此方法会导致在读取内存上放置快速类型化视图。 使用这种方法处理 64 位值会生成一个 64 位库类型的数组,其资源开销会显著增加! |
readString | readString(location, [contextInheritor]) readString(location, [length], [contextInheritor]) |
2 | 这会从调试目标的地址空间读取窄(当前代码页)字符串,将其转换为 UTF-16,并将结果作为 JavaScript 字符串返回。 如果无法读取内存,它可能会引发异常。 提供的位置可以是地址(64 位值)、位置对象或本机字符。 如果提供了可选的 contextInheritor 参数,则会在上下文(例如:地址空间和调试目标)中读取由该参数指示的内存;否则,将从调试器的当前上下文中读取它。 如果提供了可选 长度 参数,则读取字符串将为指定的长度。 |
readWideString | readWideString(location, [contextInheritor]) readWideString(location,[length],[contextInheritor]) |
2 | 这会从一个调试目标的地址空间读取宽字符(UTF-16)字符串,并将结果作为 JavaScript 字符串返回。 如果无法读取内存,它可能会引发异常。 提供的位置可以是地址(64 位值)、位置对象或本机wchar_t。 如果提供了可选的 contextInheritor 参数,则会在上下文(例如:地址空间和调试目标)中读取由该参数指示的内存;否则,将从调试器的当前上下文中读取它。 如果提供了可选 长度 参数,则读取字符串将为指定的长度。 |
JavaScript 中的数据模型概念
数据模型映射
以下数据模型概念映射到 JavaScript。
概念 | 本机接口 | JavaScript 等效项 |
---|---|---|
字符串转换 | IString显示概念 | 标准:toString(...){...} |
可迭代性 | IIterableConcept | standard: [Symbol.iterator]() {...} |
可索引性 | IIndexableConcept | 协议:getDimensionality(...) / getValueAt(...) / setValueAt(...) |
运行时类型转换 | IPreferredRuntimeTypeConcept | 协议:getPreferredRuntimeTypedObject(...) |
字符串转换
字符串转换概念(IStringDisplayableConcept)直接转换为标准 JavaScript toString 方法。 由于所有 JavaScript 对象都有字符串转换(如果未在其他地方提供),因此返回到数据模型的每个 JavaScript 对象都可以转换为显示字符串。 覆盖字符串转换只需实现自己的 toString 方法。
class myObject
{
//
// This method will be called whenever any native code calls IStringDisplayableConcept::ToDisplayString(...)
//
toString()
{
return "This is my own string conversion!";
}
}
可迭代性
数据模型关于对象是否可迭代的概念直接映射到对象是否可迭代的 ES6 协议。 具有 [Symbol.iterator] 方法的任何对象都被视为可迭代。 实现此类将使对象可迭代。
仅可迭代的对象可以具有如下所示的实现。
class myObject
{
//
// This method will be called whenever any native code calls IIterableConcept::GetIterator
//
*[Symbol.iterator]()
{
yield "First Value";
yield "Second Value";
yield "Third Value";
}
}
必须为可迭代和可索引的对象提供特殊注意事项,因为从迭代器返回的对象必须包含索引以及通过特殊返回类型的值。
可迭代和可索引
可迭代且可索引的对象需要迭代器的特殊返回值。 迭代器不生成值,而是生成 indexedValue 的实例。 索引以数组形式作为第二个参数传递给 indexedValue 构造函数。 它们可以是多维的,但必须与索引器协议中返回的维数匹配。
此代码演示了一个实现示例。
class myObject
{
//
// This method will be called whenever any native code calls IIterableConcept::GetIterator
//
*[Symbol.iterator]()
{
//
// Consider this a map which mapped 42->"First Value", 99->"Second Value", and 107->"Third Value"
//
yield new host.indexedValue("First Value", [42]);
yield new host.indexedValue("Second Value", [99]);
yield new host.indexedValue("Third Value", [107]);
}
}
可索引性
与 JavaScript 不同,数据模型在属性访问和索引之间进行了非常明确的区分。 任何希望在数据模型中将自身呈现为可索引的 JavaScript 对象都必须实现一个协议,该协议由 getDimensionality 方法组成,该方法返回索引器的维数以及一对可选的 getValueAt 和 setValueAt 方法,该方法在提供的索引处执行对象读取和写入。 如果对象是只读的或只写的,则可以忽略 getValueAt 或 setValueAt 方法
class myObject
{
//
// This method will be called whenever any native code calls IIndexableConcept::GetDimensionality or IIterableConcept::GetDefaultIndexDimensionality
//
getDimensionality()
{
//
// Pretend we are a two dimensional array.
//
return 2;
}
//
// This method will be called whenever any native code calls IIndexableConcept::GetAt
//
getValueAt(row, column)
{
return this.__values[row * this.__columnCount + column];
}
//
// This method will be called whenever any native code calls IIndexableConcept::SetAt
//
setValueAt(value, row, column)
{
this.__values[row * this.__columnCount + column] = value;
}
}
运行时类型转换
这仅适用于注册到原生类型系统的 JavaScript 原型/类。 调试器通常能够执行分析(例如 Run-Time 类型信息(RTTI)/v 表分析),以便从代码中表示的静态类型确定对象的真实运行时类型。 针对本机类型注册的数据模型可以通过 IPreferredRuntimeTypeConcept 的实现来替代此行为。 同样,注册在一个原生对象上的 JavaScript 类或原型可以通过实现一个协议提供自己的功能,该协议由 getPreferredRuntimeTypedObject 方法组成。
请注意,虽然此方法在技术上可以返回任何内容,但它被认为是错误的形式,因为它返回的内容不是运行时类型或派生类型。 这可能会导致调试器用户产生重大混淆。 但是,重写此方法对于实现 C语言风格的头文件和对象样式等内容来说非常有价值。
class myNativeModel
{
//
// This method will be called whenever the data model calls IPreferredRuntimeTypeConcept::CastToPreferredRuntimeType
//
getPreferredRuntimeTypedObject()
{
var loc = this.targetLocation;
//
// Perform analysis...
//
var runtimeLoc = loc.Add(runtimeObjectOffset);
return host.createTypedObject(runtimeLoc, runtimeModule, runtimeTypeName);
}
}