Java Debug Wire Protocol Transport Interface (jdwpTransport)

一个传输是作为动态库中的一系列函数实现的(有时称为DLL共享库)。该库导出一个onload函数,在启动时由JDWP(或其他)代理调用。

与JVMTI一样,jdwpTransport函数通过称为环境指针的接口指针访问。环境指针是指向环境的指针,类型为jdwpTransportEnv*。环境指针从onload函数返回给JDWP(或其他)代理。

一个传输可以支持单个环境,也可以支持多个环境。换句话说,一个传输可以同时被一个或多个代理使用。如果一个传输支持多个环境,那么每次调用onload函数都会返回一个新的环境指针。如果一个传输只支持单个环境,那么第二次及后续调用onload函数将返回错误。

传输是线程安全的,jdwpTransport函数可以被多个并发线程使用。例如,一个线程可能在jdwpTransport的ReadPacket函数中被阻塞,等待一个数据包,而另一个线程则调用WritePacket函数来写入一个数据包。

在大多数情况下,jdwpTransport函数返回一个表示返回状态的jdwpTransportError值。一些函数通过调用函数提供的指针返回附加值。在jdwpTransport函数分配返回值的情况下,函数将使用代理指定的内存分配例程。内存分配例程在启动时通过onload函数指定给传输。

在出现错误的情况下(即,jdwpTransport函数之一返回除JDWPTRANSPORT_ERROR_NONE之外的值),随后可以通过调用jdwpTransport函数GetLastError获取表示错误的字符串。错误是基于每个线程记录的。GetLastError函数将返回表示当前线程遇到的最后一个错误的字符串。

开发传输实现

传输库可以用支持C语言调用约定和C或C++定义的任何本机语言开发。

用于使用jdwpTransport接口的函数、数据类型和常量定义在包含文件jdwpTransport.h中。要使用这些定义,请将Java SE包含目录添加到您的包含路径中:

#include "jdwpTransport.h"

传输启动

传输库必须导出一个具有以下原型的onload函数:

JNIEXPORT jint JNICALL
jdwpTransport_OnLoad(JavaVM *jvm,
                     jdwpTransportCallback *callback,
                     jint version,
                     jdwpTransportEnv** env);

当加载库时,JDWP(或其他代理)将调用此函数。

jvm参数是代理通过调用JNI的GetJavaVM函数获得的JNI调用接口

callback参数是指向内存管理例程函数表的指针,传输必须使用该函数表来为传输实现分配内存以用于分配的返回值:

typedef struct jdwpTransportCallback {
    void* (*alloc)(jint numBytes);
    void (*free)(void *buffer);
} jdwpTransportCallback;

callback参数的生命周期是onload函数,因此传输必须在jdwpTransport_OnLoad函数中复制函数表。

函数表有两个条目。 alloc函数分配一块内存区域。它有一个参数指定要分配的字节数。它返回指向分配的内存起始位置的指针,如果无法满足内存请求,则返回NULL。如果请求的字节数为零,则返回NULL。 free函数释放先前使用alloc函数分配的内存区域。

代理提供的内存管理函数是线程安全的,传输实现不需要同步对这些函数的调用。保证内存管理函数的实现不会调用任何jdwpTransport函数。

version是代理期望的传输接口版本。这必须指定为JDWPTRANSPORT_VERSION_1_0

env是由函数返回的环境指针的指针。

jdwpTransport_OnLoad函数如果传输初始化成功则返回JNI_OK。如果初始化失败,则返回以下错误之一:

JNI_ENOMEM
JNI_EVERSION
JNI_EEXIST

如果内存不足以完成初始化,则返回JNI_ENOMEM

如果version参数中的版本不是JDWPTRANSPORT_VERSION_1_0,则返回JNI_EVERSION

如果传输仅支持单个环境,并且环境指针是由第一次调用onload函数返回的,则返回JNI_EEXIST

函数

jdwpTransport函数分为以下几类:

连接管理

