Fix some copyright lines in folly/experimental/symbolizer/
[folly.git] / folly / experimental / symbolizer / SignalHandler.cpp
index dc34c23557ac61dc7ae3e636e9296900bf188af6..91a4ae163dc4f2ec9286c4e8e048fd7aef82d13d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2013-present Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 // This is heavily inspired by the signal handler from google-glog
 
-#include "folly/experimental/symbolizer/SignalHandler.h"
+#include <folly/experimental/symbolizer/SignalHandler.h>
 
+#include <signal.h>
 #include <sys/types.h>
+
+#include <algorithm>
 #include <atomic>
 #include <ctime>
 #include <mutex>
-#include <pthread.h>
-#include <signal.h>
-#include <unistd.h>
 #include <vector>
 
 #include <glog/logging.h>
 
-#include "folly/Conv.h"
-#include "folly/FileUtil.h"
-#include "folly/Portability.h"
-#include "folly/ScopeGuard.h"
-#include "folly/experimental/symbolizer/Symbolizer.h"
+#include <folly/Conv.h>
+#include <folly/ScopeGuard.h>
+#include <folly/experimental/symbolizer/ElfCache.h>
+#include <folly/experimental/symbolizer/Symbolizer.h>
+#include <folly/portability/PThread.h>
+#include <folly/portability/SysSyscall.h>
+#include <folly/portability/Unistd.h>
 
