Path: blob/main/contrib/llvm-project/llvm/lib/Support/CrashRecoveryContext.cpp
35233 views
//===--- CrashRecoveryContext.cpp - Crash Recovery ------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "llvm/Support/CrashRecoveryContext.h"9#include "llvm/Config/llvm-config.h"10#include "llvm/Support/ErrorHandling.h"11#include "llvm/Support/ExitCodes.h"12#include "llvm/Support/Signals.h"13#include "llvm/Support/thread.h"14#include <cassert>15#include <mutex>16#include <setjmp.h>1718using namespace llvm;1920namespace {2122struct CrashRecoveryContextImpl;23static LLVM_THREAD_LOCAL const CrashRecoveryContextImpl *CurrentContext;2425struct CrashRecoveryContextImpl {26// When threads are disabled, this links up all active27// CrashRecoveryContextImpls. When threads are enabled there's one thread28// per CrashRecoveryContext and CurrentContext is a thread-local, so only one29// CrashRecoveryContextImpl is active per thread and this is always null.30const CrashRecoveryContextImpl *Next;3132CrashRecoveryContext *CRC;33::jmp_buf JumpBuffer;34volatile unsigned Failed : 1;35unsigned SwitchedThread : 1;36unsigned ValidJumpBuffer : 1;3738public:39CrashRecoveryContextImpl(CrashRecoveryContext *CRC) noexcept40: CRC(CRC), Failed(false), SwitchedThread(false), ValidJumpBuffer(false) {41Next = CurrentContext;42CurrentContext = this;43}44~CrashRecoveryContextImpl() {45if (!SwitchedThread)46CurrentContext = Next;47}4849/// Called when the separate crash-recovery thread was finished, to50/// indicate that we don't need to clear the thread-local CurrentContext.51void setSwitchedThread() {52#if defined(LLVM_ENABLE_THREADS) && LLVM_ENABLE_THREADS != 053SwitchedThread = true;54#endif55}5657// If the function ran by the CrashRecoveryContext crashes or fails, then58// 'RetCode' represents the returned error code, as if it was returned by a59// process. 'Context' represents the signal type on Unix; on Windows, it is60// the ExceptionContext.61void HandleCrash(int RetCode, uintptr_t Context) {62// Eliminate the current context entry, to avoid re-entering in case the63// cleanup code crashes.64CurrentContext = Next;6566assert(!Failed && "Crash recovery context already failed!");67Failed = true;6869if (CRC->DumpStackAndCleanupOnFailure)70sys::CleanupOnSignal(Context);7172CRC->RetCode = RetCode;7374// Jump back to the RunSafely we were called under.75if (ValidJumpBuffer)76longjmp(JumpBuffer, 1);7778// Otherwise let the caller decide of the outcome of the crash. Currently79// this occurs when using SEH on Windows with MSVC or clang-cl.80}81};8283std::mutex &getCrashRecoveryContextMutex() {84static std::mutex CrashRecoveryContextMutex;85return CrashRecoveryContextMutex;86}8788static bool gCrashRecoveryEnabled = false;8990static LLVM_THREAD_LOCAL const CrashRecoveryContext *IsRecoveringFromCrash;9192} // namespace9394static void installExceptionOrSignalHandlers();95static void uninstallExceptionOrSignalHandlers();9697CrashRecoveryContextCleanup::~CrashRecoveryContextCleanup() = default;9899CrashRecoveryContext::CrashRecoveryContext() {100// On Windows, if abort() was previously triggered (and caught by a previous101// CrashRecoveryContext) the Windows CRT removes our installed signal handler,102// so we need to install it again.103sys::DisableSystemDialogsOnCrash();104}105106CrashRecoveryContext::~CrashRecoveryContext() {107// Reclaim registered resources.108CrashRecoveryContextCleanup *i = head;109const CrashRecoveryContext *PC = IsRecoveringFromCrash;110IsRecoveringFromCrash = this;111while (i) {112CrashRecoveryContextCleanup *tmp = i;113i = tmp->next;114tmp->cleanupFired = true;115tmp->recoverResources();116delete tmp;117}118IsRecoveringFromCrash = PC;119120CrashRecoveryContextImpl *CRCI = (CrashRecoveryContextImpl *) Impl;121delete CRCI;122}123124bool CrashRecoveryContext::isRecoveringFromCrash() {125return IsRecoveringFromCrash != nullptr;126}127128CrashRecoveryContext *CrashRecoveryContext::GetCurrent() {129if (!gCrashRecoveryEnabled)130return nullptr;131132const CrashRecoveryContextImpl *CRCI = CurrentContext;133if (!CRCI)134return nullptr;135136return CRCI->CRC;137}138139void CrashRecoveryContext::Enable() {140std::lock_guard<std::mutex> L(getCrashRecoveryContextMutex());141// FIXME: Shouldn't this be a refcount or something?142if (gCrashRecoveryEnabled)143return;144gCrashRecoveryEnabled = true;145installExceptionOrSignalHandlers();146}147148void CrashRecoveryContext::Disable() {149std::lock_guard<std::mutex> L(getCrashRecoveryContextMutex());150if (!gCrashRecoveryEnabled)151return;152gCrashRecoveryEnabled = false;153uninstallExceptionOrSignalHandlers();154}155156void CrashRecoveryContext::registerCleanup(CrashRecoveryContextCleanup *cleanup)157{158if (!cleanup)159return;160if (head)161head->prev = cleanup;162cleanup->next = head;163head = cleanup;164}165166void167CrashRecoveryContext::unregisterCleanup(CrashRecoveryContextCleanup *cleanup) {168if (!cleanup)169return;170if (cleanup == head) {171head = cleanup->next;172if (head)173head->prev = nullptr;174}175else {176cleanup->prev->next = cleanup->next;177if (cleanup->next)178cleanup->next->prev = cleanup->prev;179}180delete cleanup;181}182183#if defined(_MSC_VER)184185#include <windows.h> // for GetExceptionInformation186187// If _MSC_VER is defined, we must have SEH. Use it if it's available. It's way188// better than VEH. Vectored exception handling catches all exceptions happening189// on the thread with installed exception handlers, so it can interfere with190// internal exception handling of other libraries on that thread. SEH works191// exactly as you would expect normal exception handling to work: it only192// catches exceptions if they would bubble out from the stack frame with __try /193// __except.194195static void installExceptionOrSignalHandlers() {}196static void uninstallExceptionOrSignalHandlers() {}197198// We need this function because the call to GetExceptionInformation() can only199// occur inside the __except evaluation block200static int ExceptionFilter(_EXCEPTION_POINTERS *Except) {201// Lookup the current thread local recovery object.202const CrashRecoveryContextImpl *CRCI = CurrentContext;203204if (!CRCI) {205// Something has gone horribly wrong, so let's just tell everyone206// to keep searching207CrashRecoveryContext::Disable();208return EXCEPTION_CONTINUE_SEARCH;209}210211int RetCode = (int)Except->ExceptionRecord->ExceptionCode;212if ((RetCode & 0xF0000000) == 0xE0000000)213RetCode &= ~0xF0000000; // this crash was generated by sys::Process::Exit214215// Handle the crash216const_cast<CrashRecoveryContextImpl *>(CRCI)->HandleCrash(217RetCode, reinterpret_cast<uintptr_t>(Except));218219return EXCEPTION_EXECUTE_HANDLER;220}221222#if defined(__clang__) && defined(_M_IX86)223// Work around PR44697.224__attribute__((optnone))225#endif226bool CrashRecoveryContext::RunSafely(function_ref<void()> Fn) {227if (!gCrashRecoveryEnabled) {228Fn();229return true;230}231assert(!Impl && "Crash recovery context already initialized!");232Impl = new CrashRecoveryContextImpl(this);233__try {234Fn();235} __except (ExceptionFilter(GetExceptionInformation())) {236return false;237}238return true;239}240241#else // !_MSC_VER242243#if defined(_WIN32)244// This is a non-MSVC compiler, probably mingw gcc or clang without245// -fms-extensions. Use vectored exception handling (VEH).246//247// On Windows, we can make use of vectored exception handling to catch most248// crashing situations. Note that this does mean we will be alerted of249// exceptions *before* structured exception handling has the opportunity to250// catch it. Unfortunately, this causes problems in practice with other code251// running on threads with LLVM crash recovery contexts, so we would like to252// eventually move away from VEH.253//254// Vectored works on a per-thread basis, which is an advantage over255// SetUnhandledExceptionFilter. SetUnhandledExceptionFilter also doesn't have256// any native support for chaining exception handlers, but VEH allows more than257// one.258//259// The vectored exception handler functionality was added in Windows260// XP, so if support for older versions of Windows is required,261// it will have to be added.262263#include "llvm/Support/Windows/WindowsSupport.h"264265static LONG CALLBACK ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo)266{267// DBG_PRINTEXCEPTION_WIDE_C is not properly defined on all supported268// compilers and platforms, so we define it manually.269constexpr ULONG DbgPrintExceptionWideC = 0x4001000AL;270switch (ExceptionInfo->ExceptionRecord->ExceptionCode)271{272case DBG_PRINTEXCEPTION_C:273case DbgPrintExceptionWideC:274case 0x406D1388: // set debugger thread name275return EXCEPTION_CONTINUE_EXECUTION;276}277278// Lookup the current thread local recovery object.279const CrashRecoveryContextImpl *CRCI = CurrentContext;280281if (!CRCI) {282// Something has gone horribly wrong, so let's just tell everyone283// to keep searching284CrashRecoveryContext::Disable();285return EXCEPTION_CONTINUE_SEARCH;286}287288// TODO: We can capture the stack backtrace here and store it on the289// implementation if we so choose.290291int RetCode = (int)ExceptionInfo->ExceptionRecord->ExceptionCode;292if ((RetCode & 0xF0000000) == 0xE0000000)293RetCode &= ~0xF0000000; // this crash was generated by sys::Process::Exit294295// Handle the crash296const_cast<CrashRecoveryContextImpl *>(CRCI)->HandleCrash(297RetCode, reinterpret_cast<uintptr_t>(ExceptionInfo));298299// Note that we don't actually get here because HandleCrash calls300// longjmp, which means the HandleCrash function never returns.301llvm_unreachable("Handled the crash, should have longjmp'ed out of here");302}303304// Because the Enable and Disable calls are static, it means that305// there may not actually be an Impl available, or even a current306// CrashRecoveryContext at all. So we make use of a thread-local307// exception table. The handles contained in here will either be308// non-NULL, valid VEH handles, or NULL.309static LLVM_THREAD_LOCAL const void* sCurrentExceptionHandle;310311static void installExceptionOrSignalHandlers() {312// We can set up vectored exception handling now. We will install our313// handler as the front of the list, though there's no assurances that314// it will remain at the front (another call could install itself before315// our handler). This 1) isn't likely, and 2) shouldn't cause problems.316PVOID handle = ::AddVectoredExceptionHandler(1, ExceptionHandler);317sCurrentExceptionHandle = handle;318}319320static void uninstallExceptionOrSignalHandlers() {321PVOID currentHandle = const_cast<PVOID>(sCurrentExceptionHandle);322if (currentHandle) {323// Now we can remove the vectored exception handler from the chain324::RemoveVectoredExceptionHandler(currentHandle);325326// Reset the handle in our thread-local set.327sCurrentExceptionHandle = NULL;328}329}330331#else // !_WIN32332333// Generic POSIX implementation.334//335// This implementation relies on synchronous signals being delivered to the336// current thread. We use a thread local object to keep track of the active337// crash recovery context, and install signal handlers to invoke HandleCrash on338// the active object.339//340// This implementation does not attempt to chain signal handlers in any341// reliable fashion -- if we get a signal outside of a crash recovery context we342// simply disable crash recovery and raise the signal again.343344#include <signal.h>345346static const int Signals[] =347{ SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP };348static const unsigned NumSignals = std::size(Signals);349static struct sigaction PrevActions[NumSignals];350351static void CrashRecoverySignalHandler(int Signal) {352// Lookup the current thread local recovery object.353const CrashRecoveryContextImpl *CRCI = CurrentContext;354355if (!CRCI) {356// We didn't find a crash recovery context -- this means either we got a357// signal on a thread we didn't expect it on, the application got a signal358// outside of a crash recovery context, or something else went horribly359// wrong.360//361// Disable crash recovery and raise the signal again. The assumption here is362// that the enclosing application will terminate soon, and we won't want to363// attempt crash recovery again.364//365// This call of Disable isn't thread safe, but it doesn't actually matter.366CrashRecoveryContext::Disable();367raise(Signal);368369// The signal will be thrown once the signal mask is restored.370return;371}372373// Unblock the signal we received.374sigset_t SigMask;375sigemptyset(&SigMask);376sigaddset(&SigMask, Signal);377sigprocmask(SIG_UNBLOCK, &SigMask, nullptr);378379// Return the same error code as if the program crashed, as mentioned in the380// section "Exit Status for Commands":381// https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xcu_chap02.html382int RetCode = 128 + Signal;383384// Don't consider a broken pipe as a crash (see clang/lib/Driver/Driver.cpp)385if (Signal == SIGPIPE)386RetCode = EX_IOERR;387388if (CRCI)389const_cast<CrashRecoveryContextImpl *>(CRCI)->HandleCrash(RetCode, Signal);390}391392static void installExceptionOrSignalHandlers() {393// Setup the signal handler.394struct sigaction Handler;395Handler.sa_handler = CrashRecoverySignalHandler;396Handler.sa_flags = 0;397sigemptyset(&Handler.sa_mask);398399for (unsigned i = 0; i != NumSignals; ++i) {400sigaction(Signals[i], &Handler, &PrevActions[i]);401}402}403404static void uninstallExceptionOrSignalHandlers() {405// Restore the previous signal handlers.406for (unsigned i = 0; i != NumSignals; ++i)407sigaction(Signals[i], &PrevActions[i], nullptr);408}409410#endif // !_WIN32411412bool CrashRecoveryContext::RunSafely(function_ref<void()> Fn) {413// If crash recovery is disabled, do nothing.414if (gCrashRecoveryEnabled) {415assert(!Impl && "Crash recovery context already initialized!");416CrashRecoveryContextImpl *CRCI = new CrashRecoveryContextImpl(this);417Impl = CRCI;418419CRCI->ValidJumpBuffer = true;420if (setjmp(CRCI->JumpBuffer) != 0) {421return false;422}423}424425Fn();426return true;427}428429#endif // !_MSC_VER430431[[noreturn]] void CrashRecoveryContext::HandleExit(int RetCode) {432#if defined(_WIN32)433// Since the exception code is actually of NTSTATUS type, we use the434// Microsoft-recommended 0xE prefix, to signify that this is a user error.435// This value is a combination of the customer field (bit 29) and severity436// field (bits 30-31) in the NTSTATUS specification.437::RaiseException(0xE0000000 | RetCode, 0, 0, NULL);438#else439// On Unix we don't need to raise an exception, we go directly to440// HandleCrash(), then longjmp will unwind the stack for us.441CrashRecoveryContextImpl *CRCI = (CrashRecoveryContextImpl *)Impl;442assert(CRCI && "Crash recovery context never initialized!");443CRCI->HandleCrash(RetCode, 0 /*no sig num*/);444#endif445llvm_unreachable("Most likely setjmp wasn't called!");446}447448bool CrashRecoveryContext::isCrash(int RetCode) {449#if defined(_WIN32)450// On Windows, the code is interpreted as NTSTATUS. The two high bits451// represent the severity. Values starting with 0x80000000 are reserved for452// "warnings"; values of 0xC0000000 and up are for "errors". In practice, both453// are interpreted as a non-continuable signal.454unsigned Code = ((unsigned)RetCode & 0xF0000000) >> 28;455if (Code != 0xC && Code != 8)456return false;457#else458// On Unix, signals are represented by return codes of 128 or higher.459// Exit code 128 is a reserved value and should not be raised as a signal.460if (RetCode <= 128)461return false;462#endif463return true;464}465466bool CrashRecoveryContext::throwIfCrash(int RetCode) {467if (!isCrash(RetCode))468return false;469#if defined(_WIN32)470::RaiseException(RetCode, 0, 0, NULL);471#else472llvm::sys::unregisterHandlers();473raise(RetCode - 128);474#endif475return true;476}477478// FIXME: Portability.479static void setThreadBackgroundPriority() {480#ifdef __APPLE__481setpriority(PRIO_DARWIN_THREAD, 0, PRIO_DARWIN_BG);482#endif483}484485static bool hasThreadBackgroundPriority() {486#ifdef __APPLE__487return getpriority(PRIO_DARWIN_THREAD, 0) == 1;488#else489return false;490#endif491}492493namespace {494struct RunSafelyOnThreadInfo {495function_ref<void()> Fn;496CrashRecoveryContext *CRC;497bool UseBackgroundPriority;498bool Result;499};500} // namespace501502static void RunSafelyOnThread_Dispatch(void *UserData) {503RunSafelyOnThreadInfo *Info =504reinterpret_cast<RunSafelyOnThreadInfo*>(UserData);505506if (Info->UseBackgroundPriority)507setThreadBackgroundPriority();508509Info->Result = Info->CRC->RunSafely(Info->Fn);510}511bool CrashRecoveryContext::RunSafelyOnThread(function_ref<void()> Fn,512unsigned RequestedStackSize) {513bool UseBackgroundPriority = hasThreadBackgroundPriority();514RunSafelyOnThreadInfo Info = { Fn, this, UseBackgroundPriority, false };515llvm::thread Thread(RequestedStackSize == 0516? std::nullopt517: std::optional<unsigned>(RequestedStackSize),518RunSafelyOnThread_Dispatch, &Info);519Thread.join();520521if (CrashRecoveryContextImpl *CRC = (CrashRecoveryContextImpl *)Impl)522CRC->setSwitchedThread();523return Info.Result;524}525526527