forked from OSchip/llvm-project
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:
parent
27ec195f39
commit
be28cddeea
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue