概述
Java调试线协议(JDWP)是调试器与调试的Java虚拟机(VM)之间通信所使用的协议(以下简称目标VM)。JDWP是可选的;在某些JDK实现中可能不可用。JDWP的存在可以使同一调试器能够在
- 同一台计算机上的不同进程中,或
- 在远程计算机上。
JDWP与许多协议规范不同,它仅详细说明格式和布局,而不涉及传输。JDWP实现可以通过简单的API设计来接受不同的传输机制。特定传输机制不一定支持上述每个调试器/目标VM组合。
JDWP旨在设计得足够简单以便易于实现,同时又足够灵活以支持未来的增长。
目前,JDWP不指定任何传输会合机制或任何目录服务。这在未来可能会发生变化,但可能会在单独的文档中进行讨论。
JDWP是Java平台调试架构(JPDA)中的一层。该架构还包含更高级别的Java调试接口(JDI)。JDWP旨在为JDI的高效使用提供便利;它的许多功能都是为此目的量身定制的。对于许多调试器工具,特别是用Java编程语言编写的工具,JDI比JDWP更合适。有关Java平台调试架构的更多信息,请参阅本版本的Java平台调试架构文档。
JDWP启动
在建立传输连接之后,但在发送任何数据包之前,两端之间会进行握手:
握手过程包括以下步骤:
- 调试器端向VM端发送14个字节,由字符串"
JDWP-Handshake
"的14个ASCII字符组成。 - VM端回复相同的14个字节:"
JDWP-Handshake
"。
JDWP数据包
JDWP是基于数据包的,不具有状态。有两种基本数据包类型:命令数据包和回复数据包。
命令数据包可以由调试器或目标VM发送。它们用于调试器从目标VM请求信息,或控制程序执行。目标VM发送命令数据包以通知调试器目标VM中的某些事件,如断点或异常。
回复数据包仅作为对命令数据包的响应发送,并始终提供命令的成功或失败信息。回复数据包还可以携带在命令中请求的数据(例如字段或变量的值)。目前,从目标VM发送的事件不需要调试器的响应数据包。
JDWP是异步的;在接收到第一个回复数据包之前,可以发送多个命令数据包。
命令和回复数据包的头部大小相同;这是为了使传输更容易实现和抽象。每个数据包的布局如下:
- 命令数据包
-
- 头部
- 长度(4字节)
- id(4字节)
- 标志(1字节)
- 命令集(1字节)
- 命令(1字节)
- 数据(可变)
- 头部
- 回复数据包
-
- 头部
- 长度(4字节)
- id(4字节)
- 标志(1字节)
- 错误代码(2字节)
- 数据(可变)
- 头部
通过JDWP发送的所有字段和数据应采用大端格式。(有关大端的定义,请参阅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错误代码。
数据
数据字段对于每个命令集/命令是唯一的。它还在命令和回复数据包对之间有所不同。例如,请求字段值的命令数据包将在其数据字段中包含对所需值的对象和字段id的引用。回复数据包的数据字段将包含字段的值。
详细命令信息
一般来说,命令或回复数据包的数据字段是定义命令或回复数据的多个字段组的抽象。数据字段的每个子字段都以大端(Java)格式编码。每个命令及其回复的数据字段的详细组成在本节中描述。
有一小组常见数据类型,这些数据类型在许多不同的JDWP命令和回复中是共同的。它们如下所述。
名称 | 大小 | 描述 |
---|---|---|
byte |
1 字节 | 一个字节值。 |
boolean |
1 字节 | 一个布尔值,编码为0表示假,非零表示真。 |
int |
4 字节 | 一个四字节整数值。该整数是有符号的,除非明确声明为无符号的。 |
long |
8 字节 | 一个八字节整数值。该值是有符号的,除非明确声明为无符号的。 |
objectID |
目标虚拟机特定,最多8字节(见下文) | 在目标虚拟机中唯一标识对象。一个特定对象在其生命周期中(或直到显式处置objectID)将由JDWP命令和回复中的一个objectID唯一标识(或直到显式处置objectID)。一个ObjectID不会被重新使用来标识不同的对象,除非它已经被显式处置,无论所引用的对象是否已被垃圾回收。对象ID为0表示空对象。请注意,对象ID的存在并不会阻止对象的垃圾回收。任何尝试使用其对象ID访问已被垃圾回收的对象都将导致INVALID_OBJECT错误代码。可以使用DisableCollection命令禁用垃圾回收,但通常不需要这样做。 |
tagged-objectID |
objectID大小加一字节 | 第一个字节是签名字节,用于标识对象的类型。请参阅JDWP.Tag以获取此字节的可能值(请注意,仅可使用对象标记,而不是原始标记)。紧随其后的是objectID本身。 |
threadID |
与objectID相同 | 在目标虚拟机中唯一标识已知为线程的对象。 |
threadGroupID |
与objectID相同 | 在目标虚拟机中唯一标识已知为线程组的对象。 |
stringID |
与objectID相同 | 在目标虚拟机中唯一标识已知为字符串对象的对象。注意:这与字符串(一个值)非常不同。 |
moduleID |
与objectID相同 | 在目标虚拟机中唯一标识已知为模块对象的对象。 |
classLoaderID |
与objectID相同 | 在目标虚拟机中唯一标识已知为类加载器对象的对象。 |
classObjectID |
与objectID相同 | 在目标虚拟机中唯一标识已知为类对象的对象。 |
arrayID |
与objectID相同 | 在目标虚拟机中唯一标识已知为数组的对象。 |
referenceTypeID |
目标虚拟机特定,最多8字节(见下文) | 在目标虚拟机中唯一标识引用类型。不应假定对于特定类,classObjectID和referenceTypeID是相同的。一个特定引用类型在其生命周期中将由JDWP命令和回复中的一个ID唯一标识。不会重用referenceTypeID来标识不同的引用类型,无论所引用的类是否已被卸载。 |
classID |
与referenceTypeID相同 | 在目标虚拟机中唯一标识已知为类类型的引用类型。 |
interfaceID |
与referenceTypeID相同 | 在目标虚拟机中唯一标识已知为接口类型的引用类型。 |
arrayTypeID |
与referenceTypeID相同 | 在目标虚拟机中唯一标识已知为数组类型的引用类型。 |
methodID |
目标虚拟机特定,最多8字节(见下文) | 在目标虚拟机中的某个类中唯一标识方法。方法ID必须在其类/接口或任何子类/子接口/实现类中唯一标识方法。方法ID本身不一定是唯一的;它总是与referenceTypeID配对以唯一标识一个方法。referenceTypeID可以标识方法的声明类型或子类型。 |
fieldID |
目标虚拟机特定,最多8字节(见下文) | 在目标虚拟机中的某个类中唯一标识字段。字段ID必须在其类/接口或任何子类/子接口/实现类中唯一标识字段。字段ID本身不一定是唯一的;它总是与referenceTypeID配对以唯一标识一个字段。referenceTypeID可以标识字段的声明类型或子类型。 |
frameID |
目标虚拟机特定,最多8字节(见下文) | 在目标虚拟机中唯一标识帧。frameID必须在整个VM中唯一标识帧(不仅在给定线程中)。frameID只在其线程暂停时有效。 |
location |
目标虚拟机特定 | 一个可执行位置。该位置由一个字节类型标签,后跟一个classID,后跟一个methodID,后跟一个无符号八字节索引标识,该索引标识方法内的位置。有关位置索引的详细信息,请参见下文。类型标签是必要的,以确定位置的classID是标识类还是接口。几乎所有位置都在类中,但在接口的静态初始化器中可能存在可执行代码。 |
string |
变量 | 一个UTF-8编码的字符串,不以零结尾,前面是一个四字节整数长度。 |
value |
变量 | 从目标虚拟机中检索的值。第一个字节是一个签名字节,用于标识类型。请参阅JDWP.Tag以获取此字节的可能值。紧随其后的是值本身。该值可以是objectID(请参阅获取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
的回复包(请参阅错误常量)。