]> granicus.if.org Git - llvm/commitdiff
Support for dumping current PrettyStackTrace on SIGINFO (Ctrl-T)
authorJordan Rose <jordan_rose@apple.com>
Fri, 12 Jul 2019 16:05:09 +0000 (16:05 +0000)
committerJordan Rose <jordan_rose@apple.com>
Fri, 12 Jul 2019 16:05:09 +0000 (16:05 +0000)
Support SIGINFO (and SIGUSR1 for POSIX purposes) to tell what
long-running jobs are doing, as inspired by BSD tools (including on
macOS), by dumping the current PrettyStackTrace.

This adds a new kind of signal handler for non-fatal "info" signals,
similar to the "interrupt" handler that already exists for SIGINT
(Ctrl-C). It then uses that handler to update a "generation count"
managed by the PrettyStackTrace infrastructure, which is then checked
whenever a PrettyStackTraceEntry is pushed or popped on each
thread. If the generation has changed---i.e. if the user has pressed
Ctrl-T---the stack trace is dumped, though unfortunately it can't
include the deepest entry because that one is currently being
constructed/destructed.

https://reviews.llvm.org/D63750

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@365911 91177308-0d34-0410-b5e6-96231b3b80d8

include/llvm/Support/PrettyStackTrace.h
include/llvm/Support/Signals.h
lib/Support/PrettyStackTrace.cpp
lib/Support/Unix/Signals.inc
lib/Support/Windows/Signals.inc

index f6ed7c94ba31004214ac1a861ee5e0ddb760e326..6eb070b2297ecd0cdf41638544fb741ef3fc50ba 100644 (file)
 namespace llvm {
   class raw_ostream;
 
+  /// Enables dumping a "pretty" stack trace when the program crashes.
+  ///
+  /// \see PrettyStackTraceEntry
   void EnablePrettyStackTrace();
 
+  /// Enables (or disables) dumping a "pretty" stack trace when the user sends
+  /// SIGINFO or SIGUSR1 to the current process.
+  ///
+  /// This is a per-thread decision so that a program can choose to print stack
+  /// traces only on a primary thread, or on all threads that use
+  /// PrettyStackTraceEntry.
+  ///
+  /// \see EnablePrettyStackTrace
+  /// \see PrettyStackTraceEntry
+  void EnablePrettyStackTraceOnSigInfoForThisThread(bool ShouldEnable = true);
+
   /// PrettyStackTraceEntry - This class is used to represent a frame of the
   /// "pretty" stack trace that is dumped when a program crashes. You can define
   /// subclasses of this and declare them on the program stack: when they are
index 9f2cb850c4680ebd81d0e207a1951d43dbccfeb1..a6b215a24311f1834d0e403fc69f843db76c6b33 100644 (file)
@@ -65,13 +65,25 @@ namespace sys {
   /// This function registers a function to be called when the user "interrupts"
   /// the program (typically by pressing ctrl-c).  When the user interrupts the
   /// program, the specified interrupt function is called instead of the program
-  /// being killed, and the interrupt function automatically disabled.  Note
-  /// that interrupt functions are not allowed to call any non-reentrant
+  /// being killed, and the interrupt function automatically disabled.
+  ///
+  /// Note that interrupt functions are not allowed to call any non-reentrant
   /// functions.  An null interrupt function pointer disables the current
   /// installed function.  Note also that the handler may be executed on a
   /// different thread on some platforms.
-  /// Register a function to be called when ctrl-c is pressed.
   void SetInterruptFunction(void (*IF)());
+
+  /// Registers a function to be called when an "info" signal is delivered to
+  /// the process.
+  ///
+  /// On POSIX systems, this will be SIGUSR1; on systems that have it, SIGINFO
+  /// will also be used (typically ctrl-t).
+  ///
+  /// Note that signal handlers are not allowed to call any non-reentrant
+  /// functions.  An null function pointer disables the current installed
+  /// function.  Note also that the handler may be executed on a different
+  /// thread on some platforms.
+  void SetInfoSignalFunction(void (*Handler)());
 } // End sys namespace
 } // End llvm namespace
 
index 497084ecda951bb03a8aa368ee8096d1b0b7fbef..e566440f49a2fab51d7f3551951c0bba30efc21f 100644 (file)
@@ -16,6 +16,7 @@
 #include "llvm/ADT/SmallString.h"
 #include "llvm/Config/config.h"
 #include "llvm/Support/Compiler.h"
+#include "llvm/Support/SaveAndRestore.h"
 #include "llvm/Support/Signals.h"
 #include "llvm/Support/Watchdog.h"
 #include "llvm/Support/raw_ostream.h"
@@ -41,6 +42,22 @@ using namespace llvm;
 // thread-local variable.
 static LLVM_THREAD_LOCAL PrettyStackTraceEntry *PrettyStackTraceHead = nullptr;
 
+// The use of 'volatile' here is to ensure that any particular thread always
+// reloads the value of the counter. The 'std::atomic' allows us to specify that
+// this variable is accessed in an unsychronized way (it's not actually
+// synchronizing). This does technically mean that the value may not appear to
+// be the same across threads running simultaneously on different CPUs, but in
+// practice the worst that will happen is that we won't print a stack trace when
+// we could have.
+//
+// This is initialized to 1 because 0 is used as a sentinel for "not enabled on
+// the current thread". If the user happens to overflow an 'unsigned' with
+// SIGINFO requests, it's possible that some threads will stop responding to it,
+// but the program won't crash.
+static volatile std::atomic<unsigned> GlobalSigInfoGenerationCounter =
+    ATOMIC_VAR_INIT(1);
+static LLVM_THREAD_LOCAL unsigned ThreadLocalSigInfoGenerationCounter = 0;
+
 namespace llvm {
 PrettyStackTraceEntry *ReverseStackTrace(PrettyStackTraceEntry *Head) {
   PrettyStackTraceEntry *Prev = nullptr;
@@ -56,8 +73,9 @@ static void PrintStack(raw_ostream &OS) {
   // to fail if we crashed due to stack overflow), we do an up-front pass to
   // reverse the stack, then print it, then reverse it again.
   unsigned ID = 0;
-  PrettyStackTraceEntry *ReversedStack =
-      llvm::ReverseStackTrace(PrettyStackTraceHead);
+  SaveAndRestore<PrettyStackTraceEntry *> SavedStack{PrettyStackTraceHead,
+                                                     nullptr};
+  PrettyStackTraceEntry *ReversedStack = ReverseStackTrace(SavedStack.get());
   for (const PrettyStackTraceEntry *Entry = ReversedStack; Entry;
        Entry = Entry->getNextEntry()) {
     OS << ID++ << ".\t";
@@ -67,7 +85,10 @@ static void PrintStack(raw_ostream &OS) {
   llvm::ReverseStackTrace(ReversedStack);
 }
 
-/// PrintCurStackTrace - Print the current stack trace to the specified stream.
+/// Print the current stack trace to the specified stream.
+///
+/// Marked NOINLINE so it can be called from debuggers.
+LLVM_ATTRIBUTE_NOINLINE
 static void PrintCurStackTrace(raw_ostream &OS) {
   // Don't print an empty trace.
   if (!PrettyStackTraceHead) return;
@@ -127,10 +148,24 @@ static void CrashHandler(void *) {
 #endif
 }
 
+static void printForSigInfoIfNeeded() {
+  unsigned CurrentSigInfoGeneration =
+      GlobalSigInfoGenerationCounter.load(std::memory_order_relaxed);
+  if (ThreadLocalSigInfoGenerationCounter == 0 ||
+      ThreadLocalSigInfoGenerationCounter == CurrentSigInfoGeneration) {
+    return;
+  }
+
+  PrintCurStackTrace(errs());
+  ThreadLocalSigInfoGenerationCounter = CurrentSigInfoGeneration;
+}
+
 #endif // ENABLE_BACKTRACES
 
 PrettyStackTraceEntry::PrettyStackTraceEntry() {
 #if ENABLE_BACKTRACES
+  // Handle SIGINFO first, because we haven't finished constructing yet.
+  printForSigInfoIfNeeded();
   // Link ourselves.
   NextEntry = PrettyStackTraceHead;
   PrettyStackTraceHead = this;
@@ -142,6 +177,8 @@ PrettyStackTraceEntry::~PrettyStackTraceEntry() {
   assert(PrettyStackTraceHead == this &&
          "Pretty stack trace entry destruction is out of order");
   PrettyStackTraceHead = NextEntry;
+  // Handle SIGINFO first, because we already started destructing.
+  printForSigInfoIfNeeded();
 #endif
 }
 
@@ -188,6 +225,28 @@ void llvm::EnablePrettyStackTrace() {
 #endif
 }
 
+void llvm::EnablePrettyStackTraceOnSigInfoForThisThread(bool ShouldEnable) {
+#if ENABLE_BACKTRACES
+  if (!ShouldEnable) {
+    ThreadLocalSigInfoGenerationCounter = 0;
+    return;
+  }
+
+  // The first time this is called, we register the SIGINFO handler.
+  static bool HandlerRegistered = []{
+    sys::SetInfoSignalFunction([]{
+      GlobalSigInfoGenerationCounter.fetch_add(1, std::memory_order_relaxed);
+    });
+    return false;
+  }();
+  (void)HandlerRegistered;
+
+  // Next, enable it for the current thread.
+  ThreadLocalSigInfoGenerationCounter =
+      GlobalSigInfoGenerationCounter.load(std::memory_order_relaxed);
+#endif
+}
+
 const void *llvm::SavePrettyStackState() {
 #if ENABLE_BACKTRACES
   return PrettyStackTraceHead;
index ec3935928d23b0fca8301a9d7cad2fed0d544250..634c16aa36c72e60e9580868ea67c509e0c46ba4 100644 (file)
@@ -42,6 +42,7 @@
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Mutex.h"
 #include "llvm/Support/Program.h"
+#include "llvm/Support/SaveAndRestore.h"
 #include "llvm/Support/UniqueLock.h"
 #include "llvm/Support/raw_ostream.h"
 #include <algorithm>
 using namespace llvm;
 
 static RETSIGTYPE SignalHandler(int Sig);  // defined below.
+static RETSIGTYPE InfoSignalHandler(int Sig);  // defined below.
 
+using SignalHandlerFunctionType = void (*)();
 /// The function to call if ctrl-c is pressed.
-using InterruptFunctionType = void (*)();
-static std::atomic<InterruptFunctionType> InterruptFunction =
+static std::atomic<SignalHandlerFunctionType> InterruptFunction =
+    ATOMIC_VAR_INIT(nullptr);
+static std::atomic<SignalHandlerFunctionType> InfoSignalFunction =
     ATOMIC_VAR_INIT(nullptr);
 
 namespace {
@@ -199,15 +203,15 @@ struct FilesToRemoveCleanup {
 
 static StringRef Argv0;
 
-// Signals that represent requested termination. There's no bug or failure, or
-// if there is, it's not our direct responsibility. For whatever reason, our
-// continued execution is no longer desirable.
+/// Signals that represent requested termination. There's no bug or failure, or
+/// if there is, it's not our direct responsibility. For whatever reason, our
+/// continued execution is no longer desirable.
 static const int IntSigs[] = {
-  SIGHUP, SIGINT, SIGPIPE, SIGTERM, SIGUSR1, SIGUSR2
+  SIGHUP, SIGINT, SIGPIPE, SIGTERM, SIGUSR2
 };
 
-// Signals that represent that we have a bug, and our prompt termination has
-// been ordered.
+/// Signals that represent that we have a bug, and our prompt termination has
+/// been ordered.
 static const int KillSigs[] = {
   SIGILL, SIGTRAP, SIGABRT, SIGFPE, SIGBUS, SIGSEGV, SIGQUIT
 #ifdef SIGSYS
@@ -224,11 +228,24 @@ static const int KillSigs[] = {
 #endif
 };
 
+/// Signals that represent requests for status.
+static const int InfoSigs[] = {
+  SIGUSR1
+#ifdef SIGINFO
+  , SIGINFO
+#endif
+};
+
+static const size_t NumSigs =
+    array_lengthof(IntSigs) + array_lengthof(KillSigs) +
+    array_lengthof(InfoSigs);
+
+
 static std::atomic<unsigned> NumRegisteredSignals = ATOMIC_VAR_INIT(0);
 static struct {
   struct sigaction SA;
   int SigNo;
-} RegisteredSignalInfo[array_lengthof(IntSigs) + array_lengthof(KillSigs)];
+} RegisteredSignalInfo[NumSigs];
 
 #if defined(HAVE_SIGALTSTACK)
 // Hold onto both the old and new alternate signal stack so that it's not
@@ -276,15 +293,24 @@ static void RegisterHandlers() { // Not signal-safe.
   // be able to reliably handle signals due to stack overflow.
   CreateSigAltStack();
 
-  auto registerHandler = [&](int Signal) {
+  enum class SignalKind { IsKill, IsInfo };
+  auto registerHandler = [&](int Signal, SignalKind Kind) {
     unsigned Index = NumRegisteredSignals.load();
     assert(Index < array_lengthof(RegisteredSignalInfo) &&
            "Out of space for signal handlers!");
 
     struct sigaction NewHandler;
 
-    NewHandler.sa_handler = SignalHandler;
-    NewHandler.sa_flags = SA_NODEFER | SA_RESETHAND | SA_ONSTACK;
+    switch (Kind) {
+    case SignalKind::IsKill:
+      NewHandler.sa_handler = SignalHandler;
+      NewHandler.sa_flags = SA_NODEFER | SA_RESETHAND | SA_ONSTACK;
+      break;
+    case SignalKind::IsInfo:
+      NewHandler.sa_handler = InfoSignalHandler;
+      NewHandler.sa_flags = SA_ONSTACK;
+      break;
+    }
     sigemptyset(&NewHandler.sa_mask);
 
     // Install the new handler, save the old one in RegisteredSignalInfo.
@@ -294,9 +320,11 @@ static void RegisterHandlers() { // Not signal-safe.
   };
 
   for (auto S : IntSigs)
-    registerHandler(S);
+    registerHandler(S, SignalKind::IsKill);
   for (auto S : KillSigs)
-    registerHandler(S);
+    registerHandler(S, SignalKind::IsKill);
+  for (auto S : InfoSigs)
+    registerHandler(S, SignalKind::IsInfo);
 }
 
 static void UnregisterHandlers() {
@@ -356,6 +384,12 @@ static RETSIGTYPE SignalHandler(int Sig) {
 #endif
 }
 
+static RETSIGTYPE InfoSignalHandler(int Sig) {
+  SaveAndRestore<int> SaveErrnoDuringASignalHandler(errno);
+  if (SignalHandlerFunctionType CurrentInfoFunction = InfoSignalFunction)
+    CurrentInfoFunction();
+}
+
 void llvm::sys::RunInterruptHandlers() {
   RemoveFilesToRemove();
 }
@@ -365,6 +399,11 @@ void llvm::sys::SetInterruptFunction(void (*IF)()) {
   RegisterHandlers();
 }
 
+void llvm::sys::SetInfoSignalFunction(void (*Handler)()) {
+  InfoSignalFunction.exchange(Handler);
+  RegisterHandlers();
+}
+
 // The public API
 bool llvm::sys::RemoveFileOnSignal(StringRef Filename,
                                    std::string* ErrMsg) {
index 01dc0574d9deb464232f7be7fd601720b21bc12b..6a820ef22b1e9bf2ce847d0340c317af6ee053b9 100644 (file)
@@ -556,6 +556,10 @@ void llvm::sys::SetInterruptFunction(void (*IF)()) {
   LeaveCriticalSection(&CriticalSection);
 }
 
+void llvm::sys::SetInfoSignalFunction(void (*Handler)()) {
+  // Unimplemented.
+}
+
 
 /// Add a function to be called when a signal is delivered to the process. The
 /// handler can have a cookie passed to it to identify what instance of the