/* * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #include #include #include #include #include #include "jdwpTransport.h" #include "sysSocket.h" #ifdef _WIN32 #include #include #endif /* * The Socket Transport Library. * * This module is an implementation of the Java Debug Wire Protocol Transport * Service Provider Interface - see src/share/javavm/export/jdwpTransport.h. */ static int serverSocketFD; static int socketFD = -1; static jdwpTransportCallback *callback; static JavaVM *jvm; static int tlsIndex; static jboolean initialized; static struct jdwpTransportNativeInterface_ interface; static jdwpTransportEnv single_env = (jdwpTransportEnv)&interface; #define RETURN_ERROR(err, msg) \ if (1==1) { \ setLastError(err, msg); \ return err; \ } #define RETURN_IO_ERROR(msg) RETURN_ERROR(JDWPTRANSPORT_ERROR_IO_ERROR, msg); #define RETURN_RECV_ERROR(n) \ if (n == 0) { \ RETURN_ERROR(JDWPTRANSPORT_ERROR_IO_ERROR, "premature EOF"); \ } else { \ RETURN_IO_ERROR("recv error"); \ } #define HEADER_SIZE 11 #define MAX_DATA_SIZE 1000 static jint recv_fully(int, char *, int); static jint send_fully(int, char *, int); /* * Record the last error for this thread. */ static void setLastError(jdwpTransportError err, char *newmsg) { char buf[255]; char *msg; /* get any I/O first in case any system calls override errno */ if (err == JDWPTRANSPORT_ERROR_IO_ERROR) { dbgsysGetLastIOError(buf, sizeof(buf)); } msg = (char *)dbgsysTlsGet(tlsIndex); if (msg != NULL) { (*callback->free)(msg); } if (err == JDWPTRANSPORT_ERROR_IO_ERROR) { char *join_str = ": "; int msg_len = (int)strlen(newmsg) + (int)strlen(join_str) + (int)strlen(buf) + 3; msg = (*callback->alloc)(msg_len); if (msg != NULL) { strcpy(msg, newmsg); strcat(msg, join_str); strcat(msg, buf); } } else { msg = (*callback->alloc)((int)strlen(newmsg)+1); if (msg != NULL) { strcpy(msg, newmsg); } } dbgsysTlsPut(tlsIndex, msg); } /* * Return the last error for this thread (may be NULL) */ static char* getLastError() { return (char *)dbgsysTlsGet(tlsIndex); } static jdwpTransportError setOptions(int fd) { jvalue dontcare; int err; dontcare.i = 0; /* keep compiler happy */ err = dbgsysSetSocketOption(fd, SO_REUSEADDR, JNI_TRUE, dontcare); if (err < 0) { RETURN_IO_ERROR("setsockopt SO_REUSEADDR failed"); } err = dbgsysSetSocketOption(fd, TCP_NODELAY, JNI_TRUE, dontcare); if (err < 0) { RETURN_IO_ERROR("setsockopt TCPNODELAY failed"); } return JDWPTRANSPORT_ERROR_NONE; } static jdwpTransportError handshake(int fd, jlong timeout) { const char *hello = "JDWP-Handshake"; char b[16]; int rv, helloLen, received; if (timeout > 0) { dbgsysConfigureBlocking(fd, JNI_FALSE); } helloLen = (int)strlen(hello); received = 0; while (received < helloLen) { int n; char *buf; if (timeout > 0) { rv = dbgsysPoll(fd, JNI_TRUE, JNI_FALSE, (long)timeout); if (rv <= 0) { setLastError(0, "timeout during handshake"); return JDWPTRANSPORT_ERROR_IO_ERROR; } } buf = b; buf += received; n = recv_fully(fd, buf, helloLen-received); if (n == 0) { setLastError(0, "handshake failed - connection prematurally closed"); return JDWPTRANSPORT_ERROR_IO_ERROR; } if (n < 0) { RETURN_IO_ERROR("recv failed during handshake"); } received += n; } if (timeout > 0) { dbgsysConfigureBlocking(fd, JNI_TRUE); } if (strncmp(b, hello, received) != 0) { char msg[80+2*16]; b[received] = '\0'; /* * We should really use snprintf here but it's not available on Windows. * We can't use jio_snprintf without linking the transport against the VM. */ sprintf(msg, "handshake failed - received >%s< - expected >%s<", b, hello); setLastError(0, msg); return JDWPTRANSPORT_ERROR_IO_ERROR; } if (send_fully(fd, (char*)hello, helloLen) != helloLen) { RETURN_IO_ERROR("send failed during handshake"); } return JDWPTRANSPORT_ERROR_NONE; } static uint32_t getLocalHostAddress() { // Simple routine to guess localhost address. // it looks up "localhost" and returns 127.0.0.1 if lookup // fails. struct addrinfo hints, *res = NULL; int err; // Use portable way to initialize the structure memset((void *)&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; err = getaddrinfo("localhost", NULL, &hints, &res); if (err < 0 || res == NULL) { return dbgsysHostToNetworkLong(INADDR_LOOPBACK); } // getaddrinfo might return more than one address // but we are using first one only return ((struct sockaddr_in *)(res->ai_addr))->sin_addr.s_addr; } static int getPortNumber(const char *s_port) { u_long n; char *eptr; if (*s_port == 0) { // bad address - colon with no port number in parameters return -1; } n = strtoul(s_port, &eptr, 10); if (eptr != s_port + strlen(s_port)) { // incomplete conversion - port number contains non-digit return -1; } if (n > (u_short) -1) { // check that value supplied by user is less than // maximum possible u_short value (65535) and // will not be truncated later. return -1; } return n; } static jdwpTransportError parseAddress(const char *address, struct sockaddr_in *sa) { char *colon; int port; memset((void *)sa,0,sizeof(struct sockaddr_in)); sa->sin_family = AF_INET; /* check for host:port or port */ colon = strchr(address, ':'); port = getPortNumber((colon == NULL) ? address : colon +1); if (port < 0) { RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT, "invalid port number specified"); } sa->sin_port = dbgsysHostToNetworkShort((u_short)port); if (colon == NULL) { // bind to localhost only if no address specified sa->sin_addr.s_addr = getLocalHostAddress(); } else if (strncmp(address,"localhost:",10) == 0) { // optimize for common case sa->sin_addr.s_addr = getLocalHostAddress(); } else if (*address == '*' && *(address+1) == ':') { // we are explicitly asked to bind server to all available IP addresses // has no meaning for client. sa->sin_addr.s_addr = dbgsysHostToNetworkLong(INADDR_ANY); } else { char *buf; char *hostname; uint32_t addr; buf = (*callback->alloc)((int)strlen(address)+1); if (buf == NULL) { RETURN_ERROR(JDWPTRANSPORT_ERROR_OUT_OF_MEMORY, "out of memory"); } strcpy(buf, address); buf[colon - address] = '\0'; hostname = buf; /* * First see if the host is a literal IP address. * If not then try to resolve it. */ addr = dbgsysInetAddr(hostname); if (addr == 0xffffffff) { struct hostent *hp = dbgsysGetHostByName(hostname); if (hp == NULL) { /* don't use RETURN_IO_ERROR as unknown host is normal */ setLastError(0, "gethostbyname: unknown host"); (*callback->free)(buf); return JDWPTRANSPORT_ERROR_IO_ERROR; } /* lookup was successful */ memcpy(&(sa->sin_addr), hp->h_addr_list[0], hp->h_length); } else { sa->sin_addr.s_addr = addr; } (*callback->free)(buf); } return JDWPTRANSPORT_ERROR_NONE; } static jdwpTransportError JNICALL socketTransport_getCapabilities(jdwpTransportEnv* env, JDWPTransportCapabilities* capabilitiesPtr) { JDWPTransportCapabilities result; memset(&result, 0, sizeof(result)); result.can_timeout_attach = JNI_TRUE; result.can_timeout_accept = JNI_TRUE; result.can_timeout_handshake = JNI_TRUE; *capabilitiesPtr = result; return JDWPTRANSPORT_ERROR_NONE; } static jdwpTransportError JNICALL socketTransport_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); 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]; socklen_t 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; } static jdwpTransportError JNICALL socketTransport_accept(jdwpTransportEnv* env, jlong acceptTimeout, jlong handshakeTimeout) { socklen_t socketLen; int err; struct sockaddr_in socket; jlong startTime = (jlong)0; /* * Use a default handshake timeout if not specified - this avoids an indefinite * hang in cases where something other than a debugger connects to our port. */ if (handshakeTimeout == 0) { handshakeTimeout = 2000; } do { /* * If there is an accept timeout then we put the socket in non-blocking * mode and poll for a connection. */ if (acceptTimeout > 0) { int rv; dbgsysConfigureBlocking(serverSocketFD, JNI_FALSE); startTime = dbgsysCurrentTimeMillis(); rv = dbgsysPoll(serverSocketFD, JNI_TRUE, JNI_FALSE, (long)acceptTimeout); if (rv <= 0) { /* set the last error here as could be overridden by configureBlocking */ if (rv == 0) { setLastError(JDWPTRANSPORT_ERROR_IO_ERROR, "poll failed"); } /* restore blocking state */ dbgsysConfigureBlocking(serverSocketFD, JNI_TRUE); if (rv == 0) { RETURN_ERROR(JDWPTRANSPORT_ERROR_TIMEOUT, "timed out waiting for connection"); } else { return JDWPTRANSPORT_ERROR_IO_ERROR; } } } /* * Accept the connection */ memset((void *)&socket,0,sizeof(struct sockaddr_in)); socketLen = sizeof(socket); socketFD = dbgsysAccept(serverSocketFD, (struct sockaddr *)&socket, &socketLen); /* set the last error here as could be overridden by configureBlocking */ if (socketFD < 0) { setLastError(JDWPTRANSPORT_ERROR_IO_ERROR, "accept failed"); } /* * Restore the blocking state - note that the accepted socket may be in * blocking or non-blocking mode (platform dependent). However as there * is a handshake timeout set then it will go into non-blocking mode * anyway for the handshake. */ if (acceptTimeout > 0) { dbgsysConfigureBlocking(serverSocketFD, JNI_TRUE); } if (socketFD < 0) { return JDWPTRANSPORT_ERROR_IO_ERROR; } /* handshake with the debugger */ err = handshake(socketFD, handshakeTimeout); /* * If the handshake fails then close the connection. If there if an accept * timeout then we must adjust the timeout for the next poll. */ if (err) { fprintf(stderr, "Debugger failed to attach: %s\n", getLastError()); dbgsysSocketClose(socketFD); socketFD = -1; if (acceptTimeout > 0) { long endTime = dbgsysCurrentTimeMillis(); acceptTimeout -= (endTime - startTime); if (acceptTimeout <= 0) { setLastError(JDWPTRANSPORT_ERROR_IO_ERROR, "timeout waiting for debugger to connect"); return JDWPTRANSPORT_ERROR_IO_ERROR; } } } } while (socketFD < 0); return JDWPTRANSPORT_ERROR_NONE; } static jdwpTransportError JNICALL socketTransport_stopListening(jdwpTransportEnv *env) { if (serverSocketFD < 0) { RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_STATE, "connection not open"); } if (dbgsysSocketClose(serverSocketFD) < 0) { RETURN_IO_ERROR("close failed"); } serverSocketFD = -1; return JDWPTRANSPORT_ERROR_NONE; } static jdwpTransportError JNICALL socketTransport_attach(jdwpTransportEnv* env, const char* addressString, jlong attachTimeout, jlong handshakeTimeout) { struct sockaddr_in sa; int err; if (addressString == NULL || addressString[0] == '\0') { RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT, "address is missing"); } err = parseAddress(addressString, &sa); if (err != JDWPTRANSPORT_ERROR_NONE) { return err; } socketFD = dbgsysSocket(AF_INET, SOCK_STREAM, 0); if (socketFD < 0) { RETURN_IO_ERROR("unable to create socket"); } err = setOptions(socketFD); if (err) { return err; } /* * To do a timed connect we make the socket non-blocking * and poll with a timeout; */ if (attachTimeout > 0) { dbgsysConfigureBlocking(socketFD, JNI_FALSE); } err = dbgsysConnect(socketFD, (struct sockaddr *)&sa, sizeof(sa)); if (err == DBG_EINPROGRESS && attachTimeout > 0) { err = dbgsysFinishConnect(socketFD, (long)attachTimeout); if (err == DBG_ETIMEOUT) { dbgsysConfigureBlocking(socketFD, JNI_TRUE); RETURN_ERROR(JDWPTRANSPORT_ERROR_TIMEOUT, "connect timed out"); } } if (err < 0) { RETURN_IO_ERROR("connect failed"); } if (attachTimeout > 0) { dbgsysConfigureBlocking(socketFD, JNI_TRUE); } err = handshake(socketFD, handshakeTimeout); if (err) { dbgsysSocketClose(socketFD); socketFD = -1; return err; } return JDWPTRANSPORT_ERROR_NONE; } static jboolean JNICALL socketTransport_isOpen(jdwpTransportEnv* env) { if (socketFD >= 0) { return JNI_TRUE; } else { return JNI_FALSE; } } static jdwpTransportError JNICALL socketTransport_close(jdwpTransportEnv* env) { int fd = socketFD; socketFD = -1; if (fd < 0) { return JDWPTRANSPORT_ERROR_NONE; } #ifdef _AIX /* AIX needs a workaround for I/O cancellation, see: http://publib.boulder.ibm.com/infocenter/pseries/v5r3/index.jsp?topic=/com.ibm.aix.basetechref/doc/basetrf1/close.htm ... The close subroutine is blocked until all subroutines which use the file descriptor return to usr space. For example, when a thread is calling close and another thread is calling select with the same file descriptor, the close subroutine does not return until the select call returns. ... */ shutdown(fd, 2); #endif if (dbgsysSocketClose(fd) < 0) { /* * close failed - it's pointless to restore socketFD here because * any subsequent close will likely fail as well. */ RETURN_IO_ERROR("close failed"); } return JDWPTRANSPORT_ERROR_NONE; } static jdwpTransportError JNICALL socketTransport_writePacket(jdwpTransportEnv* env, const jdwpPacket *packet) { jint len, data_len, id; /* * room for header and up to MAX_DATA_SIZE data bytes */ char header[HEADER_SIZE + MAX_DATA_SIZE]; jbyte *data; /* packet can't be null */ if (packet == NULL) { RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT, "packet is NULL"); } len = packet->type.cmd.len; /* includes header */ data_len = len - HEADER_SIZE; /* bad packet */ if (data_len < 0) { RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT, "invalid length"); } /* prepare the header for transmission */ len = (jint)dbgsysHostToNetworkLong(len); id = (jint)dbgsysHostToNetworkLong(packet->type.cmd.id); memcpy(header + 0, &len, 4); memcpy(header + 4, &id, 4); header[8] = packet->type.cmd.flags; if (packet->type.cmd.flags & JDWPTRANSPORT_FLAGS_REPLY) { jshort errorCode = dbgsysHostToNetworkShort(packet->type.reply.errorCode); memcpy(header + 9, &errorCode, 2); } else { header[9] = packet->type.cmd.cmdSet; header[10] = packet->type.cmd.cmd; } data = packet->type.cmd.data; /* Do one send for short packets, two for longer ones */ if (data_len <= MAX_DATA_SIZE) { memcpy(header + HEADER_SIZE, data, data_len); if (send_fully(socketFD, (char *)&header, HEADER_SIZE + data_len) != HEADER_SIZE + data_len) { RETURN_IO_ERROR("send failed"); } } else { memcpy(header + HEADER_SIZE, data, MAX_DATA_SIZE); if (send_fully(socketFD, (char *)&header, HEADER_SIZE + MAX_DATA_SIZE) != HEADER_SIZE + MAX_DATA_SIZE) { RETURN_IO_ERROR("send failed"); } /* Send the remaining data bytes right out of the data area. */ if (send_fully(socketFD, (char *)data + MAX_DATA_SIZE, data_len - MAX_DATA_SIZE) != data_len - MAX_DATA_SIZE) { RETURN_IO_ERROR("send failed"); } } return JDWPTRANSPORT_ERROR_NONE; } static jint recv_fully(int f, char *buf, int len) { int nbytes = 0; while (nbytes < len) { int res = dbgsysRecv(f, buf + nbytes, len - nbytes, 0); if (res < 0) { return res; } else if (res == 0) { break; /* eof, return nbytes which is less than len */ } nbytes += res; } return nbytes; } jint send_fully(int f, char *buf, int len) { int nbytes = 0; while (nbytes < len) { int res = dbgsysSend(f, buf + nbytes, len - nbytes, 0); if (res < 0) { return res; } else if (res == 0) { break; /* eof, return nbytes which is less than len */ } nbytes += res; } return nbytes; } static jdwpTransportError JNICALL socketTransport_readPacket(jdwpTransportEnv* env, jdwpPacket* packet) { jint length, data_len; jint n; /* packet can't be null */ if (packet == NULL) { RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT, "packet is null"); } /* read the length field */ n = recv_fully(socketFD, (char *)&length, sizeof(jint)); /* check for EOF */ if (n == 0) { packet->type.cmd.len = 0; return JDWPTRANSPORT_ERROR_NONE; } if (n != sizeof(jint)) { RETURN_RECV_ERROR(n); } length = (jint)dbgsysNetworkToHostLong(length); packet->type.cmd.len = length; n = recv_fully(socketFD,(char *)&(packet->type.cmd.id),sizeof(jint)); if (n < (int)sizeof(jint)) { RETURN_RECV_ERROR(n); } packet->type.cmd.id = (jint)dbgsysNetworkToHostLong(packet->type.cmd.id); n = recv_fully(socketFD,(char *)&(packet->type.cmd.flags),sizeof(jbyte)); if (n < (int)sizeof(jbyte)) { RETURN_RECV_ERROR(n); } if (packet->type.cmd.flags & JDWPTRANSPORT_FLAGS_REPLY) { n = recv_fully(socketFD,(char *)&(packet->type.reply.errorCode),sizeof(jbyte)); if (n < (int)sizeof(jshort)) { RETURN_RECV_ERROR(n); } /* FIXME - should the error be converted to host order?? */ } else { n = recv_fully(socketFD,(char *)&(packet->type.cmd.cmdSet),sizeof(jbyte)); if (n < (int)sizeof(jbyte)) { RETURN_RECV_ERROR(n); } n = recv_fully(socketFD,(char *)&(packet->type.cmd.cmd),sizeof(jbyte)); if (n < (int)sizeof(jbyte)) { RETURN_RECV_ERROR(n); } } data_len = length - ((sizeof(jint) * 2) + (sizeof(jbyte) * 3)); if (data_len < 0) { setLastError(0, "Badly formed packet received - invalid length"); return JDWPTRANSPORT_ERROR_IO_ERROR; } else if (data_len == 0) { packet->type.cmd.data = NULL; } else { packet->type.cmd.data= (*callback->alloc)(data_len); if (packet->type.cmd.data == NULL) { RETURN_ERROR(JDWPTRANSPORT_ERROR_OUT_OF_MEMORY, "out of memory"); } n = recv_fully(socketFD,(char *)packet->type.cmd.data, data_len); if (n < data_len) { (*callback->free)(packet->type.cmd.data); RETURN_RECV_ERROR(n); } } return JDWPTRANSPORT_ERROR_NONE; } static jdwpTransportError JNICALL socketTransport_getLastError(jdwpTransportEnv* env, char** msgP) { char *msg = (char *)dbgsysTlsGet(tlsIndex); if (msg == NULL) { return JDWPTRANSPORT_ERROR_MSG_NOT_AVAILABLE; } *msgP = (*callback->alloc)((int)strlen(msg)+1); if (*msgP == NULL) { return JDWPTRANSPORT_ERROR_OUT_OF_MEMORY; } strcpy(*msgP, msg); return JDWPTRANSPORT_ERROR_NONE; } jint JNICALL jdwpTransport_OnLoad(JavaVM *vm, jdwpTransportCallback* cbTablePtr, jint version, jdwpTransportEnv** result) { if (version != JDWPTRANSPORT_VERSION_1_0) { return JNI_EVERSION; } if (initialized) { /* * This library doesn't support multiple environments (yet) */ return JNI_EEXIST; } initialized = JNI_TRUE; jvm = vm; callback = cbTablePtr; /* initialize interface table */ interface.GetCapabilities = &socketTransport_getCapabilities; interface.Attach = &socketTransport_attach; interface.StartListening = &socketTransport_startListening; interface.StopListening = &socketTransport_stopListening; interface.Accept = &socketTransport_accept; interface.IsOpen = &socketTransport_isOpen; interface.Close = &socketTransport_close; interface.ReadPacket = &socketTransport_readPacket; interface.WritePacket = &socketTransport_writePacket; interface.GetLastError = &socketTransport_getLastError; *result = &single_env; /* initialized TLS */ tlsIndex = dbgsysTlsAlloc(); return JNI_OK; }