连接管理函数用于建立和关闭与调试器的连接。连接提供可靠的JDWP数据包流向和从调试器的流向。写入连接的数据包由调试器按照写入顺序读取。同样,调试器写入连接的任何数据包都按照写入顺序读取。

连接可以通过主动被动方式建立。通过主动建立连接意味着调用jdwpTransport的Attach函数来启动与调试器的连接。通过被动建立连接意味着使用jdwpTransport的StartListening函数将传输置于监听模式,以便它监听来自调试器的连接。一旦处于监听模式,就可以使用Accept函数来接受连接。无论如何建立连接,都可以使用Close函数关闭连接,并使用IsOpen测试是否向调试器打开了连接。

附加

jdwpTransportError
Attach(jdwpTransportEnv* env, const char* address,
       jlong attachTimeout, jlong handshakeTimeout)

附加到调试器。附加到调试器涉及两个步骤。首先,建立到指定address的连接。建立连接后,执行握手以确保连接确实建立到调试器。握手涉及按照Java调试线协议规范中指定的ASCII字符串JDWP-Handshake进行交换。

address参数是指向表示调试器地址的字符串的指针。确切的格式取决于传输(在基于TCP/IP的传输的情况下,地址可能包括调试器的主机名和端口号。在支持通过串行端口进行连接的传输的情况下,可能是串行端口的设备名称)。

attachTimeout参数指定附加时要使用的超时时间。如果传输支持附加超时(请参阅获取功能),并且attachTimeout为正值,则指定在附加到调试器时要使用的超时时间(多或少)(以毫秒为单位)。如果传输不支持附加超时,或者如果将attachTimeout指定为零,则在附加时不使用超时。

handshakeTimeout参数指定与调试器进行握手时要使用的超时时间。如果传输支持握手超时(请参阅获取功能),并且handshakeTimeout为正值,则指定在与调试器进行握手时要使用的超时时间(多或少)(以毫秒为单位)。握手超时的确切用法取决于传输 - 例如,一个实现可能将超时用作等待从调试器接收JDWP-Handshake消息时的字符间超时。另一个实现可能使用超时来指示握手交换允许的总持续时间。一般来说,握手超时的目的是允许在传输连接到非有效调试器的情况下进行错误处理。如果传输不支持握手超时,或者如果将handshakeTimeout指定为零,则在握手时不使用超时。

此函数返回一个通用错误或以下错误之一:

JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT
JDWPTRANSPORT_ERROR_ILLEGAL_STATE
JDWPTRANSPORT_ERROR_IO_ERROR
JDWPTRANSPORT_ERROR_TIMEOUT

如果address无效或timeout为负,则返回JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT

如果传输当前处于监听模式(请参阅开始监听),或者已经与调试器建立了打开连接(请参阅是否打开),则返回JDWPTRANSPORT_ERROR_ILLEGAL_STATE

如果在附加到调试器时出现错误(除了附加超时之外的错误),则返回JDWPTRANSPORT_ERROR_IO_ERROR。请注意,启动握手期间的错误(包括握手超时)被视为I/O错误。可以使用获取最后错误来获取错误的字符串表示。

如果传输支持附加超时,并且attachTimeout值为正,并且无法在该attachTimeout期间建立与调试器的连接,则返回JDWPTRANSPORT_ERROR_TIMEOUT

开始监听

jdwpTransportError
StartListening(jdwpTransportEnv* env, const char* address, char** actualAddress);

将传输置于监听模式,以便监听来自调试器的连接。

address参数是指向表示传输应监听的本地地址的字符串的指针。确切的格式取决于传输方式(在基于TCP/IP的传输方式的情况下,地址可能是本地TCP端口号。在支持通过串行端口进行连接的传输方式的情况下,它可能是串行端口的设备名称)。address参数可以指定为NULL或为空字符串(第一个字符为\0)。在这种情况下,传输将侦听特定于传输的默认地址。

