diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:28:47 -0800 |
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:28:47 -0800 |
| commit | f6c387128427e121477c1b32ad35cdcaa5101ba3 (patch) | |
| tree | 2aa25fa8c8c3a9caeecf98fd8ac4cd9b12717997 /vm/jdwp | |
| parent | f72d5de56a522ac3be03873bdde26f23a5eeeb3c (diff) | |
| download | android_dalvik-f6c387128427e121477c1b32ad35cdcaa5101ba3.tar.gz android_dalvik-f6c387128427e121477c1b32ad35cdcaa5101ba3.tar.bz2 android_dalvik-f6c387128427e121477c1b32ad35cdcaa5101ba3.zip | |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'vm/jdwp')
| -rw-r--r-- | vm/jdwp/ExpandBuf.c | 176 | ||||
| -rw-r--r-- | vm/jdwp/ExpandBuf.h | 57 | ||||
| -rw-r--r-- | vm/jdwp/Jdwp.h | 237 | ||||
| -rw-r--r-- | vm/jdwp/JdwpAdb.c | 676 | ||||
| -rw-r--r-- | vm/jdwp/JdwpConstants.c | 240 | ||||
| -rw-r--r-- | vm/jdwp/JdwpConstants.h | 229 | ||||
| -rw-r--r-- | vm/jdwp/JdwpEvent.c | 1290 | ||||
| -rw-r--r-- | vm/jdwp/JdwpEvent.h | 129 | ||||
| -rw-r--r-- | vm/jdwp/JdwpHandler.c | 2152 | ||||
| -rw-r--r-- | vm/jdwp/JdwpHandler.h | 47 | ||||
| -rw-r--r-- | vm/jdwp/JdwpMain.c | 440 | ||||
| -rw-r--r-- | vm/jdwp/JdwpPriv.h | 171 | ||||
| -rw-r--r-- | vm/jdwp/JdwpSocket.c | 876 | ||||
| -rw-r--r-- | vm/jdwp/README.txt | 13 |
14 files changed, 6733 insertions, 0 deletions
diff --git a/vm/jdwp/ExpandBuf.c b/vm/jdwp/ExpandBuf.c new file mode 100644 index 000000000..50c30355a --- /dev/null +++ b/vm/jdwp/ExpandBuf.c @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Implementation of an expandable byte buffer. Designed for serializing + * primitive values, e.g. JDWP replies. + */ +#include "jdwp/ExpandBuf.h" +#include "Bits.h" +#include "Common.h" + +#include <stdlib.h> +#include <string.h> + +/* + * Data structure used to track buffer use. + */ +struct ExpandBuf { + u1* storage; + int curLen; + int maxLen; +}; + +#define kInitialStorage 64 + +/* + * Allocate a JdwpBuf and some initial storage. + */ +ExpandBuf* expandBufAlloc(void) +{ + ExpandBuf* newBuf; + + newBuf = (ExpandBuf*) malloc(sizeof(*newBuf)); + newBuf->storage = (u1*) malloc(kInitialStorage); + newBuf->curLen = 0; + newBuf->maxLen = kInitialStorage; + + return newBuf; +} + +/* + * Free a JdwpBuf and associated storage. + */ +void expandBufFree(ExpandBuf* pBuf) +{ + if (pBuf == NULL) + return; + + free(pBuf->storage); + free(pBuf); +} + +/* + * Get a pointer to the start of the buffer. + */ +u1* expandBufGetBuffer(ExpandBuf* pBuf) +{ + return pBuf->storage; +} + +/* + * Get the amount of data currently in the buffer. + */ +size_t expandBufGetLength(ExpandBuf* pBuf) +{ + return pBuf->curLen; +} + + +/* + * Ensure that the buffer has enough space to hold incoming data. If it + * doesn't, resize the buffer. + */ +static void ensureSpace(ExpandBuf* pBuf, int newCount) +{ + u1* newPtr; + + if (pBuf->curLen + newCount <= pBuf->maxLen) + return; + + while (pBuf->curLen + newCount > pBuf->maxLen) + pBuf->maxLen *= 2; + + newPtr = realloc(pBuf->storage, pBuf->maxLen); + if (newPtr == NULL) { + LOGE("realloc(%d) failed\n", pBuf->maxLen); + abort(); + } + + pBuf->storage = newPtr; +} + +/* + * Allocate some space in the buffer. + */ +u1* expandBufAddSpace(ExpandBuf* pBuf, int gapSize) +{ + u1* gapStart; + + ensureSpace(pBuf, gapSize); + gapStart = pBuf->storage + pBuf->curLen; + /* do we want to garbage-fill the gap for debugging? */ + pBuf->curLen += gapSize; + + return gapStart; +} + +/* + * Append a byte. + */ +void expandBufAdd1(ExpandBuf* pBuf, u1 val) +{ + ensureSpace(pBuf, sizeof(val)); + *(pBuf->storage + pBuf->curLen) = val; + pBuf->curLen++; +} + +/* + * Append two big-endian bytes. + */ +void expandBufAdd2BE(ExpandBuf* pBuf, u2 val) +{ + ensureSpace(pBuf, sizeof(val)); + set2BE(pBuf->storage + pBuf->curLen, val); + pBuf->curLen += sizeof(val); +} + +/* + * Append four big-endian bytes. + */ +void expandBufAdd4BE(ExpandBuf* pBuf, u4 val) +{ + ensureSpace(pBuf, sizeof(val)); + set4BE(pBuf->storage + pBuf->curLen, val); + pBuf->curLen += sizeof(val); +} + +/* + * Append eight big-endian bytes. + */ +void expandBufAdd8BE(ExpandBuf* pBuf, u8 val) +{ + ensureSpace(pBuf, sizeof(val)); + set8BE(pBuf->storage + pBuf->curLen, val); + pBuf->curLen += sizeof(val); +} + +/* + * Add a UTF8 string as a 4-byte length followed by a non-NULL-terminated + * string. + * + * Because these strings are coming out of the VM, it's safe to assume that + * they can be null-terminated (either they don't have null bytes or they + * have stored null bytes in a multi-byte encoding). + */ +void expandBufAddUtf8String(ExpandBuf* pBuf, const u1* str) +{ + int strLen = strlen((const char*)str); + + ensureSpace(pBuf, sizeof(u4) + strLen); + setUtf8String(pBuf->storage + pBuf->curLen, str); + pBuf->curLen += sizeof(u4) + strLen; +} + diff --git a/vm/jdwp/ExpandBuf.h b/vm/jdwp/ExpandBuf.h new file mode 100644 index 000000000..8bdc8a700 --- /dev/null +++ b/vm/jdwp/ExpandBuf.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Expanding byte buffer, with primitives for appending basic data types. + */ +#ifndef _DALVIK_JDWP_EXPANDBUF +#define _DALVIK_JDWP_EXPANDBUF + +#include "Common.h" // need u1/u2/u4/u8 types + +struct ExpandBuf; /* private */ +typedef struct ExpandBuf ExpandBuf; + +/* create a new struct */ +ExpandBuf* expandBufAlloc(void); +/* free storage */ +void expandBufFree(ExpandBuf* pBuf); + +/* + * Accessors. The buffer pointer and length will only be valid until more + * data is added. + */ +u1* expandBufGetBuffer(ExpandBuf* pBuf); +size_t expandBufGetLength(ExpandBuf* pBuf); + +/* + * The "add" operations allocate additional storage and append the data. + * + * There are no "get" operations included with this "class", other than + * GetBuffer(). If you want to get or set data from a position other + * than the end, get a pointer to the buffer and use the inline functions + * defined elsewhere. + * + * expandBufAddSpace() returns a pointer to the *start* of the region + * added. + */ +u1* expandBufAddSpace(ExpandBuf* pBuf, int gapSize); +void expandBufAdd1(ExpandBuf* pBuf, u1 val); +void expandBufAdd2BE(ExpandBuf* pBuf, u2 val); +void expandBufAdd4BE(ExpandBuf* pBuf, u4 val); +void expandBufAdd8BE(ExpandBuf* pBuf, u8 val); +void expandBufAddUtf8String(ExpandBuf* pBuf, const u1* str); + +#endif /*_DALVIK_JDWP_EXPANDBUF*/ diff --git a/vm/jdwp/Jdwp.h b/vm/jdwp/Jdwp.h new file mode 100644 index 000000000..0a72a06f8 --- /dev/null +++ b/vm/jdwp/Jdwp.h @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * JDWP "public" interface. The main body of the VM should only use JDWP + * structures and functions declared here. + * + * The JDWP code follows the DalvikVM rules for naming conventions, but + * attempts to remain independent of VM innards (e.g. it doesn't access VM + * data structures directly). All calls go through Debugger.c. + */ +#ifndef _DALVIK_JDWP_JDWP +#define _DALVIK_JDWP_JDWP + +#include "jdwp/JdwpConstants.h" +#include "jdwp/ExpandBuf.h" +#include "Common.h" +#include "Bits.h" +#include <pthread.h> + +struct JdwpState; /* opaque */ +typedef struct JdwpState JdwpState; + +/* + * Fundamental types. + * + * ObjectId and RefTypeId must be the same size. + */ +typedef u4 FieldId; /* static or instance field */ +typedef u4 MethodId; /* any kind of method, including constructors */ +typedef u8 ObjectId; /* any object (threadID, stringID, arrayID, etc) */ +typedef u8 RefTypeId; /* like ObjectID, but unique for Class objects */ +typedef u8 FrameId; /* short-lived stack frame ID */ + +/* + * Match these with the type sizes. This way we don't have to pass + * a value and a length. + */ +INLINE FieldId dvmReadFieldId(const u1** pBuf) { return read4BE(pBuf); } +INLINE MethodId dvmReadMethodId(const u1** pBuf) { return read4BE(pBuf); } +INLINE ObjectId dvmReadObjectId(const u1** pBuf) { return read8BE(pBuf); } +INLINE RefTypeId dvmReadRefTypeId(const u1** pBuf) { return read8BE(pBuf); } +INLINE FrameId dvmReadFrameId(const u1** pBuf) { return read8BE(pBuf); } +INLINE void dvmSetFieldId(u1* buf, FieldId val) { return set4BE(buf, val); } +INLINE void dvmSetMethodId(u1* buf, MethodId val) { return set4BE(buf, val); } +INLINE void dvmSetObjectId(u1* buf, ObjectId val) { return set8BE(buf, val); } +INLINE void dvmSetRefTypeId(u1* buf, RefTypeId val) { return set8BE(buf, val); } +INLINE void dvmSetFrameId(u1* buf, FrameId val) { return set8BE(buf, val); } +INLINE void expandBufAddFieldId(ExpandBuf* pReply, FieldId id) { + expandBufAdd4BE(pReply, id); +} +INLINE void expandBufAddMethodId(ExpandBuf* pReply, MethodId id) { + expandBufAdd4BE(pReply, id); +} +INLINE void expandBufAddObjectId(ExpandBuf* pReply, ObjectId id) { + expandBufAdd8BE(pReply, id); +} +INLINE void expandBufAddRefTypeId(ExpandBuf* pReply, RefTypeId id) { + expandBufAdd8BE(pReply, id); +} +INLINE void expandBufAddFrameId(ExpandBuf* pReply, FrameId id) { + expandBufAdd8BE(pReply, id); +} + + +/* + * Holds a JDWP "location". + */ +typedef struct JdwpLocation { + u1 typeTag; /* class or interface? */ + RefTypeId classId; /* method->clazz */ + MethodId methodId; /* method in which "idx" resides */ + u8 idx; /* relative index into code block */ +} JdwpLocation; +//#define kJDWPLocationSize (25) + +/* + * How we talk to the debugger. + */ +typedef enum JdwpTransportType { + kJdwpTransportUnknown = 0, + kJdwpTransportSocket, /* transport=dt_socket */ + kJdwpTransportAndroidAdb, /* transport=dt_android_adb */ +} JdwpTransportType; + +/* + * Holds collection of JDWP initialization parameters. + */ +typedef struct JdwpStartupParams { + JdwpTransportType transport; + bool server; + bool suspend; + char host[64]; + short port; + /* more will be here someday */ +} JdwpStartupParams; + +/* + * Perform one-time initialization. + * + * Among other things, this binds to a port to listen for a connection from + * the debugger. + * + * Returns a newly-allocated JdwpState struct on success, or NULL on failure. + */ +JdwpState* dvmJdwpStartup(const JdwpStartupParams* params); + +/* + * Shut everything down. + */ +void dvmJdwpShutdown(JdwpState* state); + +/* + * Returns "true" if a debugger or DDM is connected. + */ +bool dvmJdwpIsActive(JdwpState* state); + +/* + * Return the debugger thread's handle, or 0 if the debugger thread isn't + * running. + */ +pthread_t dvmJdwpGetDebugThread(JdwpState* state); + +/* + * Get time, in milliseconds, since the last debugger activity. + */ +s8 dvmJdwpLastDebuggerActivity(JdwpState* state); + +/* + * When we hit a debugger event that requires suspension, it's important + * that we wait for the thread to suspend itself before processing any + * additional requests. (Otherwise, if the debugger immediately sends a + * "resume thread" command, the resume might arrive before the thread has + * suspended itself.) + * + * The thread should call the "set" function before sending the event to + * the debugger. The main JDWP handler loop calls "get" before processing + * an event, and will wait for thread suspension if it's set. Once the + * thread has suspended itself, the JDWP handler calls "clear" and + * continues processing the current event. This works in the suspend-all + * case because the event thread doesn't suspend itself until everything + * else has suspended. + * + * It's possible that multiple threads could encounter thread-suspending + * events at the same time, so we grab a mutex in the "set" call, and + * release it in the "clear" call. + */ +//ObjectId dvmJdwpGetWaitForEventThread(JdwpState* state); +void dvmJdwpSetWaitForEventThread(JdwpState* state, ObjectId threadId); +void dvmJdwpClearWaitForEventThread(JdwpState* state); + +/* + * Network functions. + */ +bool dvmJdwpCheckConnection(JdwpState* state); +bool dvmJdwpAcceptConnection(JdwpState* state); +bool dvmJdwpEstablishConnection(JdwpState* state); +void dvmJdwpCloseConnection(JdwpState* state); +bool dvmJdwpProcessIncoming(JdwpState* state); + + +/* + * These notify the debug code that something interesting has happened. This + * could be a thread starting or ending, an exception, or an opportunity + * for a breakpoint. These calls do not mean that an event the debugger + * is interested has happened, just that something has happened that the + * debugger *might* be interested in. + * + * The item of interest may trigger multiple events, some or all of which + * are grouped together in a single response. + * + * The event may cause the current thread or all threads (except the + * JDWP support thread) to be suspended. + */ + +/* + * The VM has finished initializing. Only called when the debugger is + * connected at the time initialization completes. + */ +bool dvmJdwpPostVMStart(JdwpState* state, bool suspend); + +/* + * A location of interest has been reached. This is used for breakpoints, + * single-stepping, and method entry/exit. (JDWP requires that these four + * events are grouped together in a single response.) + * + * In some cases "*pLoc" will just have a method and class name, e.g. when + * issuing a MethodEntry on a native method. + * + * "eventFlags" indicates the types of events that have occurred. + */ +bool dvmJdwpPostLocationEvent(JdwpState* state, const JdwpLocation* pLoc, + ObjectId thisPtr, int eventFlags); + +/* + * An exception has been thrown. + * + * Pass in a zeroed-out "*pCatchLoc" if the exception wasn't caught. + */ +bool dvmJdwpPostException(JdwpState* state, const JdwpLocation* pThrowLoc, + ObjectId excepId, RefTypeId excepClassId, const JdwpLocation* pCatchLoc, + ObjectId thisPtr); + +/* + * A thread has started or stopped. + */ +bool dvmJdwpPostThreadChange(JdwpState* state, ObjectId threadId, bool start); + +/* + * Class has been prepared. + */ +bool dvmJdwpPostClassPrepare(JdwpState* state, int tag, RefTypeId refTypeId, + const char* signature, int status); + +/* + * The VM is about to stop. + */ +bool dvmJdwpPostVMDeath(JdwpState* state); + +/* + * Send up a chunk of DDM data. + */ +void dvmJdwpDdmSendChunk(JdwpState* state, int type, int len, const u1* buf); + +#endif /*_DALVIK_JDWP_JDWP*/ diff --git a/vm/jdwp/JdwpAdb.c b/vm/jdwp/JdwpAdb.c new file mode 100644 index 000000000..317c2093b --- /dev/null +++ b/vm/jdwp/JdwpAdb.c @@ -0,0 +1,676 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "jdwp/JdwpPriv.h" +#include "jdwp/JdwpHandler.h" +#include <sys/socket.h> +#include <sys/un.h> +#include <errno.h> +#include <unistd.h> + +/* the JDWP <-> ADB transport protocol is explained in details + * in //device/tools/adb/jdwp_service.c, here's a summary. + * + * 1/ when the JDWP thread starts, it tries to connect to a Unix + * domain stream socket (@jdwp-control) that is opened by the + * ADB daemon. + * + * 2/ it then sends the current process PID as a string of 4 hexadecimal + * chars (no terminating zero) + * + * 3/ then, it uses recvmsg to receive file descriptors from the + * daemon. each incoming file descriptor is a pass-through to + * a given JDWP debugger, that can be used to read the usual + * JDWP-handshake, etc... + * + */ + +#define kInputBufferSize 8192 + +#define kMagicHandshake "JDWP-Handshake" +#define kMagicHandshakeLen (sizeof(kMagicHandshake)-1) + +#define kJdwpControlName "\0jdwp-control" +#define kJdwpControlNameLen (sizeof(kJdwpControlName)-1) + +struct JdwpNetState { + int controlSock; + int clientSock; + bool awaitingHandshake; + int wakeFds[2]; + + int inputCount; + unsigned char inputBuffer[kInputBufferSize]; + + socklen_t controlAddrLen; + union { + struct sockaddr_un controlAddrUn; + struct sockaddr controlAddrPlain; + } controlAddr; +}; + +static void +adbStateFree( JdwpNetState* netState ) +{ + if (netState == NULL) + return; + + if (netState->clientSock >= 0) { + shutdown(netState->clientSock, SHUT_RDWR); + close(netState->clientSock); + } + if (netState->controlSock >= 0) { + shutdown(netState->controlSock, SHUT_RDWR); + close(netState->controlSock); + } + if (netState->wakeFds[0] >= 0) { + close(netState->wakeFds[0]); + netState->wakeFds[0] = -1; + } + if (netState->wakeFds[1] >= 0) { + close(netState->wakeFds[1]); + netState->wakeFds[1] = -1; + } + + free(netState); +} + + +static JdwpNetState* +adbStateAlloc(void) +{ + JdwpNetState* netState = calloc(sizeof(*netState),1); + + netState->controlSock = -1; + netState->clientSock = -1; + + netState->controlAddr.controlAddrUn.sun_family = AF_UNIX; + netState->controlAddrLen = + sizeof(netState->controlAddr.controlAddrUn.sun_family) + + kJdwpControlNameLen; + + memcpy(netState->controlAddr.controlAddrUn.sun_path, + kJdwpControlName, kJdwpControlNameLen); + + netState->wakeFds[0] = -1; + netState->wakeFds[1] = -1; + + return netState; +} + + +/* + * Do initial prep work, e.g. binding to ports and opening files. This + * runs in the main thread, before the JDWP thread starts, so it shouldn't + * do anything that might block forever. + */ +static bool startup(struct JdwpState* state, const JdwpStartupParams* pParams) +{ + JdwpNetState* netState; + + LOGV("ADB transport startup\n"); + + state->netState = netState = adbStateAlloc(); + if (netState == NULL) + return false; + + return true; +} + +static int receiveClientFd(JdwpNetState* netState) +{ + struct msghdr msg; + struct cmsghdr* cmsg; + struct iovec iov; + char dummy = '!'; + union { + struct cmsghdr cm; + char buffer[CMSG_SPACE(sizeof(int))]; + } cm_un; + int ret; + + iov.iov_base = &dummy; + iov.iov_len = 1; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + msg.msg_control = cm_un.buffer; + msg.msg_controllen = sizeof(cm_un.buffer); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = msg.msg_controllen; + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + ((int*)CMSG_DATA(cmsg))[0] = -1; + + do { + ret = recvmsg(netState->controlSock, &msg, 0); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) { + LOGE("receiving file descriptor from ADB failed (socket %d): %s\n", + netState->controlSock, strerror(errno)); + return -1; + } + + return ((int*)CMSG_DATA(cmsg))[0]; +} + +/* + * Block forever, waiting for a debugger to connect to us. Called from the + * JDWP thread. + * + * This needs to un-block and return "false" if the VM is shutting down. It + * should return "true" when it successfully accepts a connection. + */ +static bool acceptConnection(struct JdwpState* state) +{ + JdwpNetState* netState = state->netState; + + /* first, ensure that we get a connection to the ADB daemon */ + + if (netState->controlSock < 0) + { + int sleep_ms = 500; + const int sleep_max_ms = 2*1000; + char buff[5]; + + netState->controlSock = socket(PF_UNIX, SOCK_STREAM, 0); + if (netState->controlSock < 0) { + LOGE("Could not create ADB control socket:%s\n", + strerror(errno)); + return false; + } + + if (pipe(netState->wakeFds) < 0) { + LOGE("pipe failed"); + return false; + } + + snprintf(buff, sizeof(buff), "%04x", getpid()); + buff[4] = 0; + + for (;;) { + int ret = connect(netState->controlSock, + &netState->controlAddr.controlAddrPlain, + netState->controlAddrLen); + if (!ret) { + /* now try to send our pid to the ADB daemon */ + do { + ret = send( netState->controlSock, buff, 4, 0 ); + } while (ret < 0 && errno == EINTR); + + if (ret >= 0) { + LOGV("PID sent as '%.*s' to ADB\n", 4, buff); + break; + } + + LOGE("Weird, can't send JDWP process pid to ADB: %s\n", + strerror(errno)); + return false; + } + LOGV("Can't connect to ADB control socket:%s\n", + strerror(errno)); + + usleep( sleep_ms*1000 ); + + sleep_ms += (sleep_ms >> 1); + if (sleep_ms > sleep_max_ms) + sleep_ms = sleep_max_ms; + } + } + + LOGV("trying to receive file descriptor from ADB\n"); + /* now we can receive a client file descriptor */ + netState->clientSock = receiveClientFd(netState); + if (netState->clientSock >= 0) { + LOGI("received file descriptor %d from ADB\n", netState->clientSock); + netState->awaitingHandshake = 1; + netState->inputCount = 0; + } + return (netState->clientSock >= 0); +} + +/* + * Connect out to a debugger (for server=n). Not required. + */ +static bool establishConnection(struct JdwpState* state) +{ + return false; +} + +/* + * Close a connection from a debugger (which may have already dropped us). + * Only called from the JDWP thread. + */ +static void closeConnection(struct JdwpState* state) +{ + JdwpNetState* netState; + + assert(state != NULL && state->netState != NULL); + + netState = state->netState; + if (netState->clientSock < 0) + return; + + LOGV("+++ closed JDWP <-> ADB connection\n"); + + close(netState->clientSock); + netState->clientSock = -1; +} + +/* + * Close all network stuff, including the socket we use to listen for + * new connections. + * + * May be called from a non-JDWP thread, e.g. when the VM is shutting down. + */ +static void adbStateShutdown(struct JdwpNetState* netState) +{ + int controlSock; + int clientSock; + + if (netState == NULL) + return; + + clientSock = netState->clientSock; + if (clientSock >= 0) { + shutdown(clientSock, SHUT_RDWR); + netState->clientSock = -1; + } + + controlSock = netState->controlSock; + if (controlSock >= 0) { + shutdown(controlSock, SHUT_RDWR); + netState->controlSock = -1; + } + + if (netState->wakeFds[1] >= 0) { + LOGV("+++ writing to wakePipe\n"); + (void) write(netState->wakeFds[1], "", 1); + } +} + +static void netShutdown(JdwpState* state) +{ + adbStateShutdown(state->netState); +} + +/* + * Free up anything we put in state->netState. This is called after + * "netShutdown", after the JDWP thread has stopped. + */ +static void netFree(struct JdwpState* state) +{ + JdwpNetState* netState = state->netState; + + adbStateFree(netState); +} + +/* + * Is a debugger connected to us? + */ +static bool isConnected(struct JdwpState* state) +{ + return (state->netState != NULL && + state->netState->clientSock >= 0); +} + +/* + * Are we still waiting for the JDWP handshake? + */ +static bool awaitingHandshake(struct JdwpState* state) +{ + return state->netState->awaitingHandshake; +} + +/* + * Figure out if we have a full packet in the buffer. + */ +static bool haveFullPacket(JdwpNetState* netState) +{ + long length; + + if (netState->awaitingHandshake) + return (netState->inputCount >= (int) kMagicHandshakeLen); + + if (netState->inputCount < 4) + return false; + + length = get4BE(netState->inputBuffer); + return (netState->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(JdwpNetState* netState, int count) +{ + assert(count > 0); + assert(count <= netState->inputCount); + + if (count == netState->inputCount) { + netState->inputCount = 0; + return; + } + + memmove(netState->inputBuffer, netState->inputBuffer + count, + netState->inputCount - count); + netState->inputCount -= count; +} + +/* + * Handle a packet. Returns "false" if we encounter a connection-fatal error. + */ +static bool handlePacket(JdwpState* state) +{ + JdwpNetState* netState = state->netState; + const unsigned char* buf = netState->inputBuffer; + JdwpReqHeader hdr; + u4 length, id; + u1 flags, cmdSet, cmd; + u2 error; + bool reply; + int dataLen; + + cmd = cmdSet = 0; // shut up gcc + + /*dumpPacket(netState->inputBuffer);*/ + + length = read4BE(&buf); + id = read4BE(&buf); + flags = read1(&buf); + if ((flags & kJDWPFlagReply) != 0) { + reply = true; + error = read2BE(&buf); + } else { + reply = false; + cmdSet = read1(&buf); + cmd = read1(&buf); + } + + assert((int) length <= netState->inputCount); + dataLen = length - (buf - netState->inputBuffer); + + if (!reply) { + ExpandBuf* pReply = expandBufAlloc(); + + hdr.length = length; + hdr.id = id; + hdr.cmdSet = cmdSet; + hdr.cmd = cmd; + dvmJdwpProcessRequest(state, &hdr, buf, dataLen, pReply); + if (expandBufGetLength(pReply) > 0) { + int cc; + + /* + * TODO: we currently assume the write() will complete in one + * go, which may not be safe for a network socket. We may need + * to mutex this against sendRequest(). + */ + cc = write(netState->clientSock, expandBufGetBuffer(pReply), + expandBufGetLength(pReply)); + if (cc != (int) expandBufGetLength(pReply)) { + LOGE("Failed sending reply to debugger: %s\n", strerror(errno)); + expandBufFree(pReply); + return false; + } + } else { + LOGW("No reply created for set=%d cmd=%d\n", cmdSet, cmd); + } + expandBufFree(pReply); + } else { + LOGV("reply?!\n"); + assert(false); + } + + LOGV("----------\n"); + + consumeBytes(netState, length); + return true; +} + +/* + * Process incoming data. If no data is available, this will block until + * some arrives. + * + * If we get a full packet, handle it. + * + * To take some of the mystery out of life, we want to reject incoming + * connections if we already have a debugger attached. If we don't, the + * debugger will just mysteriously hang until it times out. We could just + * close the listen socket, but there's a good chance we won't be able to + * bind to the same port again, which would confuse utilities. + * + * Returns "false" on error (indicating that the connection has been severed), + * "true" if things are still okay. + */ +static bool processIncoming(JdwpState* state) +{ + JdwpNetState* netState = state->netState; + int readCount; + + assert(netState->clientSock >= 0); + + if (!haveFullPacket(netState)) { + /* read some more, looping until we have data */ + errno = 0; + while (1) { + int selCount; + fd_set readfds; + int maxfd = -1; + int fd; + + FD_ZERO(&readfds); + + /* configure fds; note these may get zapped by another thread */ + fd = netState->controlSock; + if (fd >= 0) { + FD_SET(fd, &readfds); + if (maxfd < fd) + maxfd = fd; + } + fd = netState->clientSock; + if (fd >= 0) { + FD_SET(fd, &readfds); + if (maxfd < fd) + maxfd = fd; + } + fd = netState->wakeFds[0]; + if (fd >= 0) { + FD_SET(fd, &readfds); + if (maxfd < fd) + maxfd = fd; + } else { + LOGI("NOTE: entering select w/o wakepipe\n"); + } + + if (maxfd < 0) { + LOGV("+++ all fds are closed\n"); + return false; + } + + /* + * Select blocks until it sees activity on the file descriptors. + * Closing the local file descriptor does not count as activity, + * so we can't rely on that to wake us up (it works for read() + * and accept(), but not select()). + * + * We can do one of three things: (1) send a signal and catch + * EINTR, (2) open an additional fd ("wakePipe") and write to + * it when it's time to exit, or (3) time out periodically and + * re-issue the select. We're currently using #2, as it's more + * reliable than #1 and generally better than #3. Wastes two fds. + */ + selCount = select(maxfd+1, &readfds, NULL, NULL, NULL); + if (selCount < 0) { + if (errno == EINTR) + continue; + LOGE("select failed: %s\n", strerror(errno)); + goto fail; + } + + if (netState->wakeFds[0] >= 0 && + FD_ISSET(netState->wakeFds[0], &readfds)) + { + LOGD("Got wake-up signal, bailing out of select\n"); + goto fail; + } + if (netState->controlSock >= 0 && + FD_ISSET(netState->controlSock, &readfds)) + { + LOGI("Ignoring second debugger -- accepting and dropping\n"); + int sock = receiveClientFd(netState); + if (sock < 0) + LOGI("Weird -- client fd reception failed\n"); + else + close(sock); + } + if (netState->clientSock >= 0 && + FD_ISSET(netState->clientSock, &readfds)) + { + readCount = read(netState->clientSock, + netState->inputBuffer + netState->inputCount, + sizeof(netState->inputBuffer) - netState->inputCount); + if (readCount < 0) { + /* read failed */ + if (errno != EINTR) + goto fail; + LOGD("+++ EINTR hit\n"); + return true; + } else if (readCount == 0) { + /* EOF hit -- far end went away */ + LOGD("+++ peer disconnected\n"); + goto fail; + } else + break; + } + } + + netState->inputCount += readCount; + if (!haveFullPacket(netState)) + return true; /* still not there yet */ + } + + /* + * Special-case the initial handshake. For some bizarre reason we're + * expected to emulate bad tty settings by echoing the request back + * exactly as it was sent. Note the handshake is always initiated by + * the debugger, no matter who connects to whom. + * + * Other than this one case, the protocol [claims to be] stateless. + */ + if (netState->awaitingHandshake) { + int cc; + + if (memcmp(netState->inputBuffer, + kMagicHandshake, kMagicHandshakeLen) != 0) + { + LOGE("ERROR: bad handshake '%.14s'\n", netState->inputBuffer); + goto fail; + } + + errno = 0; + cc = write(netState->clientSock, netState->inputBuffer, + kMagicHandshakeLen); + if (cc != kMagicHandshakeLen) { + LOGE("Failed writing handshake bytes: %s (%d of %d)\n", + strerror(errno), cc, (int) kMagicHandshakeLen); + goto fail; + } + + consumeBytes(netState, kMagicHandshakeLen); + netState->awaitingHandshake = false; + LOGV("+++ handshake complete\n"); + return true; + } + + /* + * Handle this packet. + */ + return handlePacket(state); + +fail: + closeConnection(state); + return false; +} + +/* + * Send a request. + * + * The entire packet must be sent with a single write() call to avoid + * threading issues. + * + * Returns "true" if it was sent successfully. + */ +static bool sendRequest(JdwpState* state, ExpandBuf* pReq) +{ + JdwpNetState* netState = state->netState; + int cc; + + /* dumpPacket(expandBufGetBuffer(pReq)); */ + if (netState->clientSock < 0) { + /* can happen with some DDMS events */ + LOGV("NOT sending request -- no debugger is attached\n"); + return false; + } + + /* + * TODO: we currently assume the write() will complete in one + * go, which may not be safe for a network socket. We may need + * to mutex this against handlePacket(). + */ + errno = 0; + cc = write(netState->clientSock, expandBufGetBuffer(pReq), + expandBufGetLength(pReq)); + if (cc != (int) expandBufGetLength(pReq)) { + LOGE("Failed sending req to debugger: %s (%d of %d)\n", + strerror(errno), cc, (int) expandBufGetLength(pReq)); + return false; + } + + return true; +} + + +/* + * Our functions. + */ +static const JdwpTransport socketTransport = { + startup, + acceptConnection, + establishConnection, + closeConnection, + netShutdown, + netFree, + isConnected, + awaitingHandshake, + processIncoming, + sendRequest +}; + +/* + * Return our set. + */ +const JdwpTransport* dvmJdwpAndroidAdbTransport(void) +{ + return &socketTransport; +} + diff --git a/vm/jdwp/JdwpConstants.c b/vm/jdwp/JdwpConstants.c new file mode 100644 index 000000000..e089afa2a --- /dev/null +++ b/vm/jdwp/JdwpConstants.c @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * String constants to go along with enumerated values. (Pity we don't + * have enumerated constant reflection in C.) These are only needed for + * making the output human-readable. + */ +#include "jdwp/JdwpConstants.h" + +/* + * Return a string for the error code. + */ +const char* dvmJdwpErrorStr(enum JdwpError error) +{ + switch (error) { + case ERR_NONE: + return "NONE"; + case ERR_INVALID_THREAD: + return "INVALID_THREAD"; + case ERR_INVALID_THREAD_GROUP: + return "INVALID_THREAD_GROUP"; + case ERR_INVALID_PRIORITY: + return "INVALID_PRIORITY"; + case ERR_THREAD_NOT_SUSPENDED: + return "THREAD_NOT_SUSPENDED"; + case ERR_THREAD_SUSPENDED: + return "THREAD_SUSPENDED"; + case ERR_INVALID_OBJECT: + return "INVALID_OBJEC"; + case ERR_INVALID_CLASS: + return "INVALID_CLASS"; + case ERR_CLASS_NOT_PREPARED: + return "CLASS_NOT_PREPARED"; + case ERR_INVALID_METHODID: + return "INVALID_METHODID"; + case ERR_INVALID_LOCATION: + return "INVALID_LOCATION"; + case ERR_INVALID_FIELDID: + return "INVALID_FIELDID"; + case ERR_INVALID_FRAMEID: + return "INVALID_FRAMEID"; + case ERR_NO_MORE_FRAMES: + return "NO_MORE_FRAMES"; + case ERR_OPAQUE_FRAME: + return "OPAQUE_FRAME"; + case ERR_NOT_CURRENT_FRAME: + return "NOT_CURRENT_FRAME"; + case ERR_TYPE_MISMATCH: + return "TYPE_MISMATCH"; + case ERR_INVALID_SLOT: + return "INVALID_SLOT"; + case ERR_DUPLICATE: + return "DUPLICATE"; + case ERR_NOT_FOUND: + return "NOT_FOUND"; + case ERR_INVALID_MONITOR: + return "INVALID_MONITOR"; + case ERR_NOT_MONITOR_OWNER: + return "NOT_MONITOR_OWNER"; + case ERR_INTERRUPT: + return "INTERRUPT"; + case ERR_INVALID_CLASS_FORMAT: + return "INVALID_CLASS_FORMAT"; + case ERR_CIRCULAR_CLASS_DEFINITION: + return "CIRCULAR_CLASS_DEFINITION"; + case ERR_FAILS_VERIFICATION: + return "FAILS_VERIFICATION"; + case ERR_ADD_METHOD_NOT_IMPLEMENTED: + return "ADD_METHOD_NOT_IMPLEMENTED"; + case ERR_SCHEMA_CHANGE_NOT_IMPLEMENTED: + return "SCHEMA_CHANGE_NOT_IMPLEMENTED"; + case ERR_INVALID_TYPESTATE: + return "INVALID_TYPESTATE"; + case ERR_HIERARCHY_CHANGE_NOT_IMPLEMENTED: + return "HIERARCHY_CHANGE_NOT_IMPLEMENTED"; + case ERR_DELETE_METHOD_NOT_IMPLEMENTED: + return "DELETE_METHOD_NOT_IMPLEMENTED"; + case ERR_UNSUPPORTED_VERSION: + return "UNSUPPORTED_VERSION"; + case ERR_NAMES_DONT_MATCH: + return "NAMES_DONT_MATCH"; + case ERR_CLASS_MODIFIERS_CHANGE_NOT_IMPLEMENTED: + return "CLASS_MODIFIERS_CHANGE_NOT_IMPLEMENTED"; + case ERR_METHOD_MODIFIERS_CHANGE_NOT_IMPLEMENTED: + return "METHOD_MODIFIERS_CHANGE_NOT_IMPLEMENTED"; + case ERR_NOT_IMPLEMENTED: + return "NOT_IMPLEMENTED"; + case ERR_NULL_POINTER: + return "NULL_POINTER"; + case ERR_ABSENT_INFORMATION: + return "ABSENT_INFORMATION"; + case ERR_INVALID_EVENT_TYPE: + return "INVALID_EVENT_TYPE"; + case ERR_ILLEGAL_ARGUMENT: + return "ILLEGAL_ARGUMENT"; + case ERR_OUT_OF_MEMORY: + return "OUT_OF_MEMORY"; + case ERR_ACCESS_DENIED: + return "ACCESS_DENIED"; + case ERR_VM_DEAD: + return "VM_DEAD"; + case ERR_INTERNAL: + return "INTERNAL"; + case ERR_UNATTACHED_THREAD: + return "UNATTACHED_THREAD"; + case ERR_INVALID_TAG: + return "INVALID_TAG"; + case ERR_ALREADY_INVOKING: + return "ALREADY_INVOKING"; + case ERR_INVALID_INDEX: + return "INVALID_INDEX"; + case ERR_INVALID_LENGTH: + return "INVALID_LENGTH"; + case ERR_INVALID_STRING: + return "INVALID_STRING"; + case ERR_INVALID_CLASS_LOADER: + return "INVALID_CLASS_LOADER"; + case ERR_INVALID_ARRAY: + return "INVALID_ARRAY"; + case ERR_TRANSPORT_LOAD: + return "TRANSPORT_LOAD"; + case ERR_TRANSPORT_INIT: + return "TRANSPORT_INIT"; + case ERR_NATIVE_METHOD: + return "NATIVE_METHOD"; + case ERR_INVALID_COUNT: + return "INVALID_COUNT"; + default: + return "?UNKNOWN?"; + } +} + +/* + * Return a string for the EventKind. + */ +const char* dvmJdwpEventKindStr(enum JdwpEventKind kind) +{ + switch (kind) { + case EK_SINGLE_STEP: return "SINGLE_STEP"; + case EK_BREAKPOINT: return "BREAKPOINT"; + case EK_FRAME_POP: return "FRAME_POP"; + case EK_EXCEPTION: return "EXCEPTION"; + case EK_USER_DEFINED: return "USER_DEFINED"; + case EK_THREAD_START: return "THREAD_START"; + /*case EK_THREAD_END: return "THREAD_END";*/ + case EK_CLASS_PREPARE: return "CLASS_PREPARE"; + case EK_CLASS_UNLOAD: return "CLASS_UNLOAD"; + case EK_CLASS_LOAD: return "CLASS_LOAD"; + case EK_FIELD_ACCESS: return "FIELD_ACCESS"; + case EK_FIELD_MODIFICATION: return "FIELD_MODIFICATION"; + case EK_EXCEPTION_CATCH: return "EXCEPTION_CATCH"; + case EK_METHOD_ENTRY: return "METHOD_ENTRY"; + case EK_METHOD_EXIT: return "METHOD_EXIT"; + case EK_VM_INIT: return "VM_INIT"; + case EK_VM_DEATH: return "VM_DEATH"; + case EK_VM_DISCONNECTED: return "VM_DISCONNECTED"; + /*case EK_VM_START: return "VM_START";*/ + case EK_THREAD_DEATH: return "THREAD_DEATH"; + default: return "?UNKNOWN?"; + } +} + +/* + * Return a string for the StepDepth. + */ +const char* dvmJdwpStepDepthStr(enum JdwpStepDepth depth) +{ + switch (depth) { + case SD_INTO: return "INTO"; + case SD_OVER: return "OVER"; + case SD_OUT: return "OUT"; + default: return "?UNKNOWN?"; + } +} + +/* + * Return a string for the StepSize. + */ +const char* dvmJdwpStepSizeStr(enum JdwpStepSize size) +{ + switch (size) { + case SS_MIN: return "MIN"; + case SS_LINE: return "LINE"; + default: return "?UNKNOWN?"; + } +} + +/* + * Return a string for the SuspendPolicy. + */ +const char* dvmJdwpSuspendPolicyStr(enum JdwpSuspendPolicy policy) +{ + switch (policy) { + case SP_NONE: return "NONE"; + case SP_EVENT_THREAD: return "EVENT_THREAD"; + case SP_ALL: return "ALL"; + default: return "?UNKNOWN?"; + } +} + +/* + * Return a string for the SuspendStatus. + */ +const char* dvmJdwpSuspendStatusStr(enum JdwpSuspendStatus status) +{ + switch (status) { + case 0: return "Not SUSPENDED"; + case SUSPEND_STATUS_SUSPENDED: return "SUSPENDED"; + default: return "?UNKNOWN?"; + } +} + +/* + * Return a string for the ThreadStatus. + */ +const char* dvmJdwpThreadStatusStr(enum JdwpThreadStatus status) +{ + switch (status) { + case TS_ZOMBIE: return "ZOMBIE"; + case TS_RUNNING: return "RUNNING"; + case TS_SLEEPING: return "SLEEPING"; + case TS_MONITOR: return "MONITOR"; + case TS_WAIT: return "WAIT"; + default: return "?UNKNOWN?"; + } +}; + diff --git a/vm/jdwp/JdwpConstants.h b/vm/jdwp/JdwpConstants.h new file mode 100644 index 000000000..922dbcde1 --- /dev/null +++ b/vm/jdwp/JdwpConstants.h @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * These come out of the JDWP documentation. + */ +#ifndef _DALVIK_JDWP_JDWPCONSTANTS +#define _DALVIK_JDWP_JDWPCONSTANTS + +/* + * Error constants. + */ +enum JdwpError { + ERR_NONE = 0, + ERR_INVALID_THREAD = 10, + ERR_INVALID_THREAD_GROUP = 11, + ERR_INVALID_PRIORITY = 12, + ERR_THREAD_NOT_SUSPENDED = 13, + ERR_THREAD_SUSPENDED = 14, + ERR_INVALID_OBJECT = 20, + ERR_INVALID_CLASS = 21, + ERR_CLASS_NOT_PREPARED = 22, + ERR_INVALID_METHODID = 23, + ERR_INVALID_LOCATION = 24, + ERR_INVALID_FIELDID = 25, + ERR_INVALID_FRAMEID = 30, + ERR_NO_MORE_FRAMES = 31, + ERR_OPAQUE_FRAME = 32, + ERR_NOT_CURRENT_FRAME = 33, + ERR_TYPE_MISMATCH = 34, + ERR_INVALID_SLOT = 35, + ERR_DUPLICATE = 40, + ERR_NOT_FOUND = 41, + ERR_INVALID_MONITOR = 50, + ERR_NOT_MONITOR_OWNER = 51, + ERR_INTERRUPT = 52, + ERR_INVALID_CLASS_FORMAT = 60, + ERR_CIRCULAR_CLASS_DEFINITION = 61, + ERR_FAILS_VERIFICATION = 62, + ERR_ADD_METHOD_NOT_IMPLEMENTED = 63, + ERR_SCHEMA_CHANGE_NOT_IMPLEMENTED = 64, + ERR_INVALID_TYPESTATE = 65, + ERR_HIERARCHY_CHANGE_NOT_IMPLEMENTED = 66, + ERR_DELETE_METHOD_NOT_IMPLEMENTED = 67, + ERR_UNSUPPORTED_VERSION = 68, + ERR_NAMES_DONT_MATCH = 69, + ERR_CLASS_MODIFIERS_CHANGE_NOT_IMPLEMENTED = 70, + ERR_METHOD_MODIFIERS_CHANGE_NOT_IMPLEMENTED = 71, + ERR_NOT_IMPLEMENTED = 99, + ERR_NULL_POINTER = 100, + ERR_ABSENT_INFORMATION = 101, + ERR_INVALID_EVENT_TYPE = 102, + ERR_ILLEGAL_ARGUMENT = 103, + ERR_OUT_OF_MEMORY = 110, + ERR_ACCESS_DENIED = 111, + ERR_VM_DEAD = 112, + ERR_INTERNAL = 113, + ERR_UNATTACHED_THREAD = 115, + ERR_INVALID_TAG = 500, + ERR_ALREADY_INVOKING = 502, + ERR_INVALID_INDEX = 503, + ERR_INVALID_LENGTH = 504, + ERR_INVALID_STRING = 506, + ERR_INVALID_CLASS_LOADER = 507, + ERR_INVALID_ARRAY = 508, + ERR_TRANSPORT_LOAD = 509, + ERR_TRANSPORT_INIT = 510, + ERR_NATIVE_METHOD = 511, + ERR_INVALID_COUNT = 512, +}; +typedef enum JdwpError JdwpError; +const char* dvmJdwpErrorStr(enum JdwpError error); + + +/* + * ClassStatus constants. These are bit flags that can be ORed together. + */ +enum JdwpClassStatus { + CS_VERIFIED = 0x01, + CS_PREPARED = 0x02, + CS_INITIALIZED = 0x04, + CS_ERROR = 0x08, +}; + +/* + * EventKind constants. + */ +enum JdwpEventKind { + EK_SINGLE_STEP = 1, + EK_BREAKPOINT = 2, + EK_FRAME_POP = 3, + EK_EXCEPTION = 4, + EK_USER_DEFINED = 5, + EK_THREAD_START = 6, + EK_THREAD_END = 7, + EK_CLASS_PREPARE = 8, + EK_CLASS_UNLOAD = 9, + EK_CLASS_LOAD = 10, + EK_FIELD_ACCESS = 20, + EK_FIELD_MODIFICATION = 21, + EK_EXCEPTION_CATCH = 30, + EK_METHOD_ENTRY = 40, + EK_METHOD_EXIT = 41, + EK_VM_INIT = 90, + EK_VM_DEATH = 99, + EK_VM_DISCONNECTED = 100, /* "Never sent across JDWP */ + EK_VM_START = EK_VM_INIT, + EK_THREAD_DEATH = EK_THREAD_END, +}; +const char* dvmJdwpEventKindStr(enum JdwpEventKind kind); + +/* + * Values for "modKind" in EventRequest.Set. + */ +enum JdwpModKind { + MK_COUNT = 1, + MK_CONDITIONAL = 2, + MK_THREAD_ONLY = 3, + MK_CLASS_ONLY = 4, + MK_CLASS_MATCH = 5, + MK_CLASS_EXCLUDE = 6, + MK_LOCATION_ONLY = 7, + MK_EXCEPTION_ONLY = 8, + MK_FIELD_ONLY = 9, + MK_STEP = 10, + MK_INSTANCE_ONLY = 11, +}; + +/* + * InvokeOptions constants (bit flags). + */ +enum JdwpInvokeOptions { + INVOKE_SINGLE_THREADED = 0x01, + INVOKE_NONVIRTUAL = 0x02, +}; + +/* + * StepDepth constants. + */ +enum JdwpStepDepth { + SD_INTO = 0, /* step into method calls */ + SD_OVER = 1, /* step over method calls */ + SD_OUT = 2, /* step out of current method */ +}; +const char* dvmJdwpStepDepthStr(enum JdwpStepDepth depth); + +/* + * StepSize constants. + */ +enum JdwpStepSize { + SS_MIN = 0, /* step by minimum (e.g. 1 bytecode inst) */ + SS_LINE = 1, /* if possible, step to next line */ +}; +const char* dvmJdwpStepSizeStr(enum JdwpStepSize size); + +/* + * SuspendPolicy constants. + */ +enum JdwpSuspendPolicy { + SP_NONE = 0, /* suspend no threads */ + SP_EVENT_THREAD = 1, /* suspend event thread */ + SP_ALL = 2, /* suspend all threads */ +}; +const char* dvmJdwpSuspendPolicyStr(enum JdwpSuspendPolicy policy); + +/* + * SuspendStatus constants. + */ +enum JdwpSuspendStatus { + SUSPEND_STATUS_SUSPENDED = 1, +}; +const char* dvmJdwpSuspendStatusStr(enum JdwpSuspendStatus status); + +/* + * ThreadStatus constants. + */ +enum JdwpThreadStatus { + TS_ZOMBIE = 0, + TS_RUNNING = 1, // RUNNING + TS_SLEEPING = 2, // (in Thread.sleep()) + TS_MONITOR = 3, // WAITING (monitor wait) + TS_WAIT = 4, // (in Object.wait()) +}; +const char* dvmJdwpThreadStatusStr(enum JdwpThreadStatus status); + +/* + * TypeTag constants. + */ +enum JdwpTypeTag { + TT_CLASS = 1, + TT_INTERFACE = 2, + TT_ARRAY = 3, +}; + +/* + * Tag constants. + */ +enum JdwpType { + JT_ARRAY = '[', + JT_BYTE = 'B', + JT_CHAR = 'C', + JT_OBJECT = 'L', + JT_FLOAT = 'F', + JT_DOUBLE = 'D', + JT_INT = 'I', + JT_LONG = 'J', + JT_SHORT = 'S', + JT_VOID = 'V', + JT_BOOLEAN = 'Z', + JT_STRING = 's', + JT_THREAD = 't', + JT_THREAD_GROUP = 'g', + JT_CLASS_LOADER = 'l', + JT_CLASS_OBJECT = 'c', +}; + +#endif /*_DALVIK_JDWP_JDWPCONSTANTS*/ diff --git a/vm/jdwp/JdwpEvent.c b/vm/jdwp/JdwpEvent.c new file mode 100644 index 000000000..a3ff05ae7 --- /dev/null +++ b/vm/jdwp/JdwpEvent.c @@ -0,0 +1,1290 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Send events to the debugger. + */ +#include "jdwp/JdwpPriv.h" +#include "jdwp/JdwpConstants.h" +#include "jdwp/JdwpHandler.h" +#include "jdwp/JdwpEvent.h" +#include "jdwp/ExpandBuf.h" + +#include <stdlib.h> +#include <string.h> +#include <stddef.h> /* for offsetof() */ +#include <unistd.h> + +/* +General notes: + +The event add/remove stuff usually happens from the debugger thread, +in response to requests from the debugger, but can also happen as the +result of an event in an arbitrary thread (e.g. an event with a "count" +mod expires). It's important to keep the event list locked when processing +events. + +Event posting can happen from any thread. The JDWP thread will not usually +post anything but VM start/death, but if a JDWP request causes a class +to be loaded, the ClassPrepare event will come from the JDWP thread. + + +We can have serialization issues when we post an event to the debugger. +For example, a thread could send an "I hit a breakpoint and am suspending +myself" message to the debugger. Before it manages to suspend itself, the +debugger's response ("not interested, resume thread") arrives and is +processed. We try to resume a thread that hasn't yet suspended. + +This means that, after posting an event to the debugger, we need to wait +for the event thread to suspend itself (and, potentially, all other threads) +before processing any additional requests from the debugger. While doing +so we need to be aware that multiple threads may be hitting breakpoints +or other events simultaneously, so we either need to wait for all of them +or serialize the events with each other. + +The current mechanism works like this: + Event thread: + - If I'm going to suspend, grab the "I am posting an event" token. Wait + for it if it's not currently available. + - Post the event to the debugger. + - If appropriate, suspend others and then myself. As part of suspending + myself, release the "I am posting" token. + JDWP thread: + - When an event arrives, see if somebody is posting an event. If so, + sleep until we can acquire the "I am posting an event" token. Release + it immediately and continue processing -- the event we have already + received should not interfere with other events that haven't yet + been posted. + +Some care must be taken to avoid deadlock: + + - thread A and thread B exit near-simultaneously, and post thread-death + events with a "suspend all" clause + - thread A gets the event token, thread B sits and waits for it + - thread A wants to suspend all other threads, but thread B is waiting + for the token and can't be suspended + +So we need to mark thread B in such a way that thread A doesn't wait for it. + +If we just bracket the "grab event token" call with a change to VMWAIT +before sleeping, the switch back to RUNNING state when we get the token +will cause thread B to suspend (remember, thread A's global suspend is +still in force, even after it releases the token). Suspending while +holding the event token is very bad, because it prevents the JDWP thread +from processing incoming messages. + +We need to change to VMWAIT state at the *start* of posting an event, +and stay there until we either finish posting the event or decide to +put ourselves to sleep. That way we don't interfere with anyone else and +don't allow anyone else to interfere with us. +*/ + + +#define kJdwpEventCommandSet 64 +#define kJdwpCompositeCommand 100 + +/* + * Stuff to compare against when deciding if a mod matches. Only the + * values for mods valid for the event being evaluated will be filled in. + * The rest will be zeroed. + */ +typedef struct ModBasket { + const JdwpLocation* pLoc; /* LocationOnly */ + const char* className; /* ClassMatch/ClassExclude */ + ObjectId threadId; /* ThreadOnly */ + RefTypeId classId; /* ClassOnly */ + RefTypeId excepClassId; /* ExceptionOnly */ + bool caught; /* ExceptionOnly */ + FieldId field; /* FieldOnly */ + ObjectId thisPtr; /* InstanceOnly */ + /* nothing for StepOnly -- handled differently */ +} ModBasket; + +/* + * Get the next "request" serial number. We use this when sending + * packets to the debugger. + */ +u4 dvmJdwpNextRequestSerial(JdwpState* state) +{ + u4 result; + + dvmDbgLockMutex(&state->serialLock); + result = state->requestSerial++; + dvmDbgUnlockMutex(&state->serialLock); + + return result; +} + +/* + * Get the next "event" serial number. We use this in the response to + * message type EventRequest.Set. + */ +u4 dvmJdwpNextEventSerial(JdwpState* state) +{ + u4 result; + + dvmDbgLockMutex(&state->serialLock); + result = state->eventSerial++; + dvmDbgUnlockMutex(&state->serialLock); + + return result; +} + +/* + * Lock the "event" mutex, which guards the list of registered events. + */ +static void lockEventMutex(JdwpState* state) +{ + //dvmDbgThreadWaiting(); + dvmDbgLockMutex(&state->eventLock); + //dvmDbgThreadRunning(); +} + +/* + * Unlock the "event" mutex. + */ +static void unlockEventMutex(JdwpState* state) +{ + dvmDbgUnlockMutex(&state->eventLock); +} + +/* + * Add an event to the list. Ordering is not important. + * + * If something prevents the event from being registered, e.g. it's a + * single-step request on a thread that doesn't exist, the event will + * not be added to the list, and an appropriate error will be returned. + */ +JdwpError dvmJdwpRegisterEvent(JdwpState* state, JdwpEvent* pEvent) +{ + JdwpError err = ERR_NONE; + int i; + + lockEventMutex(state); + + assert(state != NULL); + assert(pEvent != NULL); + assert(pEvent->prev == NULL); + assert(pEvent->next == NULL); + + /* + * If one or more LocationOnly mods are used, register them with + * the interpreter. + */ + for (i = 0; i < pEvent->modCount; i++) { + JdwpEventMod* pMod = &pEvent->mods[i]; + if (pMod->modKind == MK_LOCATION_ONLY) { + /* should only be for Breakpoint, Step, and Exception */ + dvmDbgWatchLocation(&pMod->locationOnly.loc); + } + if (pMod->modKind == MK_STEP) { + /* should only be for EK_SINGLE_STEP; should only be one */ + dvmDbgConfigureStep(pMod->step.threadId, pMod->step.size, + pMod->step.depth); + } + } + + /* + * Add to list. + */ + if (state->eventList != NULL) { + pEvent->next = state->eventList; + state->eventList->prev = pEvent; + } + state->eventList = pEvent; + state->numEvents++; + +bail: + unlockEventMutex(state); + + return err; +} + +/* + * Remove an event from the list. This will also remove the event from + * any optimization tables, e.g. breakpoints. + * + * Does not free the JdwpEvent. + * + * Grab the eventLock before calling here. + */ +static void unregisterEvent(JdwpState* state, JdwpEvent* pEvent) +{ + int i; + + if (pEvent->prev == NULL) { + /* head of the list */ + assert(state->eventList == pEvent); + + state->eventList = pEvent->next; + } else { + pEvent->prev->next = pEvent->next; + } + + if (pEvent->next != NULL) { + pEvent->next->prev = pEvent->prev; + pEvent->next = NULL; + } + pEvent->prev = NULL; + + /* + * Unhook us from the interpreter, if necessary. + */ + for (i = 0; i < pEvent->modCount; i++) { + JdwpEventMod* pMod = &pEvent->mods[i]; + if (pMod->modKind == MK_LOCATION_ONLY) { + /* should only be for Breakpoint, Step, and Exception */ + dvmDbgUnwatchLocation(&pMod->locationOnly.loc); + } + if (pMod->modKind == MK_STEP) { + /* should only be for EK_SINGLE_STEP; should only be one */ + dvmDbgUnconfigureStep(pMod->step.threadId); + } + } + + state->numEvents--; + assert(state->numEvents != 0 || state->eventList == NULL); +} + +/* + * Remove the event with the given ID from the list. + * + * Failure to find the event isn't really an error, but it is a little + * weird. (It looks like Eclipse will try to be extra careful and will + * explicitly remove one-off single-step events.) + */ +void dvmJdwpUnregisterEventById(JdwpState* state, u4 requestId) +{ + JdwpEvent* pEvent; + + lockEventMutex(state); + + pEvent = state->eventList; + while (pEvent != NULL) { + if (pEvent->requestId == requestId) { + unregisterEvent(state, pEvent); + dvmJdwpEventFree(pEvent); + goto done; /* there can be only one with a given ID */ + } + + pEvent = pEvent->next; + } + + //LOGD("Odd: no match when removing event reqId=0x%04x\n", requestId); + +done: + unlockEventMutex(state); +} + +/* + * Remove all entries from the event list. + */ +void dvmJdwpUnregisterAll(JdwpState* state) +{ + JdwpEvent* pEvent; + JdwpEvent* pNextEvent; + + lockEventMutex(state); + + pEvent = state->eventList; + while (pEvent != NULL) { + pNextEvent = pEvent->next; + + unregisterEvent(state, pEvent); + dvmJdwpEventFree(pEvent); + pEvent = pNextEvent; + } + + state->eventList = NULL; + + unlockEventMutex(state); +} + + + +/* + * Allocate a JdwpEvent struct with enough space to hold the specified + * number of mod records. + */ +JdwpEvent* dvmJdwpEventAlloc(int numMods) +{ + JdwpEvent* newEvent; + int allocSize = offsetof(JdwpEvent, mods) + + numMods * sizeof(newEvent->mods[0]); + + newEvent = (JdwpEvent*)malloc(allocSize); + memset(newEvent, 0, allocSize); + return newEvent; +} + +/* + * Free a JdwpEvent. + * + * Do not call this until the event has been removed from the list. + */ +void dvmJdwpEventFree(JdwpEvent* pEvent) +{ + int i; + + if (pEvent == NULL) + return; + + /* make sure it was removed from the list */ + assert(pEvent->prev == NULL); + assert(pEvent->next == NULL); + /* want to assert state->eventList != pEvent */ + + /* + * Free any hairy bits in the mods. + */ + for (i = 0; i < pEvent->modCount; i++) { + if (pEvent->mods[i].modKind == MK_CLASS_MATCH) { + free(pEvent->mods[i].classMatch.classPattern); + pEvent->mods[i].classMatch.classPattern = NULL; + } + if (pEvent->mods[i].modKind == MK_CLASS_EXCLUDE) { + free(pEvent->mods[i].classExclude.classPattern); + pEvent->mods[i].classExclude.classPattern = NULL; + } + } + + free(pEvent); +} + +/* + * Allocate storage for matching events. To keep things simple we + * use an array with enough storage for the entire list. + * + * The state->eventLock should be held before calling. + */ +static JdwpEvent** allocMatchList(JdwpState* state) +{ + return (JdwpEvent**) malloc(sizeof(JdwpEvent*) * state->numEvents); +} + +/* + * Run through the list and remove any entries with an expired "count" mod + * from the event list, then free the match list. + */ +static void cleanupMatchList(JdwpState* state, JdwpEvent** matchList, + int matchCount) +{ + JdwpEvent** ppEvent = matchList; + + while (matchCount--) { + JdwpEvent* pEvent = *ppEvent; + int i; + + for (i = 0; i < pEvent->modCount; i++) { + if (pEvent->mods[i].modKind == MK_COUNT && + pEvent->mods[i].count.count == 0) + { + LOGV("##### Removing expired event\n"); + unregisterEvent(state, pEvent); + dvmJdwpEventFree(pEvent); + break; + } + } + + ppEvent++; + } + + free(matchList); +} + +/* + * Match a string against a "restricted regular expression", which is just + * a string that may start or end with '*' (e.g. "*.Foo" or "java.*"). + * + * ("Restricted name globbing" might have been a better term.) + */ +static bool patternMatch(const char* pattern, const char* target) +{ + int patLen = strlen(pattern); + + if (pattern[0] == '*') { + int targetLen = strlen(target); + patLen--; + // TODO: remove printf when we find a test case to verify this + LOGE(">>> comparing '%s' to '%s'\n", + pattern+1, target + (targetLen-patLen)); + + if (targetLen < patLen) + return false; + return strcmp(pattern+1, target + (targetLen-patLen)) == 0; + } else if (pattern[patLen-1] == '*') { + int i; + + return strncmp(pattern, target, patLen-1) == 0; + } else { + return strcmp(pattern, target) == 0; + } +} + +/* + * See if two locations are equal. + * + * It's tempting to do a bitwise compare ("struct ==" or memcmp), but if + * the storage wasn't zeroed out there could be undefined values in the + * padding. Besides, the odds of "idx" being equal while the others aren't + * is very small, so this is usually just a simple integer comparison. + */ +static inline bool locationMatch(const JdwpLocation* pLoc1, + const JdwpLocation* pLoc2) +{ + return pLoc1->idx == pLoc2->idx && + pLoc1->methodId == pLoc2->methodId && + pLoc1->classId == pLoc2->classId && + pLoc1->typeTag == pLoc2->typeTag; +} + +/* + * See if the event's mods match up with the contents of "basket". + * + * If we find a Count mod before rejecting an event, we decrement it. We + * need to do this even if later mods cause us to ignore the event. + */ +static bool modsMatch(JdwpState* state, JdwpEvent* pEvent, ModBasket* basket) +{ + JdwpEventMod* pMod = pEvent->mods; + int i; + + for (i = pEvent->modCount; i > 0; i--, pMod++) { + switch (pMod->modKind) { + case MK_COUNT: + assert(pMod->count.count > 0); + pMod->count.count--; + break; + case MK_CONDITIONAL: + assert(false); // should not be getting these + break; + case MK_THREAD_ONLY: + if (pMod->threadOnly.threadId != basket->threadId) + return false; + break; + case MK_CLASS_ONLY: + if (!dvmDbgMatchType(basket->classId, + pMod->classOnly.referenceTypeId)) + return false; + break; + case MK_CLASS_MATCH: + if (!patternMatch(pMod->classMatch.classPattern, + basket->className)) + return false; + break; + case MK_CLASS_EXCLUDE: + if (patternMatch(pMod->classMatch.classPattern, + basket->className)) + return false; + break; + case MK_LOCATION_ONLY: + if (!locationMatch(&pMod->locationOnly.loc, basket->pLoc)) + return false; + break; + case MK_EXCEPTION_ONLY: + if (pMod->exceptionOnly.refTypeId != 0 && + !dvmDbgMatchType(basket->excepClassId, + pMod->exceptionOnly.refTypeId)) + return false; + if ((basket->caught && !pMod->exceptionOnly.caught) || + (!basket->caught && !pMod->exceptionOnly.uncaught)) + return false; + break; + case MK_FIELD_ONLY: + // TODO + break; + case MK_STEP: + if (pMod->step.threadId != basket->threadId) + return false; + break; + case MK_INSTANCE_ONLY: + if (pMod->instanceOnly.objectId != basket->thisPtr) + return false; + break; + default: + LOGE("unhandled mod kind %d\n", pMod->modKind); + assert(false); + break; + } + } + + return true; +} + +/* + * Find all events of type "eventKind" with mods that match up with the + * rest of the arguments. + * + * Found events are appended to "matchList", and "*pMatchCount" is advanced, + * so this may be called multiple times for grouped events. + * + * DO NOT call this multiple times for the same eventKind, as Count mods are + * decremented during the scan. + */ +static void findMatchingEvents(JdwpState* state, enum JdwpEventKind eventKind, + ModBasket* basket, JdwpEvent** matchList, int* pMatchCount) +{ + JdwpEvent* pEvent; + + /* start after the existing entries */ + matchList += *pMatchCount; + + pEvent = state->eventList; + while (pEvent != NULL) { + if (pEvent->eventKind == eventKind && modsMatch(state, pEvent, basket)) + { + *matchList++ = pEvent; + (*pMatchCount)++; + } + + pEvent = pEvent->next; + } +} + +/* + * Scan through the list of matches and determine the most severe + * suspension policy. + */ +static enum JdwpSuspendPolicy scanSuspendPolicy(JdwpEvent** matchList, + int matchCount) +{ + enum JdwpSuspendPolicy policy = SP_NONE; + + while (matchCount--) { + if ((*matchList)->suspendPolicy > policy) + policy = (*matchList)->suspendPolicy; + matchList++; + } + + return policy; +} + +/* + * Three possibilities: + * SP_NONE - do nothing + * SP_EVENT_THREAD - suspend ourselves + * SP_ALL - suspend everybody except JDWP support thread + */ +static void suspendByPolicy(JdwpState* state, + enum JdwpSuspendPolicy suspendPolicy) +{ + if (suspendPolicy == SP_NONE) + return; + + if (suspendPolicy == SP_ALL) { + dvmDbgSuspendVM(true); + } else { + assert(suspendPolicy == SP_EVENT_THREAD); + } + + /* this is rare but possible -- see CLASS_PREPARE handling */ + if (dvmDbgGetThreadSelfId() == state->debugThreadId) { + LOGI("NOTE: suspendByPolicy not suspending JDWP thread\n"); + return; + } + + DebugInvokeReq* pReq = dvmDbgGetInvokeReq(); + while (true) { + pReq->ready = true; + dvmDbgSuspendSelf(); + pReq->ready = false; + + /* + * The JDWP thread has told us (and possibly all other threads) to + * resume. See if it has left anything in our DebugInvokeReq mailbox. + */ + if (!pReq->invokeNeeded) { + /*LOGD("suspendByPolicy: no invoke needed\n");*/ + break; + } + + /* grab this before posting/suspending again */ + dvmJdwpSetWaitForEventThread(state, dvmDbgGetThreadSelfId()); + + /* leave pReq->invokeNeeded raised so we can check reentrancy */ + LOGV("invoking method...\n"); + dvmDbgExecuteMethod(pReq); + + pReq->err = ERR_NONE; + + /* clear this before signaling */ + pReq->invokeNeeded = false; + + LOGV("invoke complete, signaling and self-suspending\n"); + dvmDbgLockMutex(&pReq->lock); + dvmDbgCondSignal(&pReq->cv); + dvmDbgUnlockMutex(&pReq->lock); + } +} + +/* + * Determine if there is a method invocation in progress in the current + * thread. + * + * We look at the "invokeNeeded" flag in the per-thread DebugInvokeReq + * state. If set, we're in the process of invoking a method. + */ +static bool invokeInProgress(JdwpState* state) +{ + DebugInvokeReq* pReq = dvmDbgGetInvokeReq(); + return pReq->invokeNeeded; +} + +/* + * We need the JDWP thread to hold off on doing stuff while we post an + * event and then suspend ourselves. + * + * Call this with a threadId of zero if you just want to wait for the + * current thread operation to complete. + * + * This could go to sleep waiting for another thread, so it's important + * that the thread be marked as VMWAIT before calling here. + */ +void dvmJdwpSetWaitForEventThread(JdwpState* state, ObjectId threadId) +{ + bool waited = false; + + /* this is held for very brief periods; contention is unlikely */ + dvmDbgLockMutex(&state->eventThreadLock); + + /* + * If another thread is already doing stuff, wait for it. This can + * go to sleep indefinitely. + */ + while (state->eventThreadId != 0) { + LOGV("event in progress (0x%llx), 0x%llx sleeping\n", + state->eventThreadId, threadId); + waited = true; + dvmDbgCondWait(&state->eventThreadCond, &state->eventThreadLock); + } + + if (waited || threadId != 0) + LOGV("event token grabbed (0x%llx)\n", threadId); + if (threadId != 0) + state->eventThreadId = threadId; + + dvmDbgUnlockMutex(&state->eventThreadLock); +} + +/* + * Clear the threadId and signal anybody waiting. + */ +void dvmJdwpClearWaitForEventThread(JdwpState* state) +{ + /* + * Grab the mutex. Don't try to go in/out of VMWAIT mode, as this + * function is called by dvmSuspendSelf(), and the transition back + * to RUNNING would confuse it. + */ + dvmDbgLockMutex(&state->eventThreadLock); + + assert(state->eventThreadId != 0); + LOGV("cleared event token (0x%llx)\n", state->eventThreadId); + + state->eventThreadId = 0; + + dvmDbgCondSignal(&state->eventThreadCond); + + dvmDbgUnlockMutex(&state->eventThreadLock); +} + + +/* + * Prep an event. Allocates storage for the message and leaves space for + * the header. + */ +static ExpandBuf* eventPrep(void) +{ + ExpandBuf* pReq; + + pReq = expandBufAlloc(); + expandBufAddSpace(pReq, kJDWPHeaderLen); + + return pReq; +} + +/* + * Write the header into the buffer and send the packet off to the debugger. + * + * Takes ownership of "pReq" (currently discards it). + */ +static void eventFinish(JdwpState* state, ExpandBuf* pReq) +{ + u1* buf = expandBufGetBuffer(pReq); + + set4BE(buf, expandBufGetLength(pReq)); + set4BE(buf+4, dvmJdwpNextRequestSerial(state)); + set1(buf+8, 0); /* flags */ + set1(buf+9, kJdwpEventCommandSet); + set1(buf+10, kJdwpCompositeCommand); + + dvmJdwpSendRequest(state, pReq); + + expandBufFree(pReq); +} + + +/* + * Tell the debugger that we have finished initializing. This is always + * sent, even if the debugger hasn't requested it. + * + * This should be sent "before the main thread is started and before + * any application code has been executed". The thread ID in the message + * must be for the main thread. + */ +bool dvmJdwpPostVMStart(JdwpState* state, bool suspend) +{ + enum JdwpSuspendPolicy suspendPolicy; + ObjectId threadId = dvmDbgGetThreadSelfId(); + + if (suspend) + suspendPolicy = SP_ALL; + else + suspendPolicy = SP_NONE; + + /* probably don't need this here */ + lockEventMutex(state); + + ExpandBuf* pReq = NULL; + if (true) { + LOGV("EVENT: %s\n", dvmJdwpEventKindStr(EK_VM_START)); + LOGV(" suspendPolicy=%s\n", dvmJdwpSuspendPolicyStr(suspendPolicy)); + + pReq = eventPrep(); + expandBufAdd1(pReq, suspendPolicy); + expandBufAdd4BE(pReq, 1); + + expandBufAdd1(pReq, EK_VM_START); + expandBufAdd4BE(pReq, 0); /* requestId */ + expandBufAdd8BE(pReq, threadId); + } + + unlockEventMutex(state); + + /* send request and possibly suspend ourselves */ + if (pReq != NULL) { + int oldStatus = dvmDbgThreadWaiting(); + if (suspendPolicy != SP_NONE) + dvmJdwpSetWaitForEventThread(state, threadId); + + eventFinish(state, pReq); + + suspendByPolicy(state, suspendPolicy); + dvmDbgThreadContinuing(oldStatus); + } + + return true; +} + +/* + * A location of interest has been reached. This handles: + * Breakpoint + * SingleStep + * MethodEntry + * MethodExit + * These four types must be grouped together in a single response. The + * "eventFlags" indicates the type of event(s) that have happened. + * + * Valid mods: + * Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude, InstanceOnly + * LocationOnly (for breakpoint/step only) + * Step (for step only) + * + * Interesting test cases: + * - Put a breakpoint on a native method. Eclipse creates METHOD_ENTRY + * and METHOD_EXIT events with a ClassOnly mod on the method's class. + * - Use "run to line". Eclipse creates a BREAKPOINT with Count=1. + * - Single-step to a line with a breakpoint. Should get a single + * event message with both events in it. + */ +bool dvmJdwpPostLocationEvent(JdwpState* state, const JdwpLocation* pLoc, + ObjectId thisPtr, int eventFlags) +{ + enum JdwpSuspendPolicy suspendPolicy = SP_NONE; + ModBasket basket; + JdwpEvent** matchList; + int matchCount; + char* nameAlloc = NULL; + + memset(&basket, 0, sizeof(basket)); + basket.pLoc = pLoc; + basket.classId = pLoc->classId; + basket.thisPtr = thisPtr; + basket.threadId = dvmDbgGetThreadSelfId(); + basket.className = nameAlloc = + dvmDescriptorToName(dvmDbgGetClassDescriptor(pLoc->classId)); + + /* + * On rare occasions we may need to execute interpreted code in the VM + * while handling a request from the debugger. Don't fire breakpoints + * while doing so. (I don't think we currently do this at all, so + * this is mostly paranoia.) + */ + if (basket.threadId == state->debugThreadId) { + LOGV("Ignoring location event in JDWP thread\n"); + free(nameAlloc); + return false; + } + + /* + * The debugger variable display tab may invoke the interpreter to format + * complex objects. We want to ignore breakpoints and method entry/exit + * traps while working on behalf of the debugger. + * + * If we don't ignore them, the VM will get hung up, because we'll + * suspend on a breakpoint while the debugger is still waiting for its + * method invocation to complete. + */ + if (invokeInProgress(state)) { + LOGV("Not checking breakpoints during invoke (%s)\n", basket.className); + free(nameAlloc); + return false; + } + + /* don't allow the list to be updated while we scan it */ + lockEventMutex(state); + + matchList = allocMatchList(state); + matchCount = 0; + + if ((eventFlags & DBG_BREAKPOINT) != 0) + findMatchingEvents(state, EK_BREAKPOINT, &basket, matchList, + &matchCount); + if ((eventFlags & DBG_SINGLE_STEP) != 0) + findMatchingEvents(state, EK_SINGLE_STEP, &basket, matchList, + &matchCount); + if ((eventFlags & DBG_METHOD_ENTRY) != 0) + findMatchingEvents(state, EK_METHOD_ENTRY, &basket, matchList, + &matchCount); + if ((eventFlags & DBG_METHOD_EXIT) != 0) + findMatchingEvents(state, EK_METHOD_EXIT, &basket, matchList, + &matchCount); + + ExpandBuf* pReq = NULL; + if (matchCount != 0) { + int i; + + LOGV("EVENT: %s(%d total) %s.%s thread=%llx code=%llx)\n", + dvmJdwpEventKindStr(matchList[0]->eventKind), matchCount, + basket.className, + dvmDbgGetMethodName(pLoc->classId, pLoc->methodId), + basket.threadId, pLoc->idx); + + suspendPolicy = scanSuspendPolicy(matchList, matchCount); + LOGV(" suspendPolicy=%s\n", + dvmJdwpSuspendPolicyStr(suspendPolicy)); + + pReq = eventPrep(); + expandBufAdd1(pReq, suspendPolicy); + expandBufAdd4BE(pReq, matchCount); + + for (i = 0; i < matchCount; i++) { + expandBufAdd1(pReq, matchList[i]->eventKind); + expandBufAdd4BE(pReq, matchList[i]->requestId); + expandBufAdd8BE(pReq, basket.threadId); + dvmJdwpAddLocation(pReq, pLoc); + } + } + + cleanupMatchList(state, matchList, matchCount); + unlockEventMutex(state); + + /* send request and possibly suspend ourselves */ + if (pReq != NULL) { + int oldStatus = dvmDbgThreadWaiting(); + if (suspendPolicy != SP_NONE) + dvmJdwpSetWaitForEventThread(state, basket.threadId); + + eventFinish(state, pReq); + + suspendByPolicy(state, suspendPolicy); + dvmDbgThreadContinuing(oldStatus); + } + + free(nameAlloc); + return matchCount != 0; +} + +/* + * A thread is starting or stopping. + * + * Valid mods: + * Count, ThreadOnly + */ +bool dvmJdwpPostThreadChange(JdwpState* state, ObjectId threadId, bool start) +{ + enum JdwpSuspendPolicy suspendPolicy = SP_NONE; + ModBasket basket; + JdwpEvent** matchList; + int matchCount; + + assert(threadId = dvmDbgGetThreadSelfId()); + + /* + * I don't think this can happen. + */ + if (invokeInProgress(state)) { + LOGW("Not posting thread change during invoke\n"); + return false; + } + + memset(&basket, 0, sizeof(basket)); + basket.threadId = threadId; + + /* don't allow the list to be updated while we scan it */ + lockEventMutex(state); + + matchList = allocMatchList(state); + matchCount = 0; + + if (start) + findMatchingEvents(state, EK_THREAD_START, &basket, matchList, + &matchCount); + else + findMatchingEvents(state, EK_THREAD_DEATH, &basket, matchList, + &matchCount); + + ExpandBuf* pReq = NULL; + if (matchCount != 0) { + int i; + + LOGV("EVENT: %s(%d total) thread=%llx)\n", + dvmJdwpEventKindStr(matchList[0]->eventKind), matchCount, + basket.threadId); + + suspendPolicy = scanSuspendPolicy(matchList, matchCount); + LOGV(" suspendPolicy=%s\n", + dvmJdwpSuspendPolicyStr(suspendPolicy)); + + pReq = eventPrep(); + expandBufAdd1(pReq, suspendPolicy); + expandBufAdd4BE(pReq, matchCount); + + for (i = 0; i < matchCount; i++) { + expandBufAdd1(pReq, matchList[i]->eventKind); + expandBufAdd4BE(pReq, matchList[i]->requestId); + expandBufAdd8BE(pReq, basket.threadId); + } + + } + + cleanupMatchList(state, matchList, matchCount); + unlockEventMutex(state); + + /* send request and possibly suspend ourselves */ + if (pReq != NULL) { + int oldStatus = dvmDbgThreadWaiting(); + if (suspendPolicy != SP_NONE) + dvmJdwpSetWaitForEventThread(state, basket.threadId); + + eventFinish(state, pReq); + + suspendByPolicy(state, suspendPolicy); + dvmDbgThreadContinuing(oldStatus); + } + + return matchCount != 0; +} + +/* + * Send a polite "VM is dying" message to the debugger. + * + * Skips the usual "event token" stuff. + */ +bool dvmJdwpPostVMDeath(JdwpState* state) +{ + ExpandBuf* pReq; + + LOGV("EVENT: %s\n", dvmJdwpEventKindStr(EK_VM_DEATH)); + + pReq = eventPrep(); + expandBufAdd1(pReq, SP_NONE); + expandBufAdd4BE(pReq, 1); + + expandBufAdd1(pReq, EK_VM_DEATH); + expandBufAdd4BE(pReq, 0); + eventFinish(state, pReq); + return true; +} + + +/* + * An exception has been thrown. It may or may not have been caught. + * + * Valid mods: + * Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude, LocationOnly, + * ExceptionOnly, InstanceOnly + */ +bool dvmJdwpPostException(JdwpState* state, const JdwpLocation* pThrowLoc, + ObjectId exceptionId, RefTypeId exceptionClassId, + const JdwpLocation* pCatchLoc, ObjectId thisPtr) +{ + enum JdwpSuspendPolicy suspendPolicy = SP_NONE; + ModBasket basket; + JdwpEvent** matchList; + int matchCount; + char* nameAlloc = NULL; + + memset(&basket, 0, sizeof(basket)); + basket.pLoc = pThrowLoc; + basket.classId = pThrowLoc->classId; + basket.threadId = dvmDbgGetThreadSelfId(); + basket.className = nameAlloc = + dvmDescriptorToName(dvmDbgGetClassDescriptor(basket.classId)); + basket.excepClassId = exceptionClassId; + basket.caught = (pCatchLoc->classId != 0); + basket.thisPtr = thisPtr; + + /* don't try to post an exception caused by the debugger */ + if (invokeInProgress(state)) { + LOGV("Not posting exception hit during invoke (%s)\n",basket.className); + free(nameAlloc); + return false; + } + + /* don't allow the list to be updated while we scan it */ + lockEventMutex(state); + + matchList = allocMatchList(state); + matchCount = 0; + + findMatchingEvents(state, EK_EXCEPTION, &basket, matchList, &matchCount); + + ExpandBuf* pReq = NULL; + if (matchCount != 0) { + int i; + + LOGV("EVENT: %s(%d total) thread=%llx exceptId=%llx caught=%d)\n", + dvmJdwpEventKindStr(matchList[0]->eventKind), matchCount, + basket.threadId, exceptionId, basket.caught); + LOGV(" throw: %d %llx %x %lld (%s.%s)\n", pThrowLoc->typeTag, + pThrowLoc->classId, pThrowLoc->methodId, pThrowLoc->idx, + dvmDbgGetClassDescriptor(pThrowLoc->classId), + dvmDbgGetMethodName(pThrowLoc->classId, pThrowLoc->methodId)); + if (pCatchLoc->classId == 0) { + LOGV(" catch: (not caught)\n"); + } else { + LOGV(" catch: %d %llx %x %lld (%s.%s)\n", pCatchLoc->typeTag, + pCatchLoc->classId, pCatchLoc->methodId, pCatchLoc->idx, + dvmDbgGetClassDescriptor(pCatchLoc->classId), + dvmDbgGetMethodName(pCatchLoc->classId, pCatchLoc->methodId)); + } + + suspendPolicy = scanSuspendPolicy(matchList, matchCount); + LOGV(" suspendPolicy=%s\n", + dvmJdwpSuspendPolicyStr(suspendPolicy)); + + pReq = eventPrep(); + expandBufAdd1(pReq, suspendPolicy); + expandBufAdd4BE(pReq, matchCount); + + for (i = 0; i < matchCount; i++) { + expandBufAdd1(pReq, matchList[i]->eventKind); + expandBufAdd4BE(pReq, matchList[i]->requestId); + expandBufAdd8BE(pReq, basket.threadId); + + dvmJdwpAddLocation(pReq, pThrowLoc); + expandBufAdd1(pReq, JT_OBJECT); + expandBufAdd8BE(pReq, exceptionId); + dvmJdwpAddLocation(pReq, pCatchLoc); + } + } + + cleanupMatchList(state, matchList, matchCount); + unlockEventMutex(state); + + /* send request and possibly suspend ourselves */ + if (pReq != NULL) { + int oldStatus = dvmDbgThreadWaiting(); + if (suspendPolicy != SP_NONE) + dvmJdwpSetWaitForEventThread(state, basket.threadId); + + eventFinish(state, pReq); + + suspendByPolicy(state, suspendPolicy); + dvmDbgThreadContinuing(oldStatus); + } + + free(nameAlloc); + return matchCount != 0; +} + +/* + * Announce that a class has been loaded. + * + * Valid mods: + * Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude + */ +bool dvmJdwpPostClassPrepare(JdwpState* state, int tag, RefTypeId refTypeId, + const char* signature, int status) +{ + enum JdwpSuspendPolicy suspendPolicy = SP_NONE; + ModBasket basket; + JdwpEvent** matchList; + int matchCount; + char* nameAlloc = NULL; + + memset(&basket, 0, sizeof(basket)); + basket.classId = refTypeId; + basket.threadId = dvmDbgGetThreadSelfId(); + basket.className = nameAlloc = + dvmDescriptorToName(dvmDbgGetClassDescriptor(basket.classId)); + + /* suppress class prep caused by debugger */ + if (invokeInProgress(state)) { + LOGV("Not posting class prep caused by invoke (%s)\n",basket.className); + free(nameAlloc); + return false; + } + + /* don't allow the list to be updated while we scan it */ + lockEventMutex(state); + + matchList = allocMatchList(state); + matchCount = 0; + + findMatchingEvents(state, EK_CLASS_PREPARE, &basket, matchList, + &matchCount); + + ExpandBuf* pReq = NULL; + if (matchCount != 0) { + int i; + + LOGV("EVENT: %s(%d total) thread=%llx)\n", + dvmJdwpEventKindStr(matchList[0]->eventKind), matchCount, + basket.threadId); + + suspendPolicy = scanSuspendPolicy(matchList, matchCount); + LOGV(" suspendPolicy=%s\n", + dvmJdwpSuspendPolicyStr(suspendPolicy)); + + if (basket.threadId == state->debugThreadId) { + /* + * JDWP says that, for a class prep in the debugger thread, we + * should set threadId to null and if any threads were supposed + * to be suspended then we suspend all other threads. + */ + LOGV(" NOTE: class prepare in debugger thread!\n"); + basket.threadId = 0; + if (suspendPolicy == SP_EVENT_THREAD) + suspendPolicy = SP_ALL; + } + + pReq = eventPrep(); + expandBufAdd1(pReq, suspendPolicy); + expandBufAdd4BE(pReq, matchCount); + + for (i = 0; i < matchCount; i++) { + expandBufAdd1(pReq, matchList[i]->eventKind); + expandBufAdd4BE(pReq, matchList[i]->requestId); + expandBufAdd8BE(pReq, basket.threadId); + + expandBufAdd1(pReq, tag); + expandBufAdd8BE(pReq, refTypeId); + expandBufAddUtf8String(pReq, (const u1*) signature); + expandBufAdd4BE(pReq, status); + } + } + + cleanupMatchList(state, matchList, matchCount); + + unlockEventMutex(state); + + /* send request and possibly suspend ourselves */ + if (pReq != NULL) { + int oldStatus = dvmDbgThreadWaiting(); + if (suspendPolicy != SP_NONE) + dvmJdwpSetWaitForEventThread(state, basket.threadId); + + eventFinish(state, pReq); + + suspendByPolicy(state, suspendPolicy); + dvmDbgThreadContinuing(oldStatus); + } + + free(nameAlloc); + return matchCount != 0; +} + +/* + * Unload a class. + * + * Valid mods: + * Count, ClassMatch, ClassExclude + */ +bool dvmJdwpPostClassUnload(JdwpState* state, RefTypeId refTypeId) +{ + assert(false); // TODO + return false; +} + +/* + * Get or set a field. + * + * Valid mods: + * Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude, FieldOnly, + * InstanceOnly + */ +bool dvmJdwpPostFieldAccess(JdwpState* state, int STUFF, ObjectId thisPtr, + bool modified) +{ + assert(false); // TODO + return false; +} + +/* + * Send up a chunk of DDM data. + * + * While this takes the form of a JDWP "event", it doesn't interact with + * other debugger traffic, and can't suspend the VM, so we skip all of + * the fun event token gymnastics. + */ +void dvmJdwpDdmSendChunk(JdwpState* state, int type, int len, const u1* buf) +{ + ExpandBuf* pReq; + u1* outBuf; + + /* + * Write the chunk header and data into the ExpandBuf. + */ + pReq = expandBufAlloc(); + expandBufAddSpace(pReq, kJDWPHeaderLen); + expandBufAdd4BE(pReq, type); + expandBufAdd4BE(pReq, len); + if (len > 0) { + outBuf = expandBufAddSpace(pReq, len); + memcpy(outBuf, buf, len); + } + + /* + * Go back and write the JDWP header. + */ + outBuf = expandBufGetBuffer(pReq); + + set4BE(outBuf, expandBufGetLength(pReq)); + set4BE(outBuf+4, dvmJdwpNextRequestSerial(state)); + set1(outBuf+8, 0); /* flags */ + set1(outBuf+9, kJDWPDdmCmdSet); + set1(outBuf+10, kJDWPDdmCmd); + + /* + * Send it up. + */ + //LOGD("Sending chunk (type=0x%08x len=%d)\n", type, len); + dvmJdwpSendRequest(state, pReq); + + expandBufFree(pReq); +} + diff --git a/vm/jdwp/JdwpEvent.h b/vm/jdwp/JdwpEvent.h new file mode 100644 index 000000000..1a6a2c712 --- /dev/null +++ b/vm/jdwp/JdwpEvent.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Handle registration of events, and debugger event notification. + */ +#ifndef _DALVIK_JDWP_JDWPEVENT +#define _DALVIK_JDWP_JDWPEVENT + +#include "JdwpConstants.h" +#include "ExpandBuf.h" + +/* + * Event modifiers. A JdwpEvent may have zero or more of these. + */ +typedef union JdwpEventMod { + u1 modKind; /* JdwpModKind */ + struct { + u1 modKind; + int count; + } count; + struct { + u1 modKind; + u4 exprId; + } conditional; + struct { + u1 modKind; + ObjectId threadId; + } threadOnly; + struct { + u1 modKind; + RefTypeId referenceTypeId; + } classOnly; + struct { + u1 modKind; + char* classPattern; + } classMatch; + struct { + u1 modKind; + char* classPattern; + } classExclude; + struct { + u1 modKind; + JdwpLocation loc; + } locationOnly; + struct { + u1 modKind; + u1 caught; + u1 uncaught; + RefTypeId refTypeId; + } exceptionOnly; + struct { + u1 modKind; + RefTypeId refTypeId; + FieldId fieldId; + } fieldOnly; + struct { + u1 modKind; + ObjectId threadId; + int size; /* JdwpStepSize */ + int depth; /* JdwpStepDepth */ + } step; + struct { + u1 modKind; + ObjectId objectId; + } instanceOnly; +} JdwpEventMod; + +/* + * One of these for every registered event. + * + * We over-allocate the struct to hold the modifiers. + */ +typedef struct JdwpEvent { + struct JdwpEvent* prev; /* linked list */ + struct JdwpEvent* next; + + enum JdwpEventKind eventKind; /* what kind of event is this? */ + enum JdwpSuspendPolicy suspendPolicy; /* suspend all, none, or self? */ + int modCount; /* #of entries in mods[] */ + u4 requestId; /* serial#, reported to debugger */ + + JdwpEventMod mods[1]; /* MUST be last field in struct */ +} JdwpEvent; + +/* + * Allocate an event structure with enough space. + */ +JdwpEvent* dvmJdwpEventAlloc(int numMods); +void dvmJdwpEventFree(JdwpEvent* pEvent); + +/* + * Register an event by adding it to the event list. + * + * "*pEvent" must be storage allocated with jdwpEventAlloc(). The caller + * may discard its pointer after calling this. + */ +JdwpError dvmJdwpRegisterEvent(JdwpState* state, JdwpEvent* pEvent); + +/* + * Unregister an event, given the requestId. + */ +void dvmJdwpUnregisterEventById(JdwpState* state, u4 requestId); + +/* + * Unregister all events. + */ +void dvmJdwpUnregisterAll(JdwpState* state); + +/* + * Send an event, formatted into "pReq", to the debugger. + * + * (Messages are sent asynchronously, and do not receive a reply.) + */ +bool dvmJdwpSendRequest(JdwpState* state, ExpandBuf* pReq); + +#endif /*_DALVIK_JDWP_JDWPEVENT*/ diff --git a/vm/jdwp/JdwpHandler.c b/vm/jdwp/JdwpHandler.c new file mode 100644 index 000000000..ff6ecf461 --- /dev/null +++ b/vm/jdwp/JdwpHandler.c @@ -0,0 +1,2152 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Handle messages from debugger. + * + * GENERAL NOTE: we're not currently testing the message length for + * correctness. This is usually a bad idea, but here we can probably + * get away with it so long as the debugger isn't broken. We can + * change the "read" macros to use "dataLen" to avoid wandering into + * bad territory, and have a single "is dataLen correct" check at the + * end of each function. Not needed at this time. + */ +#include "jdwp/JdwpPriv.h" +#include "jdwp/JdwpHandler.h" +#include "jdwp/JdwpEvent.h" +#include "jdwp/JdwpConstants.h" +#include "jdwp/ExpandBuf.h" + +#include "Bits.h" +#include "Atomic.h" + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#if 0 +#include <time.h> +#include <sys/time.h> +static void showTime(const char* label) +{ + struct timeval tv; + int min, sec, msec; + + gettimeofday(&tv, NULL); + min = (tv.tv_sec / 60) % 60; + sec = tv.tv_sec % 60; + msec = tv.tv_usec / 1000; + + LOGI("%02d:%02d.%03d %s\n", min, sec, msec, label); +} +#endif + +/* + * Helper function: read a "location" from an input buffer. + */ +static void jdwpReadLocation(const u1** pBuf, JdwpLocation* pLoc) +{ + memset(pLoc, 0, sizeof(*pLoc)); /* allows memcmp() later */ + pLoc->typeTag = read1(pBuf); + pLoc->classId = dvmReadObjectId(pBuf); + pLoc->methodId = dvmReadMethodId(pBuf); + pLoc->idx = read8BE(pBuf); +} + +/* + * Helper function: write a "location" into the reply buffer. + */ +void dvmJdwpAddLocation(ExpandBuf* pReply, const JdwpLocation* pLoc) +{ + expandBufAdd1(pReply, pLoc->typeTag); + expandBufAddObjectId(pReply, pLoc->classId); + expandBufAddMethodId(pReply, pLoc->methodId); + expandBufAdd8BE(pReply, pLoc->idx); +} + +/* + * Helper function: read a variable-width value from the input buffer. + */ +static u8 jdwpReadValue(const u1** pBuf, int width) +{ + u8 value; + + switch (width) { + case 1: value = read1(pBuf); break; + case 2: value = read2BE(pBuf); break; + case 4: value = read4BE(pBuf); break; + case 8: value = read8BE(pBuf); break; + default: value = (u8) -1; assert(false); break; + } + + return value; +} + +/* + * Helper function: write a variable-width value into the output input buffer. + */ +static void jdwpWriteValue(ExpandBuf* pReply, int width, u8 value) +{ + switch (width) { + case 1: expandBufAdd1(pReply, value); break; + case 2: expandBufAdd2BE(pReply, value); break; + case 4: expandBufAdd4BE(pReply, value); break; + case 8: expandBufAdd8BE(pReply, value); break; + default: assert(false); break; + } +} + +/* + * Common code for *_InvokeMethod requests. + */ +static JdwpError finishInvoke(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply, + ObjectId threadId, ObjectId objectId, RefTypeId classId, MethodId methodId) +{ + JdwpError err = ERR_NONE; + u8* argArray = NULL; + u4 numArgs; + u4 options; /* enum InvokeOptions bit flags */ + int i; + + numArgs = read4BE(&buf); + + LOGV(" --> threadId=%llx objectId=%llx\n", threadId, objectId); + LOGV(" classId=%llx methodId=%x %s.%s\n", + classId, methodId, + dvmDbgGetClassDescriptor(classId), + dvmDbgGetMethodName(classId, methodId)); + LOGV(" %d args:\n", numArgs); + + if (numArgs > 0) + argArray = (ObjectId*) malloc(sizeof(ObjectId) * numArgs); + + for (i = 0; i < (int) numArgs; i++) { + u1 typeTag; + u8 value; + int width; + + typeTag = read1(&buf); + width = dvmDbgGetTagWidth(typeTag); + value = jdwpReadValue(&buf, width); + + LOGV(" '%c'(%d): 0x%llx\n", typeTag, width, value); + argArray[i] = value; + } + + options = read4BE(&buf); + LOGV(" options=0x%04x%s%s\n", options, + (options & INVOKE_SINGLE_THREADED) ? " (SINGLE_THREADED)" : "", + (options & INVOKE_NONVIRTUAL) ? " (NONVIRTUAL)" : ""); + + + u1 resultTag; + u8 resultValue; + ObjectId exceptObjId; + + err = dvmDbgInvokeMethod(threadId, objectId, classId, methodId, + numArgs, argArray, options, + &resultTag, &resultValue, &exceptObjId); + if (err != ERR_NONE) + goto bail; + + if (err == ERR_NONE) { + int width = dvmDbgGetTagWidth(resultTag); + + expandBufAdd1(pReply, resultTag); + if (width != 0) + jdwpWriteValue(pReply, width, resultValue); + expandBufAdd1(pReply, JT_OBJECT); + expandBufAddObjectId(pReply, exceptObjId); + + LOGV(" --> returned '%c' 0x%llx (except=%08llx)\n", + resultTag, resultValue, exceptObjId); + + /* show detailed debug output */ + if (resultTag == JT_STRING && exceptObjId == 0) { + if (resultValue != 0) { + char* str = dvmDbgStringToUtf8(resultValue); + LOGV(" string '%s'\n", str); + free(str); + } else { + LOGV(" string (null)\n"); + } + } + } + +bail: + free(argArray); + return err; +} + + +/* + * Request for version info. + */ +static JdwpError handleVM_Version(JdwpState* state, const u1* buf, + int dataLen, ExpandBuf* pReply) +{ + /* text information on VM version */ + expandBufAddUtf8String(pReply, (const u1*) "Android DalvikVM 1.0.1"); + /* JDWP version numbers */ + expandBufAdd4BE(pReply, 1); // major + expandBufAdd4BE(pReply, 5); // minor + /* VM JRE version */ + expandBufAddUtf8String(pReply, (const u1*) "1.5.0"); /* e.g. 1.5.0_04 */ + /* target VM name */ + expandBufAddUtf8String(pReply, (const u1*) "DalvikVM"); + + return ERR_NONE; +} + +/* + * Given a class JNI signature (e.g. "Ljava/lang/Error;"), return the + * referenceTypeID. We need to send back more than one if the class has + * been loaded by multiple class loaders. + */ +static JdwpError handleVM_ClassesBySignature(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + char* classDescriptor = NULL; + u4 numClasses; + size_t strLen; + RefTypeId refTypeId; + int i; + + classDescriptor = readNewUtf8String(&buf, &strLen); + LOGV(" Req for class by signature '%s'\n", classDescriptor); + + /* + * TODO: if a class with the same name has been loaded multiple times + * (by different class loaders), we're supposed to return each of them. + * + * NOTE: this may mangle "className". + */ + if (!dvmDbgFindLoadedClassBySignature(classDescriptor, &refTypeId)) { + /* not currently loaded */ + LOGV(" --> no match!\n"); + numClasses = 0; + } else { + /* just the one */ + numClasses = 1; + } + + expandBufAdd4BE(pReply, numClasses); + + if (numClasses > 0) { + u1 typeTag; + u4 status; + + /* get class vs. interface and status flags */ + dvmDbgGetClassInfo(refTypeId, &typeTag, &status, NULL); + + expandBufAdd1(pReply, typeTag); + expandBufAddRefTypeId(pReply, refTypeId); + expandBufAdd4BE(pReply, status); + } + + free(classDescriptor); + + return ERR_NONE; +} + +/* + * Handle request for the thread IDs of all running threads. + * + * We exclude ourselves from the list, because we don't allow ourselves + * to be suspended, and that violates some JDWP expectations. + */ +static JdwpError handleVM_AllThreads(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + u4 threadCount; + ObjectId* pThreadIds; + ObjectId* walker; + int i; + + dvmDbgGetAllThreads(&pThreadIds, &threadCount); + + expandBufAdd4BE(pReply, threadCount); + + walker = pThreadIds; + for (i = 0; i < (int) threadCount; i++) { + expandBufAddObjectId(pReply, *walker++); + } + + free(pThreadIds); + + return ERR_NONE; +} + +/* + * List all thread groups that do not have a parent. + */ +static JdwpError handleVM_TopLevelThreadGroups(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + u4 groups; + ObjectId threadGroupId; + + /* + * TODO: maintain a list of parentless thread groups in the VM. + * + * For now, just return "system". Application threads are created + * in "main", which is a child of "system". + */ + groups = 1; + expandBufAdd4BE(pReply, groups); + //threadGroupId = debugGetMainThreadGroup(); + //expandBufAdd8BE(pReply, threadGroupId); + threadGroupId = dvmDbgGetSystemThreadGroupId(); + expandBufAddObjectId(pReply, threadGroupId); + + return ERR_NONE; +} + +/* + * Respond with the sizes of the basic debugger types. + * + * All IDs are 8 bytes. + */ +static JdwpError handleVM_IDSizes(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + expandBufAdd4BE(pReply, sizeof(FieldId)); + expandBufAdd4BE(pReply, sizeof(MethodId)); + expandBufAdd4BE(pReply, sizeof(ObjectId)); + expandBufAdd4BE(pReply, sizeof(RefTypeId)); + expandBufAdd4BE(pReply, sizeof(FrameId)); + return ERR_NONE; +} + +/* + * The debugger is politely asking to disconnect. We're good with that. + * + * We could resume threads and clean up pinned references, but we can do + * that when the TCP connection drops. + */ +static JdwpError handleVM_Dispose(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + return ERR_NONE; +} + +/* + * Suspend the execution of the application running in the VM (i.e. suspend + * all threads). + * + * This needs to increment the "suspend count" on all threads. + */ +static JdwpError handleVM_Suspend(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + dvmDbgSuspendVM(false); + return ERR_NONE; +} + +/* + * Resume execution. Decrements the "suspend count" of all threads. + */ +static JdwpError handleVM_Resume(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + dvmDbgResumeVM(); + return ERR_NONE; +} + +/* + * The debugger wants the entire VM to exit. + */ +static JdwpError handleVM_Exit(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + u4 exitCode; + + exitCode = get4BE(buf); + + LOGW("Debugger is telling the VM to exit with code=%d\n", exitCode); + + dvmDbgExit(exitCode); + return ERR_NOT_IMPLEMENTED; // shouldn't get here +} + +/* + * Create a new string in the VM and return its ID. + * + * (Ctrl-Shift-I in Eclipse on an array of objects causes it to create the + * string "java.util.Arrays".) + */ +static JdwpError handleVM_CreateString(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + char* str; + size_t strLen; + ObjectId stringId; + + str = readNewUtf8String(&buf, &strLen); + + LOGV(" Req to create string '%s'\n", str); + + stringId = dvmDbgCreateString(str); + expandBufAddObjectId(pReply, stringId); + + return ERR_NONE; +} + +/* + * Tell the debugger what we are capable of. + */ +static JdwpError handleVM_Capabilities(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + int i; + + expandBufAdd1(pReply, false); /* canWatchFieldModification */ + expandBufAdd1(pReply, false); /* canWatchFieldAccess */ + expandBufAdd1(pReply, false); /* canGetBytecodes */ + expandBufAdd1(pReply, false); /* canGetSyntheticAttribute */ + expandBufAdd1(pReply, false); /* canGetOwnedMonitorInfo */ + expandBufAdd1(pReply, false); /* canGetCurrentContendedMonitor */ + expandBufAdd1(pReply, false); /* canGetMonitorInfo */ + return ERR_NONE; +} + +/* + * Return classpath and bootclasspath. + */ +static JdwpError handleVM_ClassPaths(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + char baseDir[2] = "/"; + u4 classPaths; + u4 bootClassPaths; + int i; + + /* + * TODO: make this real. Not important for remote debugging, but + * might be useful for local debugging. + */ + classPaths = 1; + bootClassPaths = 0; + + expandBufAddUtf8String(pReply, (const u1*) baseDir); + expandBufAdd4BE(pReply, classPaths); + for (i = 0; i < (int) classPaths; i++) { + expandBufAddUtf8String(pReply, (const u1*) "."); + } + + expandBufAdd4BE(pReply, bootClassPaths); + for (i = 0; i < (int) classPaths; i++) { + /* add bootclasspath components as strings */ + } + + return ERR_NONE; +} + +/* + * Release a list of object IDs. (Seen in jdb.) + * + * Currently does nothing. + */ +static JdwpError HandleVM_DisposeObjects(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + return ERR_NONE; +} + +/* + * Tell the debugger what we are capable of. + */ +static JdwpError handleVM_CapabilitiesNew(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + int i; + + expandBufAdd1(pReply, false); /* canWatchFieldModification */ + expandBufAdd1(pReply, false); /* canWatchFieldAccess */ + expandBufAdd1(pReply, false); /* canGetBytecodes */ + expandBufAdd1(pReply, false); /* canGetSyntheticAttribute */ + expandBufAdd1(pReply, false); /* canGetOwnedMonitorInfo */ + expandBufAdd1(pReply, false); /* canGetCurrentContendedMonitor */ + expandBufAdd1(pReply, false); /* canGetMonitorInfo */ + expandBufAdd1(pReply, false); /* canRedefineClasses */ + expandBufAdd1(pReply, false); /* canAddMethod */ + expandBufAdd1(pReply, false); /* canUnrestrictedlyRedefineClasses */ + expandBufAdd1(pReply, false); /* canPopFrames */ + expandBufAdd1(pReply, false); /* canUseInstanceFilters */ + expandBufAdd1(pReply, false); /* canGetSourceDebugExtension */ + expandBufAdd1(pReply, false); /* canRequestVMDeathEvent */ + expandBufAdd1(pReply, false); /* canSetDefaultStratum */ + expandBufAdd1(pReply, false); /* 1.6: canGetInstanceInfo */ + expandBufAdd1(pReply, false); /* 1.6: canRequestMonitorEvents */ + expandBufAdd1(pReply, false); /* 1.6: canGetMonitorFrameInfo */ + expandBufAdd1(pReply, false); /* 1.6: canUseSourceNameFilters */ + expandBufAdd1(pReply, false); /* 1.6: canGetConstantPool */ + expandBufAdd1(pReply, false); /* 1.6: canForceEarlyReturn */ + + /* fill in reserved22 through reserved32; note count started at 1 */ + for (i = 22; i <= 32; i++) + expandBufAdd1(pReply, false); /* reservedN */ + return ERR_NONE; +} + +/* + * Cough up the complete list of classes. + */ +static JdwpError handleVM_AllClassesWithGeneric(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + u4 numClasses = 0; + RefTypeId* classRefBuf = NULL; + int i; + + dvmDbgGetClassList(&numClasses, &classRefBuf); + + expandBufAdd4BE(pReply, numClasses); + + for (i = 0; i < (int) numClasses; i++) { + static const u1 genericSignature[1] = ""; + u1 refTypeTag; + char* signature; + u4 status; + + dvmDbgGetClassInfo(classRefBuf[i], &refTypeTag, &status, &signature); + + expandBufAdd1(pReply, refTypeTag); + expandBufAddRefTypeId(pReply, classRefBuf[i]); + expandBufAddUtf8String(pReply, (const u1*) signature); + expandBufAddUtf8String(pReply, genericSignature); + expandBufAdd4BE(pReply, status); + + free(signature); + } + + free(classRefBuf); + + return ERR_NONE; +} + +/* + * Given a referenceTypeID, return a string with the JNI reference type + * signature (e.g. "Ljava/lang/Error;"). + */ +static JdwpError handleRT_Signature(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + char* signature; + RefTypeId refTypeId; + + refTypeId = dvmReadRefTypeId(&buf); + + LOGV(" Req for signature of refTypeId=0x%llx\n", refTypeId); + signature = dvmDbgGetSignature(refTypeId); + expandBufAddUtf8String(pReply, (const u1*) signature); + free(signature); + + return ERR_NONE; +} + +/* + * Return the modifiers (a/k/a access flags) for a reference type. + */ +static JdwpError handleRT_Modifiers(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + RefTypeId refTypeId; + u4 modBits; + + refTypeId = dvmReadRefTypeId(&buf); + modBits = dvmDbgGetAccessFlags(refTypeId); + + expandBufAdd4BE(pReply, modBits); + + return ERR_NONE; +} + +/* + * Get values from static fields in a reference type. + */ +static JdwpError handleRT_GetValues(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + RefTypeId refTypeId; + u4 numFields; + int i; + + refTypeId = dvmReadRefTypeId(&buf); + numFields = read4BE(&buf); + + expandBufAdd4BE(pReply, numFields); + for (i = 0; i < (int) numFields; i++) { + FieldId fieldId; + u1 fieldTag; + int width; + u1* ptr; + + fieldId = dvmReadFieldId(&buf); + fieldTag = dvmDbgGetFieldTag(refTypeId, fieldId); + width = dvmDbgGetTagWidth(fieldTag); + + expandBufAdd1(pReply, fieldTag); + ptr = expandBufAddSpace(pReply, width); + dvmDbgGetStaticFieldValue(refTypeId, fieldId, ptr, width); + } + + return ERR_NONE; +} + +/* + * Get the name of the source file in which a reference type was declared. + */ +static JdwpError handleRT_SourceFile(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + RefTypeId refTypeId; + const char* fileName; + + refTypeId = dvmReadRefTypeId(&buf); + + fileName = dvmDbgGetSourceFile(refTypeId); + if (fileName != NULL) { + expandBufAddUtf8String(pReply, (const u1*) fileName); + return ERR_NONE; + } else { + return ERR_ABSENT_INFORMATION; + } +} + +/* + * Return the current status of the reference type. + */ +static JdwpError handleRT_Status(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + RefTypeId refTypeId; + u1 typeTag; + u4 status; + + refTypeId = dvmReadRefTypeId(&buf); + + /* get status flags */ + dvmDbgGetClassInfo(refTypeId, &typeTag, &status, NULL); + expandBufAdd4BE(pReply, status); + return ERR_NONE; +} + +/* + * Return interfaces implemented directly by this class. + */ +static JdwpError handleRT_Interfaces(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + RefTypeId refTypeId; + u4 numInterfaces; + int i; + + refTypeId = dvmReadRefTypeId(&buf); + + LOGV(" Req for interfaces in %llx (%s)\n", refTypeId, + dvmDbgGetClassDescriptor(refTypeId)); + + dvmDbgOutputAllInterfaces(refTypeId, pReply); + + return ERR_NONE; +} + +/* + * Returns the value of the SourceDebugExtension attribute. + * + * JDB seems interested, but DEX files don't currently support this. + */ +static JdwpError handleRT_SourceDebugExtension(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + /* referenceTypeId in, string out */ + return ERR_ABSENT_INFORMATION; +} + +/* + * Like RT_Signature but with the possibility of a "generic signature". + */ +static JdwpError handleRT_SignatureWithGeneric(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + static const u1 genericSignature[1] = ""; + char* signature; + RefTypeId refTypeId; + + refTypeId = dvmReadRefTypeId(&buf); + + LOGV(" Req for signature of refTypeId=0x%llx\n", refTypeId); + signature = dvmDbgGetSignature(refTypeId); + if (signature != NULL) + expandBufAddUtf8String(pReply, (const u1*) signature); + else + expandBufAddUtf8String(pReply, (const u1*) "Lunknown;"); /* native? */ + expandBufAddUtf8String(pReply, genericSignature); + free(signature); + + return ERR_NONE; +} + +/* + * Return the instance of java.lang.ClassLoader that loaded the specified + * reference type, or null if it was loaded by the system loader. + */ +static JdwpError handleRT_ClassLoader(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + RefTypeId refTypeId; + ObjectId classLoaderId; + + refTypeId = dvmReadRefTypeId(&buf); + + expandBufAddObjectId(pReply, dvmDbgGetClassLoader(refTypeId)); + + return ERR_NONE; +} + +/* + * Given a referenceTypeId, return a block of stuff that describes the + * fields declared by a class. + */ +static JdwpError handleRT_FieldsWithGeneric(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + RefTypeId refTypeId; + int i, numFields; + + refTypeId = dvmReadRefTypeId(&buf); + LOGV(" Req for fields in refTypeId=0x%llx\n", refTypeId); + { + char* tmp = dvmDbgGetSignature(refTypeId); + LOGV(" --> '%s'\n", tmp); + free(tmp); + } + + dvmDbgOutputAllFields(refTypeId, true, pReply); + + return ERR_NONE; +} + +/* + * Given a referenceTypeID, return a block of goodies describing the + * methods declared by a class. + */ +static JdwpError handleRT_MethodsWithGeneric(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + RefTypeId refTypeId; + int i; + + refTypeId = dvmReadRefTypeId(&buf); + + LOGV(" Req for methods in refTypeId=0x%llx\n", refTypeId); + { + char* tmp = dvmDbgGetSignature(refTypeId); + LOGV(" --> '%s'\n", tmp); + free(tmp); + } + + dvmDbgOutputAllMethods(refTypeId, true, pReply); + + return ERR_NONE; +} + +/* + * Return the immediate superclass of a class. + */ +static JdwpError handleCT_Superclass(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + RefTypeId classId; + RefTypeId superClassId; + + classId = dvmReadRefTypeId(&buf); + + superClassId = dvmDbgGetSuperclass(classId); + + expandBufAddRefTypeId(pReply, superClassId); + + return ERR_NONE; +} + +/* + * Set static class values. + */ +static JdwpError handleCT_SetValues(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + RefTypeId classId; + u4 values; + int i; + + classId = dvmReadRefTypeId(&buf); + values = read4BE(&buf); + + LOGV(" Req to set %d values in classId=%llx\n", values, classId); + + for (i = 0; i < (int) values; i++) { + FieldId fieldId; + u1 fieldTag; + u8 value; + int width; + + fieldId = dvmReadFieldId(&buf); + fieldTag = dvmDbgGetStaticFieldTag(classId, fieldId); + width = dvmDbgGetTagWidth(fieldTag); + value = jdwpReadValue(&buf, width); + + LOGV(" --> field=%x tag=%c -> %lld\n", fieldId, fieldTag, value); + dvmDbgSetStaticFieldValue(classId, fieldId, value, width); + } + + return ERR_NONE; +} + +/* + * Invoke a static method. + * + * Example: Eclipse sometimes uses java/lang/Class.forName(String s) on + * values in the "variables" display. + */ +static JdwpError handleCT_InvokeMethod(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + RefTypeId classId; + ObjectId threadId; + MethodId methodId; + + classId = dvmReadRefTypeId(&buf); + threadId = dvmReadObjectId(&buf); + methodId = dvmReadMethodId(&buf); + + return finishInvoke(state, buf, dataLen, pReply, + threadId, 0, classId, methodId); +} + +/* + * Return line number information for the method, if present. + */ +static JdwpError handleM_LineTable(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + RefTypeId refTypeId; + MethodId methodId; + + refTypeId = dvmReadRefTypeId(&buf); + methodId = dvmReadMethodId(&buf); + + LOGV(" Req for line table in %s.%s\n", + dvmDbgGetClassDescriptor(refTypeId), + dvmDbgGetMethodName(refTypeId,methodId)); + + dvmDbgOutputLineTable(refTypeId, methodId, pReply); + + return ERR_NONE; +} + +/* + * Pull out the LocalVariableTable goodies. + */ +static JdwpError handleM_VariableTableWithGeneric(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + RefTypeId classId; + MethodId methodId; + + classId = dvmReadRefTypeId(&buf); + methodId = dvmReadMethodId(&buf); + + LOGV(" Req for LocalVarTab in class=%s method=%s\n", + dvmDbgGetClassDescriptor(classId), + dvmDbgGetMethodName(classId, methodId)); + + /* + * We could return ERR_ABSENT_INFORMATION here if the DEX file was + * built without local variable information. That will cause Eclipse + * to make a best-effort attempt at displaying local variables + * anonymously. However, the attempt isn't very good, so we're probably + * better off just not showing anything. + */ + dvmDbgOutputVariableTable(classId, methodId, true, pReply); + return ERR_NONE; +} + +/* + * Given an object reference, return the runtime type of the object + * (class or array). + * + * This can get called on different things, e.g. threadId gets + * passed in here. + */ +static JdwpError handleOR_ReferenceType(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId objectId; + u1 refTypeTag; + RefTypeId typeId; + + objectId = dvmReadObjectId(&buf); + LOGV(" Req for type of objectId=0x%llx\n", objectId); + + dvmDbgGetObjectType(objectId, &refTypeTag, &typeId); + + expandBufAdd1(pReply, refTypeTag); + expandBufAddRefTypeId(pReply, typeId); + + return ERR_NONE; +} + +/* + * Get values from the fields of an object. + */ +static JdwpError handleOR_GetValues(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId objectId; + u4 numFields; + int i; + + objectId = dvmReadObjectId(&buf); + numFields = read4BE(&buf); + + LOGV(" Req for %d fields from objectId=0x%llx\n", numFields, objectId); + + expandBufAdd4BE(pReply, numFields); + + for (i = 0; i < (int) numFields; i++) { + FieldId fieldId; + u1 fieldTag; + int width; + u1* ptr; + const char* fieldName; + + fieldId = dvmReadFieldId(&buf); + + fieldTag = dvmDbgGetFieldTag(objectId, fieldId); + width = dvmDbgGetTagWidth(fieldTag); + + LOGV(" --> fieldId %x --> tag '%c'(%d)\n", + fieldId, fieldTag, width); + + expandBufAdd1(pReply, fieldTag); + ptr = expandBufAddSpace(pReply, width); + dvmDbgGetFieldValue(objectId, fieldId, ptr, width); + } + + return ERR_NONE; +} + +/* + * Set values in the fields of an object. + */ +static JdwpError handleOR_SetValues(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId objectId; + u4 numFields; + int i; + + objectId = dvmReadObjectId(&buf); + numFields = read4BE(&buf); + + LOGV(" Req to set %d fields in objectId=0x%llx\n", numFields, objectId); + + for (i = 0; i < (int) numFields; i++) { + FieldId fieldId; + u1 fieldTag; + int width; + u8 value; + + fieldId = dvmReadFieldId(&buf); + + fieldTag = dvmDbgGetFieldTag(objectId, fieldId); + width = dvmDbgGetTagWidth(fieldTag); + value = jdwpReadValue(&buf, width); + + LOGV(" --> fieldId=%x tag='%c'(%d) value=%lld\n", + fieldId, fieldTag, width, value); + + dvmDbgSetFieldValue(objectId, fieldId, value, width); + } + + return ERR_NONE; +} + +/* + * Invoke an instance method. The invocation must occur in the specified + * thread, which must have been suspended by an event. + * + * The call is synchronous. All threads in the VM are resumed, unless the + * SINGLE_THREADED flag is set. + * + * If you ask Eclipse to "inspect" an object (or ask JDB to "print" an + * object), it will try to invoke the object's toString() function. This + * feature becomes crucial when examining ArrayLists with Eclipse. + */ +static JdwpError handleOR_InvokeMethod(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId objectId; + ObjectId threadId; + RefTypeId classId; + MethodId methodId; + + objectId = dvmReadObjectId(&buf); + threadId = dvmReadObjectId(&buf); + classId = dvmReadRefTypeId(&buf); + methodId = dvmReadMethodId(&buf); + + return finishInvoke(state, buf, dataLen, pReply, + threadId, objectId, classId, methodId); +} + +/* + * Disable garbage collection of the specified object. + */ +static JdwpError handleOR_DisableCollection(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + // this is currently a no-op + return ERR_NONE; +} + +/* + * Enable garbage collection of the specified object. + */ +static JdwpError handleOR_EnableCollection(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + // this is currently a no-op + return ERR_NONE; +} + +/* + * Determine whether an object has been garbage collected. + */ +static JdwpError handleOR_IsCollected(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId objectId; + + objectId = dvmReadObjectId(&buf); + + LOGV(" Req IsCollected(0x%llx)\n", objectId); + + // TODO: currently returning false; must integrate with GC + expandBufAdd1(pReply, 0); + + return ERR_NONE; +} + +/* + * Return the string value in a string object. + */ +static JdwpError handleSR_Value(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId stringObject; + char* str; + + stringObject = dvmReadObjectId(&buf); + str = dvmDbgStringToUtf8(stringObject); + + LOGV(" Req for str %llx --> '%s'\n", stringObject, str); + + expandBufAddUtf8String(pReply, (u1*) str); + free(str); + + return ERR_NONE; +} + +/* + * Return a thread's name. + */ +static JdwpError handleTR_Name(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId threadId; + char* name; + + threadId = dvmReadObjectId(&buf); + + LOGV(" Req for name of thread 0x%llx\n", threadId); + name = dvmDbgGetThreadName(threadId); + if (name == NULL) + return ERR_INVALID_THREAD; + + expandBufAddUtf8String(pReply, (u1*) name); + free(name); + + return ERR_NONE; +} + +/* + * Suspend the specified thread. + * + * It's supposed to remain suspended even if interpreted code wants to + * resume it; only the JDI is allowed to resume it. + */ +static JdwpError handleTR_Suspend(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId threadId; + + threadId = dvmReadObjectId(&buf); + + if (threadId == dvmDbgGetThreadSelfId()) { + LOGI(" Warning: ignoring request to suspend self\n"); + return ERR_THREAD_NOT_SUSPENDED; + } + LOGV(" Req to suspend thread 0x%llx\n", threadId); + + dvmDbgSuspendThread(threadId); + + return ERR_NONE; +} + +/* + * Resume the specified thread. + */ +static JdwpError handleTR_Resume(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId threadId; + + threadId = dvmReadObjectId(&buf); + + if (threadId == dvmDbgGetThreadSelfId()) { + LOGI(" Warning: ignoring request to resume self\n"); + return ERR_NONE; + } + LOGV(" Req to resume thread 0x%llx\n", threadId); + + dvmDbgResumeThread(threadId); + + return ERR_NONE; +} + +/* + * Return status of specified thread. + */ +static JdwpError handleTR_Status(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId threadId; + u4 threadStatus; + u4 suspendStatus; + + threadId = dvmReadObjectId(&buf); + + LOGV(" Req for status of thread 0x%llx\n", threadId); + + if (!dvmDbgGetThreadStatus(threadId, &threadStatus, &suspendStatus)) + return ERR_INVALID_THREAD; + + LOGV(" --> %s, %s\n", dvmJdwpThreadStatusStr(threadStatus), + dvmJdwpSuspendStatusStr(suspendStatus)); + + expandBufAdd4BE(pReply, threadStatus); + expandBufAdd4BE(pReply, suspendStatus); + + return ERR_NONE; +} + +/* + * Return the thread group that the specified thread is a member of. + */ +static JdwpError handleTR_ThreadGroup(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId threadId; + ObjectId threadGroupId; + + threadId = dvmReadObjectId(&buf); + + /* currently not handling these */ + threadGroupId = dvmDbgGetThreadGroup(threadId); + expandBufAddObjectId(pReply, threadGroupId); + + return ERR_NONE; +} + +/* + * Return the current call stack of a suspended thread. + * + * If the thread isn't suspended, the error code isn't defined, but should + * be THREAD_NOT_SUSPENDED. + */ +static JdwpError handleTR_Frames(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId threadId; + u4 startFrame, length, frames; + int i, frameCount; + + threadId = dvmReadObjectId(&buf); + startFrame = read4BE(&buf); + length = read4BE(&buf); + + if (!dvmDbgThreadExists(threadId)) + return ERR_INVALID_THREAD; + if (!dvmDbgIsSuspended(threadId)) { + LOGV(" Rejecting req for frames in running thread '%s' (%llx)\n", + dvmDbgGetThreadName(threadId), threadId); + return ERR_THREAD_NOT_SUSPENDED; + } + + frameCount = dvmDbgGetThreadFrameCount(threadId); + + LOGV(" Request for frames: threadId=%llx start=%d length=%d [count=%d]\n", + threadId, startFrame, length, frameCount); + if (frameCount <= 0) + return ERR_THREAD_NOT_SUSPENDED; /* == 0 means 100% native */ + + if (length == (u4) -1) + length = frameCount; + assert((int) startFrame >= 0 && (int) startFrame < frameCount); + assert((int) (startFrame + length) <= frameCount); + + frames = length; + expandBufAdd4BE(pReply, frames); + for (i = startFrame; i < (int) (startFrame+length); i++) { + FrameId frameId; + JdwpLocation loc; + + dvmDbgGetThreadFrame(threadId, i, &frameId, &loc); + + expandBufAdd8BE(pReply, frameId); + dvmJdwpAddLocation(pReply, &loc); + + LOGVV(" Frame %d: id=%llx loc={type=%d cls=%llx mth=%x loc=%llx}\n", + i, frameId, loc.typeTag, loc.classId, loc.methodId, loc.idx); + } + + return ERR_NONE; +} + +/* + * Returns the #of frames on the specified thread, which must be suspended. + */ +static JdwpError handleTR_FrameCount(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId threadId; + int frameCount; + + threadId = dvmReadObjectId(&buf); + + if (!dvmDbgThreadExists(threadId)) + return ERR_INVALID_THREAD; + if (!dvmDbgIsSuspended(threadId)) { + LOGV(" Rejecting req for frames in running thread '%s' (%llx)\n", + dvmDbgGetThreadName(threadId), threadId); + return ERR_THREAD_NOT_SUSPENDED; + } + + frameCount = dvmDbgGetThreadFrameCount(threadId); + if (frameCount < 0) + return ERR_INVALID_THREAD; + expandBufAdd4BE(pReply, (u4)frameCount); + + return ERR_NONE; +} + +/* + * Get the monitor that the thread is waiting on. + */ +static JdwpError handleTR_CurrentContendedMonitor(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId threadId; + + threadId = dvmReadObjectId(&buf); + + // TODO: create an Object to represent the monitor (we're currently + // just using a raw Monitor struct in the VM) + + return ERR_NOT_IMPLEMENTED; +} + +/* + * Return the suspend count for the specified thread. + * + * (The thread *might* still be running -- it might not have examined + * its suspend count recently.) + */ +static JdwpError handleTR_SuspendCount(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId threadId; + u4 suspendCount; + + threadId = dvmReadObjectId(&buf); + + suspendCount = dvmDbgGetThreadSuspendCount(threadId); + expandBufAdd4BE(pReply, suspendCount); + + return ERR_NONE; +} + +/* + * Return the name of a thread group. + * + * The Eclipse debugger recognizes "main" and "system" as special. + */ +static JdwpError handleTGR_Name(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId threadGroupId; + char* name = NULL; + + threadGroupId = dvmReadObjectId(&buf); + LOGV(" Req for name of threadGroupId=0x%llx\n", threadGroupId); + + name = dvmDbgGetThreadGroupName(threadGroupId); + if (name != NULL) + expandBufAddUtf8String(pReply, (u1*) name); + else { + expandBufAddUtf8String(pReply, (u1*) "BAD-GROUP-ID"); + LOGW("bad thread group ID\n"); + } + + free(name); + + return ERR_NONE; +} + +/* + * Returns the thread group -- if any -- that contains the specified + * thread group. + */ +static JdwpError handleTGR_Parent(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId groupId; + ObjectId parentGroup; + + groupId = dvmReadObjectId(&buf); + + parentGroup = dvmDbgGetThreadGroupParent(groupId); + expandBufAddObjectId(pReply, parentGroup); + + return ERR_NONE; +} + +/* + * Return the active threads and thread groups that are part of the + * specified thread group. + */ +static JdwpError handleTGR_Children(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId threadGroupId; + u4 threadCount; + ObjectId threadId; + ObjectId* pThreadIds; + ObjectId* walker; + int i; + + threadGroupId = dvmReadObjectId(&buf); + LOGV(" Req for threads in threadGroupId=0x%llx\n", threadGroupId); + + dvmDbgGetThreadGroupThreads(threadGroupId, &pThreadIds, &threadCount); + + expandBufAdd4BE(pReply, threadCount); + + walker = pThreadIds; + for (i = 0; i < (int) threadCount; i++) + expandBufAddObjectId(pReply, pThreadIds[i]); + free(pThreadIds); + + /* + * TODO: finish support for child groups + * + * For now, just show that "main" is a child of "system". + */ + if (threadGroupId == dvmDbgGetSystemThreadGroupId()) { + expandBufAdd4BE(pReply, 1); + expandBufAddObjectId(pReply, dvmDbgGetMainThreadGroupId()); + } else { + expandBufAdd4BE(pReply, 0); + } + + return ERR_NONE; +} + +/* + * Return the #of components in the array. + */ +static JdwpError handleAR_Length(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId arrayId; + u4 arrayLength; + + arrayId = dvmReadObjectId(&buf); + LOGV(" Req for length of array 0x%llx\n", arrayId); + + arrayLength = dvmDbgGetArrayLength(arrayId); + + LOGV(" --> %d\n", arrayLength); + + expandBufAdd4BE(pReply, arrayLength); + + return ERR_NONE; +} + +/* + * Return the values from an array. + */ +static JdwpError handleAR_GetValues(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId arrayId; + u4 firstIndex; + u4 length; + u1 tag; + + arrayId = dvmReadObjectId(&buf); + firstIndex = read4BE(&buf); + length = read4BE(&buf); + + tag = dvmDbgGetArrayElementTag(arrayId); + LOGV(" Req for array values 0x%llx first=%d len=%d (elem tag=%c)\n", + arrayId, firstIndex, length, tag); + + expandBufAdd1(pReply, tag); + expandBufAdd4BE(pReply, length); + + if (!dvmDbgOutputArray(arrayId, firstIndex, length, pReply)) + return ERR_INVALID_LENGTH; + + return ERR_NONE; +} + +/* + * Set values in an array. + */ +static JdwpError handleAR_SetValues(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId arrayId; + u4 firstIndex; + u4 values; + u1 tag; + int i; + + arrayId = dvmReadObjectId(&buf); + firstIndex = read4BE(&buf); + values = read4BE(&buf); + + LOGV(" Req to set array values 0x%llx first=%d count=%d\n", + arrayId, firstIndex, values); + + if (!dvmDbgSetArrayElements(arrayId, firstIndex, values, buf)) + return ERR_INVALID_LENGTH; + + return ERR_NONE; +} + +/* + * Return the set of classes visible to a class loader. All classes which + * have the class loader as a defining or initiating loader are returned. + */ +static JdwpError handleCLR_VisibleClasses(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId classLoaderObject; + u4 numClasses = 0; + RefTypeId* classRefBuf = NULL; + int i; + + classLoaderObject = dvmReadObjectId(&buf); + + dvmDbgGetVisibleClassList(classLoaderObject, &numClasses, &classRefBuf); + + expandBufAdd4BE(pReply, numClasses); + for (i = 0; i < (int) numClasses; i++) { + u1 refTypeTag; + + refTypeTag = dvmDbgGetClassObjectType(classRefBuf[i]); + + expandBufAdd1(pReply, refTypeTag); + expandBufAddRefTypeId(pReply, classRefBuf[i]); + } + + return ERR_NONE; +} + +/* + * Set an event trigger. + * + * Reply with a requestID. + */ +static JdwpError handleER_Set(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + JdwpEvent* pEvent; + JdwpError err; + const u1* origBuf = buf; + /*int origDataLen = dataLen;*/ + u1 eventKind; + u1 suspendPolicy; + u4 modifierCount; + u4 requestId; + int idx; + + eventKind = read1(&buf); + suspendPolicy = read1(&buf); + modifierCount = read4BE(&buf); + + LOGVV(" Set(kind=%s(%u) suspend=%s(%u) mods=%u)\n", + dvmJdwpEventKindStr(eventKind), eventKind, + dvmJdwpSuspendPolicyStr(suspendPolicy), suspendPolicy, + modifierCount); + + assert(modifierCount < 256); /* reasonableness check */ + + pEvent = dvmJdwpEventAlloc(modifierCount); + pEvent->eventKind = eventKind; + pEvent->suspendPolicy = suspendPolicy; + pEvent->modCount = modifierCount; + + /* + * Read modifiers. Ordering may be significant (see explanation of Count + * mods in JDWP doc). + */ + for (idx = 0; idx < (int) modifierCount; idx++) { + u1 modKind; + + modKind = read1(&buf); + + pEvent->mods[idx].modKind = modKind; + + switch (modKind) { + case MK_COUNT: /* report once, when "--count" reaches 0 */ + { + u4 count = read4BE(&buf); + LOGVV(" Count: %u\n", count); + if (count == 0) + return ERR_INVALID_COUNT; + pEvent->mods[idx].count.count = count; + } + break; + case MK_CONDITIONAL: /* conditional on expression) */ + { + u4 exprId = read4BE(&buf); + LOGVV(" Conditional: %d\n", exprId); + pEvent->mods[idx].conditional.exprId = exprId; + } + break; + case MK_THREAD_ONLY: /* only report events in specified thread */ + { + ObjectId threadId = dvmReadObjectId(&buf); + LOGVV(" ThreadOnly: %llx\n", threadId); + pEvent->mods[idx].threadOnly.threadId = threadId; + } + break; + case MK_CLASS_ONLY: /* for ClassPrepare, MethodEntry */ + { + RefTypeId clazzId = dvmReadRefTypeId(&buf); + LOGVV(" ClassOnly: %llx (%s)\n", + clazzId, dvmDbgGetClassDescriptor(clazzId)); + pEvent->mods[idx].classOnly.referenceTypeId = clazzId; + } + break; + case MK_CLASS_MATCH: /* restrict events to matching classes */ + { + char* pattern; + size_t strLen; + + pattern = readNewUtf8String(&buf, &strLen); + LOGVV(" ClassMatch: '%s'\n", pattern); + /* pattern is "java.foo.*", we want "java/foo/ *" */ + pEvent->mods[idx].classMatch.classPattern = + dvmDotToSlash(pattern); + free(pattern); + } + break; + case MK_CLASS_EXCLUDE: /* restrict events to non-matching classes */ + { + char* pattern; + size_t strLen; + + pattern = readNewUtf8String(&buf, &strLen); + LOGVV(" ClassExclude: '%s'\n", pattern); + pEvent->mods[idx].classExclude.classPattern = + dvmDotToSlash(pattern); + free(pattern); + } + break; + case MK_LOCATION_ONLY: /* restrict certain events based on loc */ + { + JdwpLocation loc; + + jdwpReadLocation(&buf, &loc); + LOGVV(" LocationOnly: typeTag=%d classId=%llx methodId=%x idx=%llx\n", + loc.typeTag, loc.classId, loc.methodId, loc.idx); + pEvent->mods[idx].locationOnly.loc = loc; + } + break; + case MK_EXCEPTION_ONLY: /* modifies EK_EXCEPTION events */ + { + RefTypeId exceptionOrNull; /* null == all exceptions */ + u1 caught, uncaught; + + exceptionOrNull = dvmReadRefTypeId(&buf); + caught = read1(&buf); + uncaught = read1(&buf); + LOGVV(" ExceptionOnly: type=%llx(%s) caught=%d uncaught=%d\n", + exceptionOrNull, (exceptionOrNull == 0) ? "null" + : dvmDbgGetClassDescriptor(exceptionOrNull), + caught, uncaught); + + pEvent->mods[idx].exceptionOnly.refTypeId = exceptionOrNull; + pEvent->mods[idx].exceptionOnly.caught = caught; + pEvent->mods[idx].exceptionOnly.uncaught = uncaught; + } + break; + case MK_FIELD_ONLY: /* for field access/mod events */ + { + RefTypeId declaring = dvmReadRefTypeId(&buf); + FieldId fieldId = dvmReadFieldId(&buf); + LOGVV(" FieldOnly: %llx %x\n", declaring, fieldId); + pEvent->mods[idx].fieldOnly.refTypeId = declaring; + pEvent->mods[idx].fieldOnly.fieldId = fieldId;; + } + break; + case MK_STEP: /* for use with EK_SINGLE_STEP */ + { + ObjectId threadId; + u4 size, depth; + + threadId = dvmReadObjectId(&buf); + size = read4BE(&buf); + depth = read4BE(&buf); + LOGVV(" Step: thread=%llx size=%s depth=%s\n", + threadId, dvmJdwpStepSizeStr(size), + dvmJdwpStepDepthStr(depth)); + + pEvent->mods[idx].step.threadId = threadId; + pEvent->mods[idx].step.size = size; + pEvent->mods[idx].step.depth = depth; + } + break; + case MK_INSTANCE_ONLY: /* report events related to a specific obj */ + { + ObjectId instance = dvmReadObjectId(&buf); + LOGVV(" InstanceOnly: %llx\n", instance); + pEvent->mods[idx].instanceOnly.objectId = instance; + } + break; + default: + LOGW("GLITCH: unsupported modKind=%d\n", modKind); + break; + } + } + + /* + * Make sure we consumed all data. It is possible that the remote side + * has sent us bad stuff, but for now we blame ourselves. + */ + if (buf != origBuf + dataLen) { + LOGW("GLITCH: dataLen is %d, we have consumed %d\n", dataLen, + (int) (buf - origBuf)); + } + + /* + * We reply with an integer "requestID". + */ + requestId = dvmJdwpNextEventSerial(state); + expandBufAdd4BE(pReply, requestId); + + pEvent->requestId = requestId; + + LOGV(" --> event requestId=0x%x\n", requestId); + + /* add it to the list */ + err = dvmJdwpRegisterEvent(state, pEvent); + if (err != ERR_NONE) { + /* registration failed, probably because event is bogus */ + dvmJdwpEventFree(pEvent); + LOGW("WARNING: event request rejected\n"); + } + return err; +} + +/* + * Clear an event. Failure to find an event with a matching ID is a no-op + * and does not return an error. + */ +static JdwpError handleER_Clear(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + u1 eventKind; + u4 requestId; + + eventKind = read1(&buf); + requestId = read4BE(&buf); + + LOGV(" Req to clear eventKind=%d requestId=0x%08x\n", eventKind,requestId); + + dvmJdwpUnregisterEventById(state, requestId); + + return ERR_NONE; +} + +/* + * Return the values of arguments and local variables. + */ +static JdwpError handleSF_GetValues(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId threadId; + FrameId frameId; + u4 slots; + int i; + + threadId = dvmReadObjectId(&buf); + frameId = dvmReadFrameId(&buf); + slots = read4BE(&buf); + + LOGV(" Req for %d slots in threadId=%llx frameId=%llx\n", + slots, threadId, frameId); + + expandBufAdd4BE(pReply, slots); /* "int values" */ + for (i = 0; i < (int) slots; i++) { + u4 slot; + u1 reqSigByte; + int width; + u1* ptr; + + slot = read4BE(&buf); + reqSigByte = read1(&buf); + + LOGV(" --> slot %d '%c'\n", slot, reqSigByte); + + width = dvmDbgGetTagWidth(reqSigByte); + ptr = expandBufAddSpace(pReply, width+1); + dvmDbgGetLocalValue(threadId, frameId, slot, reqSigByte, ptr, width); + } + + return ERR_NONE; +} + +/* + * Set the values of arguments and local variables. + */ +static JdwpError handleSF_SetValues(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId threadId; + FrameId frameId; + u4 slots; + int i; + + threadId = dvmReadObjectId(&buf); + frameId = dvmReadFrameId(&buf); + slots = read4BE(&buf); + + LOGV(" Req to set %d slots in threadId=%llx frameId=%llx\n", + slots, threadId, frameId); + + for (i = 0; i < (int) slots; i++) { + u4 slot; + u1 sigByte; + u8 value; + int width; + + slot = read4BE(&buf); + sigByte = read1(&buf); + width = dvmDbgGetTagWidth(sigByte); + value = jdwpReadValue(&buf, width); + + LOGV(" --> slot %d '%c' %llx\n", slot, sigByte, value); + dvmDbgSetLocalValue(threadId, frameId, slot, sigByte, value, width); + } + + return ERR_NONE; +} + +/* + * Returns the value of "this" for the specified frame. + */ +static JdwpError handleSF_ThisObject(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + ObjectId threadId; + FrameId frameId; + u1 objectTag; + ObjectId objectId; + char* typeName; + + threadId = dvmReadObjectId(&buf); + frameId = dvmReadFrameId(&buf); + + if (!dvmDbgGetThisObject(threadId, frameId, &objectId)) + return ERR_INVALID_FRAMEID; + + if (objectId == 0) { + typeName = strdup("null"); + objectTag = 0; + } else { + typeName = dvmDbgGetObjectTypeName(objectId); + objectTag = dvmDbgGetObjectTag(objectId, typeName); + } + LOGV(" Req for 'this' in thread=%llx frame=%llx --> %llx %s '%c'\n", + threadId, frameId, objectId, typeName, (char)objectTag); + free(typeName); + + expandBufAdd1(pReply, objectTag); + expandBufAddObjectId(pReply, objectId); + + return ERR_NONE; +} + +/* + * Return the reference type reflected by this class object. + * + * This appears to be required because ReferenceTypeId values are NEVER + * reused, whereas ClassIds can be recycled like any other object. (Either + * that, or I have no idea what this is for.) + */ +static JdwpError handleCOR_ReflectedType(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + RefTypeId classObjectId; + + classObjectId = dvmReadRefTypeId(&buf); + + LOGV(" Req for refTypeId for class=%llx (%s)\n", + classObjectId, dvmDbgGetClassDescriptor(classObjectId)); + + /* just hand the type back to them */ + if (dvmDbgIsInterface(classObjectId)) + expandBufAdd1(pReply, TT_INTERFACE); + else + expandBufAdd1(pReply, TT_CLASS); + expandBufAddRefTypeId(pReply, classObjectId); + + return ERR_NONE; +} + +/* + * Handle a DDM packet with a single chunk in it. + */ +static JdwpError handleDDM_Chunk(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + u1* replyBuf = NULL; + int replyLen = -1; + + LOGV(" Handling DDM packet (%.4s)\n", buf); + + /* + * On first DDM packet, notify all handlers that DDM is running. + */ + if (!state->ddmActive) { + state->ddmActive = true; + dvmDbgDdmConnected(); + } + + /* + * If they want to send something back, we copy it into the buffer. + * A no-copy approach would be nicer. + * + * TODO: consider altering the JDWP stuff to hold the packet header + * in a separate buffer. That would allow us to writev() DDM traffic + * instead of copying it into the expanding buffer. The reduction in + * heap requirements is probably more valuable than the efficiency. + */ + if (dvmDbgDdmHandlePacket(buf, dataLen, &replyBuf, &replyLen)) { + assert(replyLen > 0 && replyLen < 1*1024*1024); + memcpy(expandBufAddSpace(pReply, replyLen), replyBuf, replyLen); + free(replyBuf); + } + return ERR_NONE; +} + +/* + * Handler map decl. + */ +typedef JdwpError (*JdwpRequestHandler)(JdwpState* state, + const u1* buf, int dataLen, ExpandBuf* reply); + +typedef struct { + u1 cmdSet; + u1 cmd; + JdwpRequestHandler func; + const char* descr; +} JdwpHandlerMap; + +/* + * Map commands to functions. + * + * 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, handleVM_Version, "VirtualMachine.Version" }, + { 1, 2, handleVM_ClassesBySignature, + "VirtualMachine.ClassesBySignature" }, + //1, 3, VirtualMachine.AllClasses + { 1, 4, handleVM_AllThreads, "VirtualMachine.AllThreads" }, + { 1, 5, handleVM_TopLevelThreadGroups, + "VirtualMachine.TopLevelThreadGroups" }, + { 1, 6, handleVM_Dispose, "VirtualMachine.Dispose" }, + { 1, 7, handleVM_IDSizes, "VirtualMachine.IDSizes" }, + { 1, 8, handleVM_Suspend, "VirtualMachine.Suspend" }, + { 1, 9, handleVM_Resume, "VirtualMachine.Resume" }, + { 1, 10, handleVM_Exit, "VirtualMachine.Exit" }, + { 1, 11, handleVM_CreateString, "VirtualMachine.CreateString" }, + { 1, 12, handleVM_Capabilities, "VirtualMachine.Capabilities" }, + { 1, 13, handleVM_ClassPaths, "VirtualMachine.ClassPaths" }, + { 1, 14, HandleVM_DisposeObjects, "VirtualMachine.DisposeObjects" }, + //1, 15, HoldEvents + //1, 16, ReleaseEvents + { 1, 17, handleVM_CapabilitiesNew, + "VirtualMachine.CapabilitiesNew" }, + //1, 18, RedefineClasses + //1, 19, SetDefaultStratum + { 1, 20, handleVM_AllClassesWithGeneric, + "VirtualMachine.AllClassesWithGeneric"}, + //1, 21, InstanceCounts + + /* ReferenceType command set (2) */ + { 2, 1, handleRT_Signature, "ReferenceType.Signature" }, + { 2, 2, handleRT_ClassLoader, "ReferenceType.ClassLoader" }, + { 2, 3, handleRT_Modifiers, "ReferenceType.Modifiers" }, + //2, 4, Fields + //2, 5, Methods + { 2, 6, handleRT_GetValues, "ReferenceType.GetValues" }, + { 2, 7, handleRT_SourceFile, "ReferenceType.SourceFile" }, + //2, 8, NestedTypes + { 2, 9, handleRT_Status, "ReferenceType.Status" }, + { 2, 10, handleRT_Interfaces, "ReferenceType.Interfaces" }, + //2, 11, ClassObject + { 2, 12, handleRT_SourceDebugExtension, + "ReferenceType.SourceDebugExtension" }, + { 2, 13, handleRT_SignatureWithGeneric, + "ReferenceType.SignatureWithGeneric" }, + { 2, 14, handleRT_FieldsWithGeneric, + "ReferenceType.FieldsWithGeneric" }, + { 2, 15, handleRT_MethodsWithGeneric, + "ReferenceType.MethodsWithGeneric" }, + //2, 16, Instances + //2, 17, ClassFileVersion + //2, 18, ConstantPool + + /* ClassType command set (3) */ + { 3, 1, handleCT_Superclass, "ClassType.Superclass" }, + { 3, 2, handleCT_SetValues, "ClassType.SetValues" }, + { 3, 3, handleCT_InvokeMethod, "ClassType.InvokeMethod" }, + //3, 4, NewInstance + + /* ArrayType command set (4) */ + //4, 1, NewInstance + + /* InterfaceType command set (5) */ + + /* Method command set (6) */ + { 6, 1, handleM_LineTable, "Method.LineTable" }, + //6, 2, VariableTable + //6, 3, Bytecodes + //6, 4, IsObsolete + { 6, 5, handleM_VariableTableWithGeneric, + "Method.VariableTableWithGeneric" }, + + /* Field command set (8) */ + + /* ObjectReference command set (9) */ + { 9, 1, handleOR_ReferenceType, "ObjectReference.ReferenceType" }, + { 9, 2, handleOR_GetValues, "ObjectReference.GetValues" }, + { 9, 3, handleOR_SetValues, "ObjectReference.SetValues" }, + //9, 4, (not defined) + //9, 5, MonitorInfo + { 9, 6, handleOR_InvokeMethod, "ObjectReference.InvokeMethod" }, + { 9, 7, handleOR_DisableCollection, + "ObjectReference.DisableCollection" }, + { 9, 8, handleOR_EnableCollection, + "ObjectReference.EnableCollection" }, + { 9, 9, handleOR_IsCollected, "ObjectReference.IsCollected" }, + //9, 10, ReferringObjects + + /* StringReference command set (10) */ + { 10, 1, handleSR_Value, "StringReference.Value" }, + + /* ThreadReference command set (11) */ + { 11, 1, handleTR_Name, "ThreadReference.Name" }, + { 11, 2, handleTR_Suspend, "ThreadReference.Suspend" }, + { 11, 3, handleTR_Resume, "ThreadReference.Resume" }, + { 11, 4, handleTR_Status, "ThreadReference.Status" }, + { 11, 5, handleTR_ThreadGroup, "ThreadReference.ThreadGroup" }, + { 11, 6, handleTR_Frames, "ThreadReference.Frames" }, + { 11, 7, handleTR_FrameCount, "ThreadReference.FrameCount" }, + //11, 8, OwnedMonitors + { 11, 9, handleTR_CurrentContendedMonitor, + "ThreadReference.CurrentContendedMonitor" }, + //11, 10, Stop + //11, 11, Interrupt + { 11, 12, handleTR_SuspendCount, "ThreadReference.SuspendCount" }, + //11, 13, OwnedMonitorsStackDepthInfo + //11, 14, ForceEarlyReturn + + /* ThreadGroupReference command set (12) */ + { 12, 1, handleTGR_Name, "ThreadGroupReference.Name" }, + { 12, 2, handleTGR_Parent, "ThreadGroupReference.Parent" }, + { 12, 3, handleTGR_Children, "ThreadGroupReference.Children" }, + + /* ArrayReference command set (13) */ + { 13, 1, handleAR_Length, "ArrayReference.Length" }, + { 13, 2, handleAR_GetValues, "ArrayReference.GetValues" }, + { 13, 3, handleAR_SetValues, "ArrayReference.SetValues" }, + + /* ClassLoaderReference command set (14) */ + { 14, 1, handleCLR_VisibleClasses, + "ClassLoaderReference.VisibleClasses" }, + + /* EventRequest command set (15) */ + { 15, 1, handleER_Set, "EventRequest.Set" }, + { 15, 2, handleER_Clear, "EventRequest.Clear" }, + //15, 3, ClearAllBreakpoints + + /* StackFrame command set (16) */ + { 16, 1, handleSF_GetValues, "StackFrame.GetValues" }, + { 16, 2, handleSF_SetValues, "StackFrame.SetValues" }, + { 16, 3, handleSF_ThisObject, "StackFrame.ThisObject" }, + //16, 4, PopFrames + + /* ClassObjectReference command set (17) */ + { 17, 1, handleCOR_ReflectedType,"ClassObjectReference.ReflectedType" }, + + /* Event command set (64) */ + //64, 100, Composite <-- sent from VM to debugger, never received by VM + + { 199, 1, handleDDM_Chunk, "DDM.Chunk" }, +}; + + +/* + * Process a request from the debugger. + * + * On entry, the JDWP thread is in VMWAIT. + */ +void dvmJdwpProcessRequest(JdwpState* state, const JdwpReqHeader* pHeader, + const u1* buf, int dataLen, ExpandBuf* pReply) +{ + JdwpError result = ERR_NONE; + int i, respLen; + + /* + * Activity from a debugger, not merely ddms. Mark us as having an + * active debugger session, and zero out the last-activity timestamp. + */ + if (pHeader->cmdSet != kJDWPDdmCmdSet) { + dvmDbgActive(); + + state->lastActivitySec = 0; + MEM_BARRIER(); + } + + /* + * If a debugger event has fired in another thread, wait until the + * initiating thread has suspended itself before processing messages + * from the debugger. Otherwise we (the JDWP thread) could be told to + * resume the thread before it has suspended. + * + * We call with an argument of zero to wait for the current event + * thread to finish, and then clear the block. Depending on the thread + * suspend policy, this may allow events in other threads to fire, + * but those events have no bearing on what the debugger has sent us + * in the current request. + * + * Note that we MUST clear the event token before waking the event + * thread up, or risk waiting for the thread to suspend after we've + * told it to resume. + */ + dvmJdwpSetWaitForEventThread(state, 0); + + /* + * Tell the VM that we're running and shouldn't be interrupted by GC. + * Do this after anything that can stall indefinitely. + */ + dvmDbgThreadRunning(); + + expandBufAddSpace(pReply, kJDWPHeaderLen); + + for (i = 0; i < (int) NELEM(gHandlerMap); i++) { + if (gHandlerMap[i].cmdSet == pHeader->cmdSet && + gHandlerMap[i].cmd == pHeader->cmd) + { + LOGV("REQ: %s (cmd=%d/%d dataLen=%d id=0x%06x)\n", + gHandlerMap[i].descr, pHeader->cmdSet, pHeader->cmd, + dataLen, pHeader->id); + result = (*gHandlerMap[i].func)(state, buf, dataLen, pReply); + break; + } + } + if (i == NELEM(gHandlerMap)) { + LOGE("REQ: UNSUPPORTED (cmd=%d/%d dataLen=%d id=0x%06x)\n", + pHeader->cmdSet, pHeader->cmd, dataLen, pHeader->id); + if (dataLen > 0) + dvmPrintHexDumpDbg(buf, dataLen, LOG_TAG); + assert(!"command not implemented"); // make it *really* obvious + result = ERR_NOT_IMPLEMENTED; + } + + /* + * Set up the reply header. + * + * If we encountered an error, only send the header back. + */ + u1* replyBuf = expandBufGetBuffer(pReply); + set4BE(replyBuf + 4, pHeader->id); + set1(replyBuf + 8, kJDWPFlagReply); + set2BE(replyBuf + 9, result); + if (result == ERR_NONE) + set4BE(replyBuf + 0, expandBufGetLength(pReply)); + else + set4BE(replyBuf + 0, kJDWPHeaderLen); + + respLen = expandBufGetLength(pReply) - kJDWPHeaderLen; + IF_LOG(LOG_VERBOSE, LOG_TAG) { + LOGV("reply: dataLen=%d err=%s(%d)%s\n", respLen, + dvmJdwpErrorStr(result), result, + result != ERR_NONE ? " **FAILED**" : ""); + if (respLen > 0) + dvmPrintHexDumpDbg(expandBufGetBuffer(pReply) + kJDWPHeaderLen, + respLen, LOG_TAG); + } + + /* + * Update last-activity timestamp. We really only need this during + * the initial setup. Only update if this is a non-DDMS packet. + */ + if (pHeader->cmdSet != kJDWPDdmCmdSet) { + long lastSec, lastMsec; + + dvmJdwpGetNowMsec(&lastSec, &lastMsec); + state->lastActivityMsec = lastMsec; + MEM_BARRIER(); // updating a 64-bit value + state->lastActivitySec = lastSec; + } + + /* tell the VM that GC is okay again */ + dvmDbgThreadWaiting(); +} + diff --git a/vm/jdwp/JdwpHandler.h b/vm/jdwp/JdwpHandler.h new file mode 100644 index 000000000..3a7a98cb5 --- /dev/null +++ b/vm/jdwp/JdwpHandler.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Handle requests. + */ +#ifndef _DALVIK_JDWP_JDWPHANDLER +#define _DALVIK_JDWP_JDWPHANDLER + +#include "Common.h" +#include "ExpandBuf.h" + +/* + * JDWP message header for a request. + */ +typedef struct JdwpReqHeader { + u4 length; + u4 id; + u1 cmdSet; + u1 cmd; +} JdwpReqHeader; + +/* + * Process a request from the debugger. + * + * "buf" points past the header, to the content of the message. "dataLen" + * can therefore be zero. + */ +void dvmJdwpProcessRequest(JdwpState* state, const JdwpReqHeader* pHeader, + const u1* buf, int dataLen, ExpandBuf* pReply); + +/* helper function */ +void dvmJdwpAddLocation(ExpandBuf* pReply, const JdwpLocation* pLoc); + +#endif /*_DALVIK_JDWP_JDWPHANDLER*/ diff --git a/vm/jdwp/JdwpMain.c b/vm/jdwp/JdwpMain.c new file mode 100644 index 000000000..4166c6729 --- /dev/null +++ b/vm/jdwp/JdwpMain.c @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * JDWP initialization. + */ +#include "jdwp/JdwpPriv.h" +#include "Dalvik.h" +#include "Atomic.h" + +#include <stdlib.h> +#include <unistd.h> +#include <sys/time.h> +#include <time.h> +#include <errno.h> + + +static void* jdwpThreadStart(void* arg); + + +/* + * Initialize JDWP. + * + * Does not return until JDWP thread is running, but may return before + * the thread is accepting network connections. + */ +JdwpState* dvmJdwpStartup(const JdwpStartupParams* pParams) +{ + JdwpState* state = NULL; + int i, sleepIter; + u8 startWhen; + + /* comment this out when debugging JDWP itself */ + android_setMinPriority(LOG_TAG, ANDROID_LOG_DEBUG); + + state = (JdwpState*) calloc(1, sizeof(JdwpState)); + + state->params = *pParams; + + state->requestSerial = 0x10000000; + state->eventSerial = 0x20000000; + dvmDbgInitMutex(&state->threadStartLock); + dvmDbgInitMutex(&state->attachLock); + dvmDbgInitMutex(&state->serialLock); + dvmDbgInitMutex(&state->eventLock); + state->eventThreadId = 0; + dvmDbgInitMutex(&state->eventThreadLock); + dvmDbgInitCond(&state->threadStartCond); + dvmDbgInitCond(&state->attachCond); + dvmDbgInitCond(&state->eventThreadCond); + + switch (pParams->transport) { + case kJdwpTransportSocket: + // LOGD("prepping for JDWP over TCP\n"); + state->transport = dvmJdwpSocketTransport(); + break; + case kJdwpTransportAndroidAdb: + // LOGD("prepping for JDWP over ADB\n"); + state->transport = dvmJdwpAndroidAdbTransport(); + /* TODO */ + break; + default: + LOGE("Unknown transport %d\n", pParams->transport); + assert(false); + goto fail; + } + + if (!dvmJdwpNetStartup(state, pParams)) + goto fail; + + /* + * Grab a mutex or two before starting the thread. This ensures they + * won't signal the cond var before we're waiting. + */ + dvmDbgLockMutex(&state->threadStartLock); + if (pParams->suspend) + dvmDbgLockMutex(&state->attachLock); + + /* + * We have bound to a port, or are trying to connect outbound to a + * debugger. Create the JDWP thread and let it continue the mission. + */ + if (!dvmCreateInternalThread(&state->debugThreadHandle, "JDWP", + jdwpThreadStart, state)) + { + /* state is getting tossed, but unlock these anyway for cleanliness */ + dvmDbgUnlockMutex(&state->threadStartLock); + if (pParams->suspend) + dvmDbgUnlockMutex(&state->attachLock); + goto fail; + } + + /* + * Wait until the thread finishes basic initialization. + * TODO: cond vars should be waited upon in a loop + */ + dvmDbgCondWait(&state->threadStartCond, &state->threadStartLock); + dvmDbgUnlockMutex(&state->threadStartLock); + + + /* + * For suspend=y, wait for the debugger to connect to us or for us to + * connect to the debugger. + * + * The JDWP thread will signal us when it connects successfully or + * times out (for timeout=xxx), so we have to check to see what happened + * when we wake up. + */ + if (pParams->suspend) { + dvmChangeStatus(NULL, THREAD_VMWAIT); + dvmDbgCondWait(&state->attachCond, &state->attachLock); + dvmDbgUnlockMutex(&state->attachLock); + dvmChangeStatus(NULL, THREAD_RUNNING); + + if (!dvmJdwpIsActive(state)) { + LOGE("JDWP connection failed\n"); + goto fail; + } + + LOGI("JDWP connected\n"); + + /* + * Ordinarily we would pause briefly to allow the debugger to set + * breakpoints and so on, but for "suspend=y" the VM init code will + * pause the VM when it sends the VM_START message. + */ + } + + return state; + +fail: + dvmJdwpShutdown(state); // frees state + return NULL; +} + +/* + * Reset all session-related state. There should not be an active connection + * to the client at this point (we may be listening for a new one though). + * + * This includes freeing up the debugger event list. + */ +void dvmJdwpResetState(JdwpState* state) +{ + /* could reset the serial numbers, but no need to */ + + dvmJdwpUnregisterAll(state); + assert(state->eventList == NULL); + + /* + * Should not have one of these in progress. If the debugger went away + * mid-request, though, we could see this. + */ + if (state->eventThreadId != 0) { + LOGW("WARNING: resetting state while event in progress\n"); + assert(false); + } +} + +/* + * Tell the JDWP thread to shut down. Frees "state". + */ +void dvmJdwpShutdown(JdwpState* state) +{ + void* threadReturn; + + if (state == NULL) + return; + + if (dvmJdwpIsTransportDefined(state)) { + if (dvmJdwpIsConnected(state)) + dvmJdwpPostVMDeath(state); + + /* + * Close down the network to inspire the thread to halt. + */ + LOGD("JDWP shutting down net...\n"); + dvmJdwpNetShutdown(state); + + if (state->debugThreadStarted) { + state->run = false; + if (pthread_join(state->debugThreadHandle, &threadReturn) != 0) { + LOGW("JDWP thread join failed\n"); + } + } + + LOGV("JDWP freeing netstate...\n"); + dvmJdwpNetFree(state); + state->netState = NULL; + } + assert(state->netState == NULL); + + dvmJdwpResetState(state); + free(state); +} + +/* + * Are we talking to a debugger? + */ +bool dvmJdwpIsActive(JdwpState* state) +{ + return dvmJdwpIsConnected(state); +} + +/* + * Entry point for JDWP thread. The thread was created through the VM + * mechanisms, so there is a java/lang/Thread associated with us. + */ +static void* jdwpThreadStart(void* arg) +{ + JdwpState* state = (JdwpState*) arg; + + LOGV("JDWP: thread running\n"); + + /* + * Finish initializing "state", then notify the creating thread that + * we're running. + */ + state->debugThreadHandle = dvmThreadSelf()->handle; + state->run = true; + MEM_BARRIER(); + state->debugThreadStarted = true; // touch this last + + dvmDbgLockMutex(&state->threadStartLock); + dvmDbgCondBroadcast(&state->threadStartCond); + dvmDbgUnlockMutex(&state->threadStartLock); + + /* set the thread state to VMWAIT so GCs don't wait for us */ + dvmDbgThreadWaiting(); + + /* + * Loop forever if we're in server mode, processing connections. In + * non-server mode, we bail out of the thread when the debugger drops + * us. + * + * We broadcast a notification when a debugger attaches, after we + * successfully process the handshake. + */ + while (state->run) { + bool first; + int cc; + + if (state->params.server) { + /* + * Block forever, waiting for a connection. To support the + * "timeout=xxx" option we'll need to tweak this. + */ + if (!dvmJdwpAcceptConnection(state)) + break; + } else { + /* + * If we're not acting as a server, we need to connect out to the + * debugger. To support the "timeout=xxx" option we need to + * have a timeout if the handshake reply isn't received in a + * reasonable amount of time. + */ + if (!dvmJdwpEstablishConnection(state)) { + /* wake anybody who was waiting for us to succeed */ + dvmDbgLockMutex(&state->attachLock); + dvmDbgCondBroadcast(&state->attachCond); + dvmDbgUnlockMutex(&state->attachLock); + break; + } + } + + /* prep debug code to handle the new connection */ + dvmDbgConnected(); + + /* process requests until the debugger drops */ + first = true; + while (true) { + // sanity check -- shouldn't happen? + if (dvmThreadSelf()->status != THREAD_VMWAIT) { + LOGE("JDWP thread no longer in VMWAIT (now %d); resetting\n", + dvmThreadSelf()->status); + dvmDbgThreadWaiting(); + } + + if (!dvmJdwpProcessIncoming(state)) /* blocking read */ + break; + + if (first && !dvmJdwpAwaitingHandshake(state)) { + /* handshake worked, tell the interpreter that we're active */ + first = false; + + /* set thread ID; requires object registry to be active */ + state->debugThreadId = dvmDbgGetThreadSelfId(); + + /* wake anybody who's waiting for us */ + dvmDbgLockMutex(&state->attachLock); + dvmDbgCondBroadcast(&state->attachCond); + dvmDbgUnlockMutex(&state->attachLock); + } + } + + dvmJdwpCloseConnection(state); + + if (state->ddmActive) { + state->ddmActive = false; + + /* broadcast the disconnect; must be in RUNNING state */ + dvmDbgThreadRunning(); + dvmDbgDdmDisconnected(); + dvmDbgThreadWaiting(); + } + + /* interpreter can ignore breakpoints */ + dvmDbgDisconnected(); + + /* if we had stuff suspended, resume it now */ + dvmUndoDebuggerSuspensions(); + + dvmJdwpResetState(state); + + /* if we connected out, this was a one-shot deal */ + if (!state->params.server) + state->run = false; + } + + /* back to running, for thread shutdown */ + dvmDbgThreadRunning(); + + LOGV("JDWP: thread exiting\n"); + return NULL; +} + + +/* + * Return the thread handle, or (pthread_t)0 if the debugger isn't running. + */ +pthread_t dvmJdwpGetDebugThread(JdwpState* state) +{ + if (state == NULL) + return 0; + + return state->debugThreadHandle; +} + +#if 0 +/* + * Wait until the debugger attaches. Returns immediately if the debugger + * is already attached. + * + * If we return the instant the debugger connects, we run the risk of + * executing code before the debugger has had a chance to configure + * breakpoints or issue suspend calls. It would be nice to just sit in + * the suspended state, but most debuggers don't expect any threads to be + * suspended when they attach. + * + * There's no event we can post to tell the debugger "we've stopped, and + * we like it that way". We could send a fake breakpoint, which should + * cause the debugger to immediately send a resume, but the debugger might + * send the resume immediately or might throw an exception of its own upon + * receiving a breakpoint event that it didn't ask for. + * + * What we really want is a "wait until the debugger is done configuring + * stuff" event. We can get close with a "wait until the debugger has + * been idle for a brief period", and we can do a mild approximation with + * "just sleep for a second after it connects". + * + * We should be in THREAD_VMWAIT here, so we're not allowed to do anything + * with objects because a GC could be in progress. + * + * NOTE: this trips as soon as something connects to the socket. This + * is no longer appropriate -- we don't want to return when DDMS connects. + * We could fix this by polling for the first debugger packet, but we have + * to watch out for disconnects. If we're going to do polling, it's + * probably best to do it at a higher level. + */ +void dvmJdwpWaitForDebugger(JdwpState* state) +{ + // no more +} +#endif + +/* + * Get a notion of the current time, in milliseconds. We leave it in + * two 32-bit pieces. + */ +void dvmJdwpGetNowMsec(long* pSec, long* pMsec) +{ +#ifdef HAVE_POSIX_CLOCKS + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + *pSec = now.tv_sec; + *pMsec = now.tv_nsec / 1000000; +#else + struct timeval now; + gettimeofday(&now, NULL); + *pSec = now.tv_sec; + *pMsec = now.tv_usec / 1000; +#endif +} + +/* + * Return the time, in milliseconds, since the last debugger activity. + * + * Returns -1 if no debugger is attached, or 0 if we're in the middle of + * processing a debugger request. + */ +s8 dvmJdwpLastDebuggerActivity(JdwpState* state) +{ + long lastSec, lastMsec; + long nowSec, nowMsec; + + /* these are volatile; lastSec becomes 0 during update */ + lastSec = state->lastActivitySec; + lastMsec = state->lastActivityMsec; + + /* initializing or in the middle of something? */ + if (lastSec == 0 || state->lastActivitySec != lastSec) { + //LOGI("+++ last=busy\n"); + return 0; + } + + /* get the current time *after* latching the "last" time */ + dvmJdwpGetNowMsec(&nowSec, &nowMsec); + + s8 last = (s8)lastSec * 1000 + lastMsec; + s8 now = (s8)nowSec * 1000 + nowMsec; + + //LOGI("last is %ld.%ld --> %lld\n", lastSec, lastMsec, last); + //LOGI("now is %ld.%ld --> %lld\n", nowSec, nowMsec, now); + + + //LOGI("+++ interval=%lld\n", now - last); + return now - last; +} + diff --git a/vm/jdwp/JdwpPriv.h b/vm/jdwp/JdwpPriv.h new file mode 100644 index 000000000..087b560ad --- /dev/null +++ b/vm/jdwp/JdwpPriv.h @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * JDWP internal interfaces. + */ +#ifndef _DALVIK_JDWP_JDWPPRIV +#define _DALVIK_JDWP_JDWPPRIV + +#define LOG_TAG "jdwp" + +#include "jdwp/Jdwp.h" +#include "jdwp/JdwpEvent.h" +#include "Debugger.h" +#include <pthread.h> + +/* + * JDWP constants. + */ +#define kJDWPHeaderLen 11 +#define kJDWPFlagReply 0x80 + +/* DDM support */ +#define kJDWPDdmCmdSet 199 /* 0xc7, or 'G'+128 */ +#define kJDWPDdmCmd 1 + + +/* + * Transport-specific network status. + */ +struct JdwpNetState; +typedef struct JdwpNetState JdwpNetState; + +struct JdwpState; + +/* + * Transport functions. + */ +typedef struct JdwpTransport { + bool (*startup)(struct JdwpState* state, const JdwpStartupParams* pParams); + bool (*accept)(struct JdwpState* state); + bool (*establish)(struct JdwpState* state); + void (*close)(struct JdwpState* state); + void (*shutdown)(struct JdwpState* state); + void (*free)(struct JdwpState* state); + bool (*isConnected)(struct JdwpState* state); + bool (*awaitingHandshake)(struct JdwpState* state); + bool (*processIncoming)(struct JdwpState* state); + bool (*sendRequest)(struct JdwpState* state, ExpandBuf* pReq); +} JdwpTransport; + +const JdwpTransport* dvmJdwpSocketTransport(); +const JdwpTransport* dvmJdwpAndroidAdbTransport(); + + +/* + * State for JDWP functions. + */ +struct JdwpState { + JdwpStartupParams params; + + /* wait for creation of the JDWP thread */ + pthread_mutex_t threadStartLock; + pthread_cond_t threadStartCond; + + bool debugThreadStarted; + pthread_t debugThreadHandle; + ObjectId debugThreadId; + bool run; + + const JdwpTransport* transport; + JdwpNetState* netState; + + /* for wait-for-debugger */ + pthread_mutex_t attachLock; + pthread_cond_t attachCond; + + /* time of last debugger activity; "sec" zeroed while processing */ + volatile long lastActivitySec; + volatile long lastActivityMsec; + + /* global counters and a mutex to protect them */ + u4 requestSerial; + u4 eventSerial; + pthread_mutex_t serialLock; + + /* + * Events requested by the debugger (breakpoints, class prep, etc). + */ + int numEvents; /* #of elements in eventList */ + JdwpEvent* eventList; /* linked list of events */ + pthread_mutex_t eventLock; /* guards numEvents/eventList */ + + /* + * Synchronize suspension of event thread (to avoid receiving "resume" + * events before the thread has finished suspending itself). + */ + pthread_mutex_t eventThreadLock; + pthread_cond_t eventThreadCond; + ObjectId eventThreadId; + + /* + * DDM support. + */ + bool ddmActive; +}; + + +/* reset all session-specific data */ +void dvmJdwpResetState(JdwpState* state); + +/* atomic ops to get next serial number */ +u4 dvmJdwpNextRequestSerial(JdwpState* state); +u4 dvmJdwpNextEventSerial(JdwpState* state); + +/* get current time, in msec */ +void dvmJdwpGetNowMsec(long* pSec, long* pMsec); + + +/* + * Transport functions. + */ +INLINE bool dvmJdwpNetStartup(JdwpState* state, + const JdwpStartupParams* pParams) +{ + return (*state->transport->startup)(state, pParams); +} +INLINE bool dvmJdwpAcceptConnection(JdwpState* state) { + return (*state->transport->accept)(state); +} +INLINE bool dvmJdwpEstablishConnection(JdwpState* state) { + return (*state->transport->establish)(state); +} +INLINE void dvmJdwpCloseConnection(JdwpState* state) { + (*state->transport->close)(state); +} +INLINE void dvmJdwpNetShutdown(JdwpState* state) { + (*state->transport->shutdown)(state); +} +INLINE void dvmJdwpNetFree(JdwpState* state) { + (*state->transport->free)(state); +} +INLINE bool dvmJdwpIsTransportDefined(JdwpState* state) { + return state != NULL && state->transport != NULL; +} +INLINE bool dvmJdwpIsConnected(JdwpState* state) { + return state != NULL && (*state->transport->isConnected)(state); +} +INLINE bool dvmJdwpAwaitingHandshake(JdwpState* state) { + return (*state->transport->awaitingHandshake)(state); +} +INLINE bool dvmJdwpProcessIncoming(JdwpState* state) { + return (*state->transport->processIncoming)(state); +} +INLINE bool dvmJdwpSendRequest(JdwpState* state, ExpandBuf* pReq) { + return (*state->transport->sendRequest)(state, pReq); +} + +#endif /*_DALVIK_JDWP_JDWPPRIV*/ diff --git a/vm/jdwp/JdwpSocket.c b/vm/jdwp/JdwpSocket.c new file mode 100644 index 000000000..7b1ccfc93 --- /dev/null +++ b/vm/jdwp/JdwpSocket.c @@ -0,0 +1,876 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * JDWP TCP socket network code. + */ +#include "jdwp/JdwpPriv.h" +#include "jdwp/JdwpHandler.h" +#include "Bits.h" + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <netdb.h> + +#define kBasePort 8000 +#define kMaxPort 8040 + +#define kInputBufferSize 8192 + +#define kMagicHandshake "JDWP-Handshake" +#define kMagicHandshakeLen (sizeof(kMagicHandshake)-1) + +// fwd +static void netShutdown(JdwpNetState* state); +static void netFree(JdwpNetState* state); + + +/* + * JDWP network state. + * + * We only talk to one debugger at a time. + */ +struct JdwpNetState { + short listenPort; + int listenSock; /* listen for connection from debugger */ + int clientSock; /* active connection to debugger */ + int wakePipe[2]; /* break out of select */ + + struct in_addr remoteAddr; + unsigned short remotePort; + + bool awaitingHandshake; /* waiting for "JDWP-Handshake" */ + + /* pending data from the network; would be more efficient as circular buf */ + unsigned char inputBuffer[kInputBufferSize]; + int inputCount; +}; + +static JdwpNetState* netStartup(short port); + +/* + * Set up some stuff for transport=dt_socket. + */ +static bool prepareSocket(JdwpState* state, const JdwpStartupParams* pParams) +{ + unsigned short port; + + if (pParams->server) { + if (pParams->port != 0) { + /* try only the specified port */ + port = pParams->port; + state->netState = netStartup(port); + } else { + /* scan through a range of ports, binding to the first available */ + for (port = kBasePort; port <= kMaxPort; port++) { + state->netState = netStartup(port); + if (state->netState != NULL) + break; + } + } + if (state->netState == NULL) { + LOGE("JDWP net startup failed (req port=%d)\n", pParams->port); + return false; + } + } else { + port = pParams->port; // used in a debug msg later + state->netState = netStartup(-1); + } + + if (pParams->suspend) + LOGI("JDWP will wait for debugger on port %d\n", port); + else + LOGD("JDWP will %s on port %d\n", + pParams->server ? "listen" : "connect", port); + + return true; +} + + +/* + * Are we still waiting for the handshake string? + */ +static bool awaitingHandshake(JdwpState* state) +{ + return state->netState->awaitingHandshake; +} + +/* + * Initialize JDWP stuff. + * + * Allocates a new state structure. If "port" is non-negative, this also + * tries to bind to a listen port. If "port" is less than zero, we assume + * we're preparing for an outbound connection, and return without binding + * to anything. + * + * This may be called several times if we're probing for a port. + * + * Returns 0 on success. + */ +static JdwpNetState* netStartup(short port) +{ + JdwpNetState* netState; + int one = 1; + + netState = (JdwpNetState*) malloc(sizeof(*netState)); + memset(netState, 0, sizeof(*netState)); + netState->listenSock = -1; + netState->clientSock = -1; + netState->wakePipe[0] = -1; + netState->wakePipe[1] = -1; + + if (port < 0) + return netState; + + assert(port != 0); + + netState->listenSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (netState->listenSock < 0) { + LOGE("Socket create failed: %s\n", strerror(errno)); + goto fail; + } + + /* allow immediate re-use */ + if (setsockopt(netState->listenSock, SOL_SOCKET, SO_REUSEADDR, &one, + sizeof(one)) < 0) + { + LOGE("setsockopt(SO_REUSEADDR) failed: %s\n", strerror(errno)); + goto fail; + } + + union { + struct sockaddr_in addrInet; + struct sockaddr addrPlain; + } addr; + addr.addrInet.sin_family = AF_INET; + addr.addrInet.sin_port = htons(port); + inet_aton("127.0.0.1", &addr.addrInet.sin_addr); + + if (bind(netState->listenSock, &addr.addrPlain, sizeof(addr)) != 0) { + LOGV("attempt to bind to port %u failed: %s\n", port, strerror(errno)); + goto fail; + } + + netState->listenPort = port; + LOGVV("+++ bound to port %d\n", netState->listenPort); + + if (listen(netState->listenSock, 5) != 0) { + LOGE("Listen failed: %s\n", strerror(errno)); + goto fail; + } + + return netState; + +fail: + netShutdown(netState); + netFree(netState); + return NULL; +} + +/* + * Shut down JDWP listener. Don't free state. + * + * Note that "netState" may be partially initialized if "startup" failed. + * + * This may be called from a non-JDWP thread as part of shutting the + * JDWP thread down. + * + * (This is currently called several times during startup as we probe + * for an open port.) + */ +static void netShutdown(JdwpNetState* netState) +{ + if (netState == NULL) + return; + + int listenSock = netState->listenSock; + int clientSock = netState->clientSock; + + /* clear these out so it doesn't wake up and try to reuse them */ + netState->listenSock = netState->clientSock = -1; + + /* "shutdown" dislodges blocking read() and accept() calls */ + if (listenSock >= 0) { + shutdown(listenSock, SHUT_RDWR); + close(listenSock); + } + if (clientSock >= 0) { + shutdown(clientSock, SHUT_RDWR); + close(clientSock); + } + + /* if we might be sitting in select, kick us loose */ + if (netState->wakePipe[1] >= 0) { + LOGV("+++ writing to wakePipe\n"); + (void) write(netState->wakePipe[1], "", 1); + } +} +static void netShutdownExtern(JdwpState* state) +{ + netShutdown(state->netState); +} + +/* + * Free JDWP state. + * + * Call this after shutting the network down with netShutdown(). + */ +static void netFree(JdwpNetState* netState) +{ + if (netState == NULL) + return; + assert(netState->listenSock == -1); + assert(netState->clientSock == -1); + + if (netState->wakePipe[0] >= 0) { + close(netState->wakePipe[0]); + netState->wakePipe[0] = -1; + } + if (netState->wakePipe[1] >= 0) { + close(netState->wakePipe[1]); + netState->wakePipe[1] = -1; + } + + free(netState); +} +static void netFreeExtern(JdwpState* state) +{ + netFree(state->netState); +} + +/* + * Returns "true" if we're connected to a debugger. + */ +static bool isConnected(JdwpState* state) +{ + return (state->netState != NULL && + state->netState->clientSock >= 0); +} + +/* + * Returns "true" if the fd is ready, "false" if not. + */ +static bool isFdReadable(int sock) +{ + fd_set readfds; + struct timeval tv; + int count; + + FD_ZERO(&readfds); + FD_SET(sock, &readfds); + + tv.tv_sec = 0; + tv.tv_usec = 0; + count = select(sock+1, &readfds, NULL, NULL, &tv); + if (count <= 0) + return false; + + if (FD_ISSET(sock, &readfds)) /* make sure it's our fd */ + return true; + + LOGE("WEIRD: odd behavior in select (count=%d)\n", count); + return false; +} + +#if 0 +/* + * Check to see if we have a pending connection from the debugger. + * + * Returns true on success (meaning a connection is available). + */ +static bool checkConnection(JdwpState* state) +{ + JdwpNetState* netState = state->netState; + + assert(netState->listenSock >= 0); + /* not expecting to be called when debugger is actively connected */ + assert(netState->clientSock < 0); + + if (!isFdReadable(netState->listenSock)) + return false; + return true; +} +#endif + +/* + * 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. + * If that's not desirable, use checkConnection() to make sure something + * is pending. + */ +static bool acceptConnection(JdwpState* state) +{ + JdwpNetState* netState = state->netState; + union { + struct sockaddr_in addrInet; + struct sockaddr addrPlain; + } addr; + socklen_t addrlen; + int sock; + + if (netState->listenSock < 0) + return false; /* you're not listening! */ + + assert(netState->clientSock < 0); /* must not already be talking */ + + addrlen = sizeof(addr); + do { + sock = accept(netState->listenSock, &addr.addrPlain, &addrlen); + if (sock < 0 && errno != EINTR) { + // When we call shutdown() on the socket, accept() returns with + // EINVAL. Don't gripe about it. + if (errno == EINVAL) + LOGVV("accept failed: %s\n", strerror(errno)); + else + LOGE("accept failed: %s\n", strerror(errno)); + return false; + } + } while (sock < 0); + + netState->remoteAddr = addr.addrInet.sin_addr; + netState->remotePort = ntohs(addr.addrInet.sin_port); + LOGV("+++ accepted connection from %s:%u\n", + inet_ntoa(netState->remoteAddr), netState->remotePort); + + netState->clientSock = sock; + netState->awaitingHandshake = true; + netState->inputCount = 0; + + LOGV("Setting TCP_NODELAY on accepted socket\n"); + setNoDelay(netState->clientSock); + + if (pipe(netState->wakePipe) < 0) { + LOGE("pipe failed"); + return false; + } + + return true; +} + +/* + * Create a connection to a waiting debugger. + */ +static bool establishConnection(JdwpState* state) +{ + union { + struct sockaddr_in addrInet; + struct sockaddr addrPlain; + } addr; + struct hostent* pEntry; + char auxBuf[128]; + int cc, h_errno; + + assert(state != NULL && state->netState != NULL); + assert(!state->params.server); + assert(state->params.host[0] != '\0'); + assert(state->params.port != 0); + + /* + * Start by resolving the host name. + */ +//#undef HAVE_GETHOSTBYNAME_R +//#warning "forcing non-R" +#ifdef HAVE_GETHOSTBYNAME_R + struct hostent he; + cc = gethostbyname_r(state->params.host, &he, auxBuf, sizeof(auxBuf), + &pEntry, &h_errno); + if (cc != 0) { + LOGW("gethostbyname_r('%s') failed: %s\n", + state->params.host, strerror(errno)); + return false; + } + +#else + h_errno = 0; + pEntry = gethostbyname(state->params.host); + if (pEntry == NULL) { + LOGW("gethostbyname('%s') failed: %s\n", + state->params.host, strerror(h_errno)); + return false; + } +#endif + + /* copy it out ASAP to minimize risk of multithreaded annoyances */ + memcpy(&addr.addrInet.sin_addr, pEntry->h_addr, pEntry->h_length); + addr.addrInet.sin_family = pEntry->h_addrtype; + + addr.addrInet.sin_port = htons(state->params.port); + + LOGI("Connecting out to '%s' %d\n", + inet_ntoa(addr.addrInet.sin_addr), ntohs(addr.addrInet.sin_port)); + + /* + * Create a socket. + */ + JdwpNetState* netState; + netState = state->netState; + netState->clientSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (netState->clientSock < 0) { + LOGE("Unable to create socket: %s\n", strerror(errno)); + return false; + } + + /* + * Try to connect. + */ + if (connect(netState->clientSock, &addr.addrPlain, sizeof(addr)) != 0) { + LOGE("Unable to connect to %s:%d: %s\n", + inet_ntoa(addr.addrInet.sin_addr), ntohs(addr.addrInet.sin_port), + strerror(errno)); + close(netState->clientSock); + netState->clientSock = -1; + return false; + } + + LOGI("Connection established to %s (%s:%d)\n", + state->params.host, inet_ntoa(addr.addrInet.sin_addr), + ntohs(addr.addrInet.sin_port)); + netState->awaitingHandshake = true; + netState->inputCount = 0; + + setNoDelay(netState->clientSock); + + if (pipe(netState->wakePipe) < 0) { + LOGE("pipe failed"); + return false; + } + + return true; +} + +/* + * Close the connection to the debugger. + * + * Reset the state so we're ready to receive a new connection. + */ +static void closeConnection(JdwpState* state) +{ + JdwpNetState* netState; + + assert(state != NULL && state->netState != NULL); + + netState = state->netState; + if (netState->clientSock < 0) + return; + + LOGV("+++ closed connection to %s:%u\n", + inet_ntoa(netState->remoteAddr), netState->remotePort); + + close(netState->clientSock); + netState->clientSock = -1; + + return; +} + +/* + * Figure out if we have a full packet in the buffer. + */ +static bool haveFullPacket(JdwpNetState* netState) +{ + long length; + + if (netState->awaitingHandshake) + return (netState->inputCount >= (int) kMagicHandshakeLen); + + if (netState->inputCount < 4) + return false; + + length = get4BE(netState->inputBuffer); + return (netState->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(JdwpNetState* netState, int count) +{ + assert(count > 0); + assert(count <= netState->inputCount); + + if (count == netState->inputCount) { + netState->inputCount = 0; + return; + } + + memmove(netState->inputBuffer, netState->inputBuffer + count, + netState->inputCount - count); + netState->inputCount -= count; +} + +/* + * Dump the contents of a packet to stdout. + */ +static void dumpPacket(const unsigned char* packetBuf) +{ + const unsigned char* buf = packetBuf; + u4 length, id; + u1 flags, cmdSet, cmd; + u2 error; + bool reply; + int dataLen; + + cmd = cmdSet = 0xcc; + + length = read4BE(&buf); + id = read4BE(&buf); + flags = read1(&buf); + if ((flags & kJDWPFlagReply) != 0) { + reply = true; + error = read2BE(&buf); + } else { + reply = false; + cmdSet = read1(&buf); + cmd = read1(&buf); + } + + dataLen = length - (buf - packetBuf); + + LOGV("--- %s: dataLen=%u id=0x%08x flags=0x%02x cmd=%d/%d\n", + reply ? "reply" : "req", + dataLen, id, flags, cmdSet, cmd); + if (dataLen > 0) + dvmPrintHexDumpDbg(buf, dataLen, LOG_TAG); +} + +/* + * Handle a packet. Returns "false" if we encounter a connection-fatal error. + */ +static bool handlePacket(JdwpState* state) +{ + JdwpNetState* netState = state->netState; + const unsigned char* buf = netState->inputBuffer; + JdwpReqHeader hdr; + u4 length, id; + u1 flags, cmdSet, cmd; + u2 error; + bool reply; + int dataLen; + + cmd = cmdSet = 0; // shut up gcc + + /*dumpPacket(netState->inputBuffer);*/ + + length = read4BE(&buf); + id = read4BE(&buf); + flags = read1(&buf); + if ((flags & kJDWPFlagReply) != 0) { + reply = true; + error = read2BE(&buf); + } else { + reply = false; + cmdSet = read1(&buf); + cmd = read1(&buf); + } + + assert((int) length <= netState->inputCount); + dataLen = length - (buf - netState->inputBuffer); + + if (!reply) { + ExpandBuf* pReply = expandBufAlloc(); + + hdr.length = length; + hdr.id = id; + hdr.cmdSet = cmdSet; + hdr.cmd = cmd; + dvmJdwpProcessRequest(state, &hdr, buf, dataLen, pReply); + if (expandBufGetLength(pReply) > 0) { + int cc; + + /* + * TODO: we currently assume the write() will complete in one + * go, which may not be safe for a network socket. We may need + * to mutex this against sendRequest(). + */ + cc = write(netState->clientSock, expandBufGetBuffer(pReply), + expandBufGetLength(pReply)); + if (cc != (int) expandBufGetLength(pReply)) { + LOGE("Failed sending reply to debugger: %s\n", strerror(errno)); + expandBufFree(pReply); + return false; + } + } else { + LOGW("No reply created for set=%d cmd=%d\n", cmdSet, cmd); + } + expandBufFree(pReply); + } else { + LOGV("reply?!\n"); + assert(false); + } + + LOGV("----------\n"); + + consumeBytes(netState, length); + return true; +} + +/* + * Process incoming data. If no data is available, this will block until + * some arrives. + * + * If we get a full packet, handle it. + * + * To take some of the mystery out of life, we want to reject incoming + * connections if we already have a debugger attached. If we don't, the + * debugger will just mysteriously hang until it times out. We could just + * close the listen socket, but there's a good chance we won't be able to + * bind to the same port again, which would confuse utilities. + * + * Returns "false" on error (indicating that the connection has been severed), + * "true" if things are still okay. + */ +static bool processIncoming(JdwpState* state) +{ + JdwpNetState* netState = state->netState; + int readCount; + + assert(netState->clientSock >= 0); + + if (!haveFullPacket(netState)) { + /* read some more, looping until we have data */ + errno = 0; + while (1) { + int selCount; + fd_set readfds; + int maxfd; + int fd; + + maxfd = netState->listenSock; + if (netState->clientSock > maxfd) + maxfd = netState->clientSock; + if (netState->wakePipe[0] > maxfd) + maxfd = netState->wakePipe[0]; + + if (maxfd < 0) { + LOGV("+++ all fds are closed\n"); + return false; + } + + FD_ZERO(&readfds); + + /* configure fds; note these may get zapped by another thread */ + fd = netState->listenSock; + if (fd >= 0) + FD_SET(fd, &readfds); + fd = netState->clientSock; + if (fd >= 0) + FD_SET(fd, &readfds); + fd = netState->wakePipe[0]; + if (fd >= 0) { + FD_SET(fd, &readfds); + } else { + LOGI("NOTE: entering select w/o wakepipe\n"); + } + + /* + * Select blocks until it sees activity on the file descriptors. + * Closing the local file descriptor does not count as activity, + * so we can't rely on that to wake us up (it works for read() + * and accept(), but not select()). + * + * We can do one of three things: (1) send a signal and catch + * EINTR, (2) open an additional fd ("wakePipe") and write to + * it when it's time to exit, or (3) time out periodically and + * re-issue the select. We're currently using #2, as it's more + * reliable than #1 and generally better than #3. Wastes two fds. + */ + selCount = select(maxfd+1, &readfds, NULL, NULL, NULL); + if (selCount < 0) { + if (errno == EINTR) + continue; + LOGE("select failed: %s\n", strerror(errno)); + goto fail; + } + + if (netState->wakePipe[0] >= 0 && + FD_ISSET(netState->wakePipe[0], &readfds)) + { + if (netState->listenSock >= 0) + LOGE("Exit wake set, but not exiting?\n"); + else + LOGD("Got wake-up signal, bailing out of select\n"); + goto fail; + } + if (netState->listenSock >= 0 && + FD_ISSET(netState->listenSock, &readfds)) + { + LOGI("Ignoring second debugger -- accepting and dropping\n"); + union { + struct sockaddr_in addrInet; + struct sockaddr addrPlain; + } addr; + socklen_t addrlen; + int tmpSock; + tmpSock = accept(netState->listenSock, &addr.addrPlain, + &addrlen); + if (tmpSock < 0) + LOGI("Weird -- accept failed\n"); + else + close(tmpSock); + } + if (netState->clientSock >= 0 && + FD_ISSET(netState->clientSock, &readfds)) + { + readCount = read(netState->clientSock, + netState->inputBuffer + netState->inputCount, + sizeof(netState->inputBuffer) - netState->inputCount); + if (readCount < 0) { + /* read failed */ + if (errno != EINTR) + goto fail; + LOGD("+++ EINTR hit\n"); + return true; + } else if (readCount == 0) { + /* EOF hit -- far end went away */ + LOGD("+++ peer disconnected\n"); + goto fail; + } else + break; + } + } + + netState->inputCount += readCount; + if (!haveFullPacket(netState)) + return true; /* still not there yet */ + } + + /* + * Special-case the initial handshake. For some bizarre reason we're + * expected to emulate bad tty settings by echoing the request back + * exactly as it was sent. Note the handshake is always initiated by + * the debugger, no matter who connects to whom. + * + * Other than this one case, the protocol [claims to be] stateless. + */ + if (netState->awaitingHandshake) { + int cc; + + if (memcmp(netState->inputBuffer, + kMagicHandshake, kMagicHandshakeLen) != 0) + { + LOGE("ERROR: bad handshake '%.14s'\n", netState->inputBuffer); + goto fail; + } + + errno = 0; + cc = write(netState->clientSock, netState->inputBuffer, + kMagicHandshakeLen); + if (cc != kMagicHandshakeLen) { + LOGE("Failed writing handshake bytes: %s (%d of %d)\n", + strerror(errno), cc, (int) kMagicHandshakeLen); + goto fail; + } + + consumeBytes(netState, kMagicHandshakeLen); + netState->awaitingHandshake = false; + LOGV("+++ handshake complete\n"); + return true; + } + + /* + * Handle this packet. + */ + return handlePacket(state); + +fail: + closeConnection(state); + return false; +} + +/* + * Send a request. + * + * The entire packet must be sent with a single write() call to avoid + * threading issues. + * + * Returns "true" if it was sent successfully. + */ +static bool sendRequest(JdwpState* state, ExpandBuf* pReq) +{ + JdwpNetState* netState = state->netState; + int cc; + + dumpPacket(expandBufGetBuffer(pReq)); + if (netState->clientSock < 0) { + /* can happen with some DDMS events */ + LOGV("NOT sending request -- no debugger is attached\n"); + return false; + } + + /* + * TODO: we currently assume the write() will complete in one + * go, which may not be safe for a network socket. We may need + * to mutex this against handlePacket(). + */ + errno = 0; + cc = write(netState->clientSock, expandBufGetBuffer(pReq), + expandBufGetLength(pReq)); + if (cc != (int) expandBufGetLength(pReq)) { + LOGE("Failed sending req to debugger: %s (%d of %d)\n", + strerror(errno), cc, (int) expandBufGetLength(pReq)); + return false; + } + + return true; +} + + +/* + * Our functions. + */ +static const JdwpTransport socketTransport = { + prepareSocket, + acceptConnection, + establishConnection, + closeConnection, + netShutdownExtern, + netFreeExtern, + isConnected, + awaitingHandshake, + processIncoming, + sendRequest +}; + +/* + * Return our set. + */ +const JdwpTransport* dvmJdwpSocketTransport(void) +{ + return &socketTransport; +} + diff --git a/vm/jdwp/README.txt b/vm/jdwp/README.txt new file mode 100644 index 000000000..b511cc875 --- /dev/null +++ b/vm/jdwp/README.txt @@ -0,0 +1,13 @@ +Java Debug Wire Protocol support + +This is a reasonably complete implementation, but only messages that are +actually generated by debuggers have been implemented. The reasoning +behind this is that it's better to leave a call unimplemented than have +something that appears implemented but has never been tested. + +An attempt has been made to keep the implementation distinct from the VM, +with Debugger.c acting as a sort of portability layer, so that the code +might be useful in other projects. Once you get multiple simultaneous +events and debugger requests with thread suspension bouncing around, +though, it's difficult to keep things "generic". + |
