Include non-attached native threads in the SIGQUIT output.

These threads look something like this:

  "droid.phasebeam' prio=5 tid=? (not attached)
    | sysTid=5369 nice=-4 sched=0/0 cgrp=default
    | schedstat=( 0 0 0 ) utm=1 stm=8 core=0 HZ=100
    native: __futex_syscall3+8 [0x40074678] (libc.so)
    native: __pthread_cond_timedwait_relative+48 [0x40079474] (libc.so)
    native: __pthread_cond_timedwait+72 [0x40079528] (libc.so)
    native: android::renderscript::Signal::wait(unsigned long long)+58 [0x418bf117] (libRS.so)
    native: android::renderscript::LocklessCommandFifo::wait(unsigned long long)+38 [0x418bab97] (libRS.so)
    native: android::renderscript::LocklessCommandFifo::get(unsigned int*, unsigned int*, unsigned long long)+22 [0x418babbb] (libRS.so)
    native: android::renderscript::ThreadIO::playCoreCommands(android::renderscript::Context*, bool, unsigned long long)+126 [0x418bf84b] (libRS.so)
    native: android::renderscript::Context::threadProc(void*)+382 [0x418b7347] (libRS.so)
    native: __thread_entry+48 [0x40079d30] (libc.so)
    native: pthread_create+180 [0x40079884] (libc.so)

Also fix running tests on Mac OS, which has no /proc/self/cmdline.

Change-Id: Ib5e6f7e23dd45aecdf814e84f573361a5d91bac6
diff --git a/src/signal_catcher.cc b/src/signal_catcher.cc
index 0703b61..33600ba 100644
--- a/src/signal_catcher.cc
+++ b/src/signal_catcher.cc
@@ -36,8 +36,13 @@
 #include "thread_list.h"
 #include "utils.h"
 