如果actualAddress不是NULL,则它将设置为由StartListening函数返回的字符串的地址。返回的字符串将包含传输正在侦听的地址的字符串表示。这个地址可能与address参数中提供的地址相同,也可能不同。该字符串是使用在调用jdwpTransport_OnLoad函数时传输提供的分配回调分配的。调用者负责释放返回的字符串。

此函数返回一个通用错误或以下错误之一:

JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT
JDWPTRANSPORT_ERROR_ILLEGAL_STATE
JDWPTRANSPORT_ERROR_IO_ERROR

如果address无效,则返回JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT

如果已经有一个到调试器的打开连接(参见IsOpen),或者传输已经处于监听模式,则返回JDWPTRANSPORT_ERROR_ILLEGAL_STATE

如果在将传输置于监听模式时出现错误,则返回JDWPTRANSPORT_ERROR_IO_ERROR。错误的性质取决于传输。可以使用GetLastError来获取表示错误的字符串。

StopListening

jdwpTransportError
StopListening(jdwpTransportEnv* env)

将传输从监听模式中移出,使其不再监听来自调试器的连接。

如果传输处于监听模式,那么它将被移出此模式。如果有一个到调试器的打开连接(参见IsOpen),则此函数不受影响。换句话说,StopListening不会关闭与调试器的连接。如果传输不处于监听模式,则此函数不执行任何操作,也不返回错误。

此函数返回一个通用错误或以下错误之一:

JDWPTRANSPORT_ERROR_IO_ERROR

如果在将传输移出监听模式时出现错误,则返回JDWPTRANSPORT_ERROR_IO_ERROR。错误的性质取决于传输。可以使用GetLastError来获取表示错误的字符串。

Accept

jdwpTransportError
Accept(jdwpTransportEnv* env, jlong acceptTimeout, jlong handshakeTimeout)

接受来自调试器的连接。接受来自调试器的连接涉及两个步骤。首先,调试器建立连接。建立连接后,将执行一个握手以确保连接确实是由调试器建立的。握手涉及根据Java调试线协议规范中指定的ASCII字符串JDWP-Handshake进行交换。

acceptTimeout参数指定等待调试器连接时要使用的超时时间。如果传输支持接受超时(参见GetCapabilities),并且acceptTimeout为正值,则指定要在等待调试器连接时使用的超时时间(多或少为毫秒)。如果传输不支持接受超时,或者如果将timeout指定为零,则将无限期地阻塞等待连接。

handshakeTimeout参数指定与调试器进行握手时要使用的超时时间。如果传输支持握手超时(参见GetCapabilities),并且handshakeTimeout为正值,则指定在与调试器进行握手时要使用的超时时间(多或少为毫秒)。握手超时的确切用法取决于传输的特性 - 例如,一个实现可能将超时用作等待从调试器接收JDWP-Handshake消息的字符间超时。另一个实现可能使用超时来指示允许握手交换的总持续时间。通常,握手超时的目的是允许在除调试器以外的其他实体建立与被调试实体的连接时进行错误处理。如果传输不支持握手超时,或者如果将handshakeTimeout指定为零,则在握手时不使用超时。

此函数返回一个通用错误或以下错误之一:

JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT
JDWPTRANSPORT_ERROR_ILLEGAL_STATE
JDWPTRANSPORT_ERROR_IO_ERROR
JDWPTRANSPORT_ERROR_TIMEOUT

如果attachTimeouthandshakeTimeout为负值,则返回JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT

如果已经有一个到调试器的打开连接(参见IsOpen),或者传输不处于监听模式(参见StartListening),则返回JDWPTRANSPORT_ERROR_ILLEGAL_STATE

如果在接受来自调试器的连接时出现错误(除了接受超时),则返回JDWPTRANSPORT_ERROR_IO_ERROR。请注意,启动握手期间的错误(包括握手超时)被视为I/O错误。错误的性质取决于传输。可以使用GetLastError来获取表示错误的字符串。

如果传输支持接受超时,并且acceptTimeout值为正,并且无法在该超时期间建立与调试器的连接,则返回JDWPTRANSPORT_ERROR_TIMEOUT

