Bring across the JDWP implementation.

This compiles and links, but does nothing until we fill out the 100 or so
unimplemented methods in "debugger.cc". Note that I also need to add the
extra command-line handling for the JDWP agent stuff, and add calls from
the runtime to the various "something interesting is going on" hooks.

Change-Id: I477cf3caf9e248c384ce1d739cbfadb60e2008bc
diff --git a/src/dalvik_system_VMDebug.cc b/src/dalvik_system_VMDebug.cc
index 0540631..4537a69 100644
--- a/src/dalvik_system_VMDebug.cc
+++ b/src/dalvik_system_VMDebug.cc
@@ -15,6 +15,7 @@
  */
 
 #include "class_linker.h"
+#include "debugger.h"
 #include "jni_internal.h"
 #include "ScopedUtfChars.h"
 #include "toStringArray.h"
@@ -112,18 +113,15 @@
 }
 
 jboolean VMDebug_isDebuggerConnected(JNIEnv*, jclass) {
-  UNIMPLEMENTED(WARNING);
-  return JNI_FALSE; //dvmDbgIsDebuggerConnected();
+  return Dbg::IsDebuggerConnected();
 }
 
 jboolean VMDebug_isDebuggingEnabled(JNIEnv*, jclass) {
-  UNIMPLEMENTED(WARNING);
-  return JNI_FALSE; //return gDvm.jdwpConfigured;
+  return Dbg::IsDebuggingEnabled();
 }
 
 jlong VMDebug_lastDebuggerActivity(JNIEnv*, jclass) {
-  UNIMPLEMENTED(WARNING);
-  return 0; //dvmDbgLastDebuggerActivity();
+  return Dbg::LastDebuggerActivity();
 }
 
 void VMDebug_startInstructionCounting(JNIEnv* env, jclass) {
diff --git a/src/debugger.cc b/src/debugger.cc
new file mode 100644
index 0000000..46644e7
--- /dev/null
+++ b/src/debugger.cc
@@ -0,0 +1,442 @@
+/*
+ * 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 "debugger.h"
+
+namespace art {
+
+bool Dbg::DebuggerStartup() {
+  UNIMPLEMENTED(FATAL);
+  return false;
+}
+
+void Dbg::DebuggerShutdown() {
+  UNIMPLEMENTED(FATAL);
+}
+
+DebugInvokeReq* Dbg::GetInvokeReq() {
+  UNIMPLEMENTED(FATAL);
+  return NULL;
+}
+
+void Dbg::Connected() {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::Active() {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::Disconnected() {
+  UNIMPLEMENTED(FATAL);
+}
+
+bool Dbg::IsDebuggerConnected() {
+  UNIMPLEMENTED(WARNING);
+  return false;
+}
+
+bool Dbg::IsDebuggingEnabled() {
+  UNIMPLEMENTED(WARNING);
+  return false; //return gDvm.jdwpConfigured;
+}
+
+int64_t Dbg::LastDebuggerActivity() {
+  UNIMPLEMENTED(WARNING);
+  return -1;
+}
+
+int Dbg::ThreadRunning() {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+int Dbg::ThreadWaiting() {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+int Dbg::ThreadContinuing(int status) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+void Dbg::UndoDebuggerSuspensions() {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::Exit(int status) {
+  UNIMPLEMENTED(FATAL);
+}
+
+const char* Dbg::GetClassDescriptor(JDWP::RefTypeId id) {
+  UNIMPLEMENTED(FATAL);
+  return NULL;
+}
+
+JDWP::ObjectId Dbg::GetClassObject(JDWP::RefTypeId id) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+JDWP::RefTypeId Dbg::GetSuperclass(JDWP::RefTypeId id) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+JDWP::ObjectId Dbg::GetClassLoader(JDWP::RefTypeId id) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+uint32_t Dbg::GetAccessFlags(JDWP::RefTypeId id) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+bool Dbg::IsInterface(JDWP::RefTypeId id) {
+  UNIMPLEMENTED(FATAL);
+  return false;
+}
+
+void Dbg::GetClassList(uint32_t* pNumClasses, JDWP::RefTypeId** pClassRefBuf) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::GetVisibleClassList(JDWP::ObjectId classLoaderId, uint32_t* pNumClasses, JDWP::RefTypeId** pClassRefBuf) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::GetClassInfo(JDWP::RefTypeId classId, uint8_t* pTypeTag, uint32_t* pStatus, const char** pSignature) {
+  UNIMPLEMENTED(FATAL);
+}
+
+bool Dbg::FindLoadedClassBySignature(const char* classDescriptor, JDWP::RefTypeId* pRefTypeId) {
+  UNIMPLEMENTED(FATAL);
+  return false;
+}
+
+void Dbg::GetObjectType(JDWP::ObjectId objectId, uint8_t* pRefTypeTag, JDWP::RefTypeId* pRefTypeId) {
+  UNIMPLEMENTED(FATAL);
+}
+
+uint8_t Dbg::GetClassObjectType(JDWP::RefTypeId refTypeId) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+const char* Dbg::GetSignature(JDWP::RefTypeId refTypeId) {
+  UNIMPLEMENTED(FATAL);
+  return NULL;
+}
+
+const char* Dbg::GetSourceFile(JDWP::RefTypeId refTypeId) {
+  UNIMPLEMENTED(FATAL);
+  return NULL;
+}
+
+const char* Dbg::GetObjectTypeName(JDWP::ObjectId objectId) {
+  UNIMPLEMENTED(FATAL);
+  return NULL;
+}
+
+uint8_t Dbg::GetObjectTag(JDWP::ObjectId objectId) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+int Dbg::GetTagWidth(int tag) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+int Dbg::GetArrayLength(JDWP::ObjectId arrayId) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+uint8_t Dbg::GetArrayElementTag(JDWP::ObjectId arrayId) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+bool Dbg::OutputArray(JDWP::ObjectId arrayId, int firstIndex, int count, JDWP::ExpandBuf* pReply) {
+  UNIMPLEMENTED(FATAL);
+  return false;
+}
+
+bool Dbg::SetArrayElements(JDWP::ObjectId arrayId, int firstIndex, int count, const uint8_t* buf) {
+  UNIMPLEMENTED(FATAL);
+  return false;
+}
+
+JDWP::ObjectId Dbg::CreateString(const char* str) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+JDWP::ObjectId Dbg::CreateObject(JDWP::RefTypeId classId) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+JDWP::ObjectId Dbg::CreateArrayObject(JDWP::RefTypeId arrayTypeId, uint32_t length) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+bool Dbg::MatchType(JDWP::RefTypeId instClassId, JDWP::RefTypeId classId) {
+  UNIMPLEMENTED(FATAL);
+  return false;
+}
+
+const char* Dbg::GetMethodName(JDWP::RefTypeId refTypeId, JDWP::MethodId id) {
+  UNIMPLEMENTED(FATAL);
+  return NULL;
+}
+
+void Dbg::OutputAllFields(JDWP::RefTypeId refTypeId, bool withGeneric, JDWP::ExpandBuf* pReply) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::OutputAllMethods(JDWP::RefTypeId refTypeId, bool withGeneric, JDWP::ExpandBuf* pReply) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::OutputAllInterfaces(JDWP::RefTypeId refTypeId, JDWP::ExpandBuf* pReply) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::OutputLineTable(JDWP::RefTypeId refTypeId, JDWP::MethodId methodId, JDWP::ExpandBuf* pReply) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::OutputVariableTable(JDWP::RefTypeId refTypeId, JDWP::MethodId id, bool withGeneric, JDWP::ExpandBuf* pReply) {
+  UNIMPLEMENTED(FATAL);
+}
+
+uint8_t Dbg::GetFieldBasicTag(JDWP::ObjectId objId, JDWP::FieldId fieldId) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+uint8_t Dbg::GetStaticFieldBasicTag(JDWP::RefTypeId refTypeId, JDWP::FieldId fieldId) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+void Dbg::GetFieldValue(JDWP::ObjectId objectId, JDWP::FieldId fieldId, JDWP::ExpandBuf* pReply) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::SetFieldValue(JDWP::ObjectId objectId, JDWP::FieldId fieldId, uint64_t value, int width) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::GetStaticFieldValue(JDWP::RefTypeId refTypeId, JDWP::FieldId fieldId, JDWP::ExpandBuf* pReply) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::SetStaticFieldValue(JDWP::RefTypeId refTypeId, JDWP::FieldId fieldId, uint64_t rawValue, int width) {
+  UNIMPLEMENTED(FATAL);
+}
+
+char* Dbg::StringToUtf8(JDWP::ObjectId strId) {
+  UNIMPLEMENTED(FATAL);
+  return NULL;
+}
+
+char* Dbg::GetThreadName(JDWP::ObjectId threadId) {
+  UNIMPLEMENTED(FATAL);
+  return NULL;
+}
+
+JDWP::ObjectId Dbg::GetThreadGroup(JDWP::ObjectId threadId) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+char* Dbg::GetThreadGroupName(JDWP::ObjectId threadGroupId) {
+  UNIMPLEMENTED(FATAL);
+  return NULL;
+}
+
+JDWP::ObjectId Dbg::GetThreadGroupParent(JDWP::ObjectId threadGroupId) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+JDWP::ObjectId Dbg::GetSystemThreadGroupId() {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+JDWP::ObjectId Dbg::GetMainThreadGroupId() {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+bool Dbg::GetThreadStatus(JDWP::ObjectId threadId, uint32_t* threadStatus, uint32_t* suspendStatus) {
+  UNIMPLEMENTED(FATAL);
+  return false;
+}
+
+uint32_t Dbg::GetThreadSuspendCount(JDWP::ObjectId threadId) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+bool Dbg::ThreadExists(JDWP::ObjectId threadId) {
+  UNIMPLEMENTED(FATAL);
+  return false;
+}
+
+bool Dbg::IsSuspended(JDWP::ObjectId threadId) {
+  UNIMPLEMENTED(FATAL);
+  return false;
+}
+
+//void Dbg::WaitForSuspend(JDWP::ObjectId threadId);
+
+void Dbg::GetThreadGroupThreads(JDWP::ObjectId threadGroupId, JDWP::ObjectId** ppThreadIds, uint32_t* pThreadCount) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::GetAllThreads(JDWP::ObjectId** ppThreadIds, uint32_t* pThreadCount) {
+  UNIMPLEMENTED(FATAL);
+}
+
+int Dbg::GetThreadFrameCount(JDWP::ObjectId threadId) {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+bool Dbg::GetThreadFrame(JDWP::ObjectId threadId, int num, JDWP::FrameId* pFrameId, JDWP::JdwpLocation* pLoc) {
+  UNIMPLEMENTED(FATAL);
+  return false;
+}
+
+JDWP::ObjectId Dbg::GetThreadSelfId() {
+  UNIMPLEMENTED(FATAL);
+  return 0;
+}
+
+void Dbg::SuspendVM(bool isEvent) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::ResumeVM() {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::SuspendThread(JDWP::ObjectId threadId) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::ResumeThread(JDWP::ObjectId threadId) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::SuspendSelf() {
+  UNIMPLEMENTED(FATAL);
+}
+
+bool Dbg::GetThisObject(JDWP::ObjectId threadId, JDWP::FrameId frameId, JDWP::ObjectId* pThisId) {
+  UNIMPLEMENTED(FATAL);
+  return false;
+}
+
+void Dbg::GetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, uint8_t tag, uint8_t* buf, int expectedLen) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::SetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, uint8_t tag, uint64_t value, int width) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::PostLocationEvent(const Method* method, int pcOffset, Object* thisPtr, int eventFlags) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::PostException(void* throwFp, int throwRelPc, void* catchFp, int catchRelPc, Object* exception) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::PostThreadStart(Thread* t) {
+  UNIMPLEMENTED(WARNING);
+}
+
+void Dbg::PostThreadDeath(Thread* t) {
+  UNIMPLEMENTED(WARNING);
+}
+
+void Dbg::PostClassPrepare(Class* c) {
+  UNIMPLEMENTED(FATAL);
+}
+
+bool Dbg::WatchLocation(const JDWP::JdwpLocation* pLoc) {
+  UNIMPLEMENTED(FATAL);
+  return false;
+}
+
+void Dbg::UnwatchLocation(const JDWP::JdwpLocation* pLoc) {
+  UNIMPLEMENTED(FATAL);
+}
+
+bool Dbg::ConfigureStep(JDWP::ObjectId threadId, JDWP::JdwpStepSize size, JDWP::JdwpStepDepth depth) {
+  UNIMPLEMENTED(FATAL);
+  return false;
+}
+
+void Dbg::UnconfigureStep(JDWP::ObjectId threadId) {
+  UNIMPLEMENTED(FATAL);
+}
+
+JDWP::JdwpError Dbg::InvokeMethod(JDWP::ObjectId threadId, JDWP::ObjectId objectId, JDWP::RefTypeId classId, JDWP::MethodId methodId, uint32_t numArgs, uint64_t* argArray, uint32_t options, uint8_t* pResultTag, uint64_t* pResultValue, JDWP::ObjectId* pExceptObj) {
+  UNIMPLEMENTED(FATAL);
+  return JDWP::ERR_NONE;
+}
+
+void Dbg::ExecuteMethod(DebugInvokeReq* pReq) {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::RegisterObjectId(JDWP::ObjectId id) {
+  UNIMPLEMENTED(FATAL);
+}
+
+bool Dbg::DdmHandlePacket(const uint8_t* buf, int dataLen, uint8_t** pReplyBuf, int* pReplyLen) {
+  UNIMPLEMENTED(FATAL);
+  return false;
+}
+
+void Dbg::DdmConnected() {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::DdmDisconnected() {
+  UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::DdmSendChunk(int type, size_t length, const uint8_t* buf) {
+  UNIMPLEMENTED(WARNING) << "DdmSendChunk(" << type << ", " << length << ", " << (void*) buf << ");";
+}
+
+void Dbg::DdmSendChunkV(int type, const struct iovec* iov, int iovcnt) {
+  UNIMPLEMENTED(FATAL);
+}
+
+}  // namespace art
diff --git a/src/debugger.h b/src/debugger.h
new file mode 100644
index 0000000..b66fd11
--- /dev/null
+++ b/src/debugger.h
@@ -0,0 +1,231 @@
+/*
+ * 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.
+ */
+
+/*
+ * Dalvik-specific side of debugger support.  (The JDWP code is intended to
+ * be relatively generic.)
+ */
+#ifndef ART_DEBUGGER_H_
+#define ART_DEBUGGER_H_
+
+#include <pthread.h>
+
+#include "object.h"
+#include "jdwp/jdwp.h"
+
+namespace art {
+
+struct Thread;
+
+/*
+ * Invoke-during-breakpoint support.
+ */
+struct DebugInvokeReq {
+  /* boolean; only set when we're in the tail end of an event handler */
+  bool ready;
+
+  /* boolean; set if the JDWP thread wants this thread to do work */
+  bool invokeNeeded;
+
+  /* request */
+  Object* obj;        /* not used for ClassType.InvokeMethod */
+  Object* thread;
+  Class* class_;
+  Method* method;
+  uint32_t numArgs;
+  uint64_t* argArray;   /* will be NULL if numArgs==0 */
+  uint32_t options;
+
+  /* result */
+  JDWP::JdwpError err;
+  uint8_t resultTag;
+  JValue resultValue;
+  JDWP::ObjectId exceptObj;
+
+  /* condition variable to wait on while the method executes */
+  Mutex lock_;
+  ConditionVariable cond_;
+};
+
+class Dbg {
+public:
+  static bool DebuggerStartup();
+  static void DebuggerShutdown();
+
+  // Return the DebugInvokeReq for the current thread.
+  static DebugInvokeReq* GetInvokeReq();
+
+  /*
+   * Enable/disable breakpoints and step modes.  Used to provide a heads-up
+   * when the debugger attaches.
+   */
+  static void Connected();
+  static void Active();
+  static void Disconnected();
+
+  /*
+   * Returns "true" if a debugger is connected.  Returns "false" if it's
+   * just DDM.
+   */
+  static bool IsDebuggerConnected();
+
+  static bool IsDebuggingEnabled();
+
+  /*
+   * Time, in milliseconds, since the last debugger activity.  Does not
+   * include DDMS activity.  Returns -1 if there has been no activity.
+   * Returns 0 if we're in the middle of handling a debugger request.
+   */
+  static int64_t LastDebuggerActivity();
+
+  /*
+   * Block/allow GC depending on what we're doing.  These return the old
+   * status, which can be fed to ThreadContinuing() to restore the previous
+   * mode.
+   */
+  static int ThreadRunning();
+  static int ThreadWaiting();
+  static int ThreadContinuing(int status);
+
+  static void UndoDebuggerSuspensions();
+
+  // The debugger wants the VM to exit.
+  static void Exit(int status);
+
+  /*
+   * Class, Object, Array
+   */
+  static const char* GetClassDescriptor(JDWP::RefTypeId id);
+  static JDWP::ObjectId GetClassObject(JDWP::RefTypeId id);
+  static JDWP::RefTypeId GetSuperclass(JDWP::RefTypeId id);
+  static JDWP::ObjectId GetClassLoader(JDWP::RefTypeId id);
+  static uint32_t GetAccessFlags(JDWP::RefTypeId id);
+  static bool IsInterface(JDWP::RefTypeId id);
+  static void GetClassList(uint32_t* pNumClasses, JDWP::RefTypeId** pClassRefBuf);
+  static void GetVisibleClassList(JDWP::ObjectId classLoaderId, uint32_t* pNumClasses, JDWP::RefTypeId** pClassRefBuf);
+  static void GetClassInfo(JDWP::RefTypeId classId, uint8_t* pTypeTag, uint32_t* pStatus, const char** pSignature);
+  static bool FindLoadedClassBySignature(const char* classDescriptor, JDWP::RefTypeId* pRefTypeId);
+  static void GetObjectType(JDWP::ObjectId objectId, uint8_t* pRefTypeTag, JDWP::RefTypeId* pRefTypeId);
+  static uint8_t GetClassObjectType(JDWP::RefTypeId refTypeId);
+  static const char* GetSignature(JDWP::RefTypeId refTypeId);
+  static const char* GetSourceFile(JDWP::RefTypeId refTypeId);
+  static const char* GetObjectTypeName(JDWP::ObjectId objectId);
+  static uint8_t GetObjectTag(JDWP::ObjectId objectId);
+  static int GetTagWidth(int tag);
+
+  static int GetArrayLength(JDWP::ObjectId arrayId);
+  static uint8_t GetArrayElementTag(JDWP::ObjectId arrayId);
+  static bool OutputArray(JDWP::ObjectId arrayId, int firstIndex, int count, JDWP::ExpandBuf* pReply);
+  static bool SetArrayElements(JDWP::ObjectId arrayId, int firstIndex, int count, const uint8_t* buf);
+
+  static JDWP::ObjectId CreateString(const char* str);
+  static JDWP::ObjectId CreateObject(JDWP::RefTypeId classId);
+  static JDWP::ObjectId CreateArrayObject(JDWP::RefTypeId arrayTypeId, uint32_t length);
+
+  static bool MatchType(JDWP::RefTypeId instClassId, JDWP::RefTypeId classId);
+
+  /*
+   * Method and Field
+   */
+  static const char* GetMethodName(JDWP::RefTypeId refTypeId, JDWP::MethodId id);
+  static void OutputAllFields(JDWP::RefTypeId refTypeId, bool withGeneric, JDWP::ExpandBuf* pReply);
+  static void OutputAllMethods(JDWP::RefTypeId refTypeId, bool withGeneric, JDWP::ExpandBuf* pReply);
+  static void OutputAllInterfaces(JDWP::RefTypeId refTypeId, JDWP::ExpandBuf* pReply);
+  static void OutputLineTable(JDWP::RefTypeId refTypeId, JDWP::MethodId methodId, JDWP::ExpandBuf* pReply);
+  static void OutputVariableTable(JDWP::RefTypeId refTypeId, JDWP::MethodId id, bool withGeneric, JDWP::ExpandBuf* pReply);
+
+  static uint8_t GetFieldBasicTag(JDWP::ObjectId objId, JDWP::FieldId fieldId);
+  static uint8_t GetStaticFieldBasicTag(JDWP::RefTypeId refTypeId, JDWP::FieldId fieldId);
+  static void GetFieldValue(JDWP::ObjectId objectId, JDWP::FieldId fieldId, JDWP::ExpandBuf* pReply);
+  static void SetFieldValue(JDWP::ObjectId objectId, JDWP::FieldId fieldId, uint64_t value, int width);
+  static void GetStaticFieldValue(JDWP::RefTypeId refTypeId, JDWP::FieldId fieldId, JDWP::ExpandBuf* pReply);
+  static void SetStaticFieldValue(JDWP::RefTypeId refTypeId, JDWP::FieldId fieldId, uint64_t rawValue, int width);
+
+  static char* StringToUtf8(JDWP::ObjectId strId);
+
+  /*
+   * Thread, ThreadGroup, Frame
+   */
+  static char* GetThreadName(JDWP::ObjectId threadId);
+  static JDWP::ObjectId GetThreadGroup(JDWP::ObjectId threadId);
+  static char* GetThreadGroupName(JDWP::ObjectId threadGroupId);
+  static JDWP::ObjectId GetThreadGroupParent(JDWP::ObjectId threadGroupId);
+  static JDWP::ObjectId GetSystemThreadGroupId();
+  static JDWP::ObjectId GetMainThreadGroupId();
+
+  static bool GetThreadStatus(JDWP::ObjectId threadId, uint32_t* threadStatus, uint32_t* suspendStatus);
+  static uint32_t GetThreadSuspendCount(JDWP::ObjectId threadId);
+  static bool ThreadExists(JDWP::ObjectId threadId);
+  static bool IsSuspended(JDWP::ObjectId threadId);
+  //static void WaitForSuspend(JDWP::ObjectId threadId);
+  static void GetThreadGroupThreads(JDWP::ObjectId threadGroupId, JDWP::ObjectId** ppThreadIds, uint32_t* pThreadCount);
+  static void GetAllThreads(JDWP::ObjectId** ppThreadIds, uint32_t* pThreadCount);
+  static int GetThreadFrameCount(JDWP::ObjectId threadId);
+  static bool GetThreadFrame(JDWP::ObjectId threadId, int num, JDWP::FrameId* pFrameId, JDWP::JdwpLocation* pLoc);
+
+  static JDWP::ObjectId GetThreadSelfId();
+  static void SuspendVM(bool isEvent);
+  static void ResumeVM();
+  static void SuspendThread(JDWP::ObjectId threadId);
+  static void ResumeThread(JDWP::ObjectId threadId);
+  static void SuspendSelf();
+
+  static bool GetThisObject(JDWP::ObjectId threadId, JDWP::FrameId frameId, JDWP::ObjectId* pThisId);
+  static void GetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, uint8_t tag, uint8_t* buf, int expectedLen);
+  static void SetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, uint8_t tag, uint64_t value, int width);
+
+  /*
+   * Debugger notification
+   */
+  enum {
+    kBreakPoint     = 0x01,
+    kSingleStep     = 0x02,
+    kMethodEntry    = 0x04,
+    kMethodExit     = 0x08,
+  };
+  static void PostLocationEvent(const Method* method, int pcOffset, Object* thisPtr, int eventFlags);
+  static void PostException(void* throwFp, int throwRelPc, void* catchFp, int catchRelPc, Object* exception);
+  static void PostThreadStart(Thread* t);
+  static void PostThreadDeath(Thread* t);
+  static void PostClassPrepare(Class* c);
+
+  static bool WatchLocation(const JDWP::JdwpLocation* pLoc);
+  static void UnwatchLocation(const JDWP::JdwpLocation* pLoc);
+  static bool ConfigureStep(JDWP::ObjectId threadId, JDWP::JdwpStepSize size, JDWP::JdwpStepDepth depth);
+  static void UnconfigureStep(JDWP::ObjectId threadId);
+
+  static JDWP::JdwpError InvokeMethod(JDWP::ObjectId threadId, JDWP::ObjectId objectId, JDWP::RefTypeId classId, JDWP::MethodId methodId, uint32_t numArgs, uint64_t* argArray, uint32_t options, uint8_t* pResultTag, uint64_t* pResultValue, JDWP::ObjectId* pExceptObj);
+  static void ExecuteMethod(DebugInvokeReq* pReq);
+
+  /* perform "late registration" of an object ID */
+  static void RegisterObjectId(JDWP::ObjectId id);
+
+  /*
+   * DDM support.
+   */
+  static bool DdmHandlePacket(const uint8_t* buf, int dataLen, uint8_t** pReplyBuf, int* pReplyLen);
+  static void DdmConnected();
+  static void DdmDisconnected();
+  static void DdmSendChunk(int type, size_t len, const uint8_t* buf);
+  static void DdmSendChunkV(int type, const struct iovec* iov, int iovcnt);
+};
+
+#define CHUNK_TYPE(_name) \
+    ((_name)[0] << 24 | (_name)[1] << 16 | (_name)[2] << 8 | (_name)[3])
+
+}  // namespace art
+
+#endif  // ART_DEBUGGER_H_
diff --git a/src/jdwp/README.txt b/src/jdwp/README.txt
new file mode 100644
index 0000000..da25fb1
--- /dev/null
+++ b/src/jdwp/README.txt
@@ -0,0 +1,11 @@
+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 JDWP implementation distinct from the
+runtime, 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".
diff --git a/src/jdwp/jdwp.h b/src/jdwp/jdwp.h
new file mode 100644
index 0000000..1c205bc
--- /dev/null
+++ b/src/jdwp/jdwp.h
@@ -0,0 +1,221 @@
+/*
+ * 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.
+ */
+
+#ifndef ART_JDWP_JDWP_H_
+#define ART_JDWP_JDWP_H_
+
+#include "jdwp/jdwp_bits.h"
+#include "jdwp/jdwp_constants.h"
+#include "jdwp/jdwp_expand_buf.h"
+
+#include <pthread.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+struct iovec;
+
+namespace art {
+
+namespace JDWP {
+
+struct JdwpState;       /* opaque */
+
+/*
+ * Fundamental types.
+ *
+ * ObjectId and RefTypeId must be the same size.
+ */
+typedef uint32_t FieldId;     /* static or instance field */
+typedef uint32_t MethodId;    /* any kind of method, including constructors */
+typedef uint64_t ObjectId;    /* any object (threadID, stringID, arrayID, etc) */
+typedef uint64_t RefTypeId;   /* like ObjectID, but unique for Class objects */
+typedef uint64_t 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.
+ */
+static inline FieldId ReadFieldId(const uint8_t** pBuf) { return read4BE(pBuf); }
+static inline MethodId ReadMethodId(const uint8_t** pBuf) { return read4BE(pBuf); }
+static inline ObjectId ReadObjectId(const uint8_t** pBuf) { return read8BE(pBuf); }
+static inline RefTypeId ReadRefTypeId(const uint8_t** pBuf) { return read8BE(pBuf); }
+static inline FrameId ReadFrameId(const uint8_t** pBuf) { return read8BE(pBuf); }
+static inline void SetFieldId(uint8_t* buf, FieldId val) { return set4BE(buf, val); }
+static inline void SetMethodId(uint8_t* buf, MethodId val) { return set4BE(buf, val); }
+static inline void SetObjectId(uint8_t* buf, ObjectId val) { return set8BE(buf, val); }
+static inline void SetRefTypeId(uint8_t* buf, RefTypeId val) { return set8BE(buf, val); }
+static inline void SetFrameId(uint8_t* buf, FrameId val) { return set8BE(buf, val); }
+static inline void expandBufAddFieldId(ExpandBuf* pReply, FieldId id) { expandBufAdd4BE(pReply, id); }
+static inline void expandBufAddMethodId(ExpandBuf* pReply, MethodId id) { expandBufAdd4BE(pReply, id); }
+static inline void expandBufAddObjectId(ExpandBuf* pReply, ObjectId id) { expandBufAdd8BE(pReply, id); }
+static inline void expandBufAddRefTypeId(ExpandBuf* pReply, RefTypeId id) { expandBufAdd8BE(pReply, id); }
+static inline void expandBufAddFrameId(ExpandBuf* pReply, FrameId id) { expandBufAdd8BE(pReply, id); }
+
+
+/*
+ * Holds a JDWP "location".
+ */
+struct JdwpLocation {
+  uint8_t typeTag;        /* class or interface? */
+  RefTypeId classId;        /* method->clazz */
+  MethodId methodId;       /* method in which "idx" resides */
+  uint64_t idx;            /* relative index into code block */
+};
+
+/*
+ * How we talk to the debugger.
+ */
+enum JdwpTransportType {
+  kJdwpTransportUnknown = 0,
+  kJdwpTransportSocket,       /* transport=dt_socket */
+  kJdwpTransportAndroidAdb,   /* transport=dt_android_adb */
+};
+std::ostream& operator<<(std::ostream& os, const JdwpTransportType& rhs);
+
+/*
+ * Holds collection of JDWP initialization parameters.
+ */
+struct JdwpStartupParams {
+  JdwpTransportType transport;
+  bool server;
+  bool suspend;
+  char host[64];
+  short port;
+  /* more will be here someday */
+};
+
+/*
+ * 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* JdwpStartup(const JdwpStartupParams* params);
+
+/*
+ * Shut everything down.
+ */
+void JdwpShutdown(JdwpState* state);
+
+/*
+ * Returns "true" if a debugger or DDM is connected.
+ */
+bool JdwpIsActive(JdwpState* state);
+
+/*
+ * Return the debugger thread's handle, or 0 if the debugger thread isn't
+ * running.
+ */
+pthread_t GetDebugThread(JdwpState* state);
+
+/*
+ * Get time, in milliseconds, since the last debugger activity.
+ */
+int64_t LastDebuggerActivity(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 GetWaitForEventThread(JdwpState* state);
+void SetWaitForEventThread(JdwpState* state, ObjectId threadId);
+void ClearWaitForEventThread(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 PostVMStart(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 PostLocationEvent(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 PostException(JdwpState* state, const JdwpLocation* pThrowLoc,
+    ObjectId excepId, RefTypeId excepClassId, const JdwpLocation* pCatchLoc,
+    ObjectId thisPtr);
+
+/*
+ * A thread has started or stopped.
+ */
+bool PostThreadChange(JdwpState* state, ObjectId threadId, bool start);
+
+/*
+ * Class has been prepared.
+ */
+bool PostClassPrepare(JdwpState* state, int tag, RefTypeId refTypeId,
+    const char* signature, int status);
+
+/*
+ * The VM is about to stop.
+ */
+bool PostVMDeath(JdwpState* state);
+
+/*
+ * Send up a chunk of DDM data.
+ */
+void DdmSendChunkV(JdwpState* state, int type, const iovec* iov, int iovcnt);
+
+}  // namespace JDWP
+
+}  // namespace art
+
+#endif  // ART_JDWP_JDWP_H_
diff --git a/src/jdwp/jdwp_adb.cc b/src/jdwp/jdwp_adb.cc
new file mode 100644
index 0000000..3a89f34
--- /dev/null
+++ b/src/jdwp/jdwp_adb.cc
@@ -0,0 +1,723 @@
+/*
+ * 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 "logging.h"
+#include "jdwp/jdwp_priv.h"
+#include "jdwp/jdwp_handler.h"
+#include "stringprintf.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#ifdef HAVE_ANDROID_OS
+#include "cutils/sockets.h"
+#endif
+
+/*
+ * The JDWP <-> ADB transport protocol is explained in detail
+ * in system/core/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)
+
+namespace art {
+
+namespace JDWP {
+
+struct JdwpNetState : public JdwpNetStateBase {
+  int                 controlSock;
+  bool                awaitingHandshake;
+  bool                shuttingDown;
+  int                 wakeFds[2];
+
+  int                 inputCount;
+  unsigned char       inputBuffer[kInputBufferSize];
+
+  socklen_t           controlAddrLen;
+  union {
+    struct sockaddr_un  controlAddrUn;
+    struct sockaddr     controlAddrPlain;
+  } controlAddr;
+
+  JdwpNetState() {
+    controlSock = -1;
+    awaitingHandshake = false;
+    shuttingDown = false;
+    wakeFds[0] = -1;
+    wakeFds[1] = -1;
+
+    inputCount = 0;
+
+    controlAddr.controlAddrUn.sun_family = AF_UNIX;
+    controlAddrLen = sizeof(controlAddr.controlAddrUn.sun_family) + kJdwpControlNameLen;
+    memcpy(controlAddr.controlAddrUn.sun_path, kJdwpControlName, kJdwpControlNameLen);
+  }
+};
+
+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;
+  }
+
+  delete 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(JdwpState* state, const JdwpStartupParams* pParams) {
+  JdwpNetState*  netState;
+
+  LOG(VERBOSE) << "ADB transport startup";
+
+  state->netState = netState = new JdwpNetState;
+  if (netState == NULL) {
+    return false;
+  }
+  return true;
+}
+
+/*
+ * Receive a file descriptor from ADB.  The fd can be used to communicate
+ * directly with a debugger or DDMS.
+ *
+ * Returns the file descriptor on success.  On failure, returns -1 and
+ * closes netState->controlSock.
+ */
+static int  receiveClientFd(JdwpNetState*  netState) {
+  struct msghdr    msg;
+  struct cmsghdr*  cmsg;
+  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*)(void*)CMSG_DATA(cmsg))[0] = -1;
+
+  do {
+    ret = recvmsg(netState->controlSock, &msg, 0);
+  } while (ret < 0 && errno == EINTR);
+
+  if (ret <= 0) {
+    if (ret < 0) {
+      PLOG(WARNING) << "receiving file descriptor from ADB failed (socket " << netState->controlSock << ")";
+    }
+    close(netState->controlSock);
+    netState->controlSock = -1;
+    return -1;
+  }
+
+  return ((int*)(void*)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(JdwpState* state) {
+  JdwpNetState*  netState = state->netState;
+  int retryCount = 0;
+
+  /* first, ensure that we get a connection to the ADB daemon */
+
+retry:
+  if (netState->shuttingDown) {
+    return false;
+  }
+
+  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) {
+      PLOG(ERROR) << "Could not create ADB control socket";
+      return false;
+    }
+
+    if (pipe(netState->wakeFds) < 0) {
+      PLOG(ERROR) << "pipe failed";
+      return false;
+    }
+
+    snprintf(buff, sizeof(buff), "%04x", getpid());
+    buff[4] = 0;
+
+    for (;;) {
+      /*
+       * If adbd isn't running, because USB debugging was disabled or
+       * perhaps the system is restarting it for "adb root", the
+       * connect() will fail.  We loop here forever waiting for it
+       * to come back.
+       *
+       * Waking up and polling every couple of seconds is generally a
+       * bad thing to do, but we only do this if the application is
+       * debuggable *and* adbd isn't running.  Still, for the sake
+       * of battery life, we should consider timing out and giving
+       * up after a few minutes in case somebody ships an app with
+       * the debuggable flag set.
+       */
+      int  ret = connect(netState->controlSock, &netState->controlAddr.controlAddrPlain, netState->controlAddrLen);
+      if (!ret) {
+#ifdef HAVE_ANDROID_OS
+        if (!socket_peer_is_trusted(netState->controlSock)) {
+          if (shutdown(netState->controlSock, SHUT_RDWR)) {
+            PLOG(ERROR) << "trouble shutting down socket";
+          }
+          return false;
+        }
+#endif
+
+        /* 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) {
+          LOG(VERBOSE) << StringPrintf("PID sent as '%.*s' to ADB", 4, buff);
+          break;
+        }
+
+        PLOG(ERROR) << "Weird, can't send JDWP process pid to ADB";
+        return false;
+      }
+      PLOG(VERBOSE) << "Can't connect to ADB control socket";
+
+      usleep( sleep_ms*1000 );
+
+      sleep_ms += (sleep_ms >> 1);
+      if (sleep_ms > sleep_max_ms) {
+        sleep_ms = sleep_max_ms;
+      }
+      if (netState->shuttingDown) {
+        return false;
+      }
+    }
+  }
+
+  LOG(VERBOSE) << "trying to receive file descriptor from ADB";
+  /* now we can receive a client file descriptor */
+  netState->clientSock = receiveClientFd(netState);
+  if (netState->shuttingDown) {
+    return false;       // suppress logs and additional activity
+  }
+  if (netState->clientSock < 0) {
+    if (++retryCount > 5) {
+      LOG(ERROR) << "adb connection max retries exceeded";
+      return false;
+    }
+    goto retry;
+  } else {
+    LOG(VERBOSE) << "received file descriptor " << netState->clientSock << " from ADB";
+    netState->awaitingHandshake = 1;
+    netState->inputCount = 0;
+    return true;
+  }
+}
+
+/*
+ * Connect out to a debugger (for server=n).  Not required.
+ */
+static bool establishConnection(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(JdwpState* state) {
+  CHECK(state != NULL && state->netState != NULL);
+
+  JdwpNetState* netState = state->netState;
+  if (netState->clientSock < 0) {
+    return;
+  }
+
+  LOG(VERBOSE) << "+++ closed JDWP <-> ADB connection";
+
+  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(JdwpNetState* netState) {
+  int  controlSock;
+  int  clientSock;
+
+  if (netState == NULL) {
+    return;
+  }
+
+  netState->shuttingDown = true;
+
+  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) {
+    LOG(VERBOSE) << "+++ writing to wakePipe";
+    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(JdwpState* state) {
+  JdwpNetState*  netState = state->netState;
+  adbStateFree(netState);
+}
+
+/*
+ * Is a debugger connected to us?
+ */
+static bool isConnected(JdwpState* state) {
+  return (state->netState != NULL && state->netState->clientSock >= 0);
+}
+
+/*
+ * Are we still waiting for the JDWP handshake?
+ */
+static bool awaitingHandshake(JdwpState* state) {
+  return state->netState->awaitingHandshake;
+}
+
+/*
+ * Figure out if we have a full packet in the buffer.
+ */
+static bool haveFullPacket(JdwpNetState* netState) {
+  if (netState->awaitingHandshake) {
+    return (netState->inputCount >= (int) kMagicHandshakeLen);
+  }
+  if (netState->inputCount < 4) {
+    return false;
+  }
+  long 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) {
+  CHECK_GT(count, 0);
+  CHECK_LE(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;
+  uint32_t length, id;
+  uint8_t flags, cmdSet, cmd;
+  uint16_t error;
+  bool reply;
+  int dataLen;
+
+  cmd = cmdSet = 0;       // shut up gcc
+
+  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);
+  }
+
+  CHECK_LE((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;
+    ProcessRequest(state, &hdr, buf, dataLen, pReply);
+    if (expandBufGetLength(pReply) > 0) {
+      ssize_t cc = netState->writePacket(pReply);
+
+      if (cc != (ssize_t) expandBufGetLength(pReply)) {
+        PLOG(ERROR) << "Failed sending reply to debugger";
+        expandBufFree(pReply);
+        return false;
+      }
+    } else {
+      LOG(WARNING) << "No reply created for set=" << cmdSet << " cmd=" << cmd;
+    }
+    expandBufFree(pReply);
+  } else {
+    LOG(FATAL) << "reply?!";
+  }
+
+  LOG(VERBOSE) << "----------";
+
+  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;
+
+  CHECK_GE(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 {
+        LOG(INFO) << "NOTE: entering select w/o wakepipe";
+      }
+
+      if (maxfd < 0) {
+        LOG(VERBOSE) << "+++ all fds are closed";
+        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;
+        }
+        PLOG(ERROR) << "select failed";
+        goto fail;
+      }
+
+      if (netState->wakeFds[0] >= 0 && FD_ISSET(netState->wakeFds[0], &readfds)) {
+        LOG(DEBUG) << "Got wake-up signal, bailing out of select";
+        goto fail;
+      }
+      if (netState->controlSock >= 0 && FD_ISSET(netState->controlSock, &readfds)) {
+        int  sock = receiveClientFd(netState);
+        if (sock >= 0) {
+          LOG(INFO) << "Ignoring second debugger -- accepting and dropping";
+          close(sock);
+        } else {
+          CHECK_LT(netState->controlSock, 0);
+          /*
+           * Remote side most likely went away, so our next read
+           * on netState->clientSock will fail and throw us out
+           * of the loop.
+           */
+        }
+      }
+      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;
+          }
+          LOG(DEBUG) << "+++ EINTR hit";
+          return true;
+        } else if (readCount == 0) {
+          /* EOF hit -- far end went away */
+          LOG(VERBOSE) << "+++ peer disconnected";
+          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) {
+      LOG(ERROR) << StringPrintf("ERROR: bad handshake '%.14s'", netState->inputBuffer);
+      goto fail;
+    }
+
+    errno = 0;
+    cc = write(netState->clientSock, netState->inputBuffer, kMagicHandshakeLen);
+    if (cc != kMagicHandshakeLen) {
+      PLOG(ERROR) << "Failed writing handshake bytes (" << cc << " of " << kMagicHandshakeLen << ")";
+      goto fail;
+    }
+
+    consumeBytes(netState, kMagicHandshakeLen);
+    netState->awaitingHandshake = false;
+    LOG(VERBOSE) << "+++ handshake complete";
+    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;
+
+  if (netState->clientSock < 0) {
+    /* can happen with some DDMS events */
+    LOG(VERBOSE) << "NOT sending request -- no debugger is attached";
+    return false;
+  }
+
+  errno = 0;
+
+  ssize_t cc = netState->writePacket(pReq);
+
+  if (cc != (ssize_t) expandBufGetLength(pReq)) {
+    PLOG(ERROR) << "Failed sending req to debugger (" << cc << " of " << expandBufGetLength(pReq) << ")";
+    return false;
+  }
+
+  return true;
+}
+
+/*
+ * Send a request that was split into multiple buffers.
+ *
+ * The entire packet must be sent with a single writev() call to avoid
+ * threading issues.
+ *
+ * Returns "true" if it was sent successfully.
+ */
+static bool sendBufferedRequest(JdwpState* state, const iovec* iov, int iovcnt) {
+  JdwpNetState* netState = state->netState;
+
+  if (netState->clientSock < 0) {
+    /* can happen with some DDMS events */
+    LOG(VERBOSE) << "NOT sending request -- no debugger is attached";
+    return false;
+  }
+
+  size_t expected = 0;
+  int i;
+  for (i = 0; i < iovcnt; i++) {
+    expected += iov[i].iov_len;
+  }
+
+  ssize_t actual = netState->writeBufferedPacket(iov, iovcnt);
+  if ((size_t)actual != expected) {
+    PLOG(ERROR) << "Failed sending b-req to debugger (" << actual << " of " << expected << ")";
+    return false;
+  }
+
+  return true;
+}
+
+/*
+ * Our functions.
+ */
+static const JdwpTransport adbTransport = {
+  startup,
+  acceptConnection,
+  establishConnection,
+  closeConnection,
+  netShutdown,
+  netFree,
+  isConnected,
+  awaitingHandshake,
+  processIncoming,
+  sendRequest,
+  sendBufferedRequest
+};
+
+/*
+ * Return our set.
+ */
+const JdwpTransport* AndroidAdbTransport() {
+  return &adbTransport;
+}
+
+}  // namespace JDWP
+
+}  // namespace art
diff --git a/src/jdwp/jdwp_bits.h b/src/jdwp/jdwp_bits.h
new file mode 100644
index 0000000..b0041f1
--- /dev/null
+++ b/src/jdwp/jdwp_bits.h
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+
+#ifndef ART_JDWP_BITS_H_
+#define ART_JDWP_BITS_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+namespace art {
+
+namespace JDWP {
+
+static inline uint32_t get4BE(unsigned char const* pSrc) {
+  return (pSrc[0] << 24) | (pSrc[1] << 16) | (pSrc[2] << 8) | pSrc[3];
+}
+
+static inline uint8_t read1(unsigned const char** ppSrc) {
+  return *(*ppSrc)++;
+}
+
+static inline uint16_t read2BE(unsigned char const** ppSrc) {
+  const unsigned char* pSrc = *ppSrc;
+  *ppSrc = pSrc + 2;
+  return pSrc[0] << 8 | pSrc[1];
+}
+
+static inline uint32_t read4BE(unsigned char const** ppSrc) {
+  const unsigned char* pSrc = *ppSrc;
+  uint32_t result = pSrc[0] << 24;
+  result |= pSrc[1] << 16;
+  result |= pSrc[2] << 8;
+  result |= pSrc[3];
+  *ppSrc = pSrc + 4;
+  return result;
+}
+
+static inline uint64_t read8BE(unsigned char const** ppSrc) {
+  const unsigned char* pSrc = *ppSrc;
+  uint32_t high = pSrc[0];
+  high = (high << 8) | pSrc[1];
+  high = (high << 8) | pSrc[2];
+  high = (high << 8) | pSrc[3];
+  uint32_t low = pSrc[4];
+  low = (low << 8) | pSrc[5];
+  low = (low << 8) | pSrc[6];
+  low = (low << 8) | pSrc[7];
+  *ppSrc = pSrc + 8;
+  return ((uint64_t) high << 32) | (uint64_t) low;
+}
+
+/*
+ * Read a UTF-8 string into newly-allocated storage, and null-terminate it.
+ *
+ * Returns the string and its length.  (The latter is probably unnecessary
+ * for the way we're using UTF8.)
+ */
+static inline char* readNewUtf8String(unsigned char const** ppSrc, size_t* pLength) {
+  uint32_t length = read4BE(ppSrc);
+  char* buf = (char*) malloc(length+1);
+  memcpy(buf, *ppSrc, length);
+  buf[length] = '\0';
+  (*ppSrc) += length;
+  *pLength = length;
+  return buf;
+}
+
+static inline void set1(uint8_t* buf, uint8_t val) {
+  *buf = (uint8_t)(val);
+}
+
+static inline void set2BE(uint8_t* buf, uint16_t val) {
+  *buf++ = (uint8_t)(val >> 8);
+  *buf = (uint8_t)(val);
+}
+
+static inline void set4BE(uint8_t* buf, uint32_t val) {
+  *buf++ = (uint8_t)(val >> 24);
+  *buf++ = (uint8_t)(val >> 16);
+  *buf++ = (uint8_t)(val >> 8);
+  *buf = (uint8_t)(val);
+}
+
+static inline void set8BE(uint8_t* buf, uint64_t val) {
+  *buf++ = (uint8_t)(val >> 56);
+  *buf++ = (uint8_t)(val >> 48);
+  *buf++ = (uint8_t)(val >> 40);
+  *buf++ = (uint8_t)(val >> 32);
+  *buf++ = (uint8_t)(val >> 24);
+  *buf++ = (uint8_t)(val >> 16);
+  *buf++ = (uint8_t)(val >> 8);
+  *buf = (uint8_t)(val);
+}
+
+/*
+ * Stuff a UTF-8 string into the buffer.
+ */
+static inline void setUtf8String(uint8_t* buf, const uint8_t* str) {
+  uint32_t strLen = strlen((const char*)str);
+  set4BE(buf, strLen);
+  memcpy(buf + sizeof(uint32_t), str, strLen);
+}
+
+}  // namespace JDWP
+
+}  // namespace art
+
+#endif  // ART_JDWP_BITS_H_
diff --git a/src/jdwp/jdwp_constants.cc b/src/jdwp/jdwp_constants.cc
new file mode 100644
index 0000000..7110eed
--- /dev/null
+++ b/src/jdwp/jdwp_constants.cc
@@ -0,0 +1,270 @@
+/*
+ * 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/jdwp_constants.h"
+
+#include <iostream>
+
+namespace art {
+
+namespace JDWP {
+
+const char* ErrorStr(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?";
+  }
+}
+std::ostream& operator<<(std::ostream& os, const JdwpError& value) {
+  os << ErrorStr(value);
+  return os;
+}
+
+const char* EventKindStr(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?";
+  }
+}
+std::ostream& operator<<(std::ostream& os, const JdwpEventKind& value) {
+  os << EventKindStr(value);
+  return os;
+}
+
+const char* ModKindStr(JdwpModKind kind) {
+  switch (kind) {
+  case MK_COUNT:              return "COUNT";
+  case MK_CONDITIONAL:        return "CONDITIONAL";
+  case MK_THREAD_ONLY:        return "THREAD_ONLY";
+  case MK_CLASS_ONLY:         return "CLASS_ONLY";
+  case MK_CLASS_MATCH:        return "CLASS_MATCH";
+  case MK_CLASS_EXCLUDE:      return "CLASS_EXCLUDE";
+  case MK_LOCATION_ONLY:      return "LOCATION_ONLY";
+  case MK_EXCEPTION_ONLY:     return "EXCEPTION_ONLY";
+  case MK_FIELD_ONLY:         return "FIELD_ONLY";
+  case MK_STEP:               return "STEP";
+  case MK_INSTANCE_ONLY:      return "INSTANCE_ONLY";
+  default:                    return "?UNKNOWN?";
+  }
+}
+std::ostream& operator<<(std::ostream& os, const JdwpModKind& value) {
+  os << ModKindStr(value);
+  return os;
+}
+
+const char* StepDepthStr(JdwpStepDepth depth) {
+  switch (depth) {
+  case SD_INTO:               return "INTO";
+  case SD_OVER:               return "OVER";
+  case SD_OUT:                return "OUT";
+  default:                    return "?UNKNOWN?";
+  }
+}
+std::ostream& operator<<(std::ostream& os, const JdwpStepDepth& value) {
+  os << StepDepthStr(value);
+  return os;
+}
+
+const char* StepSizeStr(JdwpStepSize size) {
+  switch (size) {
+  case SS_MIN:                return "MIN";
+  case SS_LINE:               return "LINE";
+  default:                    return "?UNKNOWN?";
+  }
+}
+std::ostream& operator<<(std::ostream& os, const JdwpStepSize& value) {
+  os << StepSizeStr(value);
+  return os;
+}
+
+const char* SuspendPolicyStr(JdwpSuspendPolicy policy) {
+  switch (policy) {
+  case SP_NONE:               return "NONE";
+  case SP_EVENT_THREAD:       return "EVENT_THREAD";
+  case SP_ALL:                return "ALL";
+  default:                    return "?UNKNOWN?";
+  }
+}
+std::ostream& operator<<(std::ostream& os, const JdwpSuspendPolicy& value) {
+  os << SuspendPolicyStr(value);
+  return os;
+}
+
+const char* SuspendStatusStr(JdwpSuspendStatus status) {
+  switch (status) {
+  case SUSPEND_STATUS_NOT_SUSPENDED: return "Not SUSPENDED";
+  case SUSPEND_STATUS_SUSPENDED:     return "SUSPENDED";
+  default:                           return "?UNKNOWN?";
+  }
+}
+std::ostream& operator<<(std::ostream& os, const JdwpSuspendStatus& value) {
+  os << SuspendStatusStr(value);
+  return os;
+}
+
+const char* ThreadStatusStr(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?";
+  }
+};
+std::ostream& operator<<(std::ostream& os, const JdwpThreadStatus& value) {
+  os << ThreadStatusStr(value);
+  return os;
+}
+
+}  // namespace JDWP
+
+}  // namespace art
diff --git a/src/jdwp/jdwp_constants.h b/src/jdwp/jdwp_constants.h
new file mode 100644
index 0000000..dc6d1bc
--- /dev/null
+++ b/src/jdwp/jdwp_constants.h
@@ -0,0 +1,244 @@
+/*
+ * 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 ART_JDWP_JDWPCONSTANTS_H_
+#define ART_JDWP_JDWPCONSTANTS_H_
+
+#include <iosfwd>
+
+namespace art {
+
+namespace JDWP {
+
+/*
+ * 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,
+};
+std::ostream& operator<<(std::ostream& os, const JdwpError& value);
+
+
+/*
+ * 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,
+};
+std::ostream& operator<<(std::ostream& os, const JdwpClassStatus& value);
+
+/*
+ * 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,
+};
+std::ostream& operator<<(std::ostream& os, const JdwpEventKind& value);
+
+/*
+ * 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,
+};
+std::ostream& operator<<(std::ostream& os, const JdwpModKind& value);
+
+/*
+ * InvokeOptions constants (bit flags).
+ */
+enum JdwpInvokeOptions {
+  INVOKE_SINGLE_THREADED  = 0x01,
+  INVOKE_NONVIRTUAL       = 0x02,
+};
+std::ostream& operator<<(std::ostream& os, const JdwpInvokeOptions& value);
+
+/*
+ * 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 */
+};
+std::ostream& operator<<(std::ostream& os, const JdwpStepDepth& value);
+
+/*
+ * StepSize constants.
+ */
+enum JdwpStepSize {
+  SS_MIN                  = 0,    /* step by minimum (e.g. 1 bytecode inst) */
+  SS_LINE                 = 1,    /* if possible, step to next line */
+};
+std::ostream& operator<<(std::ostream& os, const JdwpStepSize& value);
+
+/*
+ * SuspendPolicy constants.
+ */
+enum JdwpSuspendPolicy {
+  SP_NONE                 = 0,    /* suspend no threads */
+  SP_EVENT_THREAD         = 1,    /* suspend event thread */
+  SP_ALL                  = 2,    /* suspend all threads */
+};
+std::ostream& operator<<(std::ostream& os, const JdwpSuspendPolicy& value);
+
+/*
+ * SuspendStatus constants.
+ */
+enum JdwpSuspendStatus {
+  SUSPEND_STATUS_NOT_SUSPENDED = 0,
+  SUSPEND_STATUS_SUSPENDED     = 1,
+};
+std::ostream& operator<<(std::ostream& os, const JdwpSuspendStatus& value);
+
+/*
+ * 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())
+};
+std::ostream& operator<<(std::ostream& os, const JdwpThreadStatus& value);
+
+/*
+ * TypeTag constants.
+ */
+enum JdwpTypeTag {
+  TT_CLASS                = 1,
+  TT_INTERFACE            = 2,
+  TT_ARRAY                = 3,
+};
+std::ostream& operator<<(std::ostream& os, const JdwpTypeTag& value);
+
+/*
+ * 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',
+};
+std::ostream& operator<<(std::ostream& os, const JdwpType& value);
+
+}  // namespace JDWP
+
+}  // namespace art
+
+#endif  // ART_JDWP_JDWPCONSTANTS_H_
diff --git a/src/jdwp/jdwp_event.cc b/src/jdwp/jdwp_event.cc
new file mode 100644
index 0000000..3a2d73e
--- /dev/null
+++ b/src/jdwp/jdwp_event.cc
@@ -0,0 +1,1211 @@
+/*
+ * 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 "debugger.h"
+#include "jdwp/jdwp_priv.h"
+#include "jdwp/jdwp_constants.h"
+#include "jdwp/jdwp_handler.h"
+#include "jdwp/jdwp_event.h"
+#include "jdwp/jdwp_expand_buf.h"
+#include "logging.h"
+#include "stringprintf.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
+
+namespace art {
+
+namespace JDWP {
+
+/*
+ * 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.
+ */
+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 */
+};
+
+/*
+ * Get the next "request" serial number.  We use this when sending
+ * packets to the debugger.
+ */
+uint32_t NextRequestSerial(JdwpState* state) {
+  MutexLock mu(state->serial_lock_);
+  return state->requestSerial++;
+}
+
+/*
+ * Get the next "event" serial number.  We use this in the response to
+ * message type EventRequest.Set.
+ */
+uint32_t NextEventSerial(JdwpState* state) {
+  MutexLock mu(state->serial_lock_);
+  return state->eventSerial++;
+}
+
+/*
+ * Lock the "event" mutex, which guards the list of registered events.
+ */
+static void lockEventMutex(JdwpState* state) {
+  //Dbg::ThreadWaiting();
+  state->event_lock_.Lock();
+  //Dbg::ThreadRunning();
+}
+
+/*
+ * Unlock the "event" mutex.
+ */
+static void unlockEventMutex(JdwpState* state) {
+  state->event_lock_.Unlock();
+}
+
+/*
+ * Dump an event to the log file.
+ */
+static void dumpEvent(const JdwpEvent* pEvent) {
+  LOG(INFO) << StringPrintf("Event id=0x%4x %p (prev=%p next=%p):", pEvent->requestId, pEvent, pEvent->prev, pEvent->next);
+  LOG(INFO) << "  kind=" << pEvent->eventKind << " susp=" << pEvent->suspendPolicy << " modCount=" << pEvent->modCount;
+
+  for (int i = 0; i < pEvent->modCount; i++) {
+    const JdwpEventMod* pMod = &pEvent->mods[i];
+    LOG(INFO) << "  " << static_cast<JdwpModKind>(pMod->modKind);
+    /* TODO - show details */
+  }
+}
+
+/*
+ * 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 RegisterEvent(JdwpState* state, JdwpEvent* pEvent) {
+  lockEventMutex(state);
+
+  CHECK(state != NULL);
+  CHECK(pEvent != NULL);
+  CHECK(pEvent->prev == NULL);
+  CHECK(pEvent->next == NULL);
+
+  /*
+   * If one or more "break"-type mods are used, register them with
+   * the interpreter.
+   */
+  for (int i = 0; i < pEvent->modCount; i++) {
+    const JdwpEventMod* pMod = &pEvent->mods[i];
+    if (pMod->modKind == MK_LOCATION_ONLY) {
+      /* should only be for Breakpoint, Step, and Exception */
+      Dbg::WatchLocation(&pMod->locationOnly.loc);
+    } else if (pMod->modKind == MK_STEP) {
+      /* should only be for EK_SINGLE_STEP; should only be one */
+      JdwpStepSize size = static_cast<JdwpStepSize>(pMod->step.size);
+      JdwpStepDepth depth = static_cast<JdwpStepDepth>(pMod->step.depth);
+      Dbg::ConfigureStep(pMod->step.threadId, size, depth);
+    } else if (pMod->modKind == MK_FIELD_ONLY) {
+      /* should be for EK_FIELD_ACCESS or EK_FIELD_MODIFICATION */
+      dumpEvent(pEvent);  /* TODO - need for field watches */
+    }
+  }
+
+  /*
+   * Add to list.
+   */
+  if (state->eventList != NULL) {
+    pEvent->next = state->eventList;
+    state->eventList->prev = pEvent;
+  }
+  state->eventList = pEvent;
+  state->numEvents++;
+
+  unlockEventMutex(state);
+
+  return ERR_NONE;
+}
+
+/*
+ * 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) {
+  if (pEvent->prev == NULL) {
+    /* head of the list */
+    CHECK(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 (int 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 */
+      Dbg::UnwatchLocation(&pMod->locationOnly.loc);
+    }
+    if (pMod->modKind == MK_STEP) {
+      /* should only be for EK_SINGLE_STEP; should only be one */
+      Dbg::UnconfigureStep(pMod->step.threadId);
+    }
+  }
+
+  state->numEvents--;
+  CHECK(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 UnregisterEventById(JdwpState* state, uint32_t requestId) {
+  lockEventMutex(state);
+
+  JdwpEvent* pEvent = state->eventList;
+  while (pEvent != NULL) {
+    if (pEvent->requestId == requestId) {
+      unregisterEvent(state, pEvent);
+      EventFree(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", requestId);
+
+done:
+  unlockEventMutex(state);
+}
+
+/*
+ * Remove all entries from the event list.
+ */
+void UnregisterAll(JdwpState* state) {
+  lockEventMutex(state);
+
+  JdwpEvent* pEvent = state->eventList;
+  while (pEvent != NULL) {
+    JdwpEvent* pNextEvent = pEvent->next;
+
+    unregisterEvent(state, pEvent);
+    EventFree(pEvent);
+    pEvent = pNextEvent;
+  }
+
+  state->eventList = NULL;
+
+  unlockEventMutex(state);
+}
+
+/*
+ * Allocate a JdwpEvent struct with enough space to hold the specified
+ * number of mod records.
+ */
+JdwpEvent* EventAlloc(int numMods) {
+  JdwpEvent* newEvent;
+  int allocSize = offsetof(JdwpEvent, mods) + numMods * sizeof(newEvent->mods[0]);
+  newEvent = reinterpret_cast<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 EventFree(JdwpEvent* pEvent) {
+  if (pEvent == NULL) {
+    return;
+  }
+
+  /* make sure it was removed from the list */
+  CHECK(pEvent->prev == NULL);
+  CHECK(pEvent->next == NULL);
+  /* want to check state->eventList != pEvent */
+
+  /*
+   * Free any hairy bits in the mods.
+   */
+  for (int 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;
+
+    for (int i = 0; i < pEvent->modCount; i++) {
+      if (pEvent->mods[i].modKind == MK_COUNT && pEvent->mods[i].count.count == 0) {
+        LOG(VERBOSE) << "##### Removing expired event";
+        unregisterEvent(state, pEvent);
+        EventFree(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
+    LOG(ERROR) << ">>> comparing '" << (pattern + 1) << "' to '" << (target + (targetLen-patLen)) << "'";
+
+    if (targetLen < patLen) {
+      return false;
+    }
+    return strcmp(pattern+1, target + (targetLen-patLen)) == 0;
+  } else if (pattern[patLen-1] == '*') {
+    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;
+
+  for (int i = pEvent->modCount; i > 0; i--, pMod++) {
+    switch (pMod->modKind) {
+    case MK_COUNT:
+      CHECK_GT(pMod->count.count, 0);
+      pMod->count.count--;
+      break;
+    case MK_CONDITIONAL:
+      CHECK(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 (!Dbg::MatchType(basket->classId, pMod->classOnly.refTypeId)) {
+        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 && !Dbg::MatchType(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:
+      if (!Dbg::MatchType(basket->classId, pMod->fieldOnly.refTypeId) || pMod->fieldOnly.fieldId != basket->field) {
+        return false;
+      }
+      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:
+      LOG(ERROR) << "unhandled mod kind " << pMod->modKind;
+      CHECK(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, JdwpEventKind eventKind,
+    ModBasket* basket, JdwpEvent** matchList, int* pMatchCount) {
+  /* start after the existing entries */
+  matchList += *pMatchCount;
+
+  JdwpEvent* 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 JdwpSuspendPolicy scanSuspendPolicy(JdwpEvent** matchList, int matchCount) {
+  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, JdwpSuspendPolicy suspendPolicy) {
+  if (suspendPolicy == SP_NONE) {
+    return;
+  }
+
+  if (suspendPolicy == SP_ALL) {
+    Dbg::SuspendVM(true);
+  } else {
+    CHECK_EQ(suspendPolicy, SP_EVENT_THREAD);
+  }
+
+  /* this is rare but possible -- see CLASS_PREPARE handling */
+  if (Dbg::GetThreadSelfId() == state->debugThreadId) {
+    LOG(INFO) << "NOTE: suspendByPolicy not suspending JDWP thread";
+    return;
+  }
+
+  DebugInvokeReq* pReq = Dbg::GetInvokeReq();
+  while (true) {
+    pReq->ready = true;
+    Dbg::SuspendSelf();
+    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");*/
+      break;
+    }
+
+    /* grab this before posting/suspending again */
+    SetWaitForEventThread(state, Dbg::GetThreadSelfId());
+
+    /* leave pReq->invokeNeeded raised so we can check reentrancy */
+    LOG(VERBOSE) << "invoking method...";
+    Dbg::ExecuteMethod(pReq);
+
+    pReq->err = ERR_NONE;
+
+    /* clear this before signaling */
+    pReq->invokeNeeded = false;
+
+    LOG(VERBOSE) << "invoke complete, signaling and self-suspending";
+    MutexLock mu(pReq->lock_);
+    pReq->cond_.Signal();
+  }
+}
+
+/*
+ * 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 = Dbg::GetInvokeReq();
+  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 SetWaitForEventThread(JdwpState* state, ObjectId threadId) {
+  bool waited = false;
+
+  /* this is held for very brief periods; contention is unlikely */
+  MutexLock mu(state->event_thread_lock_);
+
+  /*
+   * If another thread is already doing stuff, wait for it.  This can
+   * go to sleep indefinitely.
+   */
+  while (state->eventThreadId != 0) {
+    LOG(VERBOSE) << StringPrintf("event in progress (0x%llx), 0x%llx sleeping", state->eventThreadId, threadId);
+    waited = true;
+    state->event_thread_cond_.Wait(state->event_thread_lock_);
+  }
+
+  if (waited || threadId != 0) {
+    LOG(VERBOSE) << StringPrintf("event token grabbed (0x%llx)", threadId);
+  }
+  if (threadId != 0) {
+    state->eventThreadId = threadId;
+  }
+}
+
+/*
+ * Clear the threadId and signal anybody waiting.
+ */
+void ClearWaitForEventThread(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.
+   */
+  MutexLock mu(state->event_thread_lock_);
+
+  CHECK_NE(state->eventThreadId, 0U);
+  LOG(VERBOSE) << StringPrintf("cleared event token (0x%llx)", state->eventThreadId);
+
+  state->eventThreadId = 0;
+
+  state->event_thread_cond_.Signal();
+}
+
+
+/*
+ * Prep an event.  Allocates storage for the message and leaves space for
+ * the header.
+ */
+static ExpandBuf* eventPrep() {
+  ExpandBuf* 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) {
+  uint8_t* buf = expandBufGetBuffer(pReq);
+
+  set4BE(buf, expandBufGetLength(pReq));
+  set4BE(buf+4, NextRequestSerial(state));
+  set1(buf+8, 0);     /* flags */
+  set1(buf+9, kJdwpEventCommandSet);
+  set1(buf+10, kJdwpCompositeCommand);
+
+  SendRequest(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 PostVMStart(JdwpState* state, bool suspend) {
+  JdwpSuspendPolicy suspendPolicy;
+  ObjectId threadId = Dbg::GetThreadSelfId();
+
+  if (suspend) {
+    suspendPolicy = SP_ALL;
+  } else {
+    suspendPolicy = SP_NONE;
+  }
+
+  /* probably don't need this here */
+  lockEventMutex(state);
+
+  ExpandBuf* pReq = NULL;
+  if (true) {
+    LOG(VERBOSE) << "EVENT: " << EK_VM_START;
+    LOG(VERBOSE) << "  suspendPolicy=" << 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 = Dbg::ThreadWaiting();
+    if (suspendPolicy != SP_NONE) {
+      SetWaitForEventThread(state, threadId);
+    }
+
+    eventFinish(state, pReq);
+
+    suspendByPolicy(state, suspendPolicy);
+    Dbg::ThreadContinuing(oldStatus);
+  }
+
+  return true;
+}
+
+// TODO: This doesn't behave like the real dvmDescriptorToName.
+// I'm hoping this isn't used to communicate with the debugger, and we can just use descriptors.
+char* dvmDescriptorToName(const char* descriptor) {
+  return strdup(descriptor);
+}
+
+/*
+ * 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 PostLocationEvent(JdwpState* state, const JdwpLocation* pLoc, ObjectId thisPtr, int eventFlags) {
+  ModBasket basket;
+  char* nameAlloc = NULL;
+
+  memset(&basket, 0, sizeof(basket));
+  basket.pLoc = pLoc;
+  basket.classId = pLoc->classId;
+  basket.thisPtr = thisPtr;
+  basket.threadId = Dbg::GetThreadSelfId();
+  basket.className = nameAlloc = dvmDescriptorToName(Dbg::GetClassDescriptor(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) {
+    LOG(VERBOSE) << "Ignoring location event in JDWP thread";
+    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)) {
+    LOG(VERBOSE) << "Not checking breakpoints during invoke (" << basket.className << ")";
+    free(nameAlloc);
+    return false;
+  }
+
+  /* don't allow the list to be updated while we scan it */
+  lockEventMutex(state);
+
+  JdwpEvent** matchList = allocMatchList(state);
+  int matchCount = 0;
+
+  if ((eventFlags & Dbg::kBreakPoint) != 0) {
+    findMatchingEvents(state, EK_BREAKPOINT, &basket, matchList, &matchCount);
+  }
+  if ((eventFlags & Dbg::kSingleStep) != 0) {
+    findMatchingEvents(state, EK_SINGLE_STEP, &basket, matchList, &matchCount);
+  }
+  if ((eventFlags & Dbg::kMethodEntry) != 0) {
+    findMatchingEvents(state, EK_METHOD_ENTRY, &basket, matchList, &matchCount);
+  }
+  if ((eventFlags & Dbg::kMethodExit) != 0) {
+    findMatchingEvents(state, EK_METHOD_EXIT, &basket, matchList, &matchCount);
+  }
+
+  ExpandBuf* pReq = NULL;
+  JdwpSuspendPolicy suspendPolicy = SP_NONE;
+  if (matchCount != 0) {
+    LOG(VERBOSE) << "EVENT: " << matchList[0]->eventKind << "(" << matchCount << " total) "
+                 << basket.className << "." << Dbg::GetMethodName(pLoc->classId, pLoc->methodId)
+                 << " thread=" << (void*) basket.threadId << " code=" << (void*) pLoc->idx << ")";
+
+    suspendPolicy = scanSuspendPolicy(matchList, matchCount);
+    LOG(VERBOSE) << "  suspendPolicy=" << suspendPolicy;
+
+    pReq = eventPrep();
+    expandBufAdd1(pReq, suspendPolicy);
+    expandBufAdd4BE(pReq, matchCount);
+
+    for (int i = 0; i < matchCount; i++) {
+      expandBufAdd1(pReq, matchList[i]->eventKind);
+      expandBufAdd4BE(pReq, matchList[i]->requestId);
+      expandBufAdd8BE(pReq, basket.threadId);
+      AddLocation(pReq, pLoc);
+    }
+  }
+
+  cleanupMatchList(state, matchList, matchCount);
+  unlockEventMutex(state);
+
+  /* send request and possibly suspend ourselves */
+  if (pReq != NULL) {
+    int oldStatus = Dbg::ThreadWaiting();
+    if (suspendPolicy != SP_NONE) {
+      SetWaitForEventThread(state, basket.threadId);
+    }
+
+    eventFinish(state, pReq);
+
+    suspendByPolicy(state, suspendPolicy);
+    Dbg::ThreadContinuing(oldStatus);
+  }
+
+  free(nameAlloc);
+  return matchCount != 0;
+}
+
+/*
+ * A thread is starting or stopping.
+ *
+ * Valid mods:
+ *  Count, ThreadOnly
+ */
+bool PostThreadChange(JdwpState* state, ObjectId threadId, bool start) {
+  CHECK_EQ(threadId, Dbg::GetThreadSelfId());
+
+  /*
+   * I don't think this can happen.
+   */
+  if (invokeInProgress(state)) {
+    LOG(WARNING) << "Not posting thread change during invoke";
+    return false;
+  }
+
+  ModBasket basket;
+  memset(&basket, 0, sizeof(basket));
+  basket.threadId = threadId;
+
+  /* don't allow the list to be updated while we scan it */
+  lockEventMutex(state);
+
+  JdwpEvent** matchList = allocMatchList(state);
+  int matchCount = 0;
+
+  if (start) {
+    findMatchingEvents(state, EK_THREAD_START, &basket, matchList, &matchCount);
+  } else {
+    findMatchingEvents(state, EK_THREAD_DEATH, &basket, matchList, &matchCount);
+  }
+
+  ExpandBuf* pReq = NULL;
+  JdwpSuspendPolicy suspendPolicy = SP_NONE;
+  if (matchCount != 0) {
+    LOG(VERBOSE) << "EVENT: " << matchList[0]->eventKind << "(" << matchCount << " total) "
+                 << "thread=" << (void*) basket.threadId << ")";
+
+    suspendPolicy = scanSuspendPolicy(matchList, matchCount);
+    LOG(VERBOSE) << "  suspendPolicy=" << suspendPolicy;
+
+    pReq = eventPrep();
+    expandBufAdd1(pReq, suspendPolicy);
+    expandBufAdd4BE(pReq, matchCount);
+
+    for (int 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 = Dbg::ThreadWaiting();
+    if (suspendPolicy != SP_NONE) {
+      SetWaitForEventThread(state, basket.threadId);
+    }
+    eventFinish(state, pReq);
+
+    suspendByPolicy(state, suspendPolicy);
+    Dbg::ThreadContinuing(oldStatus);
+  }
+
+  return matchCount != 0;
+}
+
+/*
+ * Send a polite "VM is dying" message to the debugger.
+ *
+ * Skips the usual "event token" stuff.
+ */
+bool PostVMDeath(JdwpState* state) {
+  LOG(VERBOSE) << "EVENT: " << EK_VM_DEATH;
+
+  ExpandBuf* 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
+ *
+ * The "exceptionId" has not been added to the GC-visible object registry,
+ * because there's a pretty good chance that we're not going to send it
+ * up the debugger.
+ */
+bool PostException(JdwpState* state, const JdwpLocation* pThrowLoc,
+    ObjectId exceptionId, RefTypeId exceptionClassId,
+    const JdwpLocation* pCatchLoc, ObjectId thisPtr)
+{
+  ModBasket basket;
+  char* nameAlloc = NULL;
+
+  memset(&basket, 0, sizeof(basket));
+  basket.pLoc = pThrowLoc;
+  basket.classId = pThrowLoc->classId;
+  basket.threadId = Dbg::GetThreadSelfId();
+  basket.className = nameAlloc = dvmDescriptorToName(Dbg::GetClassDescriptor(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)) {
+    LOG(VERBOSE) << "Not posting exception hit during invoke (" << basket.className << ")";
+    free(nameAlloc);
+    return false;
+  }
+
+  /* don't allow the list to be updated while we scan it */
+  lockEventMutex(state);
+
+  JdwpEvent** matchList = allocMatchList(state);
+  int matchCount = 0;
+
+  findMatchingEvents(state, EK_EXCEPTION, &basket, matchList, &matchCount);
+
+  ExpandBuf* pReq = NULL;
+  JdwpSuspendPolicy suspendPolicy = SP_NONE;
+  if (matchCount != 0) {
+    LOG(VERBOSE) << "EVENT: " << matchList[0]->eventKind << "(" << matchCount << " total)"
+                 << " thread=" << (void*) basket.threadId
+                 << " exceptId=" << (void*) exceptionId
+                 << " caught=" << basket.caught << ")";
+    LOG(VERBOSE) << StringPrintf("  throw: %d %llx %x %lld (%s.%s)", pThrowLoc->typeTag,
+        pThrowLoc->classId, pThrowLoc->methodId, pThrowLoc->idx,
+        Dbg::GetClassDescriptor(pThrowLoc->classId),
+        Dbg::GetMethodName(pThrowLoc->classId, pThrowLoc->methodId));
+    if (pCatchLoc->classId == 0) {
+      LOG(VERBOSE) << "  catch: (not caught)";
+    } else {
+      LOG(VERBOSE) << StringPrintf("  catch: %d %llx %x %lld (%s.%s)", pCatchLoc->typeTag,
+          pCatchLoc->classId, pCatchLoc->methodId, pCatchLoc->idx,
+          Dbg::GetClassDescriptor(pCatchLoc->classId),
+          Dbg::GetMethodName(pCatchLoc->classId, pCatchLoc->methodId));
+    }
+
+    suspendPolicy = scanSuspendPolicy(matchList, matchCount);
+    LOG(VERBOSE) << "  suspendPolicy=" << suspendPolicy;
+
+    pReq = eventPrep();
+    expandBufAdd1(pReq, suspendPolicy);
+    expandBufAdd4BE(pReq, matchCount);
+
+    for (int i = 0; i < matchCount; i++) {
+      expandBufAdd1(pReq, matchList[i]->eventKind);
+      expandBufAdd4BE(pReq, matchList[i]->requestId);
+      expandBufAdd8BE(pReq, basket.threadId);
+
+      AddLocation(pReq, pThrowLoc);
+      expandBufAdd1(pReq, JT_OBJECT);
+      expandBufAdd8BE(pReq, exceptionId);
+      AddLocation(pReq, pCatchLoc);
+    }
+
+    /* don't let the GC discard it */
+    Dbg::RegisterObjectId(exceptionId);
+  }
+
+  cleanupMatchList(state, matchList, matchCount);
+  unlockEventMutex(state);
+
+  /* send request and possibly suspend ourselves */
+  if (pReq != NULL) {
+    int oldStatus = Dbg::ThreadWaiting();
+    if (suspendPolicy != SP_NONE) {
+      SetWaitForEventThread(state, basket.threadId);
+    }
+
+    eventFinish(state, pReq);
+
+    suspendByPolicy(state, suspendPolicy);
+    Dbg::ThreadContinuing(oldStatus);
+  }
+
+  free(nameAlloc);
+  return matchCount != 0;
+}
+
+/*
+ * Announce that a class has been loaded.
+ *
+ * Valid mods:
+ *  Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude
+ */
+bool PostClassPrepare(JdwpState* state, int tag, RefTypeId refTypeId, const char* signature, int status) {
+  ModBasket basket;
+  char* nameAlloc = NULL;
+
+  memset(&basket, 0, sizeof(basket));
+  basket.classId = refTypeId;
+  basket.threadId = Dbg::GetThreadSelfId();
+  basket.className = nameAlloc = dvmDescriptorToName(Dbg::GetClassDescriptor(basket.classId));
+
+  /* suppress class prep caused by debugger */
+  if (invokeInProgress(state)) {
+    LOG(VERBOSE) << "Not posting class prep caused by invoke (" << basket.className << ")";
+    free(nameAlloc);
+    return false;
+  }
+
+  /* don't allow the list to be updated while we scan it */
+  lockEventMutex(state);
+
+  JdwpEvent** matchList = allocMatchList(state);
+  int matchCount = 0;
+
+  findMatchingEvents(state, EK_CLASS_PREPARE, &basket, matchList, &matchCount);
+
+  ExpandBuf* pReq = NULL;
+  JdwpSuspendPolicy suspendPolicy = SP_NONE;
+  if (matchCount != 0) {
+    LOG(VERBOSE) << "EVENT: " << matchList[0]->eventKind << "(" << matchCount << " total) "
+                 << "thread=" << (void*) basket.threadId << ")";
+
+    suspendPolicy = scanSuspendPolicy(matchList, matchCount);
+    LOG(VERBOSE) << "  suspendPolicy=" << 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.
+       */
+      LOG(VERBOSE) << "  NOTE: class prepare in debugger thread!";
+      basket.threadId = 0;
+      if (suspendPolicy == SP_EVENT_THREAD) {
+        suspendPolicy = SP_ALL;
+      }
+    }
+
+    pReq = eventPrep();
+    expandBufAdd1(pReq, suspendPolicy);
+    expandBufAdd4BE(pReq, matchCount);
+
+    for (int 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 uint8_t*) signature);
+      expandBufAdd4BE(pReq, status);
+    }
+  }
+
+  cleanupMatchList(state, matchList, matchCount);
+
+  unlockEventMutex(state);
+
+  /* send request and possibly suspend ourselves */
+  if (pReq != NULL) {
+    int oldStatus = Dbg::ThreadWaiting();
+    if (suspendPolicy != SP_NONE) {
+      SetWaitForEventThread(state, basket.threadId);
+    }
+    eventFinish(state, pReq);
+
+    suspendByPolicy(state, suspendPolicy);
+    Dbg::ThreadContinuing(oldStatus);
+  }
+
+  free(nameAlloc);
+  return matchCount != 0;
+}
+
+bool SendBufferedRequest(JdwpState* state, const iovec* iov, int iovcnt) {
+  return (*state->transport->sendBufferedRequest)(state, iov, iovcnt);
+}
+
+/*
+ * 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 DdmSendChunkV(JdwpState* state, int type, const iovec* iov, int iovcnt) {
+  uint8_t header[kJDWPHeaderLen + 8];
+  size_t dataLen = 0;
+
+  CHECK(iov != NULL);
+  CHECK(iovcnt > 0 && iovcnt < 10);
+
+  /*
+   * "Wrap" the contents of the iovec with a JDWP/DDMS header.  We do
+   * this by creating a new copy of the vector with space for the header.
+   */
+  iovec wrapiov[iovcnt+1];
+  for (int i = 0; i < iovcnt; i++) {
+    wrapiov[i+1].iov_base = iov[i].iov_base;
+    wrapiov[i+1].iov_len = iov[i].iov_len;
+    dataLen += iov[i].iov_len;
+  }
+
+  /* form the header (JDWP plus DDMS) */
+  set4BE(header, sizeof(header) + dataLen);
+  set4BE(header+4, NextRequestSerial(state));
+  set1(header+8, 0);     /* flags */
+  set1(header+9, kJDWPDdmCmdSet);
+  set1(header+10, kJDWPDdmCmd);
+  set4BE(header+11, type);
+  set4BE(header+15, dataLen);
+
+  wrapiov[0].iov_base = header;
+  wrapiov[0].iov_len = sizeof(header);
+
+  /*
+   * Make sure we're in VMWAIT in case the write blocks.
+   */
+  int oldStatus = Dbg::ThreadWaiting();
+  SendBufferedRequest(state, wrapiov, iovcnt+1);
+  Dbg::ThreadContinuing(oldStatus);
+}
+
+}  // namespace JDWP
+
+}  // namespace art
diff --git a/src/jdwp/jdwp_event.h b/src/jdwp/jdwp_event.h
new file mode 100644
index 0000000..2b957fb
--- /dev/null
+++ b/src/jdwp/jdwp_event.h
@@ -0,0 +1,137 @@
+/*
+ * 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 ART_JDWP_JDWPEVENT_H_
+#define ART_JDWP_JDWPEVENT_H_
+
+#include "jdwp/jdwp_constants.h"
+#include "jdwp/jdwp_expand_buf.h"
+
+namespace art {
+
+namespace JDWP {
+
+/*
+ * Event modifiers.  A JdwpEvent may have zero or more of these.
+ */
+union JdwpEventMod {
+  uint8_t      modKind;                /* JdwpModKind */
+  struct {
+    uint8_t          modKind;
+    int         count;
+  } count;
+  struct {
+    uint8_t          modKind;
+    uint32_t          exprId;
+  } conditional;
+  struct {
+    uint8_t          modKind;
+    ObjectId    threadId;
+  } threadOnly;
+  struct {
+    uint8_t          modKind;
+    RefTypeId   refTypeId;
+  } classOnly;
+  struct {
+    uint8_t          modKind;
+    char*       classPattern;
+  } classMatch;
+  struct {
+    uint8_t          modKind;
+    char*       classPattern;
+  } classExclude;
+  struct {
+    uint8_t          modKind;
+    JdwpLocation loc;
+  } locationOnly;
+  struct {
+    uint8_t          modKind;
+    uint8_t          caught;
+    uint8_t          uncaught;
+    RefTypeId   refTypeId;
+  } exceptionOnly;
+  struct {
+    uint8_t          modKind;
+    RefTypeId   refTypeId;
+    FieldId     fieldId;
+  } fieldOnly;
+  struct {
+    uint8_t          modKind;
+    ObjectId    threadId;
+    int         size;           /* JdwpStepSize */
+    int         depth;          /* JdwpStepDepth */
+  } step;
+  struct {
+    uint8_t          modKind;
+    ObjectId    objectId;
+  } instanceOnly;
+};
+
+/*
+ * One of these for every registered event.
+ *
+ * We over-allocate the struct to hold the modifiers.
+ */
+struct JdwpEvent {
+  JdwpEvent* prev;           /* linked list */
+  JdwpEvent* next;
+
+  JdwpEventKind eventKind;      /* what kind of event is this? */
+  JdwpSuspendPolicy suspendPolicy;  /* suspend all, none, or self? */
+  int modCount;       /* #of entries in mods[] */
+  uint32_t requestId;      /* serial#, reported to debugger */
+
+  JdwpEventMod mods[1];        /* MUST be last field in struct */
+};
+
+/*
+ * Allocate an event structure with enough space.
+ */
+JdwpEvent* EventAlloc(int numMods);
+void EventFree(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 RegisterEvent(JdwpState* state, JdwpEvent* pEvent);
+
+/*
+ * Unregister an event, given the requestId.
+ */
+void UnregisterEventById(JdwpState* state, uint32_t requestId);
+
+/*
+ * Unregister all events.
+ */
+void UnregisterAll(JdwpState* state);
+
+/*
+ * Send an event, formatted into "pReq", to the debugger.
+ *
+ * (Messages are sent asynchronously, and do not receive a reply.)
+ */
+bool SendRequest(JdwpState* state, ExpandBuf* pReq);
+
+}  // namespace JDWP
+
+}  // namespace art
+
+#endif  // ART_JDWP_JDWPEVENT_H_
diff --git a/src/jdwp/jdwp_expand_buf.cc b/src/jdwp/jdwp_expand_buf.cc
new file mode 100644
index 0000000..f1675fd
--- /dev/null
+++ b/src/jdwp/jdwp_expand_buf.cc
@@ -0,0 +1,174 @@
+/*
+ * 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/jdwp_bits.h"
+#include "jdwp/jdwp_expand_buf.h"
+#include "logging.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+namespace art {
+
+namespace JDWP {
+
+/*
+ * Data structure used to track buffer use.
+ */
+struct ExpandBuf {
+  uint8_t*     storage;
+  int     curLen;
+  int     maxLen;
+};
+
+#define kInitialStorage 64
+
+/*
+ * Allocate a JdwpBuf and some initial storage.
+ */
+ExpandBuf* expandBufAlloc() {
+  ExpandBuf* newBuf;
+
+  newBuf = (ExpandBuf*) malloc(sizeof(*newBuf));
+  newBuf->storage = (uint8_t*) 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.
+ */
+uint8_t* 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) {
+  if (pBuf->curLen + newCount <= pBuf->maxLen) {
+    return;
+  }
+
+  while (pBuf->curLen + newCount > pBuf->maxLen) {
+    pBuf->maxLen *= 2;
+  }
+
+  uint8_t* newPtr = (uint8_t*) realloc(pBuf->storage, pBuf->maxLen);
+  if (newPtr == NULL) {
+    LOG(ERROR) << "realloc(" << pBuf->maxLen << ") failed";
+    abort();
+  }
+
+  pBuf->storage = newPtr;
+}
+
+/*
+ * Allocate some space in the buffer.
+ */
+uint8_t* expandBufAddSpace(ExpandBuf* pBuf, int gapSize) {
+  uint8_t* 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, uint8_t val) {
+  ensureSpace(pBuf, sizeof(val));
+  *(pBuf->storage + pBuf->curLen) = val;
+  pBuf->curLen++;
+}
+
+/*
+ * Append two big-endian bytes.
+ */
+void expandBufAdd2BE(ExpandBuf* pBuf, uint16_t val) {
+  ensureSpace(pBuf, sizeof(val));
+  set2BE(pBuf->storage + pBuf->curLen, val);
+  pBuf->curLen += sizeof(val);
+}
+
+/*
+ * Append four big-endian bytes.
+ */
+void expandBufAdd4BE(ExpandBuf* pBuf, uint32_t val) {
+  ensureSpace(pBuf, sizeof(val));
+  set4BE(pBuf->storage + pBuf->curLen, val);
+  pBuf->curLen += sizeof(val);
+}
+
+/*
+ * Append eight big-endian bytes.
+ */
+void expandBufAdd8BE(ExpandBuf* pBuf, uint64_t 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 uint8_t* str) {
+  int strLen = strlen((const char*)str);
+
+  ensureSpace(pBuf, sizeof(uint32_t) + strLen);
+  setUtf8String(pBuf->storage + pBuf->curLen, str);
+  pBuf->curLen += sizeof(uint32_t) + strLen;
+}
+
+}  // namespace JDWP
+
+}  // namespace art
diff --git a/src/jdwp/jdwp_expand_buf.h b/src/jdwp/jdwp_expand_buf.h
new file mode 100644
index 0000000..2c19f54
--- /dev/null
+++ b/src/jdwp/jdwp_expand_buf.h
@@ -0,0 +1,65 @@
+/*
+ * 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 ART_JDWP_EXPANDBUF_H_
+#define ART_JDWP_EXPANDBUF_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace art {
+
+namespace JDWP {
+
+struct ExpandBuf;   /* private */
+
+/* create a new struct */
+ExpandBuf* expandBufAlloc();
+/* free storage */
+void expandBufFree(ExpandBuf* pBuf);
+
+/*
+ * Accessors.  The buffer pointer and length will only be valid until more
+ * data is added.
+ */
+uint8_t* 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.
+ */
+uint8_t* expandBufAddSpace(ExpandBuf* pBuf, int gapSize);
+void expandBufAdd1(ExpandBuf* pBuf, uint8_t val);
+void expandBufAdd2BE(ExpandBuf* pBuf, uint16_t val);
+void expandBufAdd4BE(ExpandBuf* pBuf, uint32_t val);
+void expandBufAdd8BE(ExpandBuf* pBuf, uint64_t val);
+void expandBufAddUtf8String(ExpandBuf* pBuf, const uint8_t* str);
+
+}  // namespace JDWP
+
+}  // namespace art
+
+#endif  // ART_JDWP_EXPANDBUF_H_
diff --git a/src/jdwp/jdwp_handler.cc b/src/jdwp/jdwp_handler.cc
new file mode 100644
index 0000000..8fe3c3f
--- /dev/null
+++ b/src/jdwp/jdwp_handler.cc
@@ -0,0 +1,1818 @@
+/*
+ * 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 "atomic.h"
+#include "debugger.h"
+#include "jdwp/jdwp_priv.h"
+#include "jdwp/jdwp_handler.h"
+#include "jdwp/jdwp_event.h"
+#include "jdwp/jdwp_constants.h"
+#include "jdwp/jdwp_expand_buf.h"
+#include "logging.h"
+#include "macros.h"
+#include "stringprintf.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+namespace art {
+
+namespace JDWP {
+
+/*
+ * Helper function: read a "location" from an input buffer.
+ */
+static void jdwpReadLocation(const uint8_t** pBuf, JdwpLocation* pLoc) {
+  memset(pLoc, 0, sizeof(*pLoc));     /* allows memcmp() later */
+  pLoc->typeTag = read1(pBuf);
+  pLoc->classId = ReadObjectId(pBuf);
+  pLoc->methodId = ReadMethodId(pBuf);
+  pLoc->idx = read8BE(pBuf);
+}
+
+/*
+ * Helper function: write a "location" into the reply buffer.
+ */
+void AddLocation(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 uint64_t jdwpReadValue(const uint8_t** pBuf, int width) {
+  uint64_t value = -1;
+  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:    LOG(FATAL) << width; break;
+  }
+  return value;
+}
+
+/*
+ * Helper function: write a variable-width value into the output input buffer.
+ */
+static void jdwpWriteValue(ExpandBuf* pReply, int width, uint64_t 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:    LOG(FATAL) << width; break;
+  }
+}
+
+/*
+ * Common code for *_InvokeMethod requests.
+ *
+ * If "isConstructor" is set, this returns "objectId" rather than the
+ * expected-to-be-void return value of the called function.
+ */
+static JdwpError finishInvoke(JdwpState* state,
+    const uint8_t* buf, int dataLen, ExpandBuf* pReply,
+    ObjectId threadId, ObjectId objectId, RefTypeId classId, MethodId methodId,
+    bool isConstructor)
+{
+  CHECK(!isConstructor || objectId != 0);
+
+  uint32_t numArgs = read4BE(&buf);
+
+  LOG(VERBOSE) << StringPrintf("    --> threadId=%llx objectId=%llx", threadId, objectId);
+  LOG(VERBOSE) << StringPrintf("        classId=%llx methodId=%x %s.%s", classId, methodId, Dbg::GetClassDescriptor(classId), Dbg::GetMethodName(classId, methodId));
+  LOG(VERBOSE) << StringPrintf("        %d args:", numArgs);
+
+  uint64_t* argArray = NULL;
+  if (numArgs > 0) {
+    argArray = (ObjectId*) malloc(sizeof(ObjectId) * numArgs);
+  }
+
+  for (uint32_t i = 0; i < numArgs; i++) {
+    uint8_t typeTag = read1(&buf);
+    int width = Dbg::GetTagWidth(typeTag);
+    uint64_t value = jdwpReadValue(&buf, width);
+
+    LOG(VERBOSE) << StringPrintf("          '%c'(%d): 0x%llx", typeTag, width, value);
+    argArray[i] = value;
+  }
+
+  uint32_t options = read4BE(&buf);  /* enum InvokeOptions bit flags */
+  LOG(VERBOSE) << StringPrintf("        options=0x%04x%s%s", options, (options & INVOKE_SINGLE_THREADED) ? " (SINGLE_THREADED)" : "", (options & INVOKE_NONVIRTUAL) ? " (NONVIRTUAL)" : "");
+
+  uint8_t resultTag;
+  uint64_t resultValue;
+  ObjectId exceptObjId;
+  JdwpError err = Dbg::InvokeMethod(threadId, objectId, classId, methodId, numArgs, argArray, options, &resultTag, &resultValue, &exceptObjId);
+  if (err != ERR_NONE) {
+    goto bail;
+  }
+
+  if (err == ERR_NONE) {
+    if (isConstructor) {
+      expandBufAdd1(pReply, JT_OBJECT);
+      expandBufAddObjectId(pReply, objectId);
+    } else {
+      int width = Dbg::GetTagWidth(resultTag);
+
+      expandBufAdd1(pReply, resultTag);
+      if (width != 0) {
+        jdwpWriteValue(pReply, width, resultValue);
+      }
+    }
+    expandBufAdd1(pReply, JT_OBJECT);
+    expandBufAddObjectId(pReply, exceptObjId);
+
+    LOG(VERBOSE) << StringPrintf("  --> returned '%c' 0x%llx (except=%08llx)", resultTag, resultValue, exceptObjId);
+
+    /* show detailed debug output */
+    if (resultTag == JT_STRING && exceptObjId == 0) {
+      if (resultValue != 0) {
+        char* str = Dbg::StringToUtf8(resultValue);
+        LOG(VERBOSE) << StringPrintf("      string '%s'", str);
+        free(str);
+      } else {
+        LOG(VERBOSE) << "      string (null)";
+      }
+    }
+  }
+
+bail:
+  free(argArray);
+  return err;
+}
+
+
+/*
+ * Request for version info.
+ */
+static JdwpError handleVM_Version(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  /* text information on runtime version */
+  std::string version(StringPrintf("Android Runtime %s", Runtime::Current()->GetVersion()));
+  expandBufAddUtf8String(pReply, (const uint8_t*) version.c_str());
+  /* JDWP version numbers */
+  expandBufAdd4BE(pReply, 1);        // major
+  expandBufAdd4BE(pReply, 5);        // minor
+  /* VM JRE version */
+  expandBufAddUtf8String(pReply, (const uint8_t*) "1.6.0");  /* e.g. 1.6.0_22 */
+  /* target VM name */
+  expandBufAddUtf8String(pReply, (const uint8_t*) "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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  size_t strLen;
+  char* classDescriptor = readNewUtf8String(&buf, &strLen);
+  LOG(VERBOSE) << "  Req for class by signature '" << 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".
+   */
+  uint32_t numClasses;
+  RefTypeId refTypeId;
+  if (!Dbg::FindLoadedClassBySignature(classDescriptor, &refTypeId)) {
+    /* not currently loaded */
+    LOG(VERBOSE) << "    --> no match!";
+    numClasses = 0;
+  } else {
+    /* just the one */
+    numClasses = 1;
+  }
+
+  expandBufAdd4BE(pReply, numClasses);
+
+  if (numClasses > 0) {
+    uint8_t typeTag;
+    uint32_t status;
+
+    /* get class vs. interface and status flags */
+    Dbg::GetClassInfo(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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId* pThreadIds;
+  uint32_t threadCount;
+  Dbg::GetAllThreads(&pThreadIds, &threadCount);
+
+  expandBufAdd4BE(pReply, threadCount);
+
+  ObjectId* walker = pThreadIds;
+  for (uint32_t i = 0; i < 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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  /*
+   * 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".
+   */
+  uint32_t groups = 1;
+  expandBufAdd4BE(pReply, groups);
+  //threadGroupId = debugGetMainThreadGroup();
+  //expandBufAdd8BE(pReply, threadGroupId);
+  ObjectId threadGroupId = Dbg::GetSystemThreadGroupId();
+  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 uint8_t* 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 uint8_t* 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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  Dbg::SuspendVM(false);
+  return ERR_NONE;
+}
+
+/*
+ * Resume execution.  Decrements the "suspend count" of all threads.
+ */
+static JdwpError handleVM_Resume(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  Dbg::ResumeVM();
+  return ERR_NONE;
+}
+
+/*
+ * The debugger wants the entire VM to exit.
+ */
+static JdwpError handleVM_Exit(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  uint32_t exitCode = get4BE(buf);
+
+  LOG(WARNING) << "Debugger is telling the VM to exit with code=" << exitCode;
+
+  Dbg::Exit(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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  size_t strLen;
+  char* str = readNewUtf8String(&buf, &strLen);
+
+  LOG(VERBOSE) << "  Req to create string '" << str << "'";
+
+  ObjectId stringId = Dbg::CreateString(str);
+  if (stringId == 0) {
+    return ERR_OUT_OF_MEMORY;
+  }
+
+  expandBufAddObjectId(pReply, stringId);
+  return ERR_NONE;
+}
+
+/*
+ * Tell the debugger what we are capable of.
+ */
+static JdwpError handleVM_Capabilities(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  expandBufAdd1(pReply, false);   /* canWatchFieldModification */
+  expandBufAdd1(pReply, false);   /* canWatchFieldAccess */
+  expandBufAdd1(pReply, false);   /* canGetBytecodes */
+  expandBufAdd1(pReply, true);    /* 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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  char baseDir[2] = "/";
+
+  /*
+   * TODO: make this real.  Not important for remote debugging, but
+   * might be useful for local debugging.
+   */
+  uint32_t classPaths = 1;
+  uint32_t bootClassPaths = 0;
+
+  expandBufAddUtf8String(pReply, (const uint8_t*) baseDir);
+  expandBufAdd4BE(pReply, classPaths);
+  for (uint32_t i = 0; i < classPaths; i++) {
+    expandBufAddUtf8String(pReply, (const uint8_t*) ".");
+  }
+
+  expandBufAdd4BE(pReply, bootClassPaths);
+  for (uint32_t i = 0; i < 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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  return ERR_NONE;
+}
+
+/*
+ * Tell the debugger what we are capable of.
+ */
+static JdwpError handleVM_CapabilitiesNew(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  expandBufAdd1(pReply, false);   /* canWatchFieldModification */
+  expandBufAdd1(pReply, false);   /* canWatchFieldAccess */
+  expandBufAdd1(pReply, false);   /* canGetBytecodes */
+  expandBufAdd1(pReply, true);    /* 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 (int 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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  uint32_t numClasses = 0;
+  RefTypeId* classRefBuf = NULL;
+
+  Dbg::GetClassList(&numClasses, &classRefBuf);
+
+  expandBufAdd4BE(pReply, numClasses);
+
+  for (uint32_t i = 0; i < numClasses; i++) {
+    static const uint8_t genericSignature[1] = "";
+    uint8_t refTypeTag;
+    const char* signature;
+    uint32_t status;
+
+    Dbg::GetClassInfo(classRefBuf[i], &refTypeTag, &status, &signature);
+
+    expandBufAdd1(pReply, refTypeTag);
+    expandBufAddRefTypeId(pReply, classRefBuf[i]);
+    expandBufAddUtf8String(pReply, (const uint8_t*) signature);
+    expandBufAddUtf8String(pReply, genericSignature);
+    expandBufAdd4BE(pReply, status);
+  }
+
+  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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId refTypeId = ReadRefTypeId(&buf);
+
+  LOG(VERBOSE) << StringPrintf("  Req for signature of refTypeId=0x%llx", refTypeId);
+  const char* signature = Dbg::GetSignature(refTypeId);
+  expandBufAddUtf8String(pReply, (const uint8_t*) signature);
+
+  return ERR_NONE;
+}
+
+/*
+ * Return the modifiers (a/k/a access flags) for a reference type.
+ */
+static JdwpError handleRT_Modifiers(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId refTypeId = ReadRefTypeId(&buf);
+  uint32_t modBits = Dbg::GetAccessFlags(refTypeId);
+  expandBufAdd4BE(pReply, modBits);
+  return ERR_NONE;
+}
+
+/*
+ * Get values from static fields in a reference type.
+ */
+static JdwpError handleRT_GetValues(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId refTypeId = ReadRefTypeId(&buf);
+  uint32_t numFields = read4BE(&buf);
+
+  LOG(VERBOSE) << "  RT_GetValues " << numFields << ":";
+
+  expandBufAdd4BE(pReply, numFields);
+  for (uint32_t i = 0; i < numFields; i++) {
+    FieldId fieldId = ReadFieldId(&buf);
+    Dbg::GetStaticFieldValue(refTypeId, fieldId, pReply);
+  }
+
+  return ERR_NONE;
+}
+
+/*
+ * Get the name of the source file in which a reference type was declared.
+ */
+static JdwpError handleRT_SourceFile(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId refTypeId = ReadRefTypeId(&buf);
+
+  const char* fileName = Dbg::GetSourceFile(refTypeId);
+  if (fileName != NULL) {
+    expandBufAddUtf8String(pReply, (const uint8_t*) fileName);
+    return ERR_NONE;
+  } else {
+    return ERR_ABSENT_INFORMATION;
+  }
+}
+
+/*
+ * Return the current status of the reference type.
+ */
+static JdwpError handleRT_Status(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId refTypeId = ReadRefTypeId(&buf);
+
+  /* get status flags */
+  uint8_t typeTag;
+  uint32_t status;
+  Dbg::GetClassInfo(refTypeId, &typeTag, &status, NULL);
+  expandBufAdd4BE(pReply, status);
+  return ERR_NONE;
+}
+
+/*
+ * Return interfaces implemented directly by this class.
+ */
+static JdwpError handleRT_Interfaces(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId refTypeId = ReadRefTypeId(&buf);
+
+  LOG(VERBOSE) << StringPrintf("  Req for interfaces in %llx (%s)", refTypeId,
+  Dbg::GetClassDescriptor(refTypeId));
+
+  Dbg::OutputAllInterfaces(refTypeId, pReply);
+
+  return ERR_NONE;
+}
+
+/*
+ * Return the class object corresponding to this type.
+ */
+static JdwpError handleRT_ClassObject(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId refTypeId = ReadRefTypeId(&buf);
+  ObjectId classObjId = Dbg::GetClassObject(refTypeId);
+
+  LOG(VERBOSE) << StringPrintf("  RefTypeId %llx -> ObjectId %llx", refTypeId, classObjId);
+
+  expandBufAddObjectId(pReply, classObjId);
+
+  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 uint8_t* 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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  static const uint8_t genericSignature[1] = "";
+
+  RefTypeId refTypeId = ReadRefTypeId(&buf);
+
+  LOG(VERBOSE) << StringPrintf("  Req for signature of refTypeId=0x%llx", refTypeId);
+  const char* signature = Dbg::GetSignature(refTypeId);
+  if (signature != NULL) {
+    expandBufAddUtf8String(pReply, (const uint8_t*) signature);
+  } else {
+    LOG(WARNING) << StringPrintf("No signature for refTypeId=0x%llx", refTypeId);
+    expandBufAddUtf8String(pReply, (const uint8_t*) "Lunknown;");
+  }
+  expandBufAddUtf8String(pReply, genericSignature);
+
+  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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId refTypeId = ReadRefTypeId(&buf);
+
+  expandBufAddObjectId(pReply, Dbg::GetClassLoader(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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId refTypeId = ReadRefTypeId(&buf);
+  LOG(VERBOSE) << StringPrintf("  Req for fields in refTypeId=0x%llx", refTypeId);
+  LOG(VERBOSE) << StringPrintf("  --> '%s'", Dbg::GetSignature(refTypeId));
+  Dbg::OutputAllFields(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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId refTypeId = ReadRefTypeId(&buf);
+
+  LOG(VERBOSE) << StringPrintf("  Req for methods in refTypeId=0x%llx", refTypeId);
+  LOG(VERBOSE) << StringPrintf("  --> '%s'", Dbg::GetSignature(refTypeId));
+
+  Dbg::OutputAllMethods(refTypeId, true, pReply);
+
+  return ERR_NONE;
+}
+
+/*
+ * Return the immediate superclass of a class.
+ */
+static JdwpError handleCT_Superclass(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId classId = ReadRefTypeId(&buf);
+
+  RefTypeId superClassId = Dbg::GetSuperclass(classId);
+
+  expandBufAddRefTypeId(pReply, superClassId);
+
+  return ERR_NONE;
+}
+
+/*
+ * Set static class values.
+ */
+static JdwpError handleCT_SetValues(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId classId = ReadRefTypeId(&buf);
+  uint32_t values = read4BE(&buf);
+
+  LOG(VERBOSE) << StringPrintf("  Req to set %d values in classId=%llx", values, classId);
+
+  for (uint32_t i = 0; i < values; i++) {
+    FieldId fieldId = ReadFieldId(&buf);
+    uint8_t fieldTag = Dbg::GetStaticFieldBasicTag(classId, fieldId);
+    int width = Dbg::GetTagWidth(fieldTag);
+    uint64_t value = jdwpReadValue(&buf, width);
+
+    LOG(VERBOSE) << StringPrintf("    --> field=%x tag=%c -> %lld", fieldId, fieldTag, value);
+    Dbg::SetStaticFieldValue(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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId classId = ReadRefTypeId(&buf);
+  ObjectId threadId = ReadObjectId(&buf);
+  MethodId methodId = ReadMethodId(&buf);
+
+  return finishInvoke(state, buf, dataLen, pReply, threadId, 0, classId, methodId, false);
+}
+
+/*
+ * Create a new object of the requested type, and invoke the specified
+ * constructor.
+ *
+ * Example: in IntelliJ, create a watch on "new String(myByteArray)" to
+ * see the contents of a byte[] as a string.
+ */
+static JdwpError handleCT_NewInstance(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId classId = ReadRefTypeId(&buf);
+  ObjectId threadId = ReadObjectId(&buf);
+  MethodId methodId = ReadMethodId(&buf);
+
+  LOG(VERBOSE) << "Creating instance of " << Dbg::GetClassDescriptor(classId);
+  ObjectId objectId = Dbg::CreateObject(classId);
+  if (objectId == 0) {
+    return ERR_OUT_OF_MEMORY;
+  }
+  return finishInvoke(state, buf, dataLen, pReply, threadId, objectId, classId, methodId, true);
+}
+
+/*
+ * Create a new array object of the requested type and length.
+ */
+static JdwpError handleAT_newInstance(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId arrayTypeId = ReadRefTypeId(&buf);
+  uint32_t length = read4BE(&buf);
+
+  LOG(VERBOSE) << StringPrintf("Creating array %s[%u]", Dbg::GetClassDescriptor(arrayTypeId), length);
+  ObjectId objectId = Dbg::CreateArrayObject(arrayTypeId, length);
+  if (objectId == 0) {
+    return ERR_OUT_OF_MEMORY;
+  }
+  expandBufAdd1(pReply, JT_ARRAY);
+  expandBufAddObjectId(pReply, objectId);
+  return ERR_NONE;
+}
+
+/*
+ * Return line number information for the method, if present.
+ */
+static JdwpError handleM_LineTable(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId refTypeId = ReadRefTypeId(&buf);
+  MethodId methodId = ReadMethodId(&buf);
+
+  LOG(VERBOSE) << StringPrintf("  Req for line table in %s.%s", Dbg::GetClassDescriptor(refTypeId), Dbg::GetMethodName(refTypeId,methodId));
+
+  Dbg::OutputLineTable(refTypeId, methodId, pReply);
+
+  return ERR_NONE;
+}
+
+/*
+ * Pull out the LocalVariableTable goodies.
+ */
+static JdwpError handleM_VariableTableWithGeneric(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId classId = ReadRefTypeId(&buf);
+  MethodId methodId = ReadMethodId(&buf);
+
+  LOG(VERBOSE) << StringPrintf("  Req for LocalVarTab in class=%s method=%s",
+  Dbg::GetClassDescriptor(classId),
+  Dbg::GetMethodName(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.
+   */
+  Dbg::OutputVariableTable(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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId objectId = ReadObjectId(&buf);
+  LOG(VERBOSE) << StringPrintf("  Req for type of objectId=0x%llx", objectId);
+
+  uint8_t refTypeTag;
+  RefTypeId typeId;
+  Dbg::GetObjectType(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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId objectId = ReadObjectId(&buf);
+  uint32_t numFields = read4BE(&buf);
+
+  LOG(VERBOSE) << StringPrintf("  Req for %d fields from objectId=0x%llx", numFields, objectId);
+
+  expandBufAdd4BE(pReply, numFields);
+
+  for (uint32_t i = 0; i < numFields; i++) {
+    FieldId fieldId = ReadFieldId(&buf);
+    Dbg::GetFieldValue(objectId, fieldId, pReply);
+  }
+
+  return ERR_NONE;
+}
+
+/*
+ * Set values in the fields of an object.
+ */
+static JdwpError handleOR_SetValues(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId objectId = ReadObjectId(&buf);
+  uint32_t numFields = read4BE(&buf);
+
+  LOG(VERBOSE) << StringPrintf("  Req to set %d fields in objectId=0x%llx", numFields, objectId);
+
+  for (uint32_t i = 0; i < numFields; i++) {
+    FieldId fieldId = ReadFieldId(&buf);
+
+    uint8_t fieldTag = Dbg::GetFieldBasicTag(objectId, fieldId);
+    int width = Dbg::GetTagWidth(fieldTag);
+    uint64_t value = jdwpReadValue(&buf, width);
+
+    LOG(VERBOSE) << StringPrintf("    --> fieldId=%x tag='%c'(%d) value=%lld", fieldId, fieldTag, width, value);
+
+    Dbg::SetFieldValue(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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId objectId = ReadObjectId(&buf);
+  ObjectId threadId = ReadObjectId(&buf);
+  RefTypeId classId = ReadRefTypeId(&buf);
+  MethodId methodId = ReadMethodId(&buf);
+
+  return finishInvoke(state, buf, dataLen, pReply, threadId, objectId, classId, methodId, false);
+}
+
+/*
+ * Disable garbage collection of the specified object.
+ */
+static JdwpError handleOR_DisableCollection(JdwpState* state, const uint8_t* 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 uint8_t* 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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId objectId;
+
+  objectId = ReadObjectId(&buf);
+  LOG(VERBOSE) << StringPrintf("  Req IsCollected(0x%llx)", 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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId stringObject = ReadObjectId(&buf);
+  char* str = Dbg::StringToUtf8(stringObject);
+
+  LOG(VERBOSE) << StringPrintf("  Req for str %llx --> '%s'", stringObject, str);
+
+  expandBufAddUtf8String(pReply, (uint8_t*) str);
+  free(str);
+
+  return ERR_NONE;
+}
+
+/*
+ * Return a thread's name.
+ */
+static JdwpError handleTR_Name(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId threadId = ReadObjectId(&buf);
+
+  LOG(VERBOSE) << StringPrintf("  Req for name of thread 0x%llx", threadId);
+  char* name = Dbg::GetThreadName(threadId);
+  if (name == NULL) {
+    return ERR_INVALID_THREAD;
+  }
+  expandBufAddUtf8String(pReply, (uint8_t*) 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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId threadId = ReadObjectId(&buf);
+
+  if (threadId == Dbg::GetThreadSelfId()) {
+    LOG(INFO) << "  Warning: ignoring request to suspend self";
+    return ERR_THREAD_NOT_SUSPENDED;
+  }
+  LOG(VERBOSE) << StringPrintf("  Req to suspend thread 0x%llx", threadId);
+  Dbg::SuspendThread(threadId);
+  return ERR_NONE;
+}
+
+/*
+ * Resume the specified thread.
+ */
+static JdwpError handleTR_Resume(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId threadId = ReadObjectId(&buf);
+
+  if (threadId == Dbg::GetThreadSelfId()) {
+    LOG(INFO) << "  Warning: ignoring request to resume self";
+    return ERR_NONE;
+  }
+  LOG(VERBOSE) << StringPrintf("  Req to resume thread 0x%llx", threadId);
+  Dbg::ResumeThread(threadId);
+  return ERR_NONE;
+}
+
+/*
+ * Return status of specified thread.
+ */
+static JdwpError handleTR_Status(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId threadId = ReadObjectId(&buf);
+
+  LOG(VERBOSE) << StringPrintf("  Req for status of thread 0x%llx", threadId);
+
+  uint32_t threadStatus;
+  uint32_t suspendStatus;
+  if (!Dbg::GetThreadStatus(threadId, &threadStatus, &suspendStatus)) {
+    return ERR_INVALID_THREAD;
+  }
+
+  LOG(VERBOSE) << "    --> " << JdwpThreadStatus(threadStatus) << ", " << JdwpSuspendStatus(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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId threadId = ReadObjectId(&buf);
+
+  /* currently not handling these */
+  ObjectId threadGroupId = Dbg::GetThreadGroup(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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId threadId = ReadObjectId(&buf);
+  uint32_t startFrame = read4BE(&buf);
+  uint32_t length = read4BE(&buf);
+
+  if (!Dbg::ThreadExists(threadId)) {
+    return ERR_INVALID_THREAD;
+  }
+  if (!Dbg::IsSuspended(threadId)) {
+    LOG(VERBOSE) << StringPrintf("  Rejecting req for frames in running thread '%s' (%llx)", Dbg::GetThreadName(threadId), threadId);
+    return ERR_THREAD_NOT_SUSPENDED;
+  }
+
+  int frameCount = Dbg::GetThreadFrameCount(threadId);
+
+  LOG(VERBOSE) << StringPrintf("  Request for frames: threadId=%llx start=%d length=%d [count=%d]", threadId, startFrame, length, frameCount);
+  if (frameCount <= 0) {
+    return ERR_THREAD_NOT_SUSPENDED;    /* == 0 means 100% native */
+  }
+  if (length == (uint32_t) -1) {
+    length = frameCount;
+  }
+  CHECK((int) startFrame >= 0 && (int) startFrame < frameCount);
+  CHECK_LE((int) (startFrame + length), frameCount);
+
+  uint32_t frames = length;
+  expandBufAdd4BE(pReply, frames);
+  for (uint32_t i = startFrame; i < (startFrame+length); i++) {
+    FrameId frameId;
+    JdwpLocation loc;
+
+    Dbg::GetThreadFrame(threadId, i, &frameId, &loc);
+
+    expandBufAdd8BE(pReply, frameId);
+    AddLocation(pReply, &loc);
+
+    LOG(VERBOSE) << StringPrintf("    Frame %d: id=%llx loc={type=%d cls=%llx mth=%x loc=%llx}", 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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId threadId = ReadObjectId(&buf);
+
+  if (!Dbg::ThreadExists(threadId)) {
+    return ERR_INVALID_THREAD;
+  }
+  if (!Dbg::IsSuspended(threadId)) {
+    LOG(VERBOSE) << StringPrintf("  Rejecting req for frames in running thread '%s' (%llx)", Dbg::GetThreadName(threadId), threadId);
+    return ERR_THREAD_NOT_SUSPENDED;
+  }
+
+  int frameCount = Dbg::GetThreadFrameCount(threadId);
+  if (frameCount < 0) {
+    return ERR_INVALID_THREAD;
+  }
+  expandBufAdd4BE(pReply, (uint32_t)frameCount);
+
+  return ERR_NONE;
+}
+
+/*
+ * Get the monitor that the thread is waiting on.
+ */
+static JdwpError handleTR_CurrentContendedMonitor(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId threadId;
+
+  threadId = ReadObjectId(&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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId threadId = ReadObjectId(&buf);
+
+  uint32_t suspendCount = Dbg::GetThreadSuspendCount(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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId threadGroupId = ReadObjectId(&buf);
+  LOG(VERBOSE) << StringPrintf("  Req for name of threadGroupId=0x%llx", threadGroupId);
+
+  char* name = Dbg::GetThreadGroupName(threadGroupId);
+  if (name != NULL) {
+    expandBufAddUtf8String(pReply, (uint8_t*) name);
+  } else {
+    expandBufAddUtf8String(pReply, (uint8_t*) "BAD-GROUP-ID");
+    LOG(VERBOSE) << StringPrintf("bad thread group ID");
+  }
+
+  free(name);
+
+  return ERR_NONE;
+}
+
+/*
+ * Returns the thread group -- if any -- that contains the specified
+ * thread group.
+ */
+static JdwpError handleTGR_Parent(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId groupId = ReadObjectId(&buf);
+
+  ObjectId parentGroup = Dbg::GetThreadGroupParent(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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId threadGroupId = ReadObjectId(&buf);
+  LOG(VERBOSE) << StringPrintf("  Req for threads in threadGroupId=0x%llx", threadGroupId);
+
+  ObjectId* pThreadIds;
+  uint32_t threadCount;
+  Dbg::GetThreadGroupThreads(threadGroupId, &pThreadIds, &threadCount);
+
+  expandBufAdd4BE(pReply, threadCount);
+
+  for (uint32_t i = 0; i < 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 == Dbg::GetSystemThreadGroupId()) {
+    expandBufAdd4BE(pReply, 1);
+    expandBufAddObjectId(pReply, Dbg::GetMainThreadGroupId());
+  } else {
+    expandBufAdd4BE(pReply, 0);
+  }
+
+  return ERR_NONE;
+}
+
+/*
+ * Return the #of components in the array.
+ */
+static JdwpError handleAR_Length(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId arrayId = ReadObjectId(&buf);
+  LOG(VERBOSE) << StringPrintf("  Req for length of array 0x%llx", arrayId);
+
+  uint32_t arrayLength = Dbg::GetArrayLength(arrayId);
+
+  LOG(VERBOSE) << StringPrintf("    --> %d", arrayLength);
+
+  expandBufAdd4BE(pReply, arrayLength);
+
+  return ERR_NONE;
+}
+
+/*
+ * Return the values from an array.
+ */
+static JdwpError handleAR_GetValues(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId arrayId = ReadObjectId(&buf);
+  uint32_t firstIndex = read4BE(&buf);
+  uint32_t length = read4BE(&buf);
+
+  uint8_t tag = Dbg::GetArrayElementTag(arrayId);
+  LOG(VERBOSE) << StringPrintf("  Req for array values 0x%llx first=%d len=%d (elem tag=%c)", arrayId, firstIndex, length, tag);
+
+  expandBufAdd1(pReply, tag);
+  expandBufAdd4BE(pReply, length);
+
+  if (!Dbg::OutputArray(arrayId, firstIndex, length, pReply)) {
+    return ERR_INVALID_LENGTH;
+  }
+
+  return ERR_NONE;
+}
+
+/*
+ * Set values in an array.
+ */
+static JdwpError handleAR_SetValues(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId arrayId = ReadObjectId(&buf);
+  uint32_t firstIndex = read4BE(&buf);
+  uint32_t values = read4BE(&buf);
+
+  LOG(VERBOSE) << StringPrintf("  Req to set array values 0x%llx first=%d count=%d", arrayId, firstIndex, values);
+
+  if (!Dbg::SetArrayElements(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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId classLoaderObject;
+  uint32_t numClasses = 0;
+  RefTypeId* classRefBuf = NULL;
+  int i;
+
+  classLoaderObject = ReadObjectId(&buf);
+
+  Dbg::GetVisibleClassList(classLoaderObject, &numClasses, &classRefBuf);
+
+  expandBufAdd4BE(pReply, numClasses);
+  for (i = 0; i < (int) numClasses; i++) {
+    uint8_t refTypeTag = Dbg::GetClassObjectType(classRefBuf[i]);
+
+    expandBufAdd1(pReply, refTypeTag);
+    expandBufAddRefTypeId(pReply, classRefBuf[i]);
+  }
+
+  return ERR_NONE;
+}
+
+/*
+ * Return a newly-allocated string in which all occurrences of '.' have
+ * been changed to '/'.  If we find a '/' in the original string, NULL
+ * is returned to avoid ambiguity.
+ */
+char* dvmDotToSlash(const char* str) {
+  char* newStr = strdup(str);
+  char* cp = newStr;
+
+  if (newStr == NULL) {
+    return NULL;
+  }
+
+  while (*cp != '\0') {
+    if (*cp == '/') {
+      CHECK(false);
+      return NULL;
+    }
+    if (*cp == '.') {
+      *cp = '/';
+    }
+    cp++;
+  }
+
+  return newStr;
+}
+
+/*
+ * Set an event trigger.
+ *
+ * Reply with a requestID.
+ */
+static JdwpError handleER_Set(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  const uint8_t* origBuf = buf;
+
+  uint8_t eventKind = read1(&buf);
+  uint8_t suspendPolicy = read1(&buf);
+  uint32_t modifierCount = read4BE(&buf);
+
+  LOG(VERBOSE) << "  Set(kind=" << JdwpEventKind(eventKind)
+               << " suspend=" << JdwpSuspendPolicy(suspendPolicy)
+               << " mods=" << modifierCount << ")";
+
+  CHECK_LT(modifierCount, 256U);    /* reasonableness check */
+
+  JdwpEvent* pEvent = EventAlloc(modifierCount);
+  pEvent->eventKind = static_cast<JdwpEventKind>(eventKind);
+  pEvent->suspendPolicy = static_cast<JdwpSuspendPolicy>(suspendPolicy);
+  pEvent->modCount = modifierCount;
+
+  /*
+   * Read modifiers.  Ordering may be significant (see explanation of Count
+   * mods in JDWP doc).
+   */
+  for (uint32_t idx = 0; idx < modifierCount; idx++) {
+    uint8_t modKind = read1(&buf);
+
+    pEvent->mods[idx].modKind = modKind;
+
+    switch (modKind) {
+    case MK_COUNT:          /* report once, when "--count" reaches 0 */
+      {
+        uint32_t count = read4BE(&buf);
+        LOG(VERBOSE) << "    Count: " << count;
+        if (count == 0) {
+          return ERR_INVALID_COUNT;
+        }
+        pEvent->mods[idx].count.count = count;
+      }
+      break;
+    case MK_CONDITIONAL:    /* conditional on expression) */
+      {
+        uint32_t exprId = read4BE(&buf);
+        LOG(VERBOSE) << "    Conditional: " << exprId;
+        pEvent->mods[idx].conditional.exprId = exprId;
+      }
+      break;
+    case MK_THREAD_ONLY:    /* only report events in specified thread */
+      {
+        ObjectId threadId = ReadObjectId(&buf);
+        LOG(VERBOSE) << StringPrintf("    ThreadOnly: %llx", threadId);
+        pEvent->mods[idx].threadOnly.threadId = threadId;
+      }
+      break;
+    case MK_CLASS_ONLY:     /* for ClassPrepare, MethodEntry */
+      {
+        RefTypeId clazzId = ReadRefTypeId(&buf);
+        LOG(VERBOSE) << StringPrintf("    ClassOnly: %llx (%s)", clazzId, Dbg::GetClassDescriptor(clazzId));
+        pEvent->mods[idx].classOnly.refTypeId = clazzId;
+      }
+      break;
+    case MK_CLASS_MATCH:    /* restrict events to matching classes */
+      {
+        char* pattern;
+        size_t strLen;
+
+        pattern = readNewUtf8String(&buf, &strLen);
+        LOG(VERBOSE) << StringPrintf("    ClassMatch: '%s'", 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);
+        LOG(VERBOSE) << StringPrintf("    ClassExclude: '%s'", 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);
+        LOG(VERBOSE) << StringPrintf("    LocationOnly: typeTag=%d classId=%llx methodId=%x idx=%llx",
+        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 */
+        uint8_t caught, uncaught;
+
+        exceptionOrNull = ReadRefTypeId(&buf);
+        caught = read1(&buf);
+        uncaught = read1(&buf);
+        LOG(VERBOSE) << StringPrintf("    ExceptionOnly: type=%llx(%s) caught=%d uncaught=%d",
+            exceptionOrNull, (exceptionOrNull == 0) ? "null" : Dbg::GetClassDescriptor(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 = ReadRefTypeId(&buf);
+        FieldId fieldId = ReadFieldId(&buf);
+        LOG(VERBOSE) << StringPrintf("    FieldOnly: %llx %x", 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;
+        uint32_t size, depth;
+
+        threadId = ReadObjectId(&buf);
+        size = read4BE(&buf);
+        depth = read4BE(&buf);
+        LOG(VERBOSE) << StringPrintf("    Step: thread=%llx", threadId)
+                     << " size=" << JdwpStepSize(size) << " depth=" << JdwpStepDepth(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 = ReadObjectId(&buf);
+        LOG(VERBOSE) << StringPrintf("    InstanceOnly: %llx", instance);
+        pEvent->mods[idx].instanceOnly.objectId = instance;
+      }
+      break;
+    default:
+      LOG(WARNING) << "GLITCH: unsupported modKind=" << 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) {
+    LOG(WARNING) << "GLITCH: dataLen is " << dataLen << ", we have consumed " << (buf - origBuf);
+  }
+
+  /*
+   * We reply with an integer "requestID".
+   */
+  uint32_t requestId = NextEventSerial(state);
+  expandBufAdd4BE(pReply, requestId);
+
+  pEvent->requestId = requestId;
+
+  LOG(VERBOSE) << StringPrintf("    --> event requestId=%#x", requestId);
+
+  /* add it to the list */
+  JdwpError err = RegisterEvent(state, pEvent);
+  if (err != ERR_NONE) {
+    /* registration failed, probably because event is bogus */
+    EventFree(pEvent);
+    LOG(WARNING) << "WARNING: event request rejected";
+  }
+  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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  uint8_t eventKind;
+  eventKind = read1(&buf);
+  uint32_t requestId = read4BE(&buf);
+
+  LOG(VERBOSE) << StringPrintf("  Req to clear eventKind=%d requestId=%#x", eventKind, requestId);
+
+  UnregisterEventById(state, requestId);
+
+  return ERR_NONE;
+}
+
+/*
+ * Return the values of arguments and local variables.
+ */
+static JdwpError handleSF_GetValues(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId threadId = ReadObjectId(&buf);
+  FrameId frameId = ReadFrameId(&buf);
+  uint32_t slots = read4BE(&buf);
+
+  LOG(VERBOSE) << StringPrintf("  Req for %d slots in threadId=%llx frameId=%llx", slots, threadId, frameId);
+
+  expandBufAdd4BE(pReply, slots);     /* "int values" */
+  for (uint32_t i = 0; i < slots; i++) {
+    uint32_t slot = read4BE(&buf);
+    uint8_t reqSigByte = read1(&buf);
+
+    LOG(VERBOSE) << StringPrintf("    --> slot %d '%c'", slot, reqSigByte);
+
+    int width = Dbg::GetTagWidth(reqSigByte);
+    uint8_t* ptr = expandBufAddSpace(pReply, width+1);
+    Dbg::GetLocalValue(threadId, frameId, slot, reqSigByte, ptr, width);
+  }
+
+  return ERR_NONE;
+}
+
+/*
+ * Set the values of arguments and local variables.
+ */
+static JdwpError handleSF_SetValues(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId threadId = ReadObjectId(&buf);
+  FrameId frameId = ReadFrameId(&buf);
+  uint32_t slots = read4BE(&buf);
+
+  LOG(VERBOSE) << StringPrintf("  Req to set %d slots in threadId=%llx frameId=%llx", slots, threadId, frameId);
+
+  for (uint32_t i = 0; i < slots; i++) {
+    uint32_t slot = read4BE(&buf);
+    uint8_t sigByte = read1(&buf);
+    int width = Dbg::GetTagWidth(sigByte);
+    uint64_t value = jdwpReadValue(&buf, width);
+
+    LOG(VERBOSE) << StringPrintf("    --> slot %d '%c' %llx", slot, sigByte, value);
+    Dbg::SetLocalValue(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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  ObjectId threadId = ReadObjectId(&buf);
+  FrameId frameId = ReadFrameId(&buf);
+
+  ObjectId objectId;
+  if (!Dbg::GetThisObject(threadId, frameId, &objectId)) {
+    return ERR_INVALID_FRAMEID;
+  }
+
+  uint8_t objectTag = Dbg::GetObjectTag(objectId);
+  LOG(VERBOSE) << StringPrintf("  Req for 'this' in thread=%llx frame=%llx --> %llx %s '%c'", threadId, frameId, objectId, Dbg::GetObjectTypeName(objectId), (char)objectTag);
+
+  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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  RefTypeId classObjectId = ReadRefTypeId(&buf);
+
+  LOG(VERBOSE) << StringPrintf("  Req for refTypeId for class=%llx (%s)", classObjectId, Dbg::GetClassDescriptor(classObjectId));
+
+  /* just hand the type back to them */
+  if (Dbg::IsInterface(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 uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  uint8_t* replyBuf = NULL;
+  int replyLen = -1;
+
+  LOG(VERBOSE) << StringPrintf("  Handling DDM packet (%.4s)", buf);
+
+  /*
+   * On first DDM packet, notify all handlers that DDM is running.
+   */
+  if (!state->ddmActive) {
+    state->ddmActive = true;
+    Dbg::DdmConnected();
+  }
+
+  /*
+   * 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 (Dbg::DdmHandlePacket(buf, dataLen, &replyBuf, &replyLen)) {
+    CHECK(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 uint8_t* buf, int dataLen, ExpandBuf* reply);
+
+struct JdwpHandlerMap {
+  uint8_t  cmdSet;
+  uint8_t  cmd;
+  JdwpRequestHandler  func;
+  const char* descr;
+};
+
+/*
+ * 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, handleRT_ClassObject,   "ReferenceType.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,  handleCT_NewInstance,   "ClassType.NewInstance" },
+
+  /* ArrayType command set (4) */
+  { 4,    1,  handleAT_newInstance,   "ArrayType.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 ProcessRequest(JdwpState* state, const JdwpReqHeader* pHeader, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+  JdwpError result = ERR_NONE;
+  int i, respLen;
+
+  if (pHeader->cmdSet != kJDWPDdmCmdSet) {
+    /*
+     * Activity from a debugger, not merely ddms.  Mark us as having an
+     * active debugger session, and zero out the last-activity timestamp
+     * so waitForDebugger() doesn't return if we stall for a bit here.
+     */
+    Dbg::Active();
+    QuasiAtomicSwap64(0, &state->lastActivityWhen);
+  }
+
+  /*
+   * 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.
+   */
+  SetWaitForEventThread(state, 0);
+
+  /*
+   * Tell the VM that we're running and shouldn't be interrupted by GC.
+   * Do this after anything that can stall indefinitely.
+   */
+  Dbg::ThreadRunning();
+
+  expandBufAddSpace(pReply, kJDWPHeaderLen);
+
+  for (i = 0; i < (int) arraysize(gHandlerMap); i++) {
+    if (gHandlerMap[i].cmdSet == pHeader->cmdSet && gHandlerMap[i].cmd == pHeader->cmd) {
+      LOG(VERBOSE) << StringPrintf("REQ: %s (cmd=%d/%d dataLen=%d id=0x%06x)", gHandlerMap[i].descr, pHeader->cmdSet, pHeader->cmd, dataLen, pHeader->id);
+      result = (*gHandlerMap[i].func)(state, buf, dataLen, pReply);
+      break;
+    }
+  }
+  if (i == arraysize(gHandlerMap)) {
+    LOG(ERROR) << StringPrintf("REQ: UNSUPPORTED (cmd=%d/%d dataLen=%d id=0x%06x)", pHeader->cmdSet, pHeader->cmd, dataLen, pHeader->id);
+    if (dataLen > 0) {
+      HexDump(buf, dataLen);
+    }
+    LOG(FATAL) << "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.
+   */
+  uint8_t* 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 (false) {
+    LOG(INFO) << "reply: dataLen=" << respLen << " err=" << result << (result != ERR_NONE ? " **FAILED**" : "");
+    if (respLen > 0) {
+      HexDump(expandBufGetBuffer(pReply) + kJDWPHeaderLen, respLen);
+    }
+  }
+
+  /*
+   * 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) {
+    QuasiAtomicSwap64(GetNowMsec(), &state->lastActivityWhen);
+  }
+
+  /* tell the VM that GC is okay again */
+  Dbg::ThreadWaiting();
+}
+
+}  // namespace JDWP
+
+}  // namespace art
diff --git a/src/jdwp/jdwp_handler.h b/src/jdwp/jdwp_handler.h
new file mode 100644
index 0000000..6fa75d7
--- /dev/null
+++ b/src/jdwp/jdwp_handler.h
@@ -0,0 +1,54 @@
+/*
+ * 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 ART_JDWP_JDWPHANDLER_H_
+#define ART_JDWP_JDWPHANDLER_H_
+
+#include "jdwp_expand_buf.h"
+
+namespace art {
+
+namespace JDWP {
+
+/*
+ * JDWP message header for a request.
+ */
+struct JdwpReqHeader {
+  uint32_t length;
+  uint32_t id;
+  uint8_t cmdSet;
+  uint8_t cmd;
+};
+
+/*
+ * Process a request from the debugger.
+ *
+ * "buf" points past the header, to the content of the message.  "dataLen"
+ * can therefore be zero.
+ */
+void ProcessRequest(JdwpState* state, const JdwpReqHeader* pHeader,
+    const uint8_t* buf, int dataLen, ExpandBuf* pReply);
+
+/* helper function */
+void AddLocation(ExpandBuf* pReply, const JdwpLocation* pLoc);
+
+}  // namespace JDWP
+
+}  // namespace art
+
+#endif  // ART_JDWP_JDWPHANDLER_H_
diff --git a/src/jdwp/jdwp_main.cc b/src/jdwp/jdwp_main.cc
new file mode 100644
index 0000000..f2ff937
--- /dev/null
+++ b/src/jdwp/jdwp_main.cc
@@ -0,0 +1,508 @@
+/*
+ * 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 "atomic.h"
+#include "debugger.h"
+#include "jdwp/jdwp_priv.h"
+#include "logging.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <time.h>
+#include <errno.h>
+
+namespace art {
+
+namespace JDWP {
+
+static void* jdwpThreadStart(void* arg);
+
+/*
+ * JdwpNetStateBase class implementation
+ */
+JdwpNetStateBase::JdwpNetStateBase() : socket_lock_("JdwpNetStateBase lock") {
+  clientSock = -1;
+}
+
+/*
+ * Write a packet. Grabs a mutex to assure atomicity.
+ */
+ssize_t JdwpNetStateBase::writePacket(ExpandBuf* pReply) {
+  MutexLock mu(socket_lock_);
+  return write(clientSock, expandBufGetBuffer(pReply), expandBufGetLength(pReply));
+}
+
+/*
+ * Write a buffered packet. Grabs a mutex to assure atomicity.
+ */
+ssize_t JdwpNetStateBase::writeBufferedPacket(const iovec* iov, int iovcnt) {
+  MutexLock mu(socket_lock_);
+  return writev(clientSock, iov, iovcnt);
+}
+
+bool NetStartup(JdwpState* state, const JdwpStartupParams* pParams) {
+  return (*state->transport->startup)(state, pParams);
+}
+
+bool AcceptConnection(JdwpState* state) {
+  return (*state->transport->accept)(state);
+}
+
+bool EstablishConnection(JdwpState* state) {
+  return (*state->transport->establish)(state);
+}
+
+void CloseConnection(JdwpState* state) {
+  (*state->transport->close)(state);
+}
+
+void NetShutdown(JdwpState* state) {
+  (*state->transport->shutdown)(state);
+}
+
+void NetFree(JdwpState* state) {
+  (*state->transport->free)(state);
+}
+
+bool IsTransportDefined(JdwpState* state) {
+  return state != NULL && state->transport != NULL;
+}
+
+bool JdwpIsConnected(JdwpState* state) {
+  return state != NULL && (*state->transport->isConnected)(state);
+}
+
+bool AwaitingHandshake(JdwpState* state) {
+  return (*state->transport->awaitingHandshake)(state);
+}
+
+bool ProcessIncoming(JdwpState* state) {
+  return (*state->transport->processIncoming)(state);
+}
+
+bool SendRequest(JdwpState* state, ExpandBuf* pReq) {
+  return (*state->transport->sendRequest)(state, pReq);
+}
+
+static void CreateJdwpThread(JdwpState* state) {
+  CHECK_PTHREAD_CALL(pthread_create, (&state->debugThreadHandle, NULL, jdwpThreadStart, state), "JDWP thread");
+}
+
+JdwpState::JdwpState()
+    : thread_start_lock_("JDWP thread start lock"),
+      thread_start_cond_("JDWP thread start condition variable"),
+      debug_thread_started_(false),
+      debugThreadId(0),
+      run(false),
+      transport(NULL),
+      netState(NULL),
+      attach_lock_("JDWP attach lock"),
+      attach_cond_("JDWP attach condition variable"),
+      lastActivityWhen(0),
+      requestSerial(0x10000000),
+      eventSerial(0x20000000),
+      serial_lock_("JDWP serial lock"),
+      numEvents(0),
+      eventList(NULL),
+      event_lock_("JDWP event lock"),
+      event_thread_lock_("JDWP event thread lock"),
+      event_thread_cond_("JDWP event thread condition variable"),
+      eventThreadId(0),
+      ddmActive(false) {
+}
+
+/*
+ * Initialize JDWP.
+ *
+ * Does not return until JDWP thread is running, but may return before
+ * the thread is accepting network connections.
+ */
+JdwpState* JdwpStartup(const JdwpStartupParams* pParams) {
+  /* comment this out when debugging JDWP itself */
+  //android_setMinPriority(LOG_TAG, ANDROID_LOG_DEBUG);
+
+  JdwpState* state = new JdwpState;
+
+  state->params = *pParams;
+
+  switch (pParams->transport) {
+  case kJdwpTransportSocket:
+    // LOGD("prepping for JDWP over TCP");
+    state->transport = SocketTransport();
+    break;
+#ifdef HAVE_ANDROID_OS
+  case kJdwpTransportAndroidAdb:
+    // LOGD("prepping for JDWP over ADB");
+    state->transport = AndroidAdbTransport();
+    break;
+#endif
+  default:
+    LOG(FATAL) << "Unknown transport: " << pParams->transport;
+  }
+
+  if (!NetStartup(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.
+   */
+  state->thread_start_lock_.Lock();
+  if (pParams->suspend) {
+    state->attach_lock_.Lock();
+  }
+
+  /*
+   * 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.
+   */
+  CreateJdwpThread(state);
+
+  /*
+   * Wait until the thread finishes basic initialization.
+   * TODO: cond vars should be waited upon in a loop
+   */
+  state->thread_start_cond_.Wait(state->thread_start_lock_);
+  state->thread_start_lock_.Unlock();
+
+  /*
+   * 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) {
+    {
+      ScopedThreadStateChange tsc(Thread::Current(), Thread::kVmWait);
+
+      state->attach_cond_.Wait(state->attach_lock_);
+      state->attach_lock_.Unlock();
+    }
+
+    if (!JdwpIsActive(state)) {
+      LOG(ERROR) << "JDWP connection failed";
+      goto fail;
+    }
+
+    LOG(INFO) << "JDWP connected";
+
+    /*
+     * 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:
+  JdwpShutdown(state);     // frees state
+  return NULL;
+}
+
+/*
+ * Reset all session-related state.  There should not be an active connection
+ * to the client at this point.  The rest of the VM still thinks there is
+ * a debugger attached.
+ *
+ * This includes freeing up the debugger event list.
+ */
+void ResetState(JdwpState* state) {
+  /* could reset the serial numbers, but no need to */
+
+  UnregisterAll(state);
+  CHECK(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) {
+    LOG(WARNING) << "resetting state while event in progress";
+    DCHECK(false);
+  }
+}
+
+/*
+ * Tell the JDWP thread to shut down.  Frees "state".
+ */
+void JdwpShutdown(JdwpState* state) {
+  void* threadReturn;
+
+  if (state == NULL) {
+    return;
+  }
+
+  if (IsTransportDefined(state)) {
+    if (JdwpIsConnected(state)) {
+      PostVMDeath(state);
+    }
+
+    /*
+     * Close down the network to inspire the thread to halt.
+     */
+    LOG(DEBUG) << "JDWP shutting down net...";
+    NetShutdown(state);
+
+    if (state->debug_thread_started_) {
+      state->run = false;
+      if (pthread_join(state->debugThreadHandle, &threadReturn) != 0) {
+        LOG(WARNING) << "JDWP thread join failed";
+      }
+    }
+
+    LOG(DEBUG) << "JDWP freeing netstate...";
+    NetFree(state);
+    state->netState = NULL;
+  }
+  CHECK(state->netState == NULL);
+
+  ResetState(state);
+  free(state);
+}
+
+/*
+ * Are we talking to a debugger?
+ */
+bool JdwpIsActive(JdwpState* state) {
+  return JdwpIsConnected(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 = reinterpret_cast<JdwpState*>(arg);
+  CHECK(state != NULL);
+
+  Runtime* runtime = Runtime::Current();
+  runtime->AttachCurrentThread("JDWP", true);
+
+  LOG(VERBOSE) << "JDWP: thread running";
+
+  /*
+   * Finish initializing "state", then notify the creating thread that
+   * we're running.
+   */
+  state->debugThreadHandle = pthread_self();
+  state->run = true;
+  android_atomic_release_store(true, &state->debug_thread_started_);
+
+  state->thread_start_lock_.Lock();
+  state->thread_start_cond_.Wait(state->thread_start_lock_);
+  state->thread_start_lock_.Unlock();
+
+  /* set the thread state to VMWAIT so GCs don't wait for us */
+  Dbg::ThreadWaiting();
+
+  /*
+   * 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;
+
+    if (state->params.server) {
+      /*
+       * Block forever, waiting for a connection.  To support the
+       * "timeout=xxx" option we'll need to tweak this.
+       */
+      if (!AcceptConnection(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 (!EstablishConnection(state)) {
+        /* wake anybody who was waiting for us to succeed */
+        MutexLock mu(state->attach_lock_);
+        state->attach_cond_.Broadcast();
+        break;
+      }
+    }
+
+    /* prep debug code to handle the new connection */
+    Dbg::Connected();
+
+    /* process requests until the debugger drops */
+    first = true;
+    while (true) {
+      // sanity check -- shouldn't happen?
+      if (Thread::Current()->GetState() != Thread::kVmWait) {
+        LOG(ERROR) << "JDWP thread no longer in VMWAIT (now " << Thread::Current()->GetState() << "); resetting";
+        Dbg::ThreadWaiting();
+      }
+
+      if (!ProcessIncoming(state)) {
+        /* blocking read */
+        break;
+      }
+
+      if (first && !AwaitingHandshake(state)) {
+        /* handshake worked, tell the interpreter that we're active */
+        first = false;
+
+        /* set thread ID; requires object registry to be active */
+        state->debugThreadId = Dbg::GetThreadSelfId();
+
+        /* wake anybody who's waiting for us */
+        MutexLock mu(state->attach_lock_);
+        state->attach_cond_.Broadcast();
+      }
+    }
+
+    CloseConnection(state);
+
+    if (state->ddmActive) {
+      state->ddmActive = false;
+
+      /* broadcast the disconnect; must be in RUNNING state */
+      Dbg::ThreadRunning();
+      Dbg::DdmDisconnected();
+      Dbg::ThreadWaiting();
+    }
+
+    /* release session state, e.g. remove breakpoint instructions */
+    ResetState(state);
+
+    /* tell the interpreter that the debugger is no longer around */
+    Dbg::Disconnected();
+
+    /* if we had threads suspended, resume them now */
+    Dbg::UndoDebuggerSuspensions();
+
+    /* if we connected out, this was a one-shot deal */
+    if (!state->params.server) {
+      state->run = false;
+    }
+  }
+
+  /* back to running, for thread shutdown */
+  Dbg::ThreadRunning();
+
+  LOG(VERBOSE) << "JDWP: thread exiting";
+  return NULL;
+}
+
+
+/*
+ * Return the thread handle, or (pthread_t)0 if the debugger isn't running.
+ */
+pthread_t GetDebugThread(JdwpState* state) {
+  if (state == NULL) {
+    return 0;
+  }
+  return state->debugThreadHandle;
+}
+
+/*
+ * Support routines for waitForDebugger().
+ *
+ * We can't have a trivial "waitForDebugger" function that returns the
+ * instant the debugger connects, because 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 JDWP 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 approximate this with a "wait until the debugger
+ * has been idle for a brief period".
+ */
+
+/*
+ * Get a notion of the current time, in milliseconds.
+ */
+int64_t GetNowMsec() {
+#ifdef HAVE_POSIX_CLOCKS
+  struct timespec now;
+  clock_gettime(CLOCK_MONOTONIC, &now);
+  return now.tv_sec * 1000LL + now.tv_nsec / 1000000LL;
+#else
+  struct timeval now;
+  gettimeofday(&now, NULL);
+  return now.tv_sec * 1000LL + now.tv_usec / 1000LL;
+#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.
+ */
+int64_t LastDebuggerActivity(JdwpState* state) {
+  if (!Dbg::IsDebuggerConnected()) {
+    LOG(DEBUG) << "no active debugger";
+    return -1;
+  }
+
+  int64_t last = QuasiAtomicRead64(&state->lastActivityWhen);
+
+  /* initializing or in the middle of something? */
+  if (last == 0) {
+    LOG(VERBOSE) << "+++ last=busy";
+    return 0;
+  }
+
+  /* now get the current time */
+  int64_t now = GetNowMsec();
+  CHECK_GT(now, last);
+
+  LOG(VERBOSE) << "+++ debugger interval=" << (now - last);
+  return now - last;
+}
+
+static const char* kTransportNames[] = {
+  "Unknown",
+  "Socket",
+  "AndroidAdb",
+};
+std::ostream& operator<<(std::ostream& os, const JdwpTransportType& value) {
+  int32_t int_value = static_cast<int32_t>(value);
+  if (value >= kJdwpTransportUnknown && value <= kJdwpTransportAndroidAdb) {
+    os << kTransportNames[int_value];
+  } else {
+    os << "JdwpTransportType[" << int_value << "]";
+  }
+  return os;
+}
+
+}  // namespace JDWP
+
+}  // namespace art
diff --git a/src/jdwp/jdwp_priv.h b/src/jdwp/jdwp_priv.h
new file mode 100644
index 0000000..60169dd
--- /dev/null
+++ b/src/jdwp/jdwp_priv.h
@@ -0,0 +1,156 @@
+/*
+ * 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 ART_JDWP_JDWPPRIV_H_
+#define ART_JDWP_JDWPPRIV_H_
+
+#define LOG_TAG "jdwp"
+
+#include "debugger.h"
+#include "jdwp/jdwp.h"
+#include "jdwp/jdwp_event.h"
+#include "../mutex.h" // TODO: fix our include path!
+
+#include <pthread.h>
+#include <sys/uio.h>
+
+/*
+ * JDWP constants.
+ */
+#define kJDWPHeaderLen  11
+#define kJDWPFlagReply  0x80
+
+/* DDM support */
+#define kJDWPDdmCmdSet  199     /* 0xc7, or 'G'+128 */
+#define kJDWPDdmCmd     1
+
+namespace art {
+
+namespace JDWP {
+
+/*
+ * Transport-specific network status.
+ */
+struct JdwpNetState;
+struct JdwpState;
+
+/*
+ * Transport functions.
+ */
+struct JdwpTransport {
+  bool (*startup)(JdwpState* state, const JdwpStartupParams* pParams);
+  bool (*accept)(JdwpState* state);
+  bool (*establish)(JdwpState* state);
+  void (*close)(JdwpState* state);
+  void (*shutdown)(JdwpState* state);
+  void (*free)(JdwpState* state);
+  bool (*isConnected)(JdwpState* state);
+  bool (*awaitingHandshake)(JdwpState* state);
+  bool (*processIncoming)(JdwpState* state);
+  bool (*sendRequest)(JdwpState* state, ExpandBuf* pReq);
+  bool (*sendBufferedRequest)(JdwpState* state, const iovec* iov, int iovcnt);
+};
+
+const JdwpTransport* SocketTransport();
+const JdwpTransport* AndroidAdbTransport();
+
+
+/*
+ * State for JDWP functions.
+ */
+struct JdwpState {
+  JdwpState();
+
+  JdwpStartupParams params;
+
+  /* wait for creation of the JDWP thread */
+  Mutex thread_start_lock_;
+  ConditionVariable thread_start_cond_;
+
+  volatile int32_t debug_thread_started_;
+  pthread_t debugThreadHandle;
+  ObjectId debugThreadId;
+  bool run;
+
+  const JdwpTransport* transport;
+  JdwpNetState* netState;
+
+  /* for wait-for-debugger */
+  Mutex attach_lock_;
+  ConditionVariable attach_cond_;
+
+  /* time of last debugger activity, in milliseconds */
+  int64_t lastActivityWhen;
+
+  /* global counters and a mutex to protect them */
+  uint32_t requestSerial;
+  uint32_t eventSerial;
+  Mutex serial_lock_;
+
+  /*
+   * Events requested by the debugger (breakpoints, class prep, etc).
+   */
+  int numEvents;      /* #of elements in eventList */
+  JdwpEvent* eventList;      /* linked list of events */
+  Mutex event_lock_;      /* guards numEvents/eventList */
+
+  /*
+   * Synchronize suspension of event thread (to avoid receiving "resume"
+   * events before the thread has finished suspending itself).
+   */
+  Mutex event_thread_lock_;
+  ConditionVariable event_thread_cond_;
+  ObjectId eventThreadId;
+
+  /*
+   * DDM support.
+   */
+  bool ddmActive;
+};
+
+/*
+ * Base class for JdwpNetState
+ */
+class JdwpNetStateBase {
+public:
+  int clientSock;     /* active connection to debugger */
+
+  JdwpNetStateBase();
+  ssize_t writePacket(ExpandBuf* pReply);
+  ssize_t writeBufferedPacket(const iovec* iov, int iovcnt);
+
+private:
+  Mutex socket_lock_;
+};
+
+
+/* reset all session-specific data */
+void ResetState(JdwpState* state);
+
+/* atomic ops to get next serial number */
+uint32_t NextRequestSerial(JdwpState* state);
+uint32_t NextEventSerial(JdwpState* state);
+
+/* get current time, in msec */
+int64_t GetNowMsec();
+
+}  // namespace JDWP
+
+}  // namespace art
+
+#endif  // ART_JDWP_JDWPPRIV_H_
diff --git a/src/jdwp/jdwp_socket.cc b/src/jdwp/jdwp_socket.cc
new file mode 100644
index 0000000..b4062d0
--- /dev/null
+++ b/src/jdwp/jdwp_socket.cc
@@ -0,0 +1,883 @@
+/*
+ * 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/jdwp_priv.h"
+#include "jdwp/jdwp_handler.h"
+#include "logging.h"
+#include "stringprintf.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)
+
+namespace art {
+
+namespace JDWP {
+
+// 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 : public JdwpNetStateBase {
+    short   listenPort;
+    int     listenSock;         /* listen for connection from 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;
+
+    JdwpNetState()
+    {
+        listenPort  = 0;
+        listenSock  = -1;
+        wakePipe[0] = -1;
+        wakePipe[1] = -1;
+
+        awaitingHandshake = false;
+
+        inputCount = 0;
+    }
+};
+
+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) {
+      LOG(ERROR) << "JDWP net startup failed (req port=" << pParams->port << ")";
+      return false;
+    }
+  } else {
+    port = pParams->port;   // used in a debug msg later
+    state->netState = netStartup(-1);
+  }
+
+  if (pParams->suspend) {
+    LOG(INFO) << "JDWP will wait for debugger on port " << port;
+  } else {
+    LOG(INFO) << "JDWP will " << (pParams->server ? "listen" : "connect") << " on port " << 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) {
+  int one = 1;
+  JdwpNetState* netState = new JdwpNetState;
+
+  if (port < 0) {
+    return netState;
+  }
+
+  CHECK_NE(port, 0);
+
+  netState->listenSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+  if (netState->listenSock < 0) {
+    PLOG(ERROR) << "Socket create failed";
+    goto fail;
+  }
+
+  /* allow immediate re-use */
+  if (setsockopt(netState->listenSock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) {
+    PLOG(ERROR) << "setsockopt(SO_REUSEADDR) failed";
+    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) {
+    PLOG(VERBOSE) << "attempt to bind to port " << port << " failed";
+    goto fail;
+  }
+
+  netState->listenPort = port;
+  LOG(VERBOSE) << "+++ bound to port " << netState->listenPort;
+
+  if (listen(netState->listenSock, 5) != 0) {
+    PLOG(ERROR) << "Listen failed";
+    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) {
+    LOG(VERBOSE) << "+++ writing to wakePipe";
+    (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;
+  }
+  CHECK_EQ(netState->listenSock, -1);
+  CHECK_EQ(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;
+  }
+
+  delete 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.
+ */
+#if 0
+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;
+
+  LOG(ERROR) << "WEIRD: odd behavior in select (count=" << count << ")";
+  return false;
+}
+#endif
+
+#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;
+
+    CHECK_GE(netState->listenSock, 0);
+    /* not expecting to be called when debugger is actively connected */
+    CHECK_LT(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 on = 1;
+    int cc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
+    CHECK_EQ(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! */
+  }
+
+  CHECK_LT(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) {
+        PLOG(VERBOSE) << "accept failed";
+      } else {
+        PLOG(ERROR) << "accept failed";
+        return false;
+      }
+    }
+  } while (sock < 0);
+
+  netState->remoteAddr = addr.addrInet.sin_addr;
+  netState->remotePort = ntohs(addr.addrInet.sin_port);
+  LOG(VERBOSE) << "+++ accepted connection from " << inet_ntoa(netState->remoteAddr) << ":" << netState->remotePort;
+
+  netState->clientSock = sock;
+  netState->awaitingHandshake = true;
+  netState->inputCount = 0;
+
+  LOG(VERBOSE) << "Setting TCP_NODELAY on accepted socket";
+  setNoDelay(netState->clientSock);
+
+  if (pipe(netState->wakePipe) < 0) {
+    PLOG(ERROR) << "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;
+
+  CHECK(state != NULL && state->netState != NULL);
+  CHECK(!state->params.server);
+  CHECK_NE(state->params.host[0], '\0');
+  CHECK_NE(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;
+  char auxBuf[128];
+  int error;
+  int cc = gethostbyname_r(state->params.host, &he, auxBuf, sizeof(auxBuf), &pEntry, &error);
+  if (cc != 0) {
+    LOG(WARNING) << "gethostbyname_r('" << state->params.host << "') failed: " << hstrerror(error);
+    return false;
+  }
+#else
+  h_errno = 0;
+  pEntry = gethostbyname(state->params.host);
+  if (pEntry == NULL) {
+    PLOG(WARNING) << "gethostbyname('" << state->params.host << "') failed";
+    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);
+
+  LOG(INFO) << "Connecting out to " << 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) {
+    PLOG(ERROR) << "Unable to create socket";
+    return false;
+  }
+
+  /*
+   * Try to connect.
+   */
+  if (connect(netState->clientSock, &addr.addrPlain, sizeof(addr)) != 0) {
+    PLOG(ERROR) << "Unable to connect to " << inet_ntoa(addr.addrInet.sin_addr) << ":" << ntohs(addr.addrInet.sin_port);
+    close(netState->clientSock);
+    netState->clientSock = -1;
+    return false;
+  }
+
+  LOG(INFO) << "Connection established to " << 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) {
+    PLOG(ERROR) << "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;
+
+  CHECK(state != NULL && state->netState != NULL);
+
+  netState = state->netState;
+  if (netState->clientSock < 0) {
+    return;
+  }
+
+  LOG(VERBOSE) << "+++ closed connection to " << inet_ntoa(netState->remoteAddr) << ":" << netState->remotePort;
+
+  close(netState->clientSock);
+  netState->clientSock = -1;
+}
+
+/*
+ * Figure out if we have a full packet in the buffer.
+ */
+static bool haveFullPacket(JdwpNetState* netState) {
+  if (netState->awaitingHandshake) {
+    return (netState->inputCount >= (int) kMagicHandshakeLen);
+  }
+  if (netState->inputCount < 4) {
+    return false;
+  }
+  long 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) {
+  CHECK_GT(count, 0);
+  CHECK_LE(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.
+ */
+#if 0
+static void dumpPacket(const unsigned char* packetBuf)
+{
+    const unsigned char* buf = packetBuf;
+    uint32_t length, id;
+    uint8_t flags, cmdSet, cmd;
+    uint16_t 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);
+
+    LOG(VERBOSE) << StringPrintf("--- %s: dataLen=%u id=0x%08x flags=0x%02x cmd=%d/%d",
+        reply ? "reply" : "req",
+        dataLen, id, flags, cmdSet, cmd);
+    if (dataLen > 0) {
+      HexDump(buf, dataLen);
+    }
+}
+#endif
+
+/*
+ * 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;
+    uint32_t length, id;
+    uint8_t flags, cmdSet, cmd;
+    uint16_t 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);
+    }
+
+    CHECK_LE((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;
+        ProcessRequest(state, &hdr, buf, dataLen, pReply);
+        if (expandBufGetLength(pReply) > 0) {
+            ssize_t cc = netState->writePacket(pReply);
+
+            if (cc != (ssize_t) expandBufGetLength(pReply)) {
+              PLOG(ERROR) << "Failed sending reply to debugger";
+              expandBufFree(pReply);
+              return false;
+            }
+        } else {
+          LOG(WARNING) << "No reply created for set=" << cmdSet << " cmd=" << cmd;
+        }
+        expandBufFree(pReply);
+    } else {
+      LOG(ERROR) << "reply?!";
+      DCHECK(false);
+    }
+
+    LOG(VERBOSE) << "----------";
+
+    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;
+
+  CHECK_GE(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) {
+        LOG(VERBOSE) << "+++ all fds are closed";
+        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 {
+        LOG(INFO) << "NOTE: entering select w/o wakepipe";
+      }
+
+      /*
+       * 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;
+        }
+        PLOG(ERROR) << "select failed";
+        goto fail;
+      }
+
+      if (netState->wakePipe[0] >= 0 && FD_ISSET(netState->wakePipe[0], &readfds)) {
+        if (netState->listenSock >= 0) {
+          LOG(ERROR) << "Exit wake set, but not exiting?";
+        } else {
+          LOG(DEBUG) << "Got wake-up signal, bailing out of select";
+        }
+        goto fail;
+      }
+      if (netState->listenSock >= 0 && FD_ISSET(netState->listenSock, &readfds)) {
+        LOG(INFO) << "Ignoring second debugger -- accepting and dropping";
+        union {
+          struct sockaddr_in   addrInet;
+          struct sockaddr      addrPlain;
+        } addr;
+        socklen_t addrlen;
+        int tmpSock;
+        tmpSock = accept(netState->listenSock, &addr.addrPlain, &addrlen);
+        if (tmpSock < 0) {
+          LOG(INFO) << "Weird -- accept failed";
+        } 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;
+          }
+          LOG(DEBUG) << "+++ EINTR hit";
+          return true;
+        } else if (readCount == 0) {
+          /* EOF hit -- far end went away */
+          LOG(DEBUG) << "+++ peer disconnected";
+          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) {
+      LOG(ERROR) << StringPrintf("ERROR: bad handshake '%.14s'", netState->inputBuffer);
+      goto fail;
+    }
+
+    errno = 0;
+    cc = write(netState->clientSock, netState->inputBuffer, kMagicHandshakeLen);
+    if (cc != kMagicHandshakeLen) {
+      PLOG(ERROR) << "Failed writing handshake bytes (" << cc << " of " << kMagicHandshakeLen << ")";
+      goto fail;
+    }
+
+    consumeBytes(netState, kMagicHandshakeLen);
+    netState->awaitingHandshake = false;
+    LOG(VERBOSE) << "+++ handshake complete";
+    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;
+
+  /*dumpPacket(expandBufGetBuffer(pReq));*/
+  if (netState->clientSock < 0) {
+    /* can happen with some DDMS events */
+    LOG(VERBOSE) << "NOT sending request -- no debugger is attached";
+    return false;
+  }
+
+  errno = 0;
+  ssize_t cc = netState->writePacket(pReq);
+
+  if (cc != (ssize_t) expandBufGetLength(pReq)) {
+    PLOG(ERROR) << "Failed sending req to debugger (" << cc << " of " << expandBufGetLength(pReq) << ")";
+    return false;
+  }
+
+  return true;
+}
+
+/*
+ * Send a request that was split into multiple buffers.
+ *
+ * The entire packet must be sent with a single writev() call to avoid
+ * threading issues.
+ *
+ * Returns "true" if it was sent successfully.
+ */
+static bool sendBufferedRequest(JdwpState* state, const iovec* iov, int iovcnt) {
+  JdwpNetState* netState = state->netState;
+
+  if (netState->clientSock < 0) {
+    /* can happen with some DDMS events */
+    LOG(VERBOSE) << "NOT sending request -- no debugger is attached";
+    return false;
+  }
+
+  size_t expected = 0;
+  for (int i = 0; i < iovcnt; i++) {
+    expected += iov[i].iov_len;
+  }
+
+  ssize_t actual = netState->writeBufferedPacket(iov, iovcnt);
+
+  if ((size_t)actual != expected) {
+    PLOG(ERROR) << "Failed sending b-req to debugger (" << actual << " of " << expected << ")";
+    return false;
+  }
+
+  return true;
+}
+
+/*
+ * Our functions.
+ *
+ * We can't generally share the implementations with other transports,
+ * even if they're also socket-based, because our JdwpNetState will be
+ * different from theirs.
+ */
+static const JdwpTransport socketTransport = {
+  prepareSocket,
+  acceptConnection,
+  establishConnection,
+  closeConnection,
+  netShutdownExtern,
+  netFreeExtern,
+  isConnected,
+  awaitingHandshake,
+  processIncoming,
+  sendRequest,
+  sendBufferedRequest,
+};
+
+/*
+ * Return our set.
+ */
+const JdwpTransport* SocketTransport() {
+  return &socketTransport;
+}
+
+}  // namespace JDWP
+
+}  // namespace art
diff --git a/src/logging.cc b/src/logging.cc
index bf6ab91..bb68442 100644
--- a/src/logging.cc
+++ b/src/logging.cc
@@ -63,4 +63,78 @@
   return data_->buffer;
 }
 
+/*
+ * Print a hex dump in this format:
+ *
+ * 01234567: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff  0123456789abcdef\n
+ *
+ * Does not use printf() or other string-formatting calls.
+ */
+void HexDump(const void* address, size_t byte_count, bool show_actual_address) {
+  static const char gHexDigit[] = "0123456789abcdef";
+  const unsigned char* addr = reinterpret_cast<const unsigned char*>(address);
+  char out[77];           /* exact fit */
+  unsigned int offset;    /* offset to show while printing */
+
+  if (show_actual_address) {
+    offset = (int) addr;
+  } else {
+    offset = 0;
+  }
+  memset(out, ' ', sizeof(out)-1);
+  out[8] = ':';
+  out[sizeof(out)-2] = '\n';
+  out[sizeof(out)-1] = '\0';
+
+  int gap = (int) offset & 0x0f;
+  while (byte_count) {
+    unsigned int lineOffset = offset & ~0x0f;
+    int i, count;
+
+    char* hex = out;
+    char* asc = out + 59;
+
+    for (i = 0; i < 8; i++) {
+      *hex++ = gHexDigit[lineOffset >> 28];
+      lineOffset <<= 4;
+    }
+    hex++;
+    hex++;
+
+    count = ((int)byte_count > 16-gap) ? 16-gap : (int)byte_count; /* cap length */
+    CHECK_NE(count, 0);
+    CHECK_LE(count + gap, 16);
+
+    if (gap) {
+      /* only on first line */
+      hex += gap * 3;
+      asc += gap;
+    }
+
+    for (i = gap ; i < count+gap; i++) {
+      *hex++ = gHexDigit[*addr >> 4];
+      *hex++ = gHexDigit[*addr & 0x0f];
+      hex++;
+      if (*addr >= 0x20 && *addr < 0x7f /*isprint(*addr)*/)
+      *asc++ = *addr;
+      else
+      *asc++ = '.';
+      addr++;
+    }
+    for ( ; i < 16; i++) {
+      /* erase extra stuff; only happens on last line */
+      *hex++ = ' ';
+      *hex++ = ' ';
+      hex++;
+      *asc++ = ' ';
+    }
+
+    LOG(INFO) << out;
+
+    gap = 0;
+    byte_count -= count;
+    offset += count;
+  }
+}
+
 }  // namespace art
diff --git a/src/logging.h b/src/logging.h
index d3dbcd4..2e7856e 100644
--- a/src/logging.h
+++ b/src/logging.h
@@ -169,6 +169,8 @@
   DISALLOW_COPY_AND_ASSIGN(LogMessage);
 };
 
+void HexDump(const void* address, size_t byte_count, bool show_actual_address = false);
+
 }  // namespace art
 
 #endif  // ART_SRC_LOGGING_H_
diff --git a/src/org_apache_harmony_dalvik_ddmc_DdmServer.cc b/src/org_apache_harmony_dalvik_ddmc_DdmServer.cc
index 3392689..b0aba2f 100644
--- a/src/org_apache_harmony_dalvik_ddmc_DdmServer.cc
+++ b/src/org_apache_harmony_dalvik_ddmc_DdmServer.cc
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "debugger.h"
 #include "logging.h"
 
 #include "JniConstants.h"  // Last to avoid problems with LOG redefinition.
@@ -28,7 +29,7 @@
 {
   ScopedByteArrayRO data(env, javaData);
   DCHECK_LE(offset + length, static_cast<int32_t>(data.size()));
-  UNIMPLEMENTED(WARNING) << "dvmDbgDdmSendChunk(type, length, data->get() + offset);";
+  Dbg::DdmSendChunk(type, length, reinterpret_cast<const uint8_t*>(&data[offset]));
 }
 
 static JNINativeMethod gMethods[] = {
diff --git a/src/thread.cc b/src/thread.cc
index 42d9f1a..1800834 100644
--- a/src/thread.cc
+++ b/src/thread.cc
@@ -26,6 +26,7 @@
 #include <iostream>
 #include <list>
 
+#include "debugger.h"
 #include "class_linker.h"
 #include "class_loader.h"
 #include "context.h"
@@ -165,10 +166,7 @@
   // in progress while we were starting up.)
   runtime->GetThreadList()->WaitForGo();
 
-  // TODO: say "hi" to the debugger.
-  //if (gDvm.debuggerConnected) {
-  //  dvmDbgPostThreadStart(self);
-  //}
+  Dbg::PostThreadStart(self);
 
   // Invoke the 'run' method of our java.lang.Thread.
   CHECK(self->peer_ != NULL);
@@ -767,10 +765,7 @@
     // this.vmData = 0;
     SetVmData(peer_, NULL);
 
-    // TODO: say "bye" to the debugger.
-    //if (gDvm.debuggerConnected) {
-    //  dvmDbgPostThreadDeath(self);
-    //}
+    Dbg::PostThreadDeath(this);
 
     // Thread.join() is implemented as an Object.wait() on the Thread.lock
     // object. Signal anyone who is waiting.