[llvm] a1f1699 - [Support] Optionally call signal handlers when a function wrapped by the the CrashRecoveryContext fails
Alexandre Ganea via llvm-commits
llvm-commits at lists.llvm.org
Sat Jan 11 12:32:33 PST 2020
Author: Alexandre Ganea
Date: 2020-01-11T15:27:07-05:00
New Revision: a1f16998f371870ca4da8b3c00a093c607a36ddd
URL: https://github.com/llvm/llvm-project/commit/a1f16998f371870ca4da8b3c00a093c607a36ddd
DIFF: https://github.com/llvm/llvm-project/commit/a1f16998f371870ca4da8b3c00a093c607a36ddd.diff
LOG: [Support] Optionally call signal handlers when a function wrapped by the the CrashRecoveryContext fails
This patch allows for handling a failure inside a CrashRecoveryContext in the same way as the global exception/signal handler. A failure will have the same side-effect, such as cleanup of temporarty file, printing callstack, calling relevant signal handlers, and finally returning an exception code. This is an optional feature, disabled by default.
This is a support patch for D69825.
Differential Revision: https://reviews.llvm.org/D70568
Added:
Modified:
llvm/include/llvm/Support/CrashRecoveryContext.h
llvm/include/llvm/Support/Signals.h
llvm/lib/Support/CrashRecoveryContext.cpp
llvm/lib/Support/Unix/Signals.inc
llvm/lib/Support/Windows/Signals.inc
llvm/unittests/Support/CrashRecoveryTest.cpp
Removed:
################################################################################
diff --git a/llvm/include/llvm/Support/CrashRecoveryContext.h b/llvm/include/llvm/Support/CrashRecoveryContext.h
index 96e5c5ccdd01..9522c4742244 100644
--- a/llvm/include/llvm/Support/CrashRecoveryContext.h
+++ b/llvm/include/llvm/Support/CrashRecoveryContext.h
@@ -100,6 +100,14 @@ class CrashRecoveryContext {
/// Explicitly trigger a crash recovery in the current process, and
/// return failure from RunSafely(). This function does not return.
void HandleCrash();
+
+ /// In case of a crash, this is the crash identifier.
+ int RetCode = 0;
+
+ /// Selects whether handling of failures should be done in the same way as
+ /// for regular crashes. When this is active, a crash would print the
+ /// callstack, clean-up any temporary files and create a coredump/minidump.
+ bool DumpStackAndCleanupOnFailure = false;
};
/// Abstract base class of cleanup handlers.
diff --git a/llvm/include/llvm/Support/Signals.h b/llvm/include/llvm/Support/Signals.h
index 804bc8754417..e0a18e72f2a7 100644
--- a/llvm/include/llvm/Support/Signals.h
+++ b/llvm/include/llvm/Support/Signals.h
@@ -106,6 +106,15 @@ namespace sys {
/// On Unix systems, this function exits with an "IO error" exit code.
/// This is a no-op on Windows.
void DefaultOneShotPipeSignalHandler();
+
+ /// This function does the following:
+ /// - clean up any temporary files registered with RemoveFileOnSignal()
+ /// - dump the callstack from the exception context
+ /// - call any relevant interrupt/signal handlers
+ /// - create a core/mini dump of the exception context whenever possible
+ /// Context is a system-specific failure context: it is the signal type on
+ /// Unix; the ExceptionContext on Windows.
+ void CleanupOnSignal(uintptr_t Context);
} // End sys namespace
} // End llvm namespace
diff --git a/llvm/lib/Support/CrashRecoveryContext.cpp b/llvm/lib/Support/CrashRecoveryContext.cpp
index a9e0fcc73b9a..548cce058c58 100644
--- a/llvm/lib/Support/CrashRecoveryContext.cpp
+++ b/llvm/lib/Support/CrashRecoveryContext.cpp
@@ -10,9 +10,17 @@
#include "llvm/Config/llvm-config.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/ManagedStatic.h"
+#include "llvm/Support/Signals.h"
#include "llvm/Support/ThreadLocal.h"
#include <mutex>
#include <setjmp.h>
+#ifdef _WIN32
+#include <excpt.h> // for GetExceptionInformation
+#endif
+#if LLVM_ON_UNIX
+#include <sysexits.h> // EX_IOERR
+#endif
+
using namespace llvm;
namespace {
@@ -54,7 +62,11 @@ struct CrashRecoveryContextImpl {
#endif
}
- void HandleCrash() {
+ // If the function ran by the CrashRecoveryContext crashes or fails, then
+ // 'RetCode' represents the returned error code, as if it was returned by a
+ // process. 'Context' represents the signal type on Unix; on Windows, it is
+ // the ExceptionContext.
+ void HandleCrash(int RetCode, uintptr_t Context) {
// Eliminate the current context entry, to avoid re-entering in case the
// cleanup code crashes.
CurrentContext->set(Next);
@@ -62,7 +74,10 @@ struct CrashRecoveryContextImpl {
assert(!Failed && "Crash recovery context already failed!");
Failed = true;
- // FIXME: Stash the backtrace.
+ if (CRC->DumpStackAndCleanupOnFailure)
+ sys::CleanupOnSignal(Context);
+
+ CRC->RetCode = RetCode;
// Jump back to the RunSafely we were called under.
longjmp(JumpBuffer, 1);
@@ -171,19 +186,32 @@ CrashRecoveryContext::unregisterCleanup(CrashRecoveryContextCleanup *cleanup) {
static void installExceptionOrSignalHandlers() {}
static void uninstallExceptionOrSignalHandlers() {}
-bool CrashRecoveryContext::RunSafely(function_ref<void()> Fn) {
- if (!gCrashRecoveryEnabled) {
+// We need this function because the call to GetExceptionInformation() can only
+// occur inside the __except evaluation block
+static int ExceptionFilter(bool DumpStackAndCleanup,
+ _EXCEPTION_POINTERS *Except) {
+ if (DumpStackAndCleanup)
+ sys::CleanupOnSignal((uintptr_t)Except);
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+static bool InvokeFunctionCall(function_ref<void()> Fn,
+ bool DumpStackAndCleanup, int &RetCode) {
+ __try {
Fn();
- return true;
+ } __except (ExceptionFilter(DumpStackAndCleanup, GetExceptionInformation())) {
+ RetCode = GetExceptionCode();
+ return false;
}
+ return true;
+}
- bool Result = true;
- __try {
+bool CrashRecoveryContext::RunSafely(function_ref<void()> Fn) {
+ if (!gCrashRecoveryEnabled) {
Fn();
- } __except (1) { // Catch any exception.
- Result = false;
+ return true;
}
- return Result;
+ return InvokeFunctionCall(Fn, DumpStackAndCleanupOnFailure, RetCode);
}
#else // !_MSC_VER
@@ -237,7 +265,8 @@ static LONG CALLBACK ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo)
// implementation if we so choose.
// Handle the crash
- const_cast<CrashRecoveryContextImpl*>(CRCI)->HandleCrash();
+ const_cast<CrashRecoveryContextImpl *>(CRCI)->HandleCrash(
+ (int)ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo);
// Note that we don't actually get here because HandleCrash calls
// longjmp, which means the HandleCrash function never returns.
@@ -319,8 +348,16 @@ static void CrashRecoverySignalHandler(int Signal) {
sigaddset(&SigMask, Signal);
sigprocmask(SIG_UNBLOCK, &SigMask, nullptr);
+ // As per convention, -2 indicates a crash or timeout as opposed to failure to
+ // execute (see llvm/include/llvm/Support/Program.h)
+ int RetCode = -2;
+
+ // Don't consider a broken pipe as a crash (see clang/lib/Driver/Driver.cpp)
+ if (Signal == SIGPIPE)
+ RetCode = EX_IOERR;
+
if (CRCI)
- const_cast<CrashRecoveryContextImpl*>(CRCI)->HandleCrash();
+ const_cast<CrashRecoveryContextImpl *>(CRCI)->HandleCrash(RetCode, Signal);
}
static void installExceptionOrSignalHandlers() {
@@ -364,7 +401,9 @@ bool CrashRecoveryContext::RunSafely(function_ref<void()> Fn) {
void CrashRecoveryContext::HandleCrash() {
CrashRecoveryContextImpl *CRCI = (CrashRecoveryContextImpl *) Impl;
assert(CRCI && "Crash recovery context never initialized!");
- CRCI->HandleCrash();
+ // As per convention, -2 indicates a crash or timeout as opposed to failure to
+ // execute (see llvm/include/llvm/Support/Program.h)
+ CRCI->HandleCrash(-2, 0);
}
// FIXME: Portability.
diff --git a/llvm/lib/Support/Unix/Signals.inc b/llvm/lib/Support/Unix/Signals.inc
index 2b31672670c8..f68374d29f02 100644
--- a/llvm/lib/Support/Unix/Signals.inc
+++ b/llvm/lib/Support/Unix/Signals.inc
@@ -345,6 +345,22 @@ static void RemoveFilesToRemove() {
FileToRemoveList::removeAllFiles(FilesToRemove);
}
+void sys::CleanupOnSignal(uintptr_t Context) {
+ int Sig = (int)Context;
+
+ if (llvm::is_contained(InfoSigs, Sig)) {
+ InfoSignalHandler(Sig);
+ return;
+ }
+
+ RemoveFilesToRemove();
+
+ if (llvm::is_contained(IntSigs, Sig) || Sig == SIGPIPE)
+ return;
+
+ llvm::sys::RunSignalHandlers();
+}
+
// The signal handler that runs.
static RETSIGTYPE SignalHandler(int Sig) {
// Restore the signal behavior to default, so that the program actually
diff --git a/llvm/lib/Support/Windows/Signals.inc b/llvm/lib/Support/Windows/Signals.inc
index 08a3616427be..8b525f1bd4ac 100644
--- a/llvm/lib/Support/Windows/Signals.inc
+++ b/llvm/lib/Support/Windows/Signals.inc
@@ -521,10 +521,13 @@ void sys::PrintStackTraceOnErrorSignal(StringRef Argv0,
extern "C" VOID WINAPI RtlCaptureContext(PCONTEXT ContextRecord);
#endif
-void llvm::sys::PrintStackTrace(raw_ostream &OS) {
- STACKFRAME64 StackFrame = {};
- CONTEXT Context = {};
- ::RtlCaptureContext(&Context);
+static void LocalPrintStackTrace(raw_ostream &OS, PCONTEXT C) {
+ STACKFRAME64 StackFrame{};
+ CONTEXT Context{};
+ if (!C) {
+ ::RtlCaptureContext(&Context);
+ C = &Context;
+ }
#if defined(_M_X64)
StackFrame.AddrPC.Offset = Context.Rip;
StackFrame.AddrStack.Offset = Context.Rsp;
@@ -546,9 +549,12 @@ void llvm::sys::PrintStackTrace(raw_ostream &OS) {
StackFrame.AddrStack.Mode = AddrModeFlat;
StackFrame.AddrFrame.Mode = AddrModeFlat;
PrintStackTraceForThread(OS, GetCurrentProcess(), GetCurrentThread(),
- StackFrame, &Context);
+ StackFrame, C);
}
+void llvm::sys::PrintStackTrace(raw_ostream &OS) {
+ LocalPrintStackTrace(OS, nullptr);
+}
void llvm::sys::SetInterruptFunction(void (*IF)()) {
RegisterHandler();
@@ -792,6 +798,10 @@ WriteWindowsDumpFile(PMINIDUMP_EXCEPTION_INFORMATION ExceptionInfo) {
return std::error_code();
}
+void sys::CleanupOnSignal(uintptr_t Context) {
+ LLVMUnhandledExceptionFilter((LPEXCEPTION_POINTERS)Context);
+}
+
static LONG WINAPI LLVMUnhandledExceptionFilter(LPEXCEPTION_POINTERS ep) {
Cleanup();
@@ -810,42 +820,9 @@ static LONG WINAPI LLVMUnhandledExceptionFilter(LPEXCEPTION_POINTERS ep) {
<< "\n";
}
- // Initialize the STACKFRAME structure.
- STACKFRAME64 StackFrame = {};
-
-#if defined(_M_X64)
- StackFrame.AddrPC.Offset = ep->ContextRecord->Rip;
- StackFrame.AddrPC.Mode = AddrModeFlat;
- StackFrame.AddrStack.Offset = ep->ContextRecord->Rsp;
- StackFrame.AddrStack.Mode = AddrModeFlat;
- StackFrame.AddrFrame.Offset = ep->ContextRecord->Rbp;
- StackFrame.AddrFrame.Mode = AddrModeFlat;
-#elif defined(_M_IX86)
- StackFrame.AddrPC.Offset = ep->ContextRecord->Eip;
- StackFrame.AddrPC.Mode = AddrModeFlat;
- StackFrame.AddrStack.Offset = ep->ContextRecord->Esp;
- StackFrame.AddrStack.Mode = AddrModeFlat;
- StackFrame.AddrFrame.Offset = ep->ContextRecord->Ebp;
- StackFrame.AddrFrame.Mode = AddrModeFlat;
-#elif defined(_M_ARM64) || defined(_M_ARM)
- StackFrame.AddrPC.Offset = ep->ContextRecord->Pc;
- StackFrame.AddrPC.Mode = AddrModeFlat;
- StackFrame.AddrStack.Offset = ep->ContextRecord->Sp;
- StackFrame.AddrStack.Mode = AddrModeFlat;
-#if defined(_M_ARM64)
- StackFrame.AddrFrame.Offset = ep->ContextRecord->Fp;
-#else
- StackFrame.AddrFrame.Offset = ep->ContextRecord->R11;
-#endif
- StackFrame.AddrFrame.Mode = AddrModeFlat;
-#endif
-
- HANDLE hProcess = GetCurrentProcess();
- HANDLE hThread = GetCurrentThread();
- PrintStackTraceForThread(llvm::errs(), hProcess, hThread, StackFrame,
- ep->ContextRecord);
+ LocalPrintStackTrace(llvm::errs(), ep ? ep->ContextRecord : nullptr);
- _exit(ep->ExceptionRecord->ExceptionCode);
+ return EXCEPTION_EXECUTE_HANDLER;
}
static BOOL WINAPI LLVMConsoleCtrlHandler(DWORD dwCtrlType) {
diff --git a/llvm/unittests/Support/CrashRecoveryTest.cpp b/llvm/unittests/Support/CrashRecoveryTest.cpp
index d863dd06f091..798ed20145d8 100644
--- a/llvm/unittests/Support/CrashRecoveryTest.cpp
+++ b/llvm/unittests/Support/CrashRecoveryTest.cpp
@@ -8,6 +8,8 @@
#include "llvm/Support/Compiler.h"
#include "llvm/Support/CrashRecoveryContext.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Signals.h"
#include "gtest/gtest.h"
#ifdef _WIN32
@@ -23,6 +25,7 @@ static int GlobalInt = 0;
static void nullDeref() { *(volatile int *)0x10 = 0; }
static void incrementGlobal() { ++GlobalInt; }
static void llvmTrap() { LLVM_BUILTIN_TRAP; }
+static void incrementGlobalWithParam(void *) { ++GlobalInt; }
TEST(CrashRecoveryTest, Basic) {
llvm::CrashRecoveryContext::Enable();
@@ -58,6 +61,33 @@ TEST(CrashRecoveryTest, Cleanup) {
EXPECT_FALSE(CRC.RunSafely(nullDeref));
} // run cleanups
EXPECT_EQ(1, GlobalInt);
+ llvm::CrashRecoveryContext::Disable();
+}
+
+TEST(CrashRecoveryTest, DumpStackCleanup) {
+ SmallString<128> Filename;
+ std::error_code EC = sys::fs::createTemporaryFile("crash", "test", Filename);
+ EXPECT_FALSE(EC);
+ sys::RemoveFileOnSignal(Filename);
+ llvm::sys::AddSignalHandler(incrementGlobalWithParam, nullptr);
+ GlobalInt = 0;
+ llvm::CrashRecoveryContext::Enable();
+ {
+ CrashRecoveryContext CRC;
+ CRC.DumpStackAndCleanupOnFailure = true;
+ EXPECT_TRUE(CRC.RunSafely(noop));
+ }
+ EXPECT_TRUE(sys::fs::exists(Filename));
+ EXPECT_EQ(GlobalInt, 0);
+ {
+ CrashRecoveryContext CRC;
+ CRC.DumpStackAndCleanupOnFailure = true;
+ EXPECT_FALSE(CRC.RunSafely(nullDeref));
+ EXPECT_NE(CRC.RetCode, 0);
+ }
+ EXPECT_FALSE(sys::fs::exists(Filename));
+ EXPECT_EQ(GlobalInt, 1);
+ llvm::CrashRecoveryContext::Disable();
}
#ifdef _WIN32
More information about the llvm-commits
mailing list