注意:一个被阻塞在Accept中等待来自调试器的连接的线程可以被另一个调用StopListening的线程中断。在这种情况下,调用Accept的线程将返回JDWPTRANSPORT_ERROR_IO_ERROR,表示发生了I/O错误。如果一个被阻塞在Accept中的线程已经接受了一个连接并正在与调试器进行握手,则StopListening不会中断连接。

IsOpen

jboolean
isOpen(jdwpTransportEnv* env)

告诉是否有一个到调试器的打开连接。

如果有且仅有一个到调试器的打开连接,则返回JNI_TRUE。否则返回JNI_FALSE

Close

jdwpTransportError
Close(jdwpTransportEnv* env)

关闭到调试器的打开连接。

如果没有到调试器的打开连接(参见IsOpen),则此函数不执行任何操作,也不返回错误。

如果有线程在任何I/O函数(即ReadPacketWritePacket)中被阻塞,则这些I/O函数将被关闭中断,并返回JDWPTRANSPORT_ERROR_IO_ERROR,表示发生了I/O错误。

此函数返回一个通用错误或以下错误之一:

JDWPTRANSPORT_ERROR_IO_ERROR

如果在关闭连接时出现错误,则返回JDWPTRANSPORT_ERROR_IO_ERROR。错误的性质取决于传输。可以使用GetLastError来获取表示错误的字符串。

I/O函数

I/O函数用于从调试器读取和写入JDWP数据包。

ReadPacket

typedef struct {
    jint len;
    jint id;
    jbyte flags;
    jbyte cmdSet;
    jbyte cmd;
    jbyte *data;
} jdwpCmdPacket;

typedef struct {
    jint len;
    jint id;
    jbyte flags;
    jshort errorCode;
    jbyte *data;
} jdwpReplyPacket;

typedef struct jdwpPacket {
    union {
        jdwpCmdPacket cmd;
        jdwpReplyPacket reply;
    } type;
} jdwpPacket;

jdwpTransportError
ReadPacket(jdwpTransportEnv* env, jdwpPacket* packet)

从到调试器的打开连接中读取一个JDWP数据包。

此函数对打开连接执行阻塞读取。它会无限期地阻塞,直到可以返回一个完整的JDWP数据包,或者在基于流的协议的传输中遇到流的结束。

packet参数是由此函数填充的jdwpPacket结构的地址。根据数据包是命令包还是回复包,packet.type.cmd.lenpacket.type.reply.len字段将被填充为数据包的长度。如果遇到流的结束,则长度字段将设置为0,并且数据包中的所有其他字段将是未定义的。如果在读取部分但不是全部字节的数据包后遇到流的结束,则被视为I/O错误,并将返回JDWPTRANSPORT_ERROR_IO_ERROR。在这种情况下,长度字段将不会被填充。当读取整个数据包时,数据包中的所有字段都将以主机顺序的值填充。这可能与传输JDWP数据包时所需的大端顺序不同。

packet.type.cmd.datapacket.type.reply.data字段将被填充为NULL或由此函数分配的数据包数据的指针。数据包数据是使用在调用jdwpTransport_OnLoad函数时传输提供的分配回调分配的。调用者负责释放它。数据包数据的布局(即头部后的数据,如果有的话)以接收到的字节顺序返回给调用者。

ReadPacket函数不对返回的数据包进行任何完整性检查,除了检查数据包的长度(由前4个字节指示)是否>= 11字节。如果length字段小于11字节,则返回JDWPTRANSPORT_ERROR_IO_ERROR

此函数返回一个通用错误或以下错误之一:

JDWPTRANSPORT_ERROR_IO_ERROR
JDWPTRANSPORT_ERROR_ILLEGAL_STATE
JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT

如果读取时发生I/O错误,连接被另一个线程异步关闭(调用Close函数),或者接收到一个格式错误的数据包(length字段小于11字节),则返回JDWPTRANSPORT_ERROR_IO_ERROR。I/O错误是特定于传输的。可以使用GetLastError来获取表示错误的字符串。

