summaryrefslogtreecommitdiffstats
path: root/vm/jdwp
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:28:47 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:28:47 -0800
commitf6c387128427e121477c1b32ad35cdcaa5101ba3 (patch)
tree2aa25fa8c8c3a9caeecf98fd8ac4cd9b12717997 /vm/jdwp
parentf72d5de56a522ac3be03873bdde26f23a5eeeb3c (diff)
downloadandroid_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.c176
-rw-r--r--vm/jdwp/ExpandBuf.h57
-rw-r--r--vm/jdwp/Jdwp.h237
-rw-r--r--vm/jdwp/JdwpAdb.c676
-rw-r--r--vm/jdwp/JdwpConstants.c240
-rw-r--r--vm/jdwp/JdwpConstants.h229
-rw-r--r--vm/jdwp/JdwpEvent.c1290
-rw-r--r--vm/jdwp/JdwpEvent.h129
-rw-r--r--vm/jdwp/JdwpHandler.c2152
-rw-r--r--vm/jdwp/JdwpHandler.h47
-rw-r--r--vm/jdwp/JdwpMain.c440
-rw-r--r--vm/jdwp/JdwpPriv.h171
-rw-r--r--vm/jdwp/JdwpSocket.c876
-rw-r--r--vm/jdwp/README.txt13
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".
+