folly/folly/debugging/symbolizer/SignalHandler.cpp

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * 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.
 */

// This is heavily inspired by the signal handler from google-glog

#include <folly/debugging/symbolizer/SignalHandler.h>

#include <signal.h>
#include <sys/types.h>

#include <algorithm>
#include <atomic>
#include <cerrno>
#include <ctime>
#include <mutex>
#include <vector>

#include <glog/logging.h>

#include <folly/ScopeGuard.h>
#include <folly/experimental/symbolizer/Symbolizer.h>
#include <folly/lang/ToAscii.h>
#include <folly/portability/SysSyscall.h>
#include <folly/portability/Unistd.h>

namespace folly {
namespace symbolizer {

#ifndef _WIN32

const unsigned long kAllFatalSignals =;

#endif

namespace {

/**
 * Fatal signal handler registry.
 */
class FatalSignalCallbackRegistry {};

FatalSignalCallbackRegistry::FatalSignalCallbackRegistry()
    :{}

void FatalSignalCallbackRegistry::add(SignalCallback func) {}

void FatalSignalCallbackRegistry::markInstalled() {}

void FatalSignalCallbackRegistry::run() {}

std::atomic<FatalSignalCallbackRegistry*> gFatalSignalCallbackRegistry{};

FatalSignalCallbackRegistry* getFatalSignalCallbackRegistry() {}

} // namespace

void addFatalSignalCallback(SignalCallback cb) {}

void installFatalSignalCallbacks() {}

#ifndef _WIN32

namespace {

struct {} kFatalSignals[] =;

[[maybe_unused]] void callPreviousSignalHandler(int signum) {}

#if FOLLY_USE_SYMBOLIZER

// 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
//
// Initialized by installFatalSignalHandler
SafeStackTracePrinter* gStackTracePrinter;

void print(StringPiece sp) {
  gStackTracePrinter->print(sp);
}

void flush() {
  gStackTracePrinter->flush();
}

void printDec(uint64_t val) {
  char buf[to_ascii_size_max_decimal<uint64_t>];
  size_t n = to_ascii_decimal(buf, val);
  gStackTracePrinter->print(StringPiece(buf, n));
}

void printHex(uint64_t val) {
  char buf[2 + to_ascii_size_max<16, uint64_t>];
  auto out = buf + 0;
  *out++ = '0';
  *out++ = 'x';
  out += to_ascii_lower<16>(out, buf + sizeof(buf), val);
  gStackTracePrinter->print(StringPiece(buf, out - buf));
}

void dumpTimeInfo() {
  SCOPE_EXIT {
    flush();
  };
  time_t now = time(nullptr);
  print("*** Aborted at ");
  printDec(now);
  print(" (Unix time, try 'date -d @");
  printDec(now);
  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 {
    flush();
  };
  // Get the signal name, if possible.
  const char* name = nullptr;
  for (auto p = kFatalSignals; p->name; ++p) {
    if (p->number == signum) {
      name = p->name;
      break;
    }
  }

  print("*** Signal ");
  printDec(signum);
  if (name) {
    print(" (");
    print(name);
    print(")");
  }

  print(" (");
  printHex(reinterpret_cast<uint64_t>(siginfo->si_addr));
  print(") received by PID ");
  printDec(getpid());
  print(" (pthread TID ");
  printHex((uint64_t)pthread_self());
#if defined(__linux__)
  print(") (linux TID ");
  printDec(syscall(__NR_gettid));
#elif defined(__FreeBSD__)
  long tid = 0;
  syscall(432, &tid);
  print(") (freebsd TID ");
  printDec(tid);
#endif

  // Kernel-sourced signals don't give us useful info for pid/uid.
  if (siginfo->si_code <= 0) {
    print(") (maybe from PID ");
    printDec(siginfo->si_pid);
    print(", UID ");
    printDec(siginfo->si_uid);
  }

  auto reason = signal_reason(signum, siginfo->si_code);

  print(") (code: ");
  // If we can't find a reason code make a best effort to print the (int) code.
  if (reason != nullptr) {
    print(reason);
  } else {
    if (siginfo->si_code < 0) {
      print("-");
      printDec(-siginfo->si_code);
    } else {
      printDec(siginfo->si_code);
    }
  }

  print("), stack trace: ***\n");
}

// 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 */) {
  // First, let's only let one thread in here at a time.
  pthread_t myId = pthread_self();

  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
    nanosleep(&ts, nullptr);

    prevSignalThread = kInvalidThreadId;
  }

  dumpTimeInfo();
  dumpSignalInfo(signum, info);
  gStackTracePrinter->printStackTrace(true); // with symbolization

  // Run user callbacks
  auto callbacks = gFatalSignalCallbackRegistry.load(std::memory_order_acquire);
  if (callbacks) {
    callbacks->run();
  }
}

namespace {
std::atomic<bool> gFatalSignalReceived{false};
} // namespace

void signalHandler(int signum, siginfo_t* info, void* uctx) {
  gFatalSignalReceived.store(true, std::memory_order_relaxed);

  int savedErrno = errno;
  SCOPE_EXIT {
    flush();
    errno = savedErrno;
  };
  innerSignalHandler(signum, info, uctx);

  gSignalThread = kInvalidThreadId;
  // Kill ourselves with the previous handler.
  callPreviousSignalHandler(signum);
}

#endif // FOLLY_USE_SYMBOLIZER

// Small sigaltstack size threshold.
// 51392 is known to cause the signal handler to stack overflow during
// symbolization of trivial async stacks (e.g [] { CHECK(false); co_return; }).
constexpr size_t kSmallSigAltStackSize =;

[[maybe_unused]] bool isSmallSigAltStackEnabled() {}

} // namespace

#endif // _WIN32

namespace {
std::atomic<bool> gAlreadyInstalled;
}

void installFatalSignalHandler(std::bitset<64> signals) {}

bool fatalSignalReceived() {}

} // namespace symbolizer
} // namespace folly