如果没有到调试器的打开连接(参见IsOpen),则返回JDWPTRANSPORT_ERROR_ILLEGAL_STATE

JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT 如果 packetNULL 则返回

WritePacket

jdwpTransportError
WritePacket(jdwpTransportEnv* env, const jdwpPacket* packet)

将一个JDWP数据包写入到一个打开的连接中。

packet 参数是一个指向 jdwpPacket 结构的指针。数据包头中的所有字段必须以主机顺序存储。数据包数据字段 (packet.type.cmd.datapacket.type.reply.data) 必须是 NULL,或者是一个指向紧随头部的数据包数据的位置的指针。数据包数据必须以网络顺序(大端)准备好进行传输。

此函数返回一个 通用错误 或以下错误之一:

JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT
JDWPTRANSPORT_ERROR_IO_ERROR
JDWPTRANSPORT_ERROR_ILLEGAL_STATE

JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT 如果 packetNULL,或者数据包长度字段 (packet.type.cmd.lenpacket.type.reply.len) 小于11,或者大于11但数据包数据字段 (packet.type.cmd.datapacket.type.reply.data) 是 NULL

JDWPTRANSPORT_ERROR_IO_ERROR 如果在写入时发生I/O错误,或者连接被另一个线程异步关闭调用 Close 函数。I/O错误是特定于传输的。可以使用 GetLastError 来获取表示错误的字符串。

JDWPTRANSPORT_ERROR_ILLEGAL_STATE 如果没有到调试器的打开连接,则返回(参见 IsOpen)。

其他函数

GetLastError

jdwpTransportError
GetLastError(jdwpTransportEnv* env, char** msg);

返回最后错误的字符串表示。

当发生错误时,它会记录在每个线程的基础上。随后调用 GetLastError 返回最后I/O错误的字符串表示。

msg 参数是一个由此函数返回的以空字符结尾的字符串的指针。该字符串是使用在调用 jdwpTransport_OnLoad 函数时提供给传输的分配回调分配的。调用者负责释放返回的字符串。

此函数返回一个 通用错误 或以下错误之一:

JDWPTRANSPORT_ERROR_MSG_NOT_AVAILABLE

JDWPTRANSPORT_ERROR_MSG_NOT_AVAILABLE 如果此线程尚未遇到I/O错误或最后错误的字符串表示不可用,则返回。

GetCapabilities

typedef struct {
    unsigned int can_timeout_attach     :1;
    unsigned int can_timeout_accept     :1;
    unsigned int can_timeout_handshake  :1;
    unsigned int reserved3              :1;
    unsigned int reserved4              :1;
    unsigned int reserved5              :1;
    unsigned int reserved6              :1;
    unsigned int reserved7              :1;
    unsigned int reserved8              :1;
    unsigned int reserved9              :1;
    unsigned int reserved10             :1;
    unsigned int reserved11             :1;
    unsigned int reserved12             :1;
    unsigned int reserved13             :1;
    unsigned int reserved14             :1;
    unsigned int reserved15             :1;
} JDWPTransportCapabilities;

jdwpTransportError
GetCapabilities(jdwpTransportEnv* env, JDWPTransportCapabilities* capabilitiesPtr)

通过 capabilitiesPtr 返回此传输支持的可选jdwpTransport功能。该功能结构包含一系列布尔标志,指示是否支持命名功能。当前的标志集包括:

布尔标志 含义
can_timeout_attach 指示传输是否支持附加超时
can_timeout_accept 指示传输是否支持接受超时
can_timeout_handshake 指示传输在建立连接时执行初始握手时是否支持超时

此函数不返回任何错误。

通用错误

错误 含义
JDWPTRANSPORT_ERROR_NONE 未发生错误。这是成功完成函数时返回的错误代码。
JDWPTRANSPORT_ERROR_OUT_OF_MEMORY 函数需要分配内存,但没有更多可用于分配的内存。
JDWPTRANSPORT_ERROR_INTERNAL 发生意外的内部错误。