JPDA(二):架构源码浅析

之前我们已经通过使用JDI的API写出了一个简单的调试器。那么这些API后面又是隐藏了什么样的实现机制?下面就通过源码来分析下。先贴上博主辛苦整理出来的一张图。

+———-+| debugger |+———-+||#1 —–>|| JDI API\/+———-+#2 —–>| JDI Impl |+———-+||#3 —–>|| Protocol\/+—————–+#4 —–> | JDWP Transports || (JDWPTI Impl) | (libdt_socket.so)+—————–+/\#5 —–>|| JDWPTI API(jdwpTransport.h)||+————–+#6 —–> | JDWP Agent || (JVMTI Impl) | (libjdwp.so)+————–+/\#7 —–>|| JVMTI API(jvmti.h)||+———-+| debuggee |+———-+

接下来就按上图中的数字序号一步一步讲解下。

JDI API&Impl

这一部分我们在前面的栗子已经说过了,API就是com.sun.jdi包,JDI实现有JDK自带的实现,也有HotSpotSA的实现。这里就不再细说了。

Protocol

这个Protocol就是Java Debug Wire Protocol,也就是JDI实现用来跟目标VM进行数据传输的格式规范。让我们来看下SocketAttachingConnector的源码。当我们调用SocketAttachingConnector#attach时,最后会调用,

/*** Attach to the specified address with optional attach and handshake* timeout.*/public Connection attach(String address, long attachTimeout, long handshakeTimeout)throws IOException {if (address == null) {throw new NullPointerException("address is null");}if (attachTimeout < 0 || handshakeTimeout < 0) {throw new IllegalArgumentException("timeout is negative");}int splitIndex = address.indexOf(':');String host;String portStr;if (splitIndex < 0) {host = InetAddress.getLocalHost().getHostName();portStr = address;} else {host = address.substring(0, splitIndex);portStr = address.substring(splitIndex+1);}int port;try {port = Integer.decode(portStr).intValue();} catch (NumberFormatException e) {throw new IllegalArgumentException("unable to parse port number in address");}// open TCP connection to VMInetSocketAddress sa = new InetSocketAddress(host, port);Socket s = new Socket();try {s.connect(sa, (int)attachTimeout);} catch (SocketTimeoutException exc) {try {s.close();} catch (IOException x) { }throw new TransportTimeoutException("timed out trying to establish connection");}// handshake with the target VMtry {handshake(s, handshakeTimeout);} catch (IOException exc) {try {s.close();} catch (IOException x) { }throw exc;}return new SocketConnection(s);}

所以就是根据我们传进来的IP跟Port跟目标VM建立了一个TCP Socket了。后续所有的通讯都是基于这个Socket。而JDWP就是在这个Socket上面进行传输的数据格式,看下方法,

void handshake(Socket s, long timeout) throws IOException {s.setSoTimeout((int)timeout);byte[] hello = "JDWP-Handshake".getBytes("UTF-8");s.getOutputStream().write(hello);byte[] b = new byte[hello.length];int received = 0;while (received < hello.length) {int n;try {n = s.getInputStream().read(b, received, hello.length-received);} catch (SocketTimeoutException x) {throw new IOException("handshake timeout");}if (n < 0) {s.close();throw new IOException("handshake failed – connection prematurally closed");}received += n;}for (int i=0; i<hello.length; i++) {if (b[i] != hello[i]) {throw new IOException("handshake failed – unrecognized message from target VM");}}// disable read timeouts.setSoTimeout(0);}

发送的"JDWP-Handshake"就是协议里面规定的。在连接建立之后,发送数据包之前,debugger跟debuggee必须要有一个handshake的过程,handshake分为两步,

debugger发送14个字节,也就是JDWP-Handshake,给debuggee;debuggee发送同样的14个字节回应;

具体的协议细节参考官方文档,这里就不展开了。

还有一点值得说明的就是,JDWP只是规范了数据传输格式,具体的数据传输方式,例如是使用TCP还是UDP,使用哪个端口,这些都是不作约束的。

JDWP Transports

上面看到JDI实现会去跟目标VM建立TCP Socket,那么首先在目标VM就必须有人去监听那个TCP端口,这件事是谁来干的呢?就是由我们接下来要说的JDWP Transports来做的了。

通过官方文档Serviceability in the J2SE Repository可以找到对应的源码,

TechnologySource LocationBinary Location

JDWP Transportsjdk/src/share/transportjdk/src/solaris/transportjdk/src/windows/transportlibdt_socket.sodt_socket.dll,dt_shmem.dll

JDWP Agentjdk/src/share/backlibjdwp.sojdwp.dll

上面所说的建立TCP连接的代码是在share/transport/socket/socketTransport.c中,,

static jdwpTransportError JNICALLsocketTransport_startListening(jdwpTransportEnv* env, const char* address,char** actualAddress){struct sockaddr_in sa;int err;memset((void *)&sa,0,sizeof(struct sockaddr_in));sa.sin_family = AF_INET;/* no address provided */if ((address == NULL) || (address[0] == '\0')) {address = "0";}err = parseAddress(address, &sa, INADDR_ANY);if (err != JDWPTRANSPORT_ERROR_NONE) {return err;}serverSocketFD = dbgsysSocket(AF_INET, SOCK_STREAM, 0);if (serverSocketFD < 0) {RETURN_IO_ERROR("socket creation failed");}err = setOptions(serverSocketFD);if (err) {return err;}err = dbgsysBind(serverSocketFD, (struct sockaddr *)&sa, sizeof(sa));if (err < 0) {RETURN_IO_ERROR("bind failed");}err = dbgsysListen(serverSocketFD, 1);if (err < 0) {RETURN_IO_ERROR("listen failed");}{char buf[20];int len = sizeof(sa);jint portNum;err = dbgsysGetSocketName(serverSocketFD,(struct sockaddr *)&sa, &len);portNum = dbgsysNetworkToHostShort(sa.sin_port);sprintf(buf, "%d", portNum);*actualAddress = (*callback->alloc)((int)strlen(buf) + 1);if (*actualAddress == NULL) {RETURN_ERROR(JDWPTRANSPORT_ERROR_OUT_OF_MEMORY, "out of memory");} else {strcpy(*actualAddress, buf);}}return JDWPTRANSPORT_ERROR_NONE;}JDWPTI API生命不息,在任何一种博大的辉煌之后,都掩藏着许多鲜为人知的艰难的奋斗。

JPDA(二):架构源码浅析

相关文章:

你感兴趣的文章:

标签云: