| /* |
| * 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/jdwp_constants.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> |
| |
| #include <iostream> |
| #include <sstream> |
| |
| #define kInputBufferSize (256*1024) |
| |
| #define kMagicHandshakeLen 14 /* "JDWP-Handshake" */ |
| #define kJDWPHeaderLen 11 |
| #define kJDWPFlagReply 0x80 |
| |
| |
| /* |
| * Information about the remote end. |
| */ |
| struct Peer { |
| char label[2]; /* 'D' or 'V' */ |
| |
| int sock; |
| unsigned char inputBuffer[kInputBufferSize]; |
| int inputCount; |
| |
| bool awaitingHandshake; /* waiting for "JDWP-Handshake" */ |
| }; |
| |
| |
| /* |
| * Network state. |
| */ |
| struct NetState { |
| /* listen here for connection from debugger */ |
| int listenSock; |
| |
| /* connect here to contact VM */ |
| in_addr vmAddr; |
| uint16_t vmPort; |
| |
| Peer dbg; |
| Peer vm; |
| }; |
| |
| /* |
| * Function names. |
| */ |
| struct JdwpHandlerMap { |
| u1 cmdSet; |
| u1 cmd; |
| const char* descr; |
| }; |
| |
| /* |
| * 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) |
| { |
| for (int 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(uint16_t listenPort, const char* connectHost, uint16_t connectPort) { |
| NetState* netState = new 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 */ |
| { |
| int one = 1; |
| if (setsockopt(netState->listenSock, SOL_SOCKET, SO_REUSEADDR, &one, |
| sizeof(one)) < 0) |
| { |
| fprintf(stderr, "setsockopt(SO_REUSEADDR) failed: %s\n", |
| strerror(errno)); |
| goto fail; |
| } |
| } |
| |
| sockaddr_in addr; |
| addr.sin_family = AF_INET; |
| addr.sin_port = htons(listenPort); |
| addr.sin_addr.s_addr = INADDR_ANY; |
| |
| if (bind(netState->listenSock, (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. |
| */ |
| 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 = *((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); |
| delete 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) |
| { |
| 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, (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; |
| 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; |
| art::JDWP::JdwpError error = art::JDWP::ERR_NONE; |
| bool reply; |
| int dataLen; |
| |
| length = get4BE(buf+0); |
| id = get4BE(buf+4); |
| flags = get1(buf+8); |
| if ((flags & kJDWPFlagReply) != 0) { |
| reply = true; |
| error = static_cast<art::JDWP::JdwpError>(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 { |
| std::ostringstream ss; |
| ss << "TODO"; // get access to the operator<<, or regenerate it for jdwpspy? |
| printf("%s REPLY dataLen=%-5u id=0x%08x flags=0x%02x err=%d (%s) [%02d:%02d]\n", |
| prefix, dataLen, id, flags, error, ss.str().c_str(), 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) |
| { |
| 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; |
| } |