-namespace folly { namespace symbolizer {
+namespace folly {
+namespace symbolizer {
 
 namespace {
 
@@ -57,27 +60,25 @@ class FatalSignalCallbackRegistry {
 };
 
 FatalSignalCallbackRegistry::FatalSignalCallbackRegistry()
-  : installed_(false) {
-}
+    : installed_(false) {}
 
 void FatalSignalCallbackRegistry::add(SignalCallback func) {
   std::lock_guard<std::mutex> lock(mutex_);
-  CHECK(!installed_)
-    << "FatalSignalCallbackRegistry::add may not be used "
-       "after installing the signal handlers.";
+  CHECK(!installed_) << "FatalSignalCallbackRegistry::add may not be used "
+                        "after installing the signal handlers.";
   handlers_.push_back(func);
 }
 
 void FatalSignalCallbackRegistry::markInstalled() {
   std::lock_guard<std::mutex> lock(mutex_);
   CHECK(!installed_.exchange(true))
-    << "FatalSignalCallbackRegistry::markInstalled must be called "
-    << "at most once";
+      << "FatalSignalCallbackRegistry::markInstalled must be called "
+      << "at most once";
 }
 
 void FatalSignalCallbackRegistry::run() {
   if (!installed_) {
-    return;  // Shouldn't happen
+    return;
   }
 
   for (auto& fn : handlers_) {
@@ -87,20 +88,20 @@ void FatalSignalCallbackRegistry::run() {
 
 // Leak it so we don't have to worry about destruction order
 FatalSignalCallbackRegistry* gFatalSignalCallbackRegistry =
-  new FatalSignalCallbackRegistry;
+    new FatalSignalCallbackRegistry;
 
 struct {
   int number;
   const char* name;
   struct sigaction oldAction;
 } kFatalSignals[] = {
-  { SIGSEGV, "SIGSEGV" },
-  { SIGILL,  "SIGILL"  },
-  { SIGFPE,  "SIGFPE"  },
-  { SIGABRT, "SIGABRT" },
-  { SIGBUS,  "SIGBUS"  },
-  { SIGTERM, "SIGTERM" },
-  { 0,       nullptr   }
+    {SIGSEGV, "SIGSEGV", {}},
+    {SIGILL, "SIGILL", {}},
+    {SIGFPE, "SIGFPE", {}},
+    {SIGABRT, "SIGABRT", {}},
+    {SIGBUS, "SIGBUS", {}},
+    {SIGTERM, "SIGTERM", {}},
+    {0, nullptr, {}},
 };
 
 void callPreviousSignalHandler(int signum) {
@@ -110,6 +111,7 @@ void callPreviousSignalHandler(int signum) {
   for (auto p = kFatalSignals; p->name; ++p) {
     if (p->number == signum) {
       sigaction(signum, &p->oldAction, nullptr);
+      raise(signum);
       return;
     }
   }
@@ -122,16 +124,22 @@ void callPreviousSignalHandler(int signum) {
   raise(signum);
 }
 
+// Note: not thread-safe, but that's okay, as we only let one thread
+// in our signal handler at a time.
+//
+// Leak it so we don't have to worry about destruction order
+StackTracePrinter* gStackTracePrinter = new StackTracePrinter();
+
 void printDec(uint64_t val) {
   char buf[20];
   uint32_t n = uint64ToBufferUnsafe(val, buf);
-  writeFull(STDERR_FILENO, buf, n);
+  gStackTracePrinter->print(StringPiece(buf, n));
 }
 
 const char kHexChars[] = "0123456789abcdef";
 void printHex(uint64_t val) {
   // TODO(tudorb): Add this to folly/Conv.h
-  char buf[2 + 2 * sizeof(uint64_t)];  // "0x" prefix, 2 digits for each byte
+  char buf[2 + 2 * sizeof(uint64_t)]; // "0x" prefix, 2 digits for each byte
 
   char* end = buf + sizeof(buf);
   char* p = end;
@@ -142,15 +150,21 @@ void printHex(uint64_t val) {
   *--p = 'x';
   *--p = '0';
 
-  writeFull(STDERR_FILENO, p, end - p);
+  gStackTracePrinter->print(StringPiece(p, end));
 }
 
 void print(StringPiece sp) {
-  writeFull(STDERR_FILENO, sp.data(), sp.size());
+  gStackTracePrinter->print(sp);
+}
+
+void flush() {
+  gStackTracePrinter->flush();
 }
 
 void dumpTimeInfo() {
-  SCOPE_EXIT { fsyncNoInt(STDERR_FILENO); };
+  SCOPE_EXIT {
+    flush();
+  };
   time_t now = time(nullptr);
   print("*** Aborted at ");
   printDec(now);
@@ -159,8 +173,162 @@ void dumpTimeInfo() {
   print("') ***\n");
 }
 
+const char* sigill_reason(int si_code) {
+  switch (si_code) {
+    case ILL_ILLOPC:
+      return "illegal opcode";
+    case ILL_ILLOPN:
+      return "illegal operand";
+    case ILL_ILLADR:
+      return "illegal addressing mode";
+    case ILL_ILLTRP:
+      return "illegal trap";
+    case ILL_PRVOPC:
+      return "privileged opcode";
+    case ILL_PRVREG:
+      return "privileged register";
+    case ILL_COPROC:
+      return "coprocessor error";
+    case ILL_BADSTK:
+      return "internal stack error";
+
+    default:
+      return nullptr;
+  }
+}
+
+const char* sigfpe_reason(int si_code) {
+  switch (si_code) {
+    case FPE_INTDIV:
+      return "integer divide by zero";
+    case FPE_INTOVF:
+      return "integer overflow";
+    case FPE_FLTDIV:
+      return "floating-point divide by zero";
+    case FPE_FLTOVF:
+      return "floating-point overflow";
+    case FPE_FLTUND:
+      return "floating-point underflow";
+    case FPE_FLTRES:
+      return "floating-point inexact result";
+    case FPE_FLTINV:
+      return "floating-point invalid operation";
+    case FPE_FLTSUB:
+      return "subscript out of range";
+
+    default:
+      return nullptr;
+  }
+}
+
+const char* sigsegv_reason(int si_code) {
+  switch (si_code) {
+    case SEGV_MAPERR:
+      return "address not mapped to object";
+    case SEGV_ACCERR:
+      return "invalid permissions for mapped object";
+
+    default:
+      return nullptr;
+  }
+}
+
+const char* sigbus_reason(int si_code) {
+  switch (si_code) {
+    case BUS_ADRALN:
+      return "invalid address alignment";
+    case BUS_ADRERR:
+      return "nonexistent physical address";
+    case BUS_OBJERR:
+      return "object-specific hardware error";
+
+    // MCEERR_AR and MCEERR_AO: in sigaction(2) but not in headers.
+
+    default:
+      return nullptr;
+  }
+}
+
+const char* sigtrap_reason(int si_code) {
+  switch (si_code) {
+    case TRAP_BRKPT:
+      return "process breakpoint";
+    case TRAP_TRACE:
+      return "process trace trap";
+
+    // TRAP_BRANCH and TRAP_HWBKPT: in sigaction(2) but not in headers.
+
+    default:
+      return nullptr;
+  }
+}
+
+const char* sigchld_reason(int si_code) {
+  switch (si_code) {
+    case CLD_EXITED:
+      return "child has exited";
+    case CLD_KILLED:
+      return "child was killed";
+    case CLD_DUMPED:
+      return "child terminated abnormally";
+    case CLD_TRAPPED:
+      return "traced child has trapped";
+    case CLD_STOPPED:
+      return "child has stopped";
+    case CLD_CONTINUED:
+      return "stopped child has continued";
+
+    default:
+      return nullptr;
+  }
+}
+
+const char* sigio_reason(int si_code) {
+  switch (si_code) {
+    case POLL_IN:
+      return "data input available";
+    case POLL_OUT:
+      return "output buffers available";
+    case POLL_MSG:
+      return "input message available";
+    case POLL_ERR:
+      return "I/O error";
+    case POLL_PRI:
+      return "high priority input available";
+    case POLL_HUP:
+      return "device disconnected";
+
+    default:
+      return nullptr;
+  }
+}
+
+const char* signal_reason(int signum, int si_code) {
+  switch (signum) {
+    case SIGILL:
+      return sigill_reason(si_code);
+    case SIGFPE:
+      return sigfpe_reason(si_code);
+    case SIGSEGV:
+      return sigsegv_reason(si_code);
+    case SIGBUS:
+      return sigbus_reason(si_code);
+    case SIGTRAP:
+      return sigtrap_reason(si_code);
+    case SIGCHLD:
+      return sigchld_reason(si_code);
+    case SIGIO:
+      return sigio_reason(si_code); // aka SIGPOLL
+
+    default:
+      return nullptr;
+  }
+}
+
 void dumpSignalInfo(int signum, siginfo_t* siginfo) {
-  SCOPE_EXIT { fsyncNoInt(STDERR_FILENO); };
+  SCOPE_EXIT {
+    flush();
+  };
   // Get the signal name, if possible.
   const char* name = nullptr;
   for (auto p = kFatalSignals; p->name; ++p) {
@@ -182,80 +350,99 @@ void dumpSignalInfo(int signum, siginfo_t* siginfo) {
   printHex(reinterpret_cast<uint64_t>(siginfo->si_addr));
   print(") received by PID ");
   printDec(getpid());
-  print(" (TID ");
+  print(" (pthread TID ");
   printHex((uint64_t)pthread_self());
-  print("), stack trace: ***\n");
-}
+  print(") (linux TID ");
+  printDec(syscall(__NR_gettid));
+
+  // Kernel-sourced signals don't give us useful info for pid/uid.
+  if (siginfo->si_code != SI_KERNEL) {
+    print(") (maybe from PID ");
+    printDec(siginfo->si_pid);
+    print(", UID ");
+    printDec(siginfo->si_uid);
+  }
 
-void dumpStackTrace() {
-  SCOPE_EXIT { fsyncNoInt(STDERR_FILENO); };
-  // Get and symbolize stack trace
-  constexpr size_t kMaxStackTraceDepth = 100;
-  FrameArray<kMaxStackTraceDepth> addresses;
-
-  // Skip the getStackTrace frame
-  if (!getStackTrace(addresses)) {
-    print("(error retrieving stack trace)\n");
-  } else {
-    Symbolizer symbolizer;
-    symbolizer.symbolize(addresses);
-
-    FDSymbolizePrinter printer(STDERR_FILENO);
-    printer.println(addresses);
+  auto reason = signal_reason(signum, siginfo->si_code);
+
+  if (reason != nullptr) {
+    print(") (code: ");
+    print(reason);
   }
+
+  print("), stack trace: ***\n");
 }
 
-std::atomic<pthread_t*> gSignalThread;
+// On Linux, pthread_t is a pointer, so 0 is an invalid value, which we
+// take to indicate "no thread in the signal handler".
+//
+// POSIX defines PTHREAD_NULL for this purpose, but that's not available.
+constexpr pthread_t kInvalidThreadId = 0;
+
+std::atomic<pthread_t> gSignalThread(kInvalidThreadId);
+std::atomic<bool> gInRecursiveSignalHandler(false);
 
 // Here be dragons.
-void innerSignalHandler(int signum, siginfo_t* info, void* uctx) {
+void innerSignalHandler(int signum, siginfo_t* info, void* /* uctx */) {
   // First, let's only let one thread in here at a time.
   pthread_t myId = pthread_self();
 
-  pthread_t* prevSignalThread = nullptr;
-  while (!gSignalThread.compare_exchange_strong(prevSignalThread, &myId)) {
-    if (pthread_equal(*prevSignalThread, myId)) {
-      print("Entered fatal signal handler recursively. We're in trouble.\n");
+  pthread_t prevSignalThread = kInvalidThreadId;
+  while (!gSignalThread.compare_exchange_strong(prevSignalThread, myId)) {
+    if (pthread_equal(prevSignalThread, myId)) {
+      // First time here. Try to dump the stack trace without symbolization.
+      // If we still fail, well, we're mightily screwed, so we do nothing the
+      // next time around.
+      if (!gInRecursiveSignalHandler.exchange(true)) {
+        print("Entered fatal signal handler recursively. We're in trouble.\n");
+        gStackTracePrinter->printStackTrace(false); // no symbolization
+      }
       return;
     }
 
     // Wait a while, try again.
     timespec ts;
     ts.tv_sec = 0;
-    ts.tv_nsec = 100L * 1000 * 1000;  // 100ms
+    ts.tv_nsec = 100L * 1000 * 1000; // 100ms
     nanosleep(&ts, nullptr);
 
-    prevSignalThread = nullptr;
+    prevSignalThread = kInvalidThreadId;
   }
 
   dumpTimeInfo();
   dumpSignalInfo(signum, info);
-  dumpStackTrace();
+  gStackTracePrinter->printStackTrace(true); // with symbolization
 
   // Run user callbacks
   gFatalSignalCallbackRegistry->run();
 }
 
 void signalHandler(int signum, siginfo_t* info, void* uctx) {
-  SCOPE_EXIT { fsyncNoInt(STDERR_FILENO); };
+  SCOPE_EXIT {
+    flush();
+  };
   innerSignalHandler(signum, info, uctx);
 
-  gSignalThread = nullptr;
+  gSignalThread = kInvalidThreadId;
   // Kill ourselves with the previous handler.
   callPreviousSignalHandler(signum);
 }
 
-}  // namespace
+} // namespace
 
 void addFatalSignalCallback(SignalCallback cb) {
   gFatalSignalCallbackRegistry->add(cb);
 }
 
+void installFatalSignalCallbacks() {
+  gFatalSignalCallbackRegistry->markInstalled();
+}
+
 namespace {
 
 std::atomic<bool> gAlreadyInstalled;
 
-}  // namespace
+} // namespace
 
 void installFatalSignalHandler() {
   if (gAlreadyInstalled.exchange(true)) {
@@ -263,18 +450,20 @@ void installFatalSignalHandler() {
     return;
   }
 
-  gFatalSignalCallbackRegistry->markInstalled();
-
   struct sigaction sa;
   memset(&sa, 0, sizeof(sa));
   sigemptyset(&sa.sa_mask);
-  sa.sa_flags |= SA_SIGINFO;
+  // By default signal handlers are run on the signaled thread's stack.
+  // In case of stack overflow running the SIGSEGV signal handler on
+  // the same stack leads to another SIGSEGV and crashes the program.
+  // Use SA_ONSTACK, so alternate stack is used (only if configured via
+  // sigaltstack).
+  sa.sa_flags |= SA_SIGINFO | SA_ONSTACK;
   sa.sa_sigaction = &signalHandler;
 
   for (auto p = kFatalSignals; p->name; ++p) {
     CHECK_ERR(sigaction(p->number, &sa, &p->oldAction));
   }
 }
-
-}}  // namespaces
-
+} // namespace symbolizer
+} // namespace folly