Implement Dbg::Disconnected and Dbg::UndoDebuggerSuspensions.

You can now start and stop oatexec-based apps with DDMS running, with
no UNIMPLEMENTEDs.

Change-Id: Ic53e7bdd4ddd3ed93f9d807499d991ea30f48810
diff --git a/src/debugger.cc b/src/debugger.cc
index ca6643f..df32a51 100644
--- a/src/debugger.cc
+++ b/src/debugger.cc
@@ -39,6 +39,12 @@
     return id;
   }
 
+  void Clear() {
+    MutexLock mu(lock_);
+    LOG(DEBUG) << "Debugger has detached; object registry had " << map_.size() << " entries";
+    map_.clear();
+  }
+
   bool Contains(JDWP::ObjectId id) {
     MutexLock mu(lock_);
     return map_.find(id) != map_.end();
@@ -236,7 +242,14 @@
 }
 
 void Dbg::Disconnected() {
-  UNIMPLEMENTED(FATAL);
+  CHECK(gDebuggerConnected);
+
+  gDebuggerActive = false;
+
+  //dvmDisableAllSubMode(kSubModeDebuggerActive);
+
+  gRegistry->Clear();
+  gDebuggerConnected = false;
 }
 
 bool Dbg::IsDebuggerConnected() {
@@ -265,7 +278,7 @@
 }
 
 void Dbg::UndoDebuggerSuspensions() {
-  UNIMPLEMENTED(FATAL);
+  Runtime::Current()->GetThreadList()->UndoDebuggerSuspensions();
 }
 
 void Dbg::Exit(int status) {
diff --git a/src/jdwp/jdwp_event.cc b/src/jdwp/jdwp_event.cc
index c6bdddb..2c05713 100644
--- a/src/jdwp/jdwp_event.cc
+++ b/src/jdwp/jdwp_event.cc
@@ -864,13 +864,13 @@
  * Valid mods:
  *  Count, ThreadOnly
  */
-bool PostThreadChange(JdwpState* state, ObjectId threadId, bool start) {
+bool JdwpState::PostThreadChange(ObjectId threadId, bool start) {
   CHECK_EQ(threadId, Dbg::GetThreadSelfId());
 
   /*
    * I don't think this can happen.
    */
-  if (invokeInProgress(state)) {
+  if (invokeInProgress(this)) {
     LOG(WARNING) << "Not posting thread change during invoke";
     return false;
   }
@@ -879,50 +879,50 @@
   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 << ")";
+  int matchCount = 0;
+  {
+    // Don't allow the list to be updated while we scan it.
+    MutexLock mu(event_lock_);
+    JdwpEvent** matchList = allocMatchList(this);
 
-    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);
+    if (start) {
+      findMatchingEvents(this, EK_THREAD_START, &basket, matchList, &matchCount);
+    } else {
+      findMatchingEvents(this, EK_THREAD_DEATH, &basket, matchList, &matchCount);
     }
-  }
 
-  cleanupMatchList(state, matchList, matchCount);
-  unlockEventMutex(state);
+    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(this, matchList, matchCount);
+  }
 
   /* send request and possibly suspend ourselves */
   if (pReq != NULL) {
     int old_state = Dbg::ThreadWaiting();
     if (suspendPolicy != SP_NONE) {
-      state->SetWaitForEventThread(basket.threadId);
+      SetWaitForEventThread(basket.threadId);
     }
-    eventFinish(state, pReq);
+    eventFinish(this, pReq);
 
-    suspendByPolicy(state, suspendPolicy);
+    suspendByPolicy(this, suspendPolicy);
     Dbg::ThreadContinuing(old_state);
   }
 
diff --git a/src/thread.cc b/src/thread.cc
index 9f54c36..d2c43d9 100644
--- a/src/thread.cc
+++ b/src/thread.cc
@@ -411,10 +411,9 @@
      << " tid=" << GetThinLockId()
      << " " << GetState() << "\n";
 
-  int debug_suspend_count = 0; // TODO
   os << "  | group=\"" << group_name << "\""
      << " sCount=" << suspend_count_
-     << " dsCount=" << debug_suspend_count
+     << " dsCount=" << debug_suspend_count_
      << " obj=" << reinterpret_cast<void*>(peer_)
      << " self=" << reinterpret_cast<const void*>(this) << "\n";
   os << "  | sysTid=" << GetTid()
@@ -730,6 +729,7 @@
       runtime_(NULL),
       exception_(NULL),
       suspend_count_(0),
+      debug_suspend_count_(0),
       class_loader_override_(NULL),
       long_jump_context_(NULL),
       throwing_OutOfMemoryError_(false),
diff --git a/src/thread.h b/src/thread.h
index d81fd9a..e721df5 100644
--- a/src/thread.h
+++ b/src/thread.h
@@ -575,6 +575,9 @@
   // A non-zero value is used to tell the current thread to enter a safe point
   // at the next poll.
   int suspend_count_;
+  // How much of 'suspend_count_' is by request of the debugger, used to set things right
+  // when the debugger detaches. Must be <= suspend_count_.
+  int debug_suspend_count_;
 
   // Needed to get the right ClassLoader in JNI_OnLoad, but also
   // useful for testing.
diff --git a/src/thread_list.cc b/src/thread_list.cc
index 0512186..875b14e 100644
--- a/src/thread_list.cc
+++ b/src/thread_list.cc
@@ -86,6 +86,27 @@
   }
 }
 
