Add signal handlers for host builds to imitate debuggerd.

This isn't going to be as reliable as debuggerd because we're running inside
the crashed process. We could always build debuggerd for the host, and have
something to call out to that. But for now, this gives us most of the
information most of the time...

Change-Id: I992ffb3bdb2903e4b83bd5666108ebe12a813338
diff --git a/src/runtime.cc b/src/runtime.cc
index 947eec5..25789ab 100644
--- a/src/runtime.cc
+++ b/src/runtime.cc
@@ -37,6 +37,7 @@
 #include "oat_file.h"
 #include "ScopedLocalRef.h"
 #include "signal_catcher.h"
+#include "signal_set.h"
 #include "space.h"
 #include "thread.h"
 #include "thread_list.h"
@@ -658,6 +659,7 @@
                    options->image_);
 
   BlockSignals();
+  InitPlatformSignalHandlers();
 
   java_vm_ = new JavaVMExt(this, options.get());
 
@@ -822,22 +824,13 @@
 }
 
 void Runtime::BlockSignals() {
-  sigset_t sigset;
-  if (sigemptyset(&sigset) == -1) {
-    PLOG(FATAL) << "sigemptyset failed";
-  }
-  if (sigaddset(&sigset, SIGPIPE) == -1) {
-    PLOG(ERROR) << "sigaddset SIGPIPE failed";
-  }
+  SignalSet signals;
+  signals.Add(SIGPIPE);
   // SIGQUIT is used to dump the runtime's state (including stack traces).
-  if (sigaddset(&sigset, SIGQUIT) == -1) {
-    PLOG(ERROR) << "sigaddset SIGQUIT failed";
-  }
+  signals.Add(SIGQUIT);
   // SIGUSR1 is used to initiate a GC.
-  if (sigaddset(&sigset, SIGUSR1) == -1) {
-    PLOG(ERROR) << "sigaddset SIGUSR1 failed";
-  }
-  CHECK_EQ(sigprocmask(SIG_BLOCK, &sigset, NULL), 0);
+  signals.Add(SIGUSR1);
+  signals.Block();
 }
 
 void Runtime::AttachCurrentThread(const char* thread_name, bool as_daemon, Object* thread_group) {
diff --git a/src/runtime.h b/src/runtime.h
index 2bf1148..dc61b15 100644
--- a/src/runtime.h
+++ b/src/runtime.h
@@ -247,6 +247,7 @@
 
  private:
   static void PlatformAbort(const char*, int);
+  static void InitPlatformSignalHandlers();
 
   Runtime();
 
diff --git a/src/runtime_android.cc b/src/runtime_android.cc
index 4a736e6..b64f8a1 100644
--- a/src/runtime_android.cc
+++ b/src/runtime_android.cc
@@ -22,4 +22,8 @@
   // On a device, debuggerd will give us a stack trace. Nothing to do here.
 }
 
+void Runtime::InitPlatformSignalHandlers() {
+  // On a device, debuggerd will give us a stack trace. Nothing to do here.
+}
+
 }  // namespace art
diff --git a/src/runtime_linux.cc b/src/runtime_linux.cc
index a604819..15a5fff 100644
--- a/src/runtime_linux.cc
+++ b/src/runtime_linux.cc
@@ -18,13 +18,14 @@
 
 #include <cxxabi.h>
 #include <execinfo.h>
