Implement more DDMS support.

You can now launch DDMS without killing running oatexec processes...

Change-Id: I13e1d6df4f0cbd0c06b69471823a13e4e820b93b
diff --git a/build/Android.common.mk b/build/Android.common.mk
index c56bb0f..096350a 100644
--- a/build/Android.common.mk
+++ b/build/Android.common.mk
@@ -164,6 +164,7 @@
 	src/object.cc \
 	src/offsets.cc \
 	src/org_apache_harmony_dalvik_ddmc_DdmServer.cc \
+	src/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc \
 	src/os_linux.cc \
 	src/reference_table.cc \
 	src/reflection.cc \
diff --git a/src/dalvik_system_VMDebug.cc b/src/dalvik_system_VMDebug.cc
index 4537a69..d5d0917 100644
--- a/src/dalvik_system_VMDebug.cc
+++ b/src/dalvik_system_VMDebug.cc
@@ -35,6 +35,7 @@
  */
 jobjectArray VMDebug_getVmFeatureList(JNIEnv* env, jclass) {
   std::vector<std::string> features;
+  // TODO: we might need to uncomment these to make them work.
   //features.push_back("method-trace-profiling");
   //features.push_back("method-trace-profiling-streaming");
   //features.push_back("hprof-heap-dump");
diff --git a/src/dalvik_system_VMRuntime.cc b/src/dalvik_system_VMRuntime.cc
index b6a8ed7..8334059 100644
--- a/src/dalvik_system_VMRuntime.cc
+++ b/src/dalvik_system_VMRuntime.cc
@@ -15,6 +15,7 @@
  */
 
 #include "class_linker.h"
+#include "debugger.h"
 #include "jni_internal.h"
 #include "object.h"
 #include "thread.h"
@@ -87,8 +88,7 @@
 }
 
 jboolean VMRuntime_isDebuggerActive(JNIEnv*, jobject) {
-  // TODO: debugger!
-  return JNI_FALSE;
+  return Dbg::IsDebuggerConnected();
 }
 
 jobjectArray VMRuntime_properties(JNIEnv* env, jobject) {
diff --git a/src/debugger.cc b/src/debugger.cc
index 7d6d344..07c445e 100644
--- a/src/debugger.cc
+++ b/src/debugger.cc
@@ -18,6 +18,7 @@
 
 #include <sys/uio.h>
 
+#include "ScopedPrimitiveArray.h"
 #include "thread_list.h"
 
 namespace art {
@@ -607,17 +608,120 @@
   UNIMPLEMENTED(FATAL);
 }
 
+/*
+ * "buf" contains a full JDWP packet, possibly with multiple chunks.  We
+ * need to process each, accumulate the replies, and ship the whole thing
+ * back.
+ *
+ * Returns "true" if we have a reply.  The reply buffer is newly allocated,
+ * and includes the chunk type/length, followed by the data.
+ *
+ * TODO: we currently assume that the request and reply include a single
+ * chunk.  If this becomes inconvenient we will need to adapt.
+ */
 bool Dbg::DdmHandlePacket(const uint8_t* buf, int dataLen, uint8_t** pReplyBuf, int* pReplyLen) {
-  UNIMPLEMENTED(FATAL);
-  return false;
+  CHECK_GE(dataLen, 0);
+
+  Thread* self = Thread::Current();
+  JNIEnv* env = self->GetJniEnv();
+
+  static jclass Chunk_class = env->FindClass("org/apache/harmony/dalvik/ddmc/Chunk");
+  static jclass DdmServer_class = env->FindClass("org/apache/harmony/dalvik/ddmc/DdmServer");
+  static jmethodID dispatch_mid = env->GetStaticMethodID(DdmServer_class, "dispatch",
+      "(I[BII)Lorg/apache/harmony/dalvik/ddmc/Chunk;");
+  static jfieldID data_fid = env->GetFieldID(Chunk_class, "data", "[B");
+  static jfieldID length_fid = env->GetFieldID(Chunk_class, "length", "I");
+  static jfieldID offset_fid = env->GetFieldID(Chunk_class, "offset", "I");
+  static jfieldID type_fid = env->GetFieldID(Chunk_class, "type", "I");
+
+  // Create a byte[] corresponding to 'buf'.
+  jbyteArray dataArray = env->NewByteArray(dataLen);
+  if (dataArray == NULL) {
+    LOG(WARNING) << "byte[] allocation failed: " << dataLen;
+    env->ExceptionClear();
+    return false;
+  }
+  env->SetByteArrayRegion(dataArray, 0, dataLen, reinterpret_cast<const jbyte*>(buf));
+
+  const int kChunkHdrLen = 8;
+
+  // Run through and find all chunks.  [Currently just find the first.]
+  ScopedByteArrayRO contents(env, dataArray);
+  jint type = JDWP::get4BE(reinterpret_cast<const uint8_t*>(&contents[0]));
+  jint length = JDWP::get4BE(reinterpret_cast<const uint8_t*>(&contents[4]));
+  jint offset = kChunkHdrLen;
+  if (offset + length > dataLen) {
+    LOG(WARNING) << StringPrintf("bad chunk found (len=%u pktLen=%d)", length, dataLen);
+    return false;
+  }
+
+  // Call "private static Chunk dispatch(int type, byte[] data, int offset, int length)".
+  jobject chunk = env->CallStaticObjectMethod(DdmServer_class, dispatch_mid, type, dataArray, offset, length);
+  if (env->ExceptionCheck()) {
+    LOG(INFO) << StringPrintf("Exception thrown by dispatcher for 0x%08x", type);
+    env->ExceptionDescribe();
+    env->ExceptionClear();
+    return false;
+  }
+
+  if (chunk == NULL) {
+    return false;
+  }
+
+  /*
+   * Pull the pieces out of the chunk.  We copy the results into a
+   * newly-allocated buffer that the caller can free.  We don't want to
+   * continue using the Chunk object because nothing has a reference to it.
+   *
+   * We could avoid this by returning type/data/offset/length and having
+   * the caller be aware of the object lifetime issues, but that
+   * integrates the JDWP code more tightly into the VM, and doesn't work
+   * if we have responses for multiple chunks.
+   *
+   * So we're pretty much stuck with copying data around multiple times.
+   */
+  jbyteArray replyData = reinterpret_cast<jbyteArray>(env->GetObjectField(chunk, data_fid));
+  length = env->GetIntField(chunk, length_fid);
+  offset = env->GetIntField(chunk, offset_fid);
+  type = env->GetIntField(chunk, type_fid);
+
+  LOG(VERBOSE) << StringPrintf("DDM reply: type=0x%08x data=%p offset=%d length=%d", type, replyData, offset, length);
+  if (length == 0 || replyData == NULL) {
+    return false;
+  }
+
+  jsize replyLength = env->GetArrayLength(replyData);
+  if (offset + length > replyLength) {
+    LOG(WARNING) << StringPrintf("chunk off=%d len=%d exceeds reply array len %d", offset, length, replyLength);
+    return false;
+  }
+
+  uint8_t* reply = new uint8_t[length + kChunkHdrLen];
+  if (reply == NULL) {
+    LOG(WARNING) << "malloc failed: " << (length + kChunkHdrLen);
+    return false;
+  }
+  JDWP::set4BE(reply + 0, type);
+  JDWP::set4BE(reply + 4, length);
+  env->GetByteArrayRegion(replyData, offset, length, reinterpret_cast<jbyte*>(reply + kChunkHdrLen));
+
+  *pReplyBuf = reply;
+  *pReplyLen = length + kChunkHdrLen;
+
+  LOG(VERBOSE) << StringPrintf("dvmHandleDdm returning type=%.4s buf=%p len=%d", (char*) reply, reply, length);
+  return true;
 }
 
 void Dbg::DdmConnected() {
-  UNIMPLEMENTED(FATAL);
+  LOG(VERBOSE) << "Broadcasting DDM connect...";
+  //broadcast(CONNECTED);
+  UNIMPLEMENTED(WARNING);
 }
 
 void Dbg::DdmDisconnected() {
-  UNIMPLEMENTED(FATAL);
+  LOG(VERBOSE) << "Broadcasting DDM disconnect...";
+  //broadcast(DISCONNECTED);
+  //gDvm.ddmThreadNotification = false;
 }
 
 void Dbg::DdmSendChunk(int type, size_t byte_count, const uint8_t* buf) {
diff --git a/src/org_apache_harmony_dalvik_ddmc_DdmServer.cc b/src/org_apache_harmony_dalvik_ddmc_DdmServer.cc
index b0aba2f..b46ee1f 100644
--- a/src/org_apache_harmony_dalvik_ddmc_DdmServer.cc
+++ b/src/org_apache_harmony_dalvik_ddmc_DdmServer.cc
@@ -39,7 +39,7 @@
 }  // namespace
 
 void register_org_apache_harmony_dalvik_ddmc_DdmServer(JNIEnv* env) {
-    jniRegisterNativeMethods(env, "org/apache/harmony/dalvik/ddmc/DdmServer", gMethods, NELEM(gMethods));
+  jniRegisterNativeMethods(env, "org/apache/harmony/dalvik/ddmc/DdmServer", gMethods, NELEM(gMethods));
 }
 
 }  // namespace art