+void ThreadList::ModifySuspendCount(Thread* thread, int delta, bool for_debugger) {
+#ifndef NDEBUG
+  DCHECK(delta == -1 || delta == +1 || delta == thread->debug_suspend_count_) << delta;
+  DCHECK_GE(thread->suspend_count_, thread->debug_suspend_count_);
+  if (delta == -1) {
+    DCHECK_GT(thread->suspend_count_, 0);
+    if (for_debugger) {
+      DCHECK_GT(thread->debug_suspend_count_, 0);
+    }
+  }
+#else
+  if (delta == -1 && thread->suspend_count_ <= 0) {
+    LOG(FATAL) << *thread << " suspend count already zero";
+  }
+#endif
+  thread->suspend_count_ += delta;
+  if (for_debugger) {
+    thread->debug_suspend_count_ += delta;
+  }
+}
+
 void ThreadList::FullSuspendCheck(Thread* thread) {
   CHECK(thread != NULL);
   CHECK_GE(thread->suspend_count_, 0);
@@ -137,7 +158,7 @@
       if (verbose_) {
         LOG(INFO) << "requesting thread suspend: " << *thread;
       }
-      ++thread->suspend_count_;
+      ModifySuspendCount(thread, +1, for_debugger);
     }
   }
 
@@ -187,7 +208,7 @@
 
   {
     MutexLock mu(thread_suspend_count_lock_);
-    ++thread->suspend_count_;
+    ModifySuspendCount(thread, +1, false);
   }
 
   thread->WaitUntilSuspended();
@@ -210,7 +231,7 @@
   // though.
   ThreadListLocker locker(this);
   MutexLock mu(thread_suspend_count_lock_);
-  ++self->suspend_count_;
+  ModifySuspendCount(self, +1, true);
 
   // Suspend ourselves.
   CHECK_GT(self->suspend_count_, 0);
@@ -261,11 +282,7 @@
       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";
-      }
+      ModifySuspendCount(thread, -1, for_debugger);
     }
   }
 
@@ -297,11 +314,7 @@
     if (!Contains(thread)) {
       return;
     }
-    if (thread->suspend_count_ > 0) {
-      --thread->suspend_count_;
-    } else {
-      LOG(WARNING) << *thread << " suspend count already zero";
-    }
+    ModifySuspendCount(thread, -1, false);
   }
 
   {
@@ -329,6 +342,35 @@
   }
 }
 
+void ThreadList::UndoDebuggerSuspensions() {
+  Thread* self = Thread::Current();
+
+  if (verbose_) {
+    LOG(INFO) << *self << " UndoDebuggerSuspensions starting";
+  }
+
+  {
+    ThreadListLocker locker(this);
+    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_suspend_count_ == 0) {
+        continue;
+      }
+      ModifySuspendCount(thread, -thread->debug_suspend_count_, true);
+    }
+  }
+
+  {
+    MutexLock mu(thread_suspend_count_lock_);
+    thread_suspend_count_cond_.Broadcast();
+  }
+
+  if (verbose_) {
+    LOG(INFO) << "UndoDebuggerSuspensions(" << *self << ") complete";
+  }
+}
+
 void ThreadList::Register() {
   Thread* self = Thread::Current();
 
diff --git a/src/thread_list.h b/src/thread_list.h
index 6dd7455..d19622e 100644
--- a/src/thread_list.h
+++ b/src/thread_list.h
@@ -40,6 +40,7 @@
   void SuspendAll(bool for_debugger = false);
   void SuspendSelfForDebugger();
   void RunWhileSuspended(Thread* thread, void (*callback)(void*), void* arg);
+  void UndoDebuggerSuspensions();
 
   // Iterates over all the threads. The caller must hold the thread list lock.
   void ForEach(void (*callback)(Thread*));
@@ -65,6 +66,8 @@
   void SuspendAllDaemonThreads();
   void WaitForNonDaemonThreadsToExit();
 
+  static void ModifySuspendCount(Thread* thread, int delta, bool for_debugger);
+
   bool verbose_;
 
   mutable Mutex thread_list_lock_;