/*
* Copyright (c) 1998, 2018, 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 <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <ctype.h>
#include "jni.h"
#include "jdwpTransport.h"
#include "sysSocket.h"
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <sys/socket.h>
#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 MAX_DATA_SIZE 1000
static jint recv_fully(int, char *, int);
static jint send_fully(int, char *, int);
/* version >= JDWPTRANSPORT_VERSION_1_1 */
typedef struct {
uint32_t subnet;
uint32_t netmask;
} AllowedPeerInfo;
#define STR(x) #x
#define MAX_PEER_ENTRIES 32
#define MAX_PEERS_STR STR(MAX_PEER_ENTRIES)
static AllowedPeerInfo _peers[MAX_PEER_ENTRIES];
static int _peers_cnt = 0;
/*
* 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);
}
/* Set options common to client and server sides */
static jdwpTransportError
setOptionsCommon(int fd)
{
jvalue dontcare;
int err;
dontcare.i = 0; /* keep compiler happy */
err = dbgsysSetSocketOption(fd, TCP_NODELAY, JNI_TRUE, dontcare);
if (err < 0) {
RETURN_IO_ERROR("setsockopt TCPNODELAY failed");
}
return JDWPTRANSPORT_ERROR_NONE;
}
/* Set the SO_REUSEADDR option */
static jdwpTransportError
setReuseAddrOption(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");
}
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;
uint32_t addr;
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
addr = ((struct sockaddr_in *)(res->ai_addr))->sin_addr.s_addr;
freeaddrinfo(res);
return 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;
int ai;
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 addrinfo hints;
struct addrinfo *results = NULL;
memset (&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
ai = dbgsysGetAddrInfo(hostname, NULL, &hints, &results);
if (ai != 0) {
/* don't use RETURN_IO_ERROR as unknown host is normal */
setLastError(0, "getaddrinfo: unknown host");
(*callback->free)(buf);
return JDWPTRANSPORT_ERROR_IO_ERROR;
}
/* lookup was successful */
sa->sin_addr = ((struct sockaddr_in *)results->ai_addr)->sin_addr;
freeaddrinfo(results);
} else {
sa->sin_addr.s_addr = addr;
}
(*callback->free)(buf);
}
return JDWPTRANSPORT_ERROR_NONE;
}
static const char *
ip_s2u(const char *instr, uint32_t *ip) {
// Convert string representation of ip to integer
// in network byte order (big-endian)
char t[4] = { 0, 0, 0, 0 };
const char *s = instr;
int i = 0;
while (1) {
if (*s == '.') {
++i;
++s;
continue;
}
if (*s == 0 || *s == '+' || *s == '/') {
break;
}
if (*s < '0' || *s > '9') {
return instr;
}
t[i] = (t[i] * 10) + (*s - '0');
++s;
}
*ip = *(uint32_t*)(t);
return s;
}
static const char *
mask_s2u(const char *instr, uint32_t *mask) {
// Convert the number of bits to a netmask
// in network byte order (big-endian)
unsigned char m = 0;
const char *s = instr;
while (1) {
if (*s == 0 || *s == '+') {
break;
}
if (*s < '0' || *s > '9') {
return instr;
}
m = (m * 10) + (*s - '0');
++s;
}
if (m == 0 || m > 32) {
// Drop invalid input
return instr;
}
*mask = htonl((uint32_t)(~0) << (32 - m));
return s;
}
static int
ip_in_subnet(uint32_t subnet, uint32_t mask, uint32_t ipaddr) {
return (ipaddr & mask) == subnet;
}
static jdwpTransportError
parseAllowedPeers(const char *allowed_peers) {
// Build a list of allowed peers from char string
// of format 192.168.0.10+192.168.0.0/24
const char *s = NULL;
const char *p = allowed_peers;
uint32_t ip = 0;
uint32_t mask = 0xFFFFFFFF;
while (1) {
s = ip_s2u(p, &ip);
if (s == p) {
_peers_cnt = 0;
fprintf(stderr, "Error in allow option: '%s'\n", s);
RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT,
"invalid IP address in allow option");
}
if (*s == '/') {
// netmask specified
s = mask_s2u(s + 1, &mask);
if (*(s - 1) == '/') {
// Input is not consumed, something bad happened
_peers_cnt = 0;
fprintf(stderr, "Error in allow option: '%s'\n", s);
RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT,
"invalid netmask in allow option");
}
} else {
// reset netmask
mask = 0xFFFFFFFF;
}
if (*s == '+' || *s == 0) {
if (_peers_cnt >= MAX_PEER_ENTRIES) {
fprintf(stderr, "Error in allow option: '%s'\n", allowed_peers);
RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT,
"exceeded max number of allowed peers: " MAX_PEERS_STR);
}
_peers[_peers_cnt].subnet = ip;
_peers[_peers_cnt].netmask = mask;
_peers_cnt++;
if (*s == 0) {
// end of options
break;
}
// advance to next IP block
p = s + 1;
}
}
return JDWPTRANSPORT_ERROR_NONE;
}
static int
isPeerAllowed(struct sockaddr_in *peer) {
int i;
for (i = 0; i < _peers_cnt; ++i) {
int peer_ip = peer->sin_addr.s_addr;
if (ip_in_subnet(_peers[i].subnet, _peers[i].netmask, peer_ip)) {
return 1;
}
}
return 0;
}
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 = setOptionsCommon(serverSocketFD);
if (err) {
return err;
}
if (sa.sin_port != 0) {
/*
* Only need SO_REUSEADDR if we're using a fixed port. If we
* start seeing EADDRINUSE due to collisions in free ports
* then we should retry the dbgsysBind() a few times.
*/
err = setReuseAddrOption(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 = JDWPTRANSPORT_ERROR_NONE;
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;
}
/*
* version >= JDWPTRANSPORT_VERSION_1_1:
* Verify that peer is allowed to connect.
*/
if (_peers_cnt > 0) {
if (!isPeerAllowed(&socket)) {
char ebuf[64] = { 0 };
char buf[INET_ADDRSTRLEN] = { 0 };
const char* addr_str = inet_ntop(AF_INET, &(socket.sin_addr), buf, INET_ADDRSTRLEN);
sprintf(ebuf, "ERROR: Peer not allowed to connect: %s\n",
(addr_str == NULL) ? "<bad address>" : addr_str);
dbgsysSocketClose(socketFD);
socketFD = -1;
err = JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
setLastError(err, ebuf);
}
}
if (socketFD > 0) {
/* 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 != JDWPTRANSPORT_ERROR_NONE) {
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 = setOptionsCommon(socketFD);
if (err) {
return err;
}
/*
* We don't call setReuseAddrOption() for the non-server socket
* case. If we start seeing EADDRINUSE due to collisions in free
* ports then we should retry the dbgsysConnect() a few times.
*/
/*
* 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[JDWP_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 - JDWP_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 + JDWP_HEADER_SIZE, data, data_len);
if (send_fully(socketFD, (char *)&header, JDWP_HEADER_SIZE + data_len) !=
JDWP_HEADER_SIZE + data_len) {
RETURN_IO_ERROR("send failed");
}
} else {
memcpy(header + JDWP_HEADER_SIZE, data, MAX_DATA_SIZE);
if (send_fully(socketFD, (char *)&header, JDWP_HEADER_SIZE + MAX_DATA_SIZE) !=
JDWP_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;
}
static jdwpTransportError JNICALL
socketTransport_setConfiguration(jdwpTransportEnv* env, jdwpTransportConfiguration* cfg) {
const char* allowed_peers = NULL;
if (cfg == NULL) {
RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT,
"NULL pointer to transport configuration is invalid");
}
allowed_peers = cfg->allowed_peers;
_peers_cnt = 0;
if (allowed_peers != NULL) {
size_t len = strlen(allowed_peers);
if (len == 0) { /* Impossible: parseOptions() would reject it */
fprintf(stderr, "Error in allow option: '%s'\n", allowed_peers);
RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT,
"allow option should not be empty");
} else if (*allowed_peers == '*') {
if (len != 1) {
fprintf(stderr, "Error in allow option: '%s'\n", allowed_peers);
RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT,
"allow option '*' cannot be expanded");
}
} else {
int err = parseAllowedPeers(allowed_peers);
if (err != JDWPTRANSPORT_ERROR_NONE) {
return err;
}
}
}
return JDWPTRANSPORT_ERROR_NONE;
}
JNIEXPORT jint JNICALL
jdwpTransport_OnLoad(JavaVM *vm, jdwpTransportCallback* cbTablePtr,
jint version, jdwpTransportEnv** env)
{
if (version < JDWPTRANSPORT_VERSION_1_0 ||
version > JDWPTRANSPORT_VERSION_1_1) {
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;
if (version >= JDWPTRANSPORT_VERSION_1_1) {
interface.SetTransportConfiguration = &socketTransport_setConfiguration;
}
*env = &single_env;
/* initialized TLS */
tlsIndex = dbgsysTlsAlloc();
return JNI_OK;
}