Basic DDMS thread support.

DDMS can now see our running threads...

Change-Id: I42d2fce4db9eb846fa0b4aac46ca6bb3443a6c9f
diff --git a/src/debugger.cc b/src/debugger.cc
index 07c445e..ca6643f 100644
--- a/src/debugger.cc
+++ b/src/debugger.cc
@@ -19,6 +19,7 @@
 #include <sys/uio.h>
 
 #include "ScopedPrimitiveArray.h"
+#include "stack_indirect_reference_table.h"
 #include "thread_list.h"
 
 namespace art {
@@ -62,6 +63,8 @@
 static bool gDebuggerConnected;  // debugger or DDMS is connected.
 static bool gDebuggerActive;     // debugger is making requests.
 
+static bool gDdmThreadNotification = false;
+
 static ObjectRegistry* gRegistry = NULL;
 
 /*
@@ -146,6 +149,8 @@
  * "transport=dt_socket,address=8000,server=y,suspend=n"
  */
 bool Dbg::ParseJdwpOptions(const std::string& options) {
+  LOG(VERBOSE) << "ParseJdwpOptions: " << options;
+
   std::vector<std::string> pairs;
   Split(options, ',', pairs);
 
@@ -559,20 +564,6 @@
   UNIMPLEMENTED(FATAL);
 }
 
-void Dbg::PostThreadStart(Thread* t) {
-  if (!gDebuggerConnected) {
-    return;
-  }
-  UNIMPLEMENTED(WARNING);
-}
-
-void Dbg::PostThreadDeath(Thread* t) {
-  if (!gDebuggerConnected) {
-    return;
-  }
-  UNIMPLEMENTED(WARNING);
-}
-
 void Dbg::PostClassPrepare(Class* c) {
   UNIMPLEMENTED(FATAL);
 }
@@ -712,16 +703,101 @@
   return true;
 }
 
