752 lines
20 KiB
C
752 lines
20 KiB
C
/*
|
|
* Copyright 2006 The Android Open Source Project
|
|
*
|
|
* JDWP spy. This is a rearranged version of the JDWP code from the VM.
|
|
*/
|
|
#include "Common.h"
|
|
#include "jdwp/JdwpConstants.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#include <time.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
|
|
#define kInputBufferSize (256*1024)
|
|
|
|
#define kMagicHandshakeLen 14 /* "JDWP-Handshake" */
|
|
#define kJDWPHeaderLen 11
|
|
#define kJDWPFlagReply 0x80
|
|
|
|
|
|
/*
|
|
* Information about the remote end.
|
|
*/
|
|
typedef struct Peer {
|
|
char label[2]; /* 'D' or 'V' */
|
|
|
|
int sock;
|
|
unsigned char inputBuffer[kInputBufferSize];
|
|
int inputCount;
|
|
|
|
bool awaitingHandshake; /* waiting for "JDWP-Handshake" */
|
|
} Peer;
|
|
|
|
|
|
/*
|
|
* Network state.
|
|
*/
|
|
typedef struct NetState {
|
|
/* listen here for connection from debugger */
|
|
int listenSock;
|
|
|
|
/* connect here to contact VM */
|
|
struct in_addr vmAddr;
|
|
short vmPort;
|
|
|
|
Peer dbg;
|
|
Peer vm;
|
|
} NetState;
|
|
|
|
/*
|
|
* Function names.
|
|
*/
|
|
typedef struct {
|
|
u1 cmdSet;
|
|
u1 cmd;
|
|
const char* descr;
|
|
} JdwpHandlerMap;
|
|
|
|
/*
|
|
* Map commands to names.
|
|
*
|
|
* Command sets 0-63 are incoming requests, 64-127 are outbound requests,
|
|
* and 128-256 are vendor-defined.
|
|
*/
|
|
static const JdwpHandlerMap gHandlerMap[] = {
|
|
/* VirtualMachine command set (1) */
|
|
{ 1, 1, "VirtualMachine.Version" },
|
|
{ 1, 2, "VirtualMachine.ClassesBySignature" },
|
|
{ 1, 3, "VirtualMachine.AllClasses" },
|
|
{ 1, 4, "VirtualMachine.AllThreads" },
|
|
{ 1, 5, "VirtualMachine.TopLevelThreadGroups" },
|
|
{ 1, 6, "VirtualMachine.Dispose" },
|
|
{ 1, 7, "VirtualMachine.IDSizes" },
|
|
{ 1, 8, "VirtualMachine.Suspend" },
|
|
{ 1, 9, "VirtualMachine.Resume" },
|
|
{ 1, 10, "VirtualMachine.Exit" },
|
|
{ 1, 11, "VirtualMachine.CreateString" },
|
|
{ 1, 12, "VirtualMachine.Capabilities" },
|
|
{ 1, 13, "VirtualMachine.ClassPaths" },
|
|
{ 1, 14, "VirtualMachine.DisposeObjects" },
|
|
{ 1, 15, "VirtualMachine.HoldEvents" },
|
|
{ 1, 16, "VirtualMachine.ReleaseEvents" },
|
|
{ 1, 17, "VirtualMachine.CapabilitiesNew" },
|
|
{ 1, 18, "VirtualMachine.RedefineClasses" },
|
|
{ 1, 19, "VirtualMachine.SetDefaultStratum" },
|
|
{ 1, 20, "VirtualMachine.AllClassesWithGeneric"},
|
|
{ 1, 21, "VirtualMachine.InstanceCounts"},
|
|
|
|
/* ReferenceType command set (2) */
|
|
{ 2, 1, "ReferenceType.Signature" },
|
|
{ 2, 2, "ReferenceType.ClassLoader" },
|
|
{ 2, 3, "ReferenceType.Modifiers" },
|
|
{ 2, 4, "ReferenceType.Fields" },
|
|
{ 2, 5, "ReferenceType.Methods" },
|
|
{ 2, 6, "ReferenceType.GetValues" },
|
|
{ 2, 7, "ReferenceType.SourceFile" },
|
|
{ 2, 8, "ReferenceType.NestedTypes" },
|
|
{ 2, 9, "ReferenceType.Status" },
|
|
{ 2, 10, "ReferenceType.Interfaces" },
|
|
{ 2, 11, "ReferenceType.ClassObject" },
|
|
{ 2, 12, "ReferenceType.SourceDebugExtension" },
|
|
{ 2, 13, "ReferenceType.SignatureWithGeneric" },
|
|
{ 2, 14, "ReferenceType.FieldsWithGeneric" },
|
|
{ 2, 15, "ReferenceType.MethodsWithGeneric" },
|
|
{ 2, 16, "ReferenceType.Instances" },
|
|
{ 2, 17, "ReferenceType.ClassFileVersion" },
|
|
{ 2, 18, "ReferenceType.ConstantPool" },
|
|
|
|
/* ClassType command set (3) */
|
|
{ 3, 1, "ClassType.Superclass" },
|
|
{ 3, 2, "ClassType.SetValues" },
|
|
{ 3, 3, "ClassType.InvokeMethod" },
|
|
{ 3, 4, "ClassType.NewInstance" },
|
|
|
|
/* ArrayType command set (4) */
|
|
{ 4, 1, "ArrayType.NewInstance" },
|
|
|
|
/* InterfaceType command set (5) */
|
|
|
|
/* Method command set (6) */
|
|
{ 6, 1, "Method.LineTable" },
|
|
{ 6, 2, "Method.VariableTable" },
|
|
{ 6, 3, "Method.Bytecodes" },
|
|
{ 6, 4, "Method.IsObsolete" },
|
|
{ 6, 5, "Method.VariableTableWithGeneric" },
|
|
|
|
/* Field command set (8) */
|
|
|
|
/* ObjectReference command set (9) */
|
|
{ 9, 1, "ObjectReference.ReferenceType" },
|
|
{ 9, 2, "ObjectReference.GetValues" },
|
|
{ 9, 3, "ObjectReference.SetValues" },
|
|
{ 9, 4, "ObjectReference.UNUSED" },
|
|
{ 9, 5, "ObjectReference.MonitorInfo" },
|
|
{ 9, 6, "ObjectReference.InvokeMethod" },
|
|
{ 9, 7, "ObjectReference.DisableCollection" },
|
|
{ 9, 8, "ObjectReference.EnableCollection" },
|
|
{ 9, 9, "ObjectReference.IsCollected" },
|
|
{ 9, 10, "ObjectReference.ReferringObjects" },
|
|
|
|
/* StringReference command set (10) */
|
|
{ 10, 1, "StringReference.Value" },
|
|
|
|
/* ThreadReference command set (11) */
|
|
{ 11, 1, "ThreadReference.Name" },
|
|
{ 11, 2, "ThreadReference.Suspend" },
|
|
{ 11, 3, "ThreadReference.Resume" },
|
|
{ 11, 4, "ThreadReference.Status" },
|
|
{ 11, 5, "ThreadReference.ThreadGroup" },
|
|
{ 11, 6, "ThreadReference.Frames" },
|
|
{ 11, 7, "ThreadReference.FrameCount" },
|
|
{ 11, 8, "ThreadReference.OwnedMonitors" },
|
|
{ 11, 9, "ThreadReference.CurrentContendedMonitor" },
|
|
{ 11, 10, "ThreadReference.Stop" },
|
|
{ 11, 11, "ThreadReference.Interrupt" },
|
|
{ 11, 12, "ThreadReference.SuspendCount" },
|
|
{ 11, 13, "ThreadReference.OwnedMonitorsStackDepthInfo" },
|
|
{ 11, 14, "ThreadReference.ForceEarlyReturn" },
|
|
|
|
/* ThreadGroupReference command set (12) */
|
|
{ 12, 1, "ThreadGroupReference.Name" },
|
|
{ 12, 2, "ThreadGroupReference.Parent" },
|
|
{ 12, 3, "ThreadGroupReference.Children" },
|
|
|
|
/* ArrayReference command set (13) */
|
|
{ 13, 1, "ArrayReference.Length" },
|
|
{ 13, 2, "ArrayReference.GetValues" },
|
|
{ 13, 3, "ArrayReference.SetValues" },
|
|
|
|
/* ClassLoaderReference command set (14) */
|
|
{ 14, 1, "ArrayReference.VisibleClasses" },
|
|
|
|
/* EventRequest command set (15) */
|
|
{ 15, 1, "EventRequest.Set" },
|
|
{ 15, 2, "EventRequest.Clear" },
|
|
{ 15, 3, "EventRequest.ClearAllBreakpoints" },
|
|
|
|
/* StackFrame command set (16) */
|
|
{ 16, 1, "StackFrame.GetValues" },
|
|
{ 16, 2, "StackFrame.SetValues" },
|
|
{ 16, 3, "StackFrame.ThisObject" },
|
|
{ 16, 4, "StackFrame.PopFrames" },
|
|
|
|
/* ClassObjectReference command set (17) */
|
|
{ 17, 1, "ClassObjectReference.ReflectedType" },
|
|
|
|
/* Event command set (64) */
|
|
{ 64, 100, "Event.Composite" },
|
|
|
|
/* DDMS */
|
|
{ 199, 1, "DDMS.Chunk" },
|
|
};
|
|
|
|
/*
|
|
* Look up a command's name.
|
|
*/
|
|
static const char* getCommandName(int cmdSet, int cmd)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < (int) NELEM(gHandlerMap); i++) {
|
|
if (gHandlerMap[i].cmdSet == cmdSet &&
|
|
gHandlerMap[i].cmd == cmd)
|
|
{
|
|
return gHandlerMap[i].descr;
|
|
}
|
|
}
|
|
|
|
return "?UNKNOWN?";
|
|
}
|
|
|
|
|
|
void jdwpNetFree(NetState* netState); /* fwd */
|
|
|
|
/*
|
|
* Allocate state structure and bind to the listen port.
|
|
*
|
|
* Returns 0 on success.
|
|
*/
|
|
NetState* jdwpNetStartup(unsigned short listenPort, const char* connectHost,
|
|
unsigned short connectPort)
|
|
{
|
|
NetState* netState;
|
|
int one = 1;
|
|
|
|
netState = (NetState*) malloc(sizeof(*netState));
|
|
memset(netState, 0, sizeof(*netState));
|
|
netState->listenSock = -1;
|
|
netState->dbg.sock = netState->vm.sock = -1;
|
|
|
|
strcpy(netState->dbg.label, "D");
|
|
strcpy(netState->vm.label, "V");
|
|
|
|
/*
|
|
* Set up a socket to listen for connections from the debugger.
|
|
*/
|
|
|
|
netState->listenSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (netState->listenSock < 0) {
|
|
fprintf(stderr, "Socket create failed: %s\n", strerror(errno));
|
|
goto fail;
|
|
}
|
|
|
|
/* allow immediate re-use if we die */
|
|
if (setsockopt(netState->listenSock, SOL_SOCKET, SO_REUSEADDR, &one,
|
|
sizeof(one)) < 0)
|
|
{
|
|
fprintf(stderr, "setsockopt(SO_REUSEADDR) failed: %s\n",
|
|
strerror(errno));
|
|
goto fail;
|
|
}
|
|
|
|
struct sockaddr_in addr;
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(listenPort);
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
|
|
if (bind(netState->listenSock, (struct sockaddr*) &addr, sizeof(addr)) != 0)
|
|
{
|
|
fprintf(stderr, "attempt to bind to port %u failed: %s\n",
|
|
listenPort, strerror(errno));
|
|
goto fail;
|
|
}
|
|
|
|
fprintf(stderr, "+++ bound to port %u\n", listenPort);
|
|
|
|
if (listen(netState->listenSock, 5) != 0) {
|
|
fprintf(stderr, "Listen failed: %s\n", strerror(errno));
|
|
goto fail;
|
|
}
|
|
|
|
/*
|
|
* Do the hostname lookup for the VM.
|
|
*/
|
|
struct hostent* pHost;
|
|
|
|
pHost = gethostbyname(connectHost);
|
|
if (pHost == NULL) {
|
|
fprintf(stderr, "Name lookup of '%s' failed: %s\n",
|
|
connectHost, strerror(h_errno));
|
|
goto fail;
|
|
}
|
|
|
|
netState->vmAddr = *((struct in_addr*) pHost->h_addr_list[0]);
|
|
netState->vmPort = connectPort;
|
|
|
|
fprintf(stderr, "+++ connect host resolved to %s\n",
|
|
inet_ntoa(netState->vmAddr));
|
|
|
|
return netState;
|
|
|
|
fail:
|
|
jdwpNetFree(netState);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Shut down JDWP listener. Don't free state.
|
|
*
|
|
* Note that "netState" may be partially initialized if "startup" failed.
|
|
*/
|
|
void jdwpNetShutdown(NetState* netState)
|
|
{
|
|
int listenSock = netState->listenSock;
|
|
int dbgSock = netState->dbg.sock;
|
|
int vmSock = netState->vm.sock;
|
|
|
|
/* clear these out so it doesn't wake up and try to reuse them */
|
|
/* (important when multi-threaded) */
|
|
netState->listenSock = netState->dbg.sock = netState->vm.sock = -1;
|
|
|
|
if (listenSock >= 0) {
|
|
shutdown(listenSock, SHUT_RDWR);
|
|
close(listenSock);
|
|
}
|
|
if (dbgSock >= 0) {
|
|
shutdown(dbgSock, SHUT_RDWR);
|
|
close(dbgSock);
|
|
}
|
|
if (vmSock >= 0) {
|
|
shutdown(vmSock, SHUT_RDWR);
|
|
close(vmSock);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Shut down JDWP listener and free its state.
|
|
*/
|
|
void jdwpNetFree(NetState* netState)
|
|
{
|
|
if (netState == NULL)
|
|
return;
|
|
|
|
jdwpNetShutdown(netState);
|
|
free(netState);
|
|
}
|
|
|
|
/*
|
|
* Disable the TCP Nagle algorithm, which delays transmission of outbound
|
|
* packets until the previous transmissions have been acked. JDWP does a
|
|
* lot of back-and-forth with small packets, so this may help.
|
|
*/
|
|
static int setNoDelay(int fd)
|
|
{
|
|
int cc, on = 1;
|
|
|
|
cc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
|
|
assert(cc == 0);
|
|
return cc;
|
|
}
|
|
|
|
/*
|
|
* Accept a connection. This will block waiting for somebody to show up.
|
|
*/
|
|
bool jdwpAcceptConnection(NetState* netState)
|
|
{
|
|
struct sockaddr_in addr;
|
|
socklen_t addrlen;
|
|
int sock;
|
|
|
|
if (netState->listenSock < 0)
|
|
return false; /* you're not listening! */
|
|
|
|
assert(netState->dbg.sock < 0); /* must not already be talking */
|
|
|
|
addrlen = sizeof(addr);
|
|
do {
|
|
sock = accept(netState->listenSock, (struct sockaddr*) &addr, &addrlen);
|
|
if (sock < 0 && errno != EINTR) {
|
|
fprintf(stderr, "accept failed: %s\n", strerror(errno));
|
|
return false;
|
|
}
|
|
} while (sock < 0);
|
|
|
|
fprintf(stderr, "+++ accepted connection from %s:%u\n",
|
|
inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
|
|
|
|
netState->dbg.sock = sock;
|
|
netState->dbg.awaitingHandshake = true;
|
|
netState->dbg.inputCount = 0;
|
|
|
|
setNoDelay(sock);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Close the connections to the debugger and VM.
|
|
*
|
|
* Reset the state so we're ready to receive a new connection.
|
|
*/
|
|
void jdwpCloseConnection(NetState* netState)
|
|
{
|
|
if (netState->dbg.sock >= 0) {
|
|
fprintf(stderr, "+++ closing connection to debugger\n");
|
|
close(netState->dbg.sock);
|
|
netState->dbg.sock = -1;
|
|
}
|
|
if (netState->vm.sock >= 0) {
|
|
fprintf(stderr, "+++ closing connection to vm\n");
|
|
close(netState->vm.sock);
|
|
netState->vm.sock = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Figure out if we have a full packet in the buffer.
|
|
*/
|
|
static bool haveFullPacket(Peer* pPeer)
|
|
{
|
|
long length;
|
|
|
|
if (pPeer->awaitingHandshake)
|
|
return (pPeer->inputCount >= kMagicHandshakeLen);
|
|
|
|
if (pPeer->inputCount < 4)
|
|
return false;
|
|
|
|
length = get4BE(pPeer->inputBuffer);
|
|
return (pPeer->inputCount >= length);
|
|
}
|
|
|
|
/*
|
|
* Consume bytes from the buffer.
|
|
*
|
|
* This would be more efficient with a circular buffer. However, we're
|
|
* usually only going to find one packet, which is trivial to handle.
|
|
*/
|
|
static void consumeBytes(Peer* pPeer, int count)
|
|
{
|
|
assert(count > 0);
|
|
assert(count <= pPeer->inputCount);
|
|
|
|
if (count == pPeer->inputCount) {
|
|
pPeer->inputCount = 0;
|
|
return;
|
|
}
|
|
|
|
memmove(pPeer->inputBuffer, pPeer->inputBuffer + count,
|
|
pPeer->inputCount - count);
|
|
pPeer->inputCount -= count;
|
|
}
|
|
|
|
/*
|
|
* Get the current time.
|
|
*/
|
|
static void getCurrentTime(int* pMin, int* pSec)
|
|
{
|
|
time_t now;
|
|
struct tm* ptm;
|
|
|
|
now = time(NULL);
|
|
ptm = localtime(&now);
|
|
*pMin = ptm->tm_min;
|
|
*pSec = ptm->tm_sec;
|
|
}
|
|
|
|
/*
|
|
* Dump the contents of a packet to stdout.
|
|
*/
|
|
static void dumpPacket(const unsigned char* packetBuf, const char* srcName,
|
|
const char* dstName)
|
|
{
|
|
const unsigned char* buf = packetBuf;
|
|
char prefix[3];
|
|
u4 length, id;
|
|
u1 flags, cmdSet=0, cmd=0;
|
|
u2 error=0;
|
|
bool reply;
|
|
int dataLen;
|
|
|
|
length = get4BE(buf+0);
|
|
id = get4BE(buf+4);
|
|
flags = get1(buf+8);
|
|
if ((flags & kJDWPFlagReply) != 0) {
|
|
reply = true;
|
|
error = get2BE(buf+9);
|
|
} else {
|
|
reply = false;
|
|
cmdSet = get1(buf+9);
|
|
cmd = get1(buf+10);
|
|
}
|
|
|
|
buf += kJDWPHeaderLen;
|
|
dataLen = length - (buf - packetBuf);
|
|
|
|
if (!reply) {
|
|
prefix[0] = srcName[0];
|
|
prefix[1] = '>';
|
|
} else {
|
|
prefix[0] = dstName[0];
|
|
prefix[1] = '<';
|
|
}
|
|
prefix[2] = '\0';
|
|
|
|
int min, sec;
|
|
getCurrentTime(&min, &sec);
|
|
|
|
if (!reply) {
|
|
printf("%s REQUEST dataLen=%-5u id=0x%08x flags=0x%02x cmd=%d/%d [%02d:%02d]\n",
|
|
prefix, dataLen, id, flags, cmdSet, cmd, min, sec);
|
|
printf("%s --> %s\n", prefix, getCommandName(cmdSet, cmd));
|
|
} else {
|
|
printf("%s REPLY dataLen=%-5u id=0x%08x flags=0x%02x err=%d (%s) [%02d:%02d]\n",
|
|
prefix, dataLen, id, flags, error, dvmJdwpErrorStr(error), min,sec);
|
|
}
|
|
if (dataLen > 0)
|
|
printHexDump2(buf, dataLen, prefix);
|
|
printf("%s ----------\n", prefix);
|
|
}
|
|
|
|
/*
|
|
* Handle a packet. Returns "false" if we encounter a connection-fatal error.
|
|
*/
|
|
static bool handlePacket(Peer* pDst, Peer* pSrc)
|
|
{
|
|
const unsigned char* buf = pSrc->inputBuffer;
|
|
u4 length;
|
|
u1 flags;
|
|
int cc;
|
|
|
|
length = get4BE(buf+0);
|
|
flags = get1(buf+9);
|
|
|
|
assert((int) length <= pSrc->inputCount);
|
|
|
|
dumpPacket(buf, pSrc->label, pDst->label);
|
|
|
|
cc = write(pDst->sock, buf, length);
|
|
if (cc != (int) length) {
|
|
fprintf(stderr, "Failed sending packet: %s\n", strerror(errno));
|
|
return false;
|
|
}
|
|
/*printf("*** wrote %d bytes from %c to %c\n",
|
|
cc, pSrc->label[0], pDst->label[0]);*/
|
|
|
|
consumeBytes(pSrc, length);
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Handle incoming data. If we have a full packet in the buffer, process it.
|
|
*/
|
|
static bool handleIncoming(Peer* pWritePeer, Peer* pReadPeer)
|
|
{
|
|
if (haveFullPacket(pReadPeer)) {
|
|
if (pReadPeer->awaitingHandshake) {
|
|
printf("Handshake [%c]: %.14s\n",
|
|
pReadPeer->label[0], pReadPeer->inputBuffer);
|
|
if (write(pWritePeer->sock, pReadPeer->inputBuffer,
|
|
kMagicHandshakeLen) != kMagicHandshakeLen)
|
|
{
|
|
fprintf(stderr,
|
|
"+++ [%c] handshake write failed\n", pReadPeer->label[0]);
|
|
goto fail;
|
|
}
|
|
consumeBytes(pReadPeer, kMagicHandshakeLen);
|
|
pReadPeer->awaitingHandshake = false;
|
|
} else {
|
|
if (!handlePacket(pWritePeer, pReadPeer))
|
|
goto fail;
|
|
}
|
|
} else {
|
|
/*printf("*** %c not full yet\n", pReadPeer->label[0]);*/
|
|
}
|
|
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Process incoming data. If no data is available, this will block until
|
|
* some arrives.
|
|
*
|
|
* Returns "false" on error (indicating that the connection has been severed).
|
|
*/
|
|
bool jdwpProcessIncoming(NetState* netState)
|
|
{
|
|
int cc;
|
|
|
|
assert(netState->dbg.sock >= 0);
|
|
assert(netState->vm.sock >= 0);
|
|
|
|
while (!haveFullPacket(&netState->dbg) && !haveFullPacket(&netState->vm)) {
|
|
/* read some more */
|
|
int highFd;
|
|
fd_set readfds;
|
|
|
|
highFd = (netState->dbg.sock > netState->vm.sock) ?
|
|
netState->dbg.sock+1 : netState->vm.sock+1;
|
|
FD_ZERO(&readfds);
|
|
FD_SET(netState->dbg.sock, &readfds);
|
|
FD_SET(netState->vm.sock, &readfds);
|
|
|
|
errno = 0;
|
|
cc = select(highFd, &readfds, NULL, NULL, NULL);
|
|
if (cc < 0) {
|
|
if (errno == EINTR) {
|
|
fprintf(stderr, "+++ EINTR on select\n");
|
|
continue;
|
|
}
|
|
fprintf(stderr, "+++ select failed: %s\n", strerror(errno));
|
|
goto fail;
|
|
}
|
|
|
|
if (FD_ISSET(netState->dbg.sock, &readfds)) {
|
|
cc = read(netState->dbg.sock,
|
|
netState->dbg.inputBuffer + netState->dbg.inputCount,
|
|
sizeof(netState->dbg.inputBuffer) - netState->dbg.inputCount);
|
|
if (cc < 0) {
|
|
if (errno == EINTR) {
|
|
fprintf(stderr, "+++ EINTR on read\n");
|
|
continue;
|
|
}
|
|
fprintf(stderr, "+++ dbg read failed: %s\n", strerror(errno));
|
|
goto fail;
|
|
}
|
|
if (cc == 0) {
|
|
if (sizeof(netState->dbg.inputBuffer) ==
|
|
netState->dbg.inputCount)
|
|
fprintf(stderr, "+++ debugger sent huge message\n");
|
|
else
|
|
fprintf(stderr, "+++ debugger disconnected\n");
|
|
goto fail;
|
|
}
|
|
|
|
/*printf("*** %d bytes from dbg\n", cc);*/
|
|
netState->dbg.inputCount += cc;
|
|
}
|
|
|
|
if (FD_ISSET(netState->vm.sock, &readfds)) {
|
|
cc = read(netState->vm.sock,
|
|
netState->vm.inputBuffer + netState->vm.inputCount,
|
|
sizeof(netState->vm.inputBuffer) - netState->vm.inputCount);
|
|
if (cc < 0) {
|
|
if (errno == EINTR) {
|
|
fprintf(stderr, "+++ EINTR on read\n");
|
|
continue;
|
|
}
|
|
fprintf(stderr, "+++ vm read failed: %s\n", strerror(errno));
|
|
goto fail;
|
|
}
|
|
if (cc == 0) {
|
|
if (sizeof(netState->vm.inputBuffer) ==
|
|
netState->vm.inputCount)
|
|
fprintf(stderr, "+++ vm sent huge message\n");
|
|
else
|
|
fprintf(stderr, "+++ vm disconnected\n");
|
|
goto fail;
|
|
}
|
|
|
|
/*printf("*** %d bytes from vm\n", cc);*/
|
|
netState->vm.inputCount += cc;
|
|
}
|
|
}
|
|
|
|
if (!handleIncoming(&netState->dbg, &netState->vm))
|
|
goto fail;
|
|
if (!handleIncoming(&netState->vm, &netState->dbg))
|
|
goto fail;
|
|
|
|
return true;
|
|
|
|
fail:
|
|
jdwpCloseConnection(netState);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Connect to the VM.
|
|
*/
|
|
bool jdwpConnectToVm(NetState* netState)
|
|
{
|
|
struct sockaddr_in addr;
|
|
int sock = -1;
|
|
|
|
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (sock < 0) {
|
|
fprintf(stderr, "Socket create failed: %s\n", strerror(errno));
|
|
goto fail;
|
|
}
|
|
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr = netState->vmAddr;
|
|
addr.sin_port = htons(netState->vmPort);
|
|
if (connect(sock, (struct sockaddr*) &addr, sizeof(addr)) != 0) {
|
|
fprintf(stderr, "Connection to %s:%u failed: %s\n",
|
|
inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), strerror(errno));
|
|
goto fail;
|
|
}
|
|
fprintf(stderr, "+++ connected to VM %s:%u\n",
|
|
inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
|
|
|
|
netState->vm.sock = sock;
|
|
netState->vm.awaitingHandshake = true;
|
|
netState->vm.inputCount = 0;
|
|
|
|
setNoDelay(netState->vm.sock);
|
|
return true;
|
|
|
|
fail:
|
|
if (sock >= 0)
|
|
close(sock);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Establish network connections and start things running.
|
|
*
|
|
* We wait for a new connection from the debugger. When one arrives we
|
|
* open a connection to the VM. If one side or the other goes away, we
|
|
* drop both ends and go back to listening.
|
|
*/
|
|
int run(const char* connectHost, int connectPort, int listenPort)
|
|
{
|
|
NetState* state;
|
|
|
|
state = jdwpNetStartup(listenPort, connectHost, connectPort);
|
|
if (state == NULL)
|
|
return -1;
|
|
|
|
while (true) {
|
|
if (!jdwpAcceptConnection(state))
|
|
break;
|
|
|
|
if (jdwpConnectToVm(state)) {
|
|
while (true) {
|
|
if (!jdwpProcessIncoming(state))
|
|
break;
|
|
}
|
|
}
|
|
|
|
jdwpCloseConnection(state);
|
|
}
|
|
|
|
jdwpNetFree(state);
|
|
|
|
return 0;
|
|
}
|
|
|