概述
Java Debug Wire Protocol (JDWP) 是用于调试器和它所调试的 Java 虚拟机 (VM)(以下称为目标 VM)之间进行通信的协议。 JDWP 是可选的;它可能在 JDK 的某些实现中不可用。 JDWP的存在可以让同一个调试器工作
- 在同一台计算机上的不同进程中,或者
- 在远程计算机上。
JDWP 与许多协议规范的不同之处在于它仅详细说明格式和布局,而不详细说明传输。 JDWP 实现可以设计为通过简单的 API 接受不同的传输机制。特定的传输不一定支持上面列出的每个调试器/目标 VM 组合。
JDWP 设计得足够简单以便于实现,但它又足够灵活以适应未来的发展。
目前,JDWP 没有指定任何传输交会机制或任何目录服务。将来可能会更改,但可能会在单独的文档中解决。
JDWP 是 Java 平台调试器架构 (JPDA) 中的一层。该体系结构还包含更高级别的 Java 调试接口 (JDI)。 JDWP旨在促进JDI的高效使用;它的许多功能都是为此目的量身定制的。 JDI 比 JDWP 更适合许多调试器工具,尤其是那些用 Java 编程语言编写的工具。有关 Java 平台调试器架构的更多信息,请参阅此版本的 Java 平台调试器体系结构文档。
JDWP 启动
建立传输连接后,在发送任何数据包之前,连接的两端会发生握手:
握手过程有以下步骤:
- 调试器端向 VM 端发送 14 个字节,由字符串“
JDWP-Handshake
”的 14 个 ASCII 字符组成。 - VM 端回复相同的 14 个字节:“
JDWP-Handshake
”。
JDWP 数据包
JDWP 是基于数据包的,不是有状态的。有两种基本的数据包类型:命令数据包和回复数据包。
命令数据包可以由调试器或目标 VM 发送。调试器使用它们从目标 VM 请求信息,或控制程序执行。命令数据包由目标 VM 发送,以通知调试器目标 VM 中的某些事件,例如断点或异常。
回复数据包仅在响应命令数据包时发送,并始终提供命令成功或失败的信息。回复数据包还可以携带命令中请求的数据(例如,字段或变量的值)。目前,从目标 VM 发送的事件不需要来自调试器的响应数据包。
JDWP 是异步的;在收到第一个回复包之前,可能会发送多个命令包。
命令和回复包头大小相等;这是为了使传输更容易实现和抽象。每个数据包的布局如下所示:
- Command Packet
-
- Header
- 长度(4 字节)
- 标识(4 个字节)
- 标志(1 字节)
- 命令集(1 字节)
- 命令(1 字节)
- 数据(变量)
- Header
- Reply Packet
-
- Header
- 长度(4 字节)
- 标识(4 个字节)
- 标志(1 字节)
- 错误代码(2 字节)
- 数据(变量)
- Header
通过 JDWP 发送的所有字段和数据都应采用大端格式。 (有关 big-endian 的定义,请参阅 Java 虚拟机规范。)前三个字段在两种数据包类型中是相同的。
命令和回复数据包字段
共享标头字段
长度
长度字段是整个数据包的大小,以字节为单位,包括长度字段。标头大小为 11 字节,因此没有数据的数据包会将此字段设置为 11。
id
id 字段用于唯一标识每个数据包命令/回复对。回复数据包与其回复的命令数据包具有相同的 id。这允许匹配异步命令和回复。 id 字段在从一个源发送的所有未完成命令中必须是唯一的。 (源自调试器的未完成命令可能使用与源自目标 VM 的未完成命令相同的 id。)除此之外,对 id 的分配没有要求。
一个简单的单调计数器应该足以满足大多数实现。它将允许 2^32 个独特的未完成数据包并且是最简单的实现方案。
标识
标志用于改变任何命令的排队和处理方式,以及标记来自目标 VM 的命令数据包。当前定义了一个标志位;协议的未来版本可能会定义额外的标志。
0x80
- 回复包
回复位在设置时表示此数据包是回复。
命令包头字段
命令集
此字段可用作以有意义的方式对命令进行分组的方法。 Sun 定义的命令集用于按它们在 JDI 中支持的接口对命令进行分组。例如,支持 JDI VirtualMachine 接口的所有命令都分组在一个 VirtualMachine 命令集中。
命令集空间大致划分如下:
0
-63
- 发送到目标 VM 的命令集
64
-127
- 发送到调试器的命令集
128
-256
- 供应商定义的命令和扩展。
命令
该字段标识命令集中的特定命令。该字段与命令集字段一起用于指示应如何处理命令包。更简洁地说,它们告诉接收者该做什么。具体命令将在本文档后面介绍。
回复数据包标头字段
错误代码
该字段用于指示正在回复的命令包是否已成功处理。零值表示成功,非零值表示错误。返回的错误代码可能特定于每个命令集/命令,但它通常映射到一个 JVM TI 错误代码。
Data
数据字段对于每个命令集/命令都是唯一的。命令和回复数据包对之间也是不同的。例如,请求字段值的命令包将包含对其数据字段中所需值的对象和字段 id 的引用。回复数据包的数据字段将包含该字段的值。
详细的命令信息
通常,命令或回复数据包的数据字段是定义命令或回复数据的一组多个字段的抽象。数据字段的每个子字段都以大端 (Java) 格式编码。本节描述了每个命令及其回复的数据字段的详细组成。
有许多不同的 JDWP 命令和回复通用的一小组通用数据类型。它们在下面描述。
Name | 尺寸 | Description |
---|---|---|
byte |
1字节 | 一个字节值。 |
boolean |
1字节 | 一个boolean,编码为 0 表示假,非零表示真。 |
int |
4字节 | 四字节整数值。除非明确声明为无符号,否则整数是有符号的。 |
long |
8字节 | 八字节整数值。除非明确声明为无符号,否则该值是有符号的。 |
objectID |
特定于目标 VM,最多 8 个字节(见下文) | 唯一标识目标 VM 中的对象。一个特定的对象将在 JDWP 命令中由一个 objectID 标识,并在其整个生命周期内进行回复(或者直到 objectID 被显式处置)。除非已显式处置,否则不会重复使用 ObjectID 来标识不同的对象,无论引用的对象是否已被垃圾回收。 objectID 为 0 表示空对象。请注意,对象 ID 的存在不会阻止对象的垃圾回收。任何尝试使用其对象 ID 访问垃圾收集对象都将导致 INVALID_OBJECT 错误代码。可以使用 DisableCollection 命令禁用垃圾收集,但通常不需要这样做。 |
tagged-objectID |
objectID 的大小加上一个字节 | 第一个字节是签名字节,用于标识对象的类型。有关此字节的可能值,请参阅 JDWP.Tag(请注意,只能使用对象标签,而不是原始标签)。 objectID 本身紧跟其后。 |
threadID |
与 objectID 相同 | 唯一标识目标 VM 中已知为线程的对象。 |
threadGroupID |
与 objectID 相同 | 唯一标识目标 VM 中已知为线程组的对象。 |
stringID |
与 objectID 相同 | 唯一标识目标 VM 中已知为字符串对象的对象。注意:这与字符串有很大不同,字符串是一个值。 |
moduleID |
与 objectID 相同 | 唯一标识目标 VM 中已知为模块对象的对象。 |
classLoaderID |
与 objectID 相同 | 唯一标识目标 VM 中已知为类加载器对象的对象。 |
classObjectID |
与 objectID 相同 | 唯一标识目标 VM 中已知为类对象的对象。 |
arrayID |
与 objectID 相同 | 唯一标识目标 VM 中已知为数组的对象。 |
referenceTypeID |
特定于目标 VM,最多 8 个字节(见下文) | 唯一标识目标 VM 中的引用类型。不应假设对于特定类别,classObjectID 和 referenceTypeID 是相同的。一个特定的引用类型将在 JDWP 命令和整个生命周期的回复中由一个 ID 标识。无论引用的类是否已卸载,都不会重复使用 referenceTypeID 来标识不同的引用类型。 |
classID |
与 referenceTypeID 相同 | 唯一标识目标 VM 中已知为类类型的引用类型。 |
interfaceID |
与 referenceTypeID 相同 | 唯一标识目标 VM 中已知为接口类型的引用类型。 |
arrayTypeID |
与 referenceTypeID 相同 | 唯一标识目标 VM 中已知为数组类型的引用类型。 |
methodID |
特定于目标 VM,最多 8 个字节(见下文) | 唯一标识目标 VM 中某个类中的方法。 methodID 必须在其类/接口或其任何子类/子接口/实现器中唯一标识该方法。 methodID 本身不一定是唯一的;它总是与 referenceTypeID 配对以唯一标识一种方法。 referenceTypeID 可以标识方法的声明类型或子类型。 |
fieldID |
特定于目标 VM,最多 8 个字节(见下文) | 唯一标识目标 VM 中某个类中的字段。 fieldID 必须在其类/接口或其任何子类/子接口/实现器中唯一标识该字段。 fieldID 本身不一定是唯一的;它总是与 referenceTypeID 配对以唯一标识一个字段。 referenceTypeID 可以标识字段的声明类型或子类型。 |
frameID |
特定于目标 VM,最多 8 个字节(见下文) | 唯一标识目标 VM 中的框架。 frameID 必须唯一标识整个 VM 中的帧(不仅在给定线程中)。 frameID 只需要在其线程挂起期间有效。 |
location |
目标虚拟机特定 | 可执行位置。该位置由一个字节 类型标签 后跟一个 classID 后跟一个 methodID 后跟一个无符号的八字节索引标识,该索引标识方法内的位置。有关位置索引的详细信息,请参阅 以下。类型标签对于识别位置的 classID 是识别类还是接口是必需的。几乎所有位置都在类中,但可以在接口的静态初始值设定项中包含可执行代码。 |
string |
多变的 | UTF-8 编码的字符串,不以零结尾,前面有一个四字节整数长度。 |
value |
多变的 | 从目标 VM 检索的值。第一个字节是用于标识类型的签名字节。有关此字节的可能值,请参阅 JDWP.Tag。紧随其后的是值本身。此值可以是对象 ID(请参阅获取 ID 大小)或原始值(1 到 8 个字节)。有关每种值类型的更多详细信息,请参见下表。 |
untagged-value |
多变的 | 如上所述的 value 没有签名字节。当可以从上下文确定签名信息时使用此形式。 |
arrayregion |
多变的 | 用于某些数组操作的值的紧凑表示。第一个字节是用于标识类型的签名字节。有关此字节的可能值,请参阅 JDWP.Tag。接下来是一个四字节整数,指示序列中值的数量。接下来是值本身:原始值被编码为 untagged-values 的序列;对象值被编码为 values 的序列。 |
- 该方法的起始位置索引小于该方法中的所有其他位置。
- 该方法结束位置的索引大于该方法中的所有其他位置。
- 如果某个方法存在行号表,则属于特定行的位置必须位于该行的位置索引和表中下一行的位置索引之间。
方法中的索引值从方法中的第一个可执行点到最后一个可执行点单调递增。对于许多实现,方法中的每个字节码指令都有自己的索引,但这不是必需的。
在不同的目标 VM 实现中,对象 ID、引用类型 ID、字段 ID、方法 ID 和框架 ID 的大小可能不同。通常,它们的大小对应于 JNI 和 JVMDI 调用中用于这些项目的本机标识符的大小。这些类型中的任何一个的最大大小都是 8 个字节。调试器使用 VirtualMachine 命令集中的“idSizes”命令来确定每种类型的大小。
如果被调试者收到一个带有未实现或未识别的命令集或命令的命令包,则它会返回一个错误代码字段设置为 NOT_IMPLEMENTED
的回复包(请参阅 错误常量 )。