+#if !defined(__APPLE__)
+#define HAVE_PROC_CMDLINE
+#endif
+
 namespace art {
 
+#if defined(HAVE_PROC_CMDLINE)
 static bool ReadCmdLine(std::string& result) {
   if (!ReadFileToString("/proc/self/cmdline", &result)) {
     return false;
@@ -45,6 +50,7 @@
   std::replace(result.begin(), result.end(), '\0', ' ');
   return true;
 }
+#endif
 
 SignalCatcher::SignalCatcher(const std::string& stack_trace_file)
     : stack_trace_file_(stack_trace_file),
@@ -53,9 +59,11 @@
       thread_(NULL) {
   SetHaltFlag(false);
 
+#if defined(HAVE_PROC_CMDLINE)
   // Stash the original command line for SIGQUIT reporting.
   // By then, /proc/self/cmdline will have been rewritten to something like "system_server".
   CHECK(ReadCmdLine(cmd_line_));
+#endif
 
   // Create a raw pthread; its start routine will attach to the runtime.
   CHECK_PTHREAD_CALL(pthread_create, (&pthread_, NULL, &Run, this), "signal catcher thread");
@@ -121,6 +129,7 @@
   os << "\n"
      << "----- pid " << getpid() << " at " << GetIsoDate() << " -----\n";
 
+#if defined(HAVE_PROC_CMDLINE)
   std::string current_cmd_line;
   if (ReadCmdLine(current_cmd_line) && current_cmd_line != cmd_line_) {
     os << "Cmdline: " << current_cmd_line;
@@ -130,6 +139,7 @@
   if (current_cmd_line != cmd_line_) {
     os << "Original command line: " << cmd_line_ << "\n";
   }
+#endif
 
   os << "Build type: " << (kIsDebugBuild ? "debug" : "optimized") << "\n";
 
diff --git a/src/thread.cc b/src/thread.cc
index 51b8e31..d854c3b 100644
--- a/src/thread.cc
+++ b/src/thread.cc
@@ -438,16 +438,16 @@
   name.assign(*name_);
 }
 
-void Thread::DumpState(std::ostream& os) const {
+void Thread::DumpState(std::ostream& os, const Thread* thread, pid_t tid) {
   std::string group_name;
   int priority;
   bool is_daemon = false;
 
-  if (peer_ != NULL) {
-    priority = DecodeField(WellKnownClasses::java_lang_Thread_priority)->GetInt(peer_);
-    is_daemon = DecodeField(WellKnownClasses::java_lang_Thread_daemon)->GetBoolean(peer_);
+  if (thread != NULL && thread->peer_ != NULL) {
+    priority = DecodeField(WellKnownClasses::java_lang_Thread_priority)->GetInt(thread->peer_);
+    is_daemon = DecodeField(WellKnownClasses::java_lang_Thread_daemon)->GetBoolean(thread->peer_);
 
-    Object* thread_group = GetThreadGroup();
+    Object* thread_group = thread->GetThreadGroup();
     if (thread_group != NULL) {
       Field* group_name_field = DecodeField(WellKnownClasses::java_lang_ThreadGroup_name);
       String* group_name_string = reinterpret_cast<String*>(group_name_field->GetObject(thread_group));
@@ -461,33 +461,49 @@
   sched_param sp;
   CHECK_PTHREAD_CALL(pthread_getschedparam, (pthread_self(), &policy, &sp), __FUNCTION__);
 
-  std::string scheduler_group_name(GetSchedulerGroupName(GetTid()));
+  std::string scheduler_group_name(GetSchedulerGroupName(tid));
   if (scheduler_group_name.empty()) {
     scheduler_group_name = "default";
   }
 
-  os << '"' << *name_ << '"';
-  if (is_daemon) {
-    os << " daemon";
+  if (thread != NULL) {
+    os << '"' << *thread->name_ << '"';
+    if (is_daemon) {
+      os << " daemon";
+    }
+    os << " prio=" << priority
+       << " tid=" << thread->GetThinLockId()
+       << " " << thread->GetState() << "\n";
+  } else {
+    std::string thread_name;
+    if (ReadFileToString(StringPrintf("/proc/self/task/%d/comm", tid), &thread_name)) {
+      thread_name.resize(thread_name.size() - 1); // Lose the trailing '\n'.
+    } else {
+      thread_name = "<unknown>";
+    }
+    os << '"' << thread_name << '"'
+       << " prio=" << priority
+       << " tid=?"
+       << " (not attached)\n";
   }
-  os << " prio=" << priority
-     << " tid=" << GetThinLockId()
-     << " " << GetState() << "\n";
 
-  os << "  | group=\"" << group_name << "\""
-     << " sCount=" << suspend_count_
-     << " dsCount=" << debug_suspend_count_
-     << " obj=" << reinterpret_cast<void*>(peer_)
-     << " self=" << reinterpret_cast<const void*>(this) << "\n";
-  os << "  | sysTid=" << GetTid()
-     << " nice=" << getpriority(PRIO_PROCESS, GetTid())
+  if (thread != NULL) {
+    os << "  | group=\"" << group_name << "\""
+       << " sCount=" << thread->suspend_count_
+       << " dsCount=" << thread->debug_suspend_count_
+       << " obj=" << reinterpret_cast<void*>(thread->peer_)
+       << " self=" << reinterpret_cast<const void*>(thread) << "\n";
+  }
+  os << "  | sysTid=" << tid
+     << " nice=" << getpriority(PRIO_PROCESS, tid)
      << " sched=" << policy << "/" << sp.sched_priority
-     << " cgrp=" << scheduler_group_name
-     << " handle=" << pthread_self() << "\n";
+     << " cgrp=" << scheduler_group_name << "\n";
+
+  // TODO: fix this; it's never worked in art! << " handle=" << pthread_self() << "\n";
 
   // Grab the scheduler stats for this thread.
   std::string scheduler_stats;
-  if (ReadFileToString(StringPrintf("/proc/self/task/%d/schedstat", GetTid()), &scheduler_stats)) {
+  if (ReadFileToString(StringPrintf("/proc/self/task/%d/schedstat", tid), &scheduler_stats)) {
     scheduler_stats.resize(scheduler_stats.size() - 1); // Lose the trailing '\n'.
   } else {
     scheduler_stats = "0 0 0";
@@ -496,15 +512,21 @@
   int utime = 0;
   int stime = 0;
   int task_cpu = 0;
-  GetTaskStats(GetTid(), utime, stime, task_cpu);
+  GetTaskStats(tid, utime, stime, task_cpu);
 
   os << "  | schedstat=( " << scheduler_stats << " )"
      << " utm=" << utime
      << " stm=" << stime
-     << " core=" << task_cpu;
-  os << " HZ=" << sysconf(_SC_CLK_TCK) << "\n"
-     << "  | stack=" << reinterpret_cast<void*>(stack_begin_) << "-" << reinterpret_cast<void*>(stack_end_)
-     << " stackSize=" << PrettySize(stack_size_) << "\n";
+     << " core=" << task_cpu
+     << " HZ=" << sysconf(_SC_CLK_TCK) << "\n";
+  if (thread != NULL) {
+    os << "  | stack=" << reinterpret_cast<void*>(thread->stack_begin_) << "-" << reinterpret_cast<void*>(thread->stack_end_)
+       << " stackSize=" << PrettySize(thread->stack_size_) << "\n";
+  }
+}
+
+void Thread::DumpState(std::ostream& os) const {
+  Thread::DumpState(os, this, GetTid());
 }
 
 #if !defined(ART_USE_LLVM_COMPILER)
diff --git a/src/thread.h b/src/thread.h
index f51b581..7d885c0 100644
--- a/src/thread.h
+++ b/src/thread.h
@@ -129,6 +129,10 @@
   // When full == false, dumps a one-line summary of thread state (used for operator<<).
   void Dump(std::ostream& os, bool full = true) const;
 
+  // Dumps the SIGQUIT per-thread header. 'thread' can be NULL for a non-attached thread, in which
+  // case we use 'tid' to identify the thread, and we'll include as much information as we can.
+  static void DumpState(std::ostream& os, const Thread* thread, pid_t tid);
+
   ThreadState GetState() const {
     return state_;
   }
diff --git a/src/thread_list.cc b/src/thread_list.cc
index e8b412c..3d0e0be 100644
--- a/src/thread_list.cc
+++ b/src/thread_list.cc
@@ -16,11 +16,14 @@
 
 #include "thread_list.h"
 
+#include <dirent.h>
+#include <sys/types.h>
 #include <unistd.h>
 
 #include "debugger.h"
 #include "scoped_heap_lock.h"
 #include "scoped_thread_list_lock.h"
+#include "utils.h"
 
 namespace art {
 
@@ -50,6 +53,15 @@
   return find(list_.begin(), list_.end(), thread) != list_.end();
 }
 
+bool ThreadList::Contains(pid_t tid) {
+  for (It it = list_.begin(), end = list_.end(); it != end; ++it) {
+    if ((*it)->tid_ == tid) {
+      return true;
+    }
+  }
+  return false;
+}
+
 pid_t ThreadList::GetLockOwner() {
   return thread_list_lock_.GetOwner();
 }
@@ -57,6 +69,32 @@
 void ThreadList::DumpForSigQuit(std::ostream& os) {
   ScopedThreadListLock thread_list_lock;
   DumpLocked(os);
+  DumpUnattachedThreads(os);
+}
+
+static void DumpUnattachedThread(std::ostream& os, pid_t tid) {
+  Thread::DumpState(os, NULL, tid);
+  DumpKernelStack(os, tid, "  kernel: ", false);
+  DumpNativeStack(os, tid, "  native: ", false);
+  os << "\n";
+}
+
+void ThreadList::DumpUnattachedThreads(std::ostream& os) {
+  DIR* d = opendir("/proc/self/task");
+  if (!d) {
+    return;
+  }
+
+  dirent de;
+  dirent* result;
+  while (!readdir_r(d, &de, &result) && result != NULL) {
+    char* end;
+    pid_t tid = strtol(de.d_name, &end, 10);
+    if (!*end && !Contains(tid)) {
+      DumpUnattachedThread(os, tid);
+    }
+  }
+  closedir(d);
 }
 
 void ThreadList::DumpLocked(std::ostream& os) {
diff --git a/src/thread_list.h b/src/thread_list.h
index f0b4f6b..9e45bfb 100644
--- a/src/thread_list.h
+++ b/src/thread_list.h
@@ -64,6 +64,9 @@
   void ReleaseThreadId(uint32_t id);
 
   bool Contains(Thread* thread);
+  bool Contains(pid_t tid);
+
+  void DumpUnattachedThreads(std::ostream& os);
 
   bool AllOtherThreadsAreDaemons();
   void SuspendAllDaemonThreads();