diff --git a/llvm/include/llvm/Support/CrashRecoveryContext.h b/llvm/include/llvm/Support/CrashRecoveryContext.h --- a/llvm/include/llvm/Support/CrashRecoveryContext.h +++ b/llvm/include/llvm/Support/CrashRecoveryContext.h @@ -100,6 +100,14 @@ /// 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 --- a/llvm/include/llvm/Support/Signals.h +++ b/llvm/include/llvm/Support/Signals.h @@ -106,6 +106,15 @@ /// 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 --- 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 #include +#ifdef _WIN32 +#include // for GetExceptionInformation +#endif +#if LLVM_ON_UNIX +#include // EX_IOERR +#endif + using namespace llvm; namespace { @@ -54,7 +62,11 @@ #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 @@ 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 @@ static void installExceptionOrSignalHandlers() {} static void uninstallExceptionOrSignalHandlers() {} -bool CrashRecoveryContext::RunSafely(function_ref 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 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 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 @@ // implementation if we so choose. // Handle the crash - const_cast(CRCI)->HandleCrash(); + const_cast(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 @@ 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(CRCI)->HandleCrash(); + const_cast(CRCI)->HandleCrash(RetCode, Signal); } static void installExceptionOrSignalHandlers() { @@ -364,7 +401,9 @@ 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 --- a/llvm/lib/Support/Unix/Signals.inc +++ b/llvm/lib/Support/Unix/Signals.inc @@ -345,6 +345,22 @@ 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 --- a/llvm/lib/Support/Windows/Signals.inc +++ b/llvm/lib/Support/Windows/Signals.inc @@ -521,10 +521,13 @@ 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 @@ 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 @@ 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 @@ << "\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 --- 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 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 @@ 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