+void DdmBroadcast(bool connect) {
+  LOG(VERBOSE) << "Broadcasting DDM " << (connect ? "connect" : "disconnect") << "...";
+
+  Thread* self = Thread::Current();
+  if (self->GetState() != Thread::kRunnable) {
+    LOG(ERROR) << "DDM broadcast in thread state " << self->GetState();
+    /* try anyway? */
+  }
+
+  JNIEnv* env = self->GetJniEnv();
+  static jclass DdmServer_class = env->FindClass("org/apache/harmony/dalvik/ddmc/DdmServer");
+  static jmethodID broadcast_mid = env->GetStaticMethodID(DdmServer_class, "broadcast", "(I)V");
+  jint event = connect ? 1 /*DdmServer.CONNECTED*/ : 2 /*DdmServer.DISCONNECTED*/;
+  env->CallStaticVoidMethod(DdmServer_class, broadcast_mid, event);
+  if (env->ExceptionCheck()) {
+    LOG(ERROR) << "DdmServer.broadcast " << event << " failed";
+    env->ExceptionDescribe();
+    env->ExceptionClear();
+  }
+}
+
 void Dbg::DdmConnected() {
-  LOG(VERBOSE) << "Broadcasting DDM connect...";
-  //broadcast(CONNECTED);
-  UNIMPLEMENTED(WARNING);
+  DdmBroadcast(true);
 }
 
 void Dbg::DdmDisconnected() {
-  LOG(VERBOSE) << "Broadcasting DDM disconnect...";
-  //broadcast(DISCONNECTED);
-  //gDvm.ddmThreadNotification = false;
+  DdmBroadcast(false);
+  gDdmThreadNotification = false;
+}
+
+/*
+ * Send a notification when a thread starts or stops.
+ *
+ * Because we broadcast the full set of threads when the notifications are
+ * first enabled, it's possible for "thread" to be actively executing.
+ */
+void DdmSendThreadNotification(Thread* t, bool started) {
+  if (!gDdmThreadNotification) {
+    return;
+  }
+
+  if (started) {
+    SirtRef<String> name(t->GetName());
+    size_t char_count = (name.get() != NULL) ? name->GetLength() : 0;
+
+    size_t byte_count = char_count*2 + sizeof(uint32_t)*2;
+    std::vector<uint8_t> buf(byte_count);
+    JDWP::set4BE(&buf[0], t->GetThinLockId());
+    JDWP::set4BE(&buf[4], char_count);
+    if (char_count > 0) {
+      // Copy the UTF-16 string, transforming to big-endian.
+      const jchar* src = name->GetCharArray()->GetData();
+      jchar* dst = reinterpret_cast<jchar*>(&buf[8]);
+      while (char_count--) {
+        JDWP::set2BE(reinterpret_cast<uint8_t*>(dst++), *src++);
+      }
+    }
+    Dbg::DdmSendChunk(CHUNK_TYPE("THCR"), buf.size(), &buf[0]);
+  } else {
+    uint8_t buf[4];
+    JDWP::set4BE(&buf[0], t->GetThinLockId());
+    Dbg::DdmSendChunk(CHUNK_TYPE("THDE"), 4, buf);
+  }
+}
+
+void DdmSendThreadStartCallback(Thread* t) {
+  DdmSendThreadNotification(t, true);
+}
+
+void Dbg::DdmSetThreadNotification(bool enable) {
+  // We lock the thread list to avoid sending duplicate events or missing
+  // a thread change. We should be okay holding this lock while sending
+  // the messages out. (We have to hold it while accessing a live thread.)
+  ThreadListLock lock;
+
+  gDdmThreadNotification = enable;
+  if (enable) {
+    Runtime::Current()->GetThreadList()->ForEach(DdmSendThreadStartCallback);
+  }
+}
+
+void PostThreadStartOrStop(Thread* t, bool is_start) {
+  if (gDebuggerActive) {
+    JDWP::ObjectId id = gRegistry->Add(t->GetPeer());
+    gJdwpState->PostThreadChange(id, is_start);
+  }
+  DdmSendThreadNotification(t, is_start);
+}
+
+void Dbg::PostThreadStart(Thread* t) {
+  PostThreadStartOrStop(t, true);
+}
+
+void Dbg::PostThreadDeath(Thread* t) {
+  PostThreadStartOrStop(t, false);
 }
 
 void Dbg::DdmSendChunk(int type, size_t byte_count, const uint8_t* buf) {
diff --git a/src/debugger.h b/src/debugger.h
index 6eabf33..111282c 100644
--- a/src/debugger.h
+++ b/src/debugger.h
@@ -227,6 +227,7 @@
   /*
    * DDM support.
    */
+  static void DdmSetThreadNotification(bool enable);
   static bool DdmHandlePacket(const uint8_t* buf, int dataLen, uint8_t** pReplyBuf, int* pReplyLen);
   static void DdmConnected();
   static void DdmDisconnected();
diff --git a/src/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc b/src/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
index 16d3671..fab4968 100644
--- a/src/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
+++ b/src/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
@@ -85,8 +85,7 @@
 }
 
 static void DdmVmInternal_threadNotify(JNIEnv* env, jclass, jboolean enable) {
-  UNIMPLEMENTED(WARNING);
-  //dvmDdmSetThreadNotification(enable);
+  Dbg::DdmSetThreadNotification(enable);
 }
 
 static JNINativeMethod gMethods[] = {
diff --git a/src/thread_list.cc b/src/thread_list.cc
index 0be0f09..26faede 100644
--- a/src/thread_list.cc
+++ b/src/thread_list.cc
@@ -382,6 +382,13 @@
   thread_exit_cond_.Signal();
 }
 
+void ThreadList::ForEach(void (*callback)(Thread*)) {
+  thread_list_lock_.AssertHeld();
+  for (It it = list_.begin(), end = list_.end(); it != end; ++it) {
+    callback(*it);
+  }
+}
+
 void ThreadList::VisitRoots(Heap::RootVisitor* visitor, void* arg) const {
   ThreadListLocker locker(this);
   for (It it = list_.begin(), end = list_.end(); it != end; ++it) {
diff --git a/src/thread_list.h b/src/thread_list.h
index baa7dad..6dd7455 100644
--- a/src/thread_list.h
+++ b/src/thread_list.h
@@ -41,6 +41,9 @@
   void SuspendSelfForDebugger();
   void RunWhileSuspended(Thread* thread, void (*callback)(void*), void* arg);
 
+  // Iterates over all the threads. The caller must hold the thread list lock.
+  void ForEach(void (*callback)(Thread*));
+
   void Register();
   void Unregister();