调试器 API

Debugger 接口

Mozilla 的 JavaScript 引擎 SpiderMonkey 提供了一个名为 Debugger 的调试接口,它允许 JavaScript 代码观察和操纵其他 JavaScript 代码的执行。Firefox 的内置开发者工具和 Firebug 扩展程序都使用 Debugger 来实现其 JavaScript 调试器。但是,Debugger 非常通用,可用于实现其他类型的工具,例如跟踪器、覆盖率分析、修补和继续等等。

Debugger 具有三个基本特性

  • 它是一个源代码级别的接口:它根据 JavaScript 语言进行操作,而不是机器语言。它对 JavaScript 对象、堆栈帧、环境和代码进行操作,并提供一致的接口,无论被调试者是解释执行、编译执行还是优化执行。如果您精通 JavaScript 语言,那么您应该具备使用 Debugger 所需的所有背景知识,即使您从未深入了解过该语言的实现。

  • 它供JavaScript 代码使用。JavaScript 既是被调试语言,也是工具实现语言,因此使 JavaScript 在 Web 上有效的特性可以用于为开发人员创建工具。正如预期的那样,JavaScript API Debugger 是一个健全的接口:使用(甚至滥用)Debugger 绝不应该导致 Gecko 崩溃。错误会抛出正确的 JavaScript 异常。

  • 它是一个线程内调试 API。被调试者和使用 Debugger 观察它的代码必须在同一个线程中运行。跨线程、跨进程和跨设备工具必须使用 Debugger 从同一线程内观察被调试者,然后自行处理任何必要的通信。(Firefox 的内置工具为此目的定义了一个协议)。

在 Gecko 中,Debugger API 仅对 chrome 代码可用。根据设计,它不应该引入安全漏洞,因此原则上它也可以提供给内容;但很难证明增加攻击面的安全风险是合理的。

Debugger API 目前无法观察自托管 JavaScript。这不是 API 设计固有的,而是自托管基础设施尚未准备好应对 Debugger API 可以执行的入侵。

调试器实例和影子对象

Debugger 将被调试者状态的各个方面反映为 JavaScript 值——不仅是实际的 JavaScript 值(如对象和基本类型),还包括堆栈帧、环境、脚本和编译单元,这些通常不能作为它们本身的对象访问。

以下是一个正在运行计时器回调函数的 JavaScript 程序

A running JavaScript program and its Debugger shadows

正在运行的 JavaScript 程序及其调试器影子

此图显示了构成调试器 API 的各种类型的影子对象(它们都遵循一些通用约定)

  • 一个Debugger.Object 表示一个被调试者对象,提供一个面向反射的 API,防止调试器意外调用 getter、setter、代理陷阱等等。

  • 一个Debugger.Script 表示一个 JavaScript 代码块——函数体或顶级脚本。给定一个 Debugger.Script,可以设置断点,在源代码位置和字节码偏移量之间转换(偏离“源代码级别”设计原则),并找到代码的其他静态特性。

  • 一个Debugger.Frame 表示一个正在运行的堆栈帧。您可以使用它们遍历堆栈并找到每个帧的脚本和环境。您还可以为帧设置 onSteponPop 处理程序。

  • 一个Debugger.Environment 表示一个环境,将变量名与存储位置关联起来。环境可能属于一个正在运行的堆栈帧、被函数闭包捕获,或者将某个全局对象的属性反映为变量。

Debugger 实例本身并不是被调试者中任何内容的影子;相反,它维护着一组要被视为被调试者的全局对象。一个 Debugger 仅观察在这些全局对象的范围内发生的执行。您可以设置函数,在推送新的堆栈帧时调用;在加载新代码时调用;等等。

此图中省略了 Debugger.Source 实例,它们表示 JavaScript 编译单元。一个 Debugger.Source 可以提供其源代码的完整副本,并解释代码如何进入系统,无论是通过对 eval 的调用、<script> 元素还是其他方式。一个 Debugger.Script 指向其派生的 Debugger.Source

还省略了 DebuggerDebugger.Memory 实例,它包含用于观察被调试者内存使用情况的方法和访问器。

所有这些类型都遵循一些通用约定,在深入研究任何特定类型的规范之前,您应该先浏览这些约定。

所有影子对象对于每个 Debugger 和每个引用都是唯一的。对于给定的 Debugger,只有一个 Debugger.Object 引用特定的被调试者对象;对于特定的堆栈帧,只有一个 Debugger.Frame;等等。因此,工具可以将关于影子的引用的元数据存储为影子本身上的属性,并在再次遇到相同的引用时找到该元数据。并且由于影子是每个 Debugger 的,因此工具可以这样做而无需担心干扰使用自己 Debugger 实例的其他工具。

示例

以下是一些您可以自己尝试的事情,它们展示了 Debugger 的一些功能

Gecko 特定功能

虽然 Debugger 核心 API 仅处理任何 JavaScript 实现通用的概念,但它也包含一些 Gecko 特定的功能

  • [全局跟踪][global] 支持一次调试 Gecko 实例中运行的所有代码——“chrome 调试”模型。

  • [对象包装器][wrapper] 函数有助于操作跨越权限边界的对象引用。

源代码元数据

从文件生成

js/src/doc/Debugger/Debugger-API.md

水印

sha256:6ee2381145a0d2e53d2f798f3f682e82dd7ab0caa0ac4dd5e56601c2e49913a7

变更集

ffa775dd5bd4