+#include <signal.h>
 
 #include "logging.h"
 #include "stringprintf.h"
 
 namespace art {
 
-std::string Demangle(const std::string& mangled_name) {
+static std::string Demangle(const std::string& mangled_name) {
   if (mangled_name.empty()) {
     return "??";
   }
@@ -41,9 +42,7 @@
   return mangled_name + "()";
 }
 
-void Runtime::PlatformAbort(const char* /*file*/, int /*line_number*/) {
-  // On the host, we don't have debuggerd to dump a stack for us.
-
+static void Backtrace() {
   // Get the raw stack frames.
   size_t MAX_STACK_FRAMES = 64;
   void* frames[MAX_STACK_FRAMES];
@@ -86,4 +85,134 @@
   free(symbols);
 }
 
+static const char* GetSignalCodeName(int signal_number, int signal_code) {
+  // Try the signal-specific codes...
+  switch (signal_number) {
+    case SIGILL:
+      switch (signal_code) {
+        case ILL_ILLOPC: return "ILL_ILLOPC";
+        case ILL_ILLOPN: return "ILL_ILLOPN";
+        case ILL_ILLADR: return "ILL_ILLADR";
+        case ILL_ILLTRP: return "ILL_ILLTRP";
+        case ILL_PRVOPC: return "ILL_PRVOPC";
+        case ILL_PRVREG: return "ILL_PRVREG";
+        case ILL_COPROC: return "ILL_COPROC";
+        case ILL_BADSTK: return "ILL_BADSTK";
+      }
+      break;
+    case SIGBUS:
+      switch (signal_code) {
+        case BUS_ADRALN: return "BUS_ADRALN";
+        case BUS_ADRERR: return "BUS_ADRERR";
+        case BUS_OBJERR: return "BUS_OBJERR";
+      }
+      break;
+    case SIGFPE:
+      switch (signal_code) {
+        case FPE_INTDIV: return "FPE_INTDIV";
+        case FPE_INTOVF: return "FPE_INTOVF";
+        case FPE_FLTDIV: return "FPE_FLTDIV";
+        case FPE_FLTOVF: return "FPE_FLTOVF";
+        case FPE_FLTUND: return "FPE_FLTUND";
+        case FPE_FLTRES: return "FPE_FLTRES";
+        case FPE_FLTINV: return "FPE_FLTINV";
+        case FPE_FLTSUB: return "FPE_FLTSUB";
+      }
+      break;
+    case SIGSEGV:
+      switch (signal_code) {
+        case SEGV_MAPERR: return "SEGV_MAPERR";
+        case SEGV_ACCERR: return "SEGV_ACCERR";
+      }
+      break;
+    case SIGTRAP:
+      switch (signal_code) {
+        case TRAP_BRKPT: return "TRAP_BRKPT";
+        case TRAP_TRACE: return "TRAP_TRACE";
+      }
+      break;
+  }
+  // Then the other codes...
+  switch (signal_code) {
+    case SI_USER:     return "SI_USER";
+    case SI_KERNEL:   return "SI_KERNEL";
+    case SI_QUEUE:    return "SI_QUEUE";
+    case SI_TIMER:    return "SI_TIMER";
+    case SI_MESGQ:    return "SI_MESGQ";
+    case SI_ASYNCIO:  return "SI_ASYNCIO";
+    case SI_SIGIO:    return "SI_SIGIO";
+    case SI_TKILL:    return "SI_TKILL";
+  }
+  // Then give up...
+  return "?";
+}
+
+static void HandleUnexpectedSignal(int signal_number, siginfo_t* info, void*) {
+  const char* signal_name = "?";
+  bool has_address = false;
+  if (signal_number == SIGILL) {
+    signal_name = "SIGILL";
+    has_address = true;
+  } else if (signal_number == SIGTRAP) {
+    signal_name = "SIGTRAP";
+  } else if (signal_number == SIGABRT) {
+    signal_name = "SIGABRT";
+  } else if (signal_number == SIGBUS) {
+    signal_name = "SIGBUS";
+    has_address = true;
+  } else if (signal_number == SIGFPE) {
+    signal_name = "SIGFPE";
+    has_address = true;
+  } else if (signal_number == SIGSEGV) {
+    signal_name = "SIGSEGV";
+    has_address = true;
+  } else if (signal_number == SIGSTKFLT) {
+    signal_name = "SIGSTKFLT";
+  } else if (signal_number == SIGPIPE) {
+    signal_name = "SIGPIPE";
+  }
+
+  LOG(INTERNAL_FATAL) << "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n"
+                      << StringPrintf("Fatal signal %d (%s), code %d (%s)",
+                                      signal_number, signal_name,
+                                      info->si_code,
+                                      GetSignalCodeName(signal_number, info->si_code))
+                      << (has_address ? StringPrintf(" fault addr %p", info->si_addr) : "");
+  Backtrace();
+
+  // TODO: make this part optional, like it is on the device?
+  // Wait for debugger to attach.
+  LOG(INTERNAL_FATAL) << "********************************************************\n"
+                      << "* Process " << getpid() << " has been suspended while crashing. Attach gdb:\n"
+                      << "*     gdb -p " << getpid() << "\n"
+                      << "********************************************************\n";
+  while (true) {
+  }
+}
+
+void Runtime::PlatformAbort(const char* /*file*/, int /*line_number*/) {
+  // On the host, we don't have debuggerd to dump a stack for us when we LOG(FATAL).
+  Backtrace();
+}
+
+void Runtime::InitPlatformSignalHandlers() {
+  // On the host, we don't have debuggerd to dump a stack for us when something unexpected happens.
+  struct sigaction action;
+  memset(&action, 0, sizeof(action));
+  sigemptyset(&action.sa_mask);
+  action.sa_sigaction = HandleUnexpectedSignal;
+  action.sa_flags = SA_RESTART | SA_SIGINFO;
+
+  int rc = 0;
+  rc += sigaction(SIGILL, &action, NULL);
+  rc += sigaction(SIGTRAP, &action, NULL);
+  rc += sigaction(SIGABRT, &action, NULL);
+  rc += sigaction(SIGBUS, &action, NULL);
+  rc += sigaction(SIGFPE, &action, NULL);
+  rc += sigaction(SIGSEGV, &action, NULL);
+  rc += sigaction(SIGSTKFLT, &action, NULL);
+  rc += sigaction(SIGPIPE, &action, NULL);
+  CHECK_EQ(rc, 0);
+}
+
 }  // namespace art
diff --git a/src/signal_catcher.cc b/src/signal_catcher.cc
index dd31fa7..5a9bf4e 100644
--- a/src/signal_catcher.cc
+++ b/src/signal_catcher.cc
@@ -31,6 +31,7 @@
 #include "os.h"
 #include "runtime.h"
 #include "scoped_heap_lock.h"
+#include "signal_set.h"
 #include "thread.h"
 #include "thread_list.h"
 #include "utils.h"
@@ -135,21 +136,14 @@
   Runtime::Current()->GetHeap()->CollectGarbage(false);
 }
 
-int SignalCatcher::WaitForSignal(sigset_t& mask) {
+int SignalCatcher::WaitForSignal(SignalSet& signals) {
   ScopedThreadStateChange tsc(thread_, kVmWait);
 
   // Signals for sigwait() must be blocked but not ignored.  We
   // block signals like SIGQUIT for all threads, so the condition
   // is met.  When the signal hits, we wake up, without any signal
   // handlers being invoked.
-
-  // Sleep in sigwait() until a signal arrives. gdb causes EINTR failures.
-  int signal_number;
-  int rc = TEMP_FAILURE_RETRY(sigwait(&mask, &signal_number));
-  if (rc != 0) {
-    PLOG(FATAL) << "sigwait failed";
-  }
-
+  int signal_number = signals.Wait();
   if (!ShouldHalt()) {
     // Let the user know we got the signal, just in case the system's too screwed for us to
     // actually do what they want us to do...
@@ -177,13 +171,12 @@
   }
 
   // Set up mask with signals we want to handle.
-  sigset_t mask;
-  sigemptyset(&mask);
-  sigaddset(&mask, SIGQUIT);
-  sigaddset(&mask, SIGUSR1);
+  SignalSet signals;
+  signals.Add(SIGQUIT);
+  signals.Add(SIGUSR1);
 
   while (true) {
-    int signal_number = signal_catcher->WaitForSignal(mask);
+    int signal_number = signal_catcher->WaitForSignal(signals);
     if (signal_catcher->ShouldHalt()) {
       runtime->DetachCurrentThread();
       return NULL;
diff --git a/src/signal_catcher.h b/src/signal_catcher.h
index f237b12..131b07c 100644
--- a/src/signal_catcher.h
+++ b/src/signal_catcher.h
@@ -22,6 +22,7 @@
 namespace art {
 
 class Runtime;
+class SignalSet;
 class Thread;
 
 /*
@@ -43,7 +44,7 @@
   void Output(const std::string& s);
   void SetHaltFlag(bool new_value);
   bool ShouldHalt();
-  int WaitForSignal(sigset_t& mask);
+  int WaitForSignal(SignalSet& signals);
 
   std::string stack_trace_file_;
   mutable Mutex lock_;
diff --git a/src/signal_set.h b/src/signal_set.h
new file mode 100644
index 0000000..030a2d1
--- /dev/null
+++ b/src/signal_set.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2012 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_SRC_SIGNAL_SET_H_
+#define ART_SRC_SIGNAL_SET_H_
+
+#include <signal.h>
+
+#include "logging.h"
+
+namespace art {
+
+class SignalSet {
+ public:
+  SignalSet() {
+    if (sigemptyset(&set_) == -1) {
+      PLOG(FATAL) << "sigemptyset failed";
+    }
+  }
+
+  void Add(int signal) {
+    if (sigaddset(&set_, signal) == -1) {
+      PLOG(FATAL) << "sigaddset " << signal << " failed";
+    }
+  }
+
+  void Block() {
+    if (sigprocmask(SIG_BLOCK, &set_, NULL) == -1) {
+      PLOG(FATAL) << "sigprocmask failed";
+    }
+  }
+
+  int Wait() {
+    // Sleep in sigwait() until a signal arrives. gdb causes EINTR failures.
+    int signal_number;
+    int rc = TEMP_FAILURE_RETRY(sigwait(&set_, &signal_number));
+    if (rc != 0) {
+      PLOG(FATAL) << "sigwait failed";
+    }
+    return signal_number;
+  }
+
+ private:
+  sigset_t set_;
+};
+
+}  // namespace art
+
+#endif  // ART_SRC_SIGNAL_SET_H_