Java 平台调试器架构服务提供者接口
Java调试接口(JDI)中的服务提供者接口使得可以开发和部署Connector
和TransportService
实现。 TransportService
类代表了Connector
使用的基础传输服务,并用于在调试器和目标VM之间建立连接并传输Java调试线协议(JDWP)数据包。
除了JDI中的服务提供者接口外,JDK还包括一个名为Java调试线协议传输接口(jdwpTransport)的传输库接口。这是一个本地(C/C++)接口,允许开发和部署传输库。传输库由调试对象端的JDWP代理加载,并用于在调试器和VM之间建立连接并传输JDWP数据包。
本页面描述了新接口可能被使用的一些场景。它还提供了开发和部署新Connector和传输实现所涉及的任务概述。
示例场景
服务提供者接口和本地传输接口预计将被以下类别的用户使用:
- 需要开发新的
LaunchingConnector
实现或希望为远程调试提供超越Oracle提供的TCP/IP和共享内存传输的其他传输选项的调试器和工具供应商。 - 需要使用不同于TCP/IP的传输的嵌入式设备、操作系统或虚拟机供应商。
鉴于上述用户类别,以下描述了可能使用新接口的一些场景。
-
在许多环境中,特别是嵌入式设备,调试器可能需要使用除TCP/IP之外的传输连接到目标VM。在这种环境中,新的服务提供者接口可用于开发和支持使用替代传输进行调试。
在调试对象端,可以通过实现jdwpTransport接口开发新传输的传输库。对于调试器,可以开发一个TransportService实现。当TransportService实现部署时,JDI VirtualMachineManager实现将自动创建一个AttachingConnector和一个ListeningConnector,以允许远程调试目标VM。
-
在某些环境中,调试器可能需要使用除传输之外的机制连接到目标VM。例如,调试器可能被用于以只读方式附加到崩溃转储或挂起进程。
对于这些示例,可以开发一个AttachingConnector实现。AttachingConnector实现扩展com.sun.jdi.connect.AttachingConnectors,部署后将出现在VirtualMachineManager的attachingConnectors()方法返回的附加连接器列表中。
-
在许多环境中,目标VM将以非常专有的方式启动。在这种环境中,Oracle提供的com.sun.jdi.CommandLineLaunch LaunchingConnector使用的连接器参数或启动机制可能不足够。在这种情况下,操作系统或虚拟机供应商可能决定开发自己的LaunchingConnector,以便启动目标VM。这个新的LaunchingConnector将具有适当的Connector参数,用于配置目标VM进行调试。部署后,它将出现在VirtualMachineManager对象的launchingConnectors()方法返回的Connectors列表中。
-
另一个示例出现在企业环境中,集成开发环境(IDE)实现者希望支持通过非Oracle提供的传输进行调试。例如,IDE可能希望提供通过TLS/SSL进行安全连接的调试选项。
在这个示例中,IDE实现者使用jdwpTransport接口开发传输库。这允许调试对象使用新传输。在调试器端,IDE实现者有选择。一种选择是开发和部署一个TransportService实现。这个选项将允许使用新传输进行远程调试。
另外,IDE实现者可能决定创建一个封装传输的Connector实现。当IDE实现者希望添加新的Connector参数时,这个选项是合适的。例如,对于安全传输,IDE实现者可能希望有一个具有用于指定密钥库、密码短语或其他配置安全连接所需选项的Connector。如果需要新的Connector,则IDE实现者为调试对象端开发传输库,为调试器端开发Connector。Connector的类型由实现者选择 - 一个示例是LaunchConnector,它启动调试对象并在调试器和调试对象之间建立安全连接。
开发Connector
开发Connector
涉及创建LaunchingConnector
、AttachingConnector
或ListeningConnector
的具体实现。
每个Connector
实现必须有一个公共无参构造函数,除了实现所有Connector
方法。构造函数将在初始化期间由VirtualMachineManager调用。构造函数可以是空操作,也可以执行初始化任务,如加载传输服务。构造函数不会抛出任何已检查异常,因此在初始化期间出现的任何问题应该作为Error
或其他未检查异常抛出。
Connectors
不需要使用TransportService
。一些Connectors
可能使用除传输之外的机制连接到目标VM(在“示例场景”部分中列出了连接到崩溃转储或挂起进程的AttachingConnectors
的示例)。对于使用TransportService
实现的Connectors
,Connector
可以直接引用TransportService
实现,也可以在运行时加载实现。希望使用Oracle提供的传输的Connectors应该使用以下代码加载传输服务:
try {
Class<?> c = Class.forName("com.sun.tools.jdi.SocketTransportService");
ts = (TransportService)c.newInstance();
} catch (Exception x) {
throw new Error(x);
}
Java SE实现不需要包含Oracle的套接字或共享内存传输服务实现,因此在上面的示例中,如果传输服务不存在,将抛出一个Error
。
假设已知Connector
的类型,则在开发实现时应注意以下事项:
- 应仔细考虑
Connector.Arguments
列表。对于一些Connector
实现,默认参数构造和解析可能是实现中的主要部分。 Connector
需要命名其使用的传输。如果一个实现使用现有的TransportService
,则推荐的传输名称是底层TransportService
的名称。也就是说,Connector
的transport()
实现返回一个Transport
,该Transport
的name()
方法返回一个表示传输名称的java.lang.String
。- 大多数
Connector
实现将建立与目标VM的Connection
。一旦建立,Connector
的launch
、attach
或accept
方法将返回一个VirtualMachine
实例给调试器。为了便于创建VirtualMachine
镜像,VirtualMachineManager
有一个创建VirtualMachine
的方法。以下代码片段显示了该方法的示例用法:
VirtualMachine vm = Bootstrap.virtualMachineManager().createVirtualMachine(conn);
\
\
`VirtualMachineManager`还涉及另一种形式的`createVirtualMachine`方法,供`LaunchingConnector`实例使用。
另一种形式允许`LaunchingConnector`指定代表调试对象的`java.lang.Process`。有关更多详细信息,请参阅`com.sun.jdi.VirtualMachineManager`的规范。
部署Connector
部署Connector
需要将Connector
实现的类打包到一个jar文件中,同时包含一个服务配置文件,列出Connector
的完全限定类名。然后将jar文件部署到系统类加载器可见的位置。
必须包含在jar文件中的服务配置文件名为META-INF/services/com.sun.jdi.connect.Connector
。该文件简单地列出了jar文件中包含的Connector
的完全限定类名。可以在同一个jar文件中包含多个Connector
实现,在这种情况下,每个Connector
的类名将在单独的行上列出。
假设您希望部署一个名为SimpleLaunchingConnector
的启动连接器。为了部署这个连接器,您创建一个类似以下内容的文件META-INF/services/com.sun.jdi.connect.Connector
:
# 一个非常简单的启动连接器
SimpleLaunchingConnector
然后将这个服务配置文件打包到一个jar文件中,同时包含组成Connector实现的类:
jar cf SimpleLaunchingConnector.jar \
META-INF/services/com.sun.jdi.connect.Connector \
SimpleLaunchingConnector.class
然后将jar文件复制到系统类加载器可见的位置。
部署后,当调试器重新启动时,调试器将找到Connector
。也就是说,SimpleLaunchingConnector
将包含在VirtualMachineManager
的allConnectors()
方法返回的Connectors
列表中。此外,作为一个启动连接器,它还将出现在launchingConnectors()
方法返回的启动连接器列表中。
开发TransportService
开发传输服务需要开发两个组件:
com.sun.jdi.connect.spi.TransportService
的具体实现。- 实现
jdwpTransport
接口的调试端共享库。
开发传输服务需要对传输和底层通信协议有很高的熟悉度。传输服务基本上将JDWP绑定到底层通信协议上。它提供的服务是可靠的,调试器和被调试程序之间的JDWP数据包在不重复或丢失数据的情况下进行交换。由于数据包必须以可靠的方式进行交换,这可能意味着传输服务中需要包含超出底层通信协议提供的附加协议支持。例如,如果需要通过原始且不可靠的串行连接进行调试,则传输服务实现者可能需要在实现中构建错误检测和恢复,以确保JDWP数据包可以在调试器和被调试程序之间可靠传输。
假设了解了传输和底层通信协议的细节,接下来要考虑以下事项:
TransportService
的capabilities()
方法返回一个TransportService.Capabilities
,以指示传输服务的能力。因此,传输服务实现者需要考虑:- 传输是否支持同时连接到单个监听地址的多个并发连接?
- 在连接、握手或等待建立连接时是否可以合理地实现超时?
- 本机传输库可以同时被一个或多个JDWP(或其他)代理使用。如果传输支持多个环境,则每次调用
jdwpTransport_OnLoad
函数都会返回一个新的环境指针。如果传输只支持单个环境,则对jdwpTransport_OnLoad
的第二次及后续调用将返回错误。因此,传输库实现者必须决定库实现是否支持一个或多个环境。 - 地址或连接端点由
Strings
表示。因此,实现者必须设计一个地址方案,以便将地址的所有组件编码为String
。例如,串行端口的地址和配置可能被编码为类似以下内容:
/dev/ttya;9600,1
- 设计一个适当的
ListenKey
实现。由于一个TransportService
可能同时用于监听多个不同的地址,因此监听键用于唯一标识每个“监听器”。 - 为
TransportService
选择适当的名称和描述。在启动时,VirtualMachineManager
将创建一个附加和监听连接器来封装传输服务。这些Connectors
的名称和描述将基于传输服务的名称和描述 - 例如,如果TransportService
的name()
方法返回“Serial”,那么VirtualMachineManager
创建的Connectors
将被命名为“SerialAttach”和“SerialListen”。
一旦上述问题得到解决,创建TransportService
涉及扩展com.sun.jdi.connect.spi.TransportService
并提供一个实现。附加和接受方法返回一个com.sun.jdi.connect.spi.Connection
的实例,因此需要一个Connection
的实现,以便调试器可以与被调试程序交换JDWP数据包。
开发本机传输库需要实现jdwpTransport规范中列出的每个函数。函数原型和定义在${java_home}/include/jdwpTransport.h
中定义。
传输库实现者将函数实现编译并链接到动态库(或等效物)中。该库导出一个jdwpTransport_OnLoad
函数,JDWP代理在加载传输库时将调用该函数。一些嵌入式环境不支持动态链接,在这种环境中,传输库可能需要静态链接。在这种情况下,没有任何库加载,但jdwpTransport_OnLoad
函数仍将被调用以初始化传输库并获取环境指针。
部署传输服务
TransportService
的部署方式类似于Connector
。要部署TransportService
,需要将TransportService
实现的类打包到一个jar文件中,并附带一个服务配置文件,列出TransportService
的完全限定类名。然后将jar文件部署在系统类加载器可见的位置。
必须包含在jar文件中的服务配置文件的名称为META-INF/services/com.sun.jdi.connect.spi.TransportService
。与Connector
部署一样,配置文件可能会列出多个传输服务实现的类名,以防jar文件包含多个实现。
对于传输服务com.sun.SerialTransportService
,服务配置文件将类似于以下内容:
# 串行线传输
com.foo.SerialTransportService
然后将此服务配置文件打包到一个jar文件中,同时包含组成实现的类。对于此示例,我们将假设实现涉及多个类:
jar cf SerialTransportService.jar \
META-INF/services/com.sun.jdi.connect.spi.TransportService \
com/foo/SerialTransportService.class \
com/foo/SerialConnection.class \
com/foo/SerialCapabilities.clas \
com/foo/SerialIO.class \
com/foo/SerialProtocol.class
与部署Connectors
一样,然后将jar文件复制到系统类加载器可见的位置。
TransportService
可能具有本机方法或依赖于其他需要本机库的API。在这种情况下,本机库必须位于允许使用System.loadLibrary
加载的位置。
本机传输库由JDWP代理加载。它必须位于操作系统的正常运行时库搜索路径上。例如,在Linux系统上,它必须位于由LD_LIBRARY_PATH
环境变量指定的搜索路径上。