diff options
author | Jordan Rose <jordan_rose@apple.com> | 2019-07-12 16:05:09 +0000 |
---|---|---|
committer | Jordan Rose <jordan_rose@apple.com> | 2019-07-12 16:05:09 +0000 |
commit | be28cddeeaa6e4377de9365e506645ee1eb97e35 (patch) | |
tree | f827146dfe75e117c032ac5493ff8c5c778922a8 /llvm/lib/Support | |
parent | 27ec195f391cf1bcf39fe25f345bc63061566c3c (diff) | |
download | bcm5719-llvm-be28cddeeaa6e4377de9365e506645ee1eb97e35.tar.gz bcm5719-llvm-be28cddeeaa6e4377de9365e506645ee1eb97e35.zip |
Support for dumping current PrettyStackTrace on SIGINFO (Ctrl-T)
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
llvm-svn: 365911
Diffstat (limited to 'llvm/lib/Support')
-rw-r--r-- | llvm/lib/Support/PrettyStackTrace.cpp | 65 | ||||
-rw-r--r-- | llvm/lib/Support/Unix/Signals.inc | 67 | ||||
-rw-r--r-- | llvm/lib/Support/Windows/Signals.inc | 4 |
3 files changed, 119 insertions, 17 deletions
diff --git a/llvm/lib/Support/PrettyStackTrace.cpp b/llvm/lib/Support/PrettyStackTrace.cpp index 497084ecda9..e566440f49a 100644 --- a/llvm/lib/Support/PrettyStackTrace.cpp +++ b/llvm/lib/Support/PrettyStackTrace.cpp @@ -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; diff --git a/llvm/lib/Support/Unix/Signals.inc b/llvm/lib/Support/Unix/Signals.inc index ec3935928d2..634c16aa36c 100644 --- a/llvm/lib/Support/Unix/Signals.inc +++ b/llvm/lib/Support/Unix/Signals.inc @@ -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> @@ -80,10 +81,13 @@ 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) { diff --git a/llvm/lib/Support/Windows/Signals.inc b/llvm/lib/Support/Windows/Signals.inc index 01dc0574d9d..6a820ef22b1 100644 --- a/llvm/lib/Support/Windows/Signals.inc +++ b/llvm/lib/Support/Windows/Signals.inc @@ -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 |