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
This commit is contained in:
Jordan Rose 2019-07-12 16:05:09 +00:00
parent 27ec195f39
commit be28cddeea
5 changed files with 148 additions and 20 deletions

View File

@ -21,8 +21,22 @@
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

View 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

View 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;

View 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>
@ -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) {

View 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