diff --git a/src/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc b/src/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
new file mode 100644
index 0000000..16d3671
--- /dev/null
+++ b/src/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
@@ -0,0 +1,109 @@
+/*
+ * 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"
+#include "logging.h"
+
+#include "JniConstants.h"  // Last to avoid problems with LOG redefinition.
+#include "ScopedPrimitiveArray.h"  // Last to avoid problems with LOG redefinition.
+
+namespace art {
+
+namespace {
+
+static void DdmVmInternal_enableRecentAllocations(JNIEnv* env, jclass, jboolean enable) {
+  UNIMPLEMENTED(WARNING);
+  if (enable) {
+    //dvmEnableAllocTracker();
+  } else {
+    //dvmDisableAllocTracker();
+  }
+}
+
+static jbyteArray DdmVmInternal_getRecentAllocations(JNIEnv* env, jclass) {
+  UNIMPLEMENTED(WARNING);
+  return NULL;
+  //ArrayObject* data = dvmDdmGetRecentAllocations();
+  //dvmReleaseTrackedAlloc(data, NULL);
+  //return reinterpret_cast<jbyteArray>(addLocalReference(env, data));
+}
+
+static jboolean DdmVmInternal_getRecentAllocationStatus(JNIEnv* env, jclass) {
+  UNIMPLEMENTED(WARNING);
+  return JNI_FALSE;
+  //return (gDvm.allocRecords != NULL);
+}
+
+/*
+ * Get a stack trace as an array of StackTraceElement objects.  Returns
+ * NULL on failure, e.g. if the threadId couldn't be found.
+ */
+static jobjectArray DdmVmInternal_getStackTraceById(JNIEnv* env, jclass, jint threadId) {
+  UNIMPLEMENTED(WARNING);
+  return NULL;
+  //ArrayObject* trace = dvmDdmGetStackTraceById(threadId);
+  //return reinterpret_cast<jobjectArray>(addLocalReference(env, trace));
+}
+
+static jbyteArray DdmVmInternal_getThreadStats(JNIEnv* env, jclass) {
+  UNIMPLEMENTED(WARNING);
+  return NULL;
+  //ArrayObject* result = dvmDdmGenerateThreadStats();
+  //dvmReleaseTrackedAlloc(result, NULL);
+  //return reinterpret_cast<jbyteArray>(addLocalReference(env, result));
+}
+
+static jint DdmVmInternal_heapInfoNotify(JNIEnv* env, jclass, jint when) {
+  UNIMPLEMENTED(WARNING);
+  return 0;
+  //return dvmDdmHandleHpifChunk(when);
+}
+
+/*
+ * Enable DDM heap notifications.
+ * @param when: 0=never (off), 1=during GC
+ * @param what: 0=merged objects, 1=distinct objects
+ * @param native: false=virtual heap, true=native heap
+ */
+static jboolean DdmVmInternal_heapSegmentNotify(JNIEnv* env, jclass, jint when, jint what, jboolean native) {
+  UNIMPLEMENTED(WARNING);
+  return JNI_FALSE;
+  //return dvmDdmHandleHpsgNhsgChunk(when, what, native);
+}
+
+static void DdmVmInternal_threadNotify(JNIEnv* env, jclass, jboolean enable) {
+  UNIMPLEMENTED(WARNING);
+  //dvmDdmSetThreadNotification(enable);
+}
+
+static JNINativeMethod gMethods[] = {
+  NATIVE_METHOD(DdmVmInternal, enableRecentAllocations, "(Z)V"),
+  NATIVE_METHOD(DdmVmInternal, getRecentAllocations, "()[B"),
+  NATIVE_METHOD(DdmVmInternal, getRecentAllocationStatus, "()Z"),
+  NATIVE_METHOD(DdmVmInternal, getStackTraceById, "(I)[Ljava/lang/StackTraceElement;"),
+  NATIVE_METHOD(DdmVmInternal, getThreadStats, "()[B"),
+  NATIVE_METHOD(DdmVmInternal, heapInfoNotify, "(I)Z"),
+  NATIVE_METHOD(DdmVmInternal, heapSegmentNotify, "(IIZ)Z"),
+  NATIVE_METHOD(DdmVmInternal, threadNotify, "(Z)V"),
+};
+
+}  // namespace
+
+void register_org_apache_harmony_dalvik_ddmc_DdmVmInternal(JNIEnv* env) {
+  jniRegisterNativeMethods(env, "org/apache/harmony/dalvik/ddmc/DdmVmInternal", gMethods, NELEM(gMethods));
+}
+
+}  // namespace art
diff --git a/src/runtime.cc b/src/runtime.cc
index c777fa1..7595ed8 100644
--- a/src/runtime.cc
+++ b/src/runtime.cc
@@ -570,7 +570,7 @@
   REGISTER(register_java_lang_reflect_Proxy);
   REGISTER(register_java_util_concurrent_atomic_AtomicLong);
   REGISTER(register_org_apache_harmony_dalvik_ddmc_DdmServer);
-  //REGISTER(register_org_apache_harmony_dalvik_ddmc_DdmVmInternal);
+  REGISTER(register_org_apache_harmony_dalvik_ddmc_DdmVmInternal);
   REGISTER(register_sun_misc_Unsafe);
 #undef REGISTER
 }
diff --git a/src/thread_list.cc b/src/thread_list.cc
index f386547..0be0f09 100644
--- a/src/thread_list.cc
+++ b/src/thread_list.cc
@@ -132,12 +132,13 @@
     MutexLock mu(thread_suspend_count_lock_);
     for (It it = list_.begin(), end = list_.end(); it != end; ++it) {
       Thread* thread = *it;
-      if (thread != self && thread != debug_thread) {
-        if (verbose_) {
-          LOG(INFO) << "requesting thread suspend: " << *thread;
-        }
-        ++thread->suspend_count_;
+      if (thread == self || (for_debugger && thread == debug_thread)) {
+        continue;
       }
+      if (verbose_) {
+        LOG(INFO) << "requesting thread suspend: " << *thread;
+      }
+      ++thread->suspend_count_;
     }
   }
 
@@ -157,11 +158,12 @@
    */
   for (It it = list_.begin(), end = list_.end(); it != end; ++it) {
     Thread* thread = *it;
-    if (thread != self && thread != debug_thread) {
-      thread->WaitUntilSuspended();
-      if (verbose_) {
-        LOG(INFO) << "thread suspended: " << *thread;
-      }
+    if (thread == self || (for_debugger && thread == debug_thread)) {
+      continue;
+    }
+    thread->WaitUntilSuspended();
+    if (verbose_) {
+      LOG(INFO) << "thread suspended: " << *thread;
     }
   }
 
@@ -257,12 +259,13 @@
     MutexLock mu(thread_suspend_count_lock_);
     for (It it = list_.begin(), end = list_.end(); it != end; ++it) {
       Thread* thread = *it;
-      if (thread != self && thread != debug_thread) {
-        if (thread->suspend_count_ > 0) {
-          --thread->suspend_count_;
-        } else {
-          LOG(WARNING) << *thread << " suspend count already zero";
-        }
+      if (thread == self || (for_debugger && thread == debug_thread)) {
+        continue;
+      }
+      if (thread->suspend_count_ > 0) {
+        --thread->suspend_count_;
+      } else {
+        LOG(WARNING) << *thread << " suspend count already zero";
       }
     }
   }