diff options
Diffstat (limited to 'llvm/lib')
-rw-r--r-- | llvm/lib/Support/Signals.cpp | 47 | ||||
-rw-r--r-- | llvm/lib/Support/Unix/Signals.inc | 234 | ||||
-rw-r--r-- | llvm/lib/Support/Windows/Signals.inc | 5 |
3 files changed, 87 insertions, 199 deletions
diff --git a/llvm/lib/Support/Signals.cpp b/llvm/lib/Support/Signals.cpp index 6270a9bbef5..414619bc3c9 100644 --- a/llvm/lib/Support/Signals.cpp +++ b/llvm/lib/Support/Signals.cpp @@ -36,42 +36,19 @@ using namespace llvm; -// Use explicit storage to avoid accessing cl::opt in a signal handler. -static bool DisableSymbolicationFlag = false; -static cl::opt<bool, true> +static cl::opt<bool> DisableSymbolication("disable-symbolication", cl::desc("Disable symbolizing crash backtraces."), - cl::location(DisableSymbolicationFlag), cl::Hidden); - -// Callbacks to run in signal handler must be lock-free because a signal handler -// could be running as we add new callbacks. We don't add unbounded numbers of -// callbacks, an array is therefore sufficient. -// Assume two pointers are always lock-free. -struct LLVM_ALIGNAS(sizeof(void *) * 2) CallbackAndCookie { - sys::SignalHandlerCallback Callback; - void *Cookie; -}; -static constexpr size_t MaxSignalHandlerCallbacks = 8; -static std::atomic<CallbackAndCookie> CallBacksToRun[MaxSignalHandlerCallbacks]; - -void sys::RunSignalHandlers() { // Signal-safe. - for (size_t I = 0; I < MaxSignalHandlerCallbacks; ++I) { - CallbackAndCookie DoNothing{nullptr, nullptr}; - auto Entry = CallBacksToRun[I].exchange(DoNothing); - if (Entry.Callback) - (*Entry.Callback)(Entry.Cookie); - } -} - -static void insertSignalHandler(sys::SignalHandlerCallback FnPtr, - void *Cookie) { // Signal-safe. - CallbackAndCookie Entry = CallbackAndCookie{FnPtr, Cookie}; - for (size_t I = 0; I < MaxSignalHandlerCallbacks; ++I) { - CallbackAndCookie Expected{nullptr, nullptr}; - if (CallBacksToRun[I].compare_exchange_strong(Expected, Entry)) - return; - } - llvm_unreachable("too many signal callbacks already registered"); + cl::init(false), cl::Hidden); + +static ManagedStatic<std::vector<std::pair<void (*)(void *), void *>>> + CallBacksToRun; +void sys::RunSignalHandlers() { + if (!CallBacksToRun.isConstructed()) + return; + for (auto &I : *CallBacksToRun) + I.first(I.second); + CallBacksToRun->clear(); } static bool findModulesAndOffsets(void **StackTrace, int Depth, @@ -91,7 +68,7 @@ static FormattedNumber format_ptr(void *PC) { LLVM_ATTRIBUTE_USED static bool printSymbolizedStackTrace(StringRef Argv0, void **StackTrace, int Depth, llvm::raw_ostream &OS) { - if (DisableSymbolicationFlag) + if (DisableSymbolication) return false; // Don't recursively invoke the llvm-symbolizer binary. diff --git a/llvm/lib/Support/Unix/Signals.inc b/llvm/lib/Support/Unix/Signals.inc index 1f3d7c9012a..d828674de49 100644 --- a/llvm/lib/Support/Unix/Signals.inc +++ b/llvm/lib/Support/Unix/Signals.inc @@ -11,27 +11,6 @@ // Unix signals occurring while your program is running. // //===----------------------------------------------------------------------===// -// -// This file is extremely careful to only do signal-safe things while in a -// signal handler. In particular, memory allocation and acquiring a mutex -// while in a signal handler should never occur. ManagedStatic isn't usable from -// a signal handler for 2 reasons: -// -// 1. Creating a new one allocates. -// 2. The signal handler could fire while llvm_shutdown is being processed, in -// which case the ManagedStatic is in an unknown state because it could -// already have been destroyed, or be in the process of being destroyed. -// -// Modifying the behavior of the signal handlers (such as registering new ones) -// can acquire a mutex, but all this guarantees is that the signal handler -// behavior is only modified by one thread at a time. A signal handler can still -// fire while this occurs! -// -// Adding work to a signal handler requires lock-freedom (and assume atomics are -// always lock-free) because the signal handler could fire while new work is -// being added. -// -//===----------------------------------------------------------------------===// #include "Unix.h" #include "llvm/ADT/STLExtras.h" @@ -81,119 +60,12 @@ using namespace llvm; static RETSIGTYPE SignalHandler(int Sig); // defined below. -/// The function to call if ctrl-c is pressed. -using InterruptFunctionType = void (*)(); -static std::atomic<InterruptFunctionType> InterruptFunction = - ATOMIC_VAR_INIT(nullptr); - -/// Signal-safe removal of files. -/// Inserting and erasing from the list isn't signal-safe, but removal of files -/// themselves is signal-safe. Memory is freed when the head is freed, deletion -/// is therefore not signal-safe either. -class FileToRemoveList { - std::atomic<char *> Filename = ATOMIC_VAR_INIT(nullptr); - std::atomic<FileToRemoveList *> Next = ATOMIC_VAR_INIT(nullptr); - - FileToRemoveList() = default; - // Not signal-safe. - FileToRemoveList(const std::string &str) : Filename(strdup(str.c_str())) {} - -public: - // Not signal-safe. - ~FileToRemoveList() { - if (FileToRemoveList *N = Next.exchange(nullptr)) - delete N; - if (char *F = Filename.exchange(nullptr)) - free(F); - } - - // Not signal-safe. - static void insert(std::atomic<FileToRemoveList *> &Head, - const std::string &Filename) { - // Insert the new file at the end of the list. - FileToRemoveList *NewHead = new FileToRemoveList(Filename); - std::atomic<FileToRemoveList *> *InsertionPoint = &Head; - FileToRemoveList *OldHead = nullptr; - while (!InsertionPoint->compare_exchange_strong(OldHead, NewHead)) { - InsertionPoint = &OldHead->Next; - OldHead = nullptr; - } - } - - // Not signal-safe. - static void erase(std::atomic<FileToRemoveList *> &Head, - const std::string &Filename) { - // Use a lock to avoid concurrent erase: the comparison would access - // free'd memory. - static ManagedStatic<sys::SmartMutex<true>> Lock; - sys::SmartScopedLock<true> Writer(*Lock); - - for (FileToRemoveList *Current = Head.load(); Current; - Current = Current->Next.load()) { - if (char *OldFilename = Current->Filename.load()) { - if (OldFilename == Filename) { - // Leave an empty filename. - OldFilename = Current->Filename.exchange(nullptr); - // The filename might have become null between the time we - // compared it and we exchanged it. - if (OldFilename) - free(OldFilename); - } - } - } - } +static ManagedStatic<sys::SmartMutex<true> > SignalsMutex; - // Signal-safe. - static void removeAllFiles(std::atomic<FileToRemoveList *> &Head) { - // If cleanup were to occur while we're removing files we'd have a bad time. - // Make sure we're OK by preventing cleanup from doing anything while we're - // removing files. If cleanup races with us and we win we'll have a leak, - // but we won't crash. - FileToRemoveList *OldHead = Head.exchange(nullptr); - - for (FileToRemoveList *currentFile = OldHead; currentFile; - currentFile = currentFile->Next.load()) { - // If erasing was occuring while we're trying to remove files we'd look - // at free'd data. Take away the path and put it back when done. - if (char *path = currentFile->Filename.exchange(nullptr)) { - // Get the status so we can determine if it's a file or directory. If we - // can't stat the file, ignore it. - struct stat buf; - if (stat(path, &buf) != 0) - continue; - - // If this is not a regular file, ignore it. We want to prevent removal - // of special files like /dev/null, even if the compiler is being run - // with the super-user permissions. - if (!S_ISREG(buf.st_mode)) - continue; - - // Otherwise, remove the file. We ignore any errors here as there is - // nothing else we can do. - unlink(path); - - // We're done removing the file, erasing can safely proceed. - currentFile->Filename.exchange(path); - } - } +/// The function to call if ctrl-c is pressed. +static void (*InterruptFunction)() = nullptr; - // We're done removing files, cleanup can safely proceed. - Head.exchange(OldHead); - } -}; -static std::atomic<FileToRemoveList *> FilesToRemove = ATOMIC_VAR_INIT(nullptr); - -/// Clean up the list in a signal-friendly manner. -/// Recall that signals can fire during llvm_shutdown. If this occurs we should -/// either clean something up or nothing at all, but we shouldn't crash! -struct FilesToRemoveCleanup { - // Not signal-safe. - ~FilesToRemoveCleanup() { - FileToRemoveList *Head = FilesToRemove.exchange(nullptr); - if (Head) - delete Head; - } -}; +static ManagedStatic<std::vector<std::string>> FilesToRemove; static StringRef Argv0; @@ -222,12 +94,13 @@ static const int KillSigs[] = { #endif }; -static std::atomic<unsigned> NumRegisteredSignals = ATOMIC_VAR_INIT(0); +static unsigned NumRegisteredSignals = 0; static struct { struct sigaction SA; int SigNo; } RegisteredSignalInfo[array_lengthof(IntSigs) + array_lengthof(KillSigs)]; + #if defined(HAVE_SIGALTSTACK) // Hold onto both the old and new alternate signal stack so that it's not // reported as a leak. We don't make any attempt to remove our alt signal @@ -259,24 +132,18 @@ static void CreateSigAltStack() { static void CreateSigAltStack() {} #endif -static void RegisterHandlers() { // Not signal-safe. - // The mutex prevents other threads from registering handlers while we're - // doing it. We also have to protect the handlers and their count because - // a signal handler could fire while we're registeting handlers. - static ManagedStatic<sys::SmartMutex<true>> SignalHandlerRegistrationMutex; - sys::SmartScopedLock<true> Guard(*SignalHandlerRegistrationMutex); +static void RegisterHandlers() { + sys::SmartScopedLock<true> Guard(*SignalsMutex); // If the handlers are already registered, we're done. - if (NumRegisteredSignals.load() != 0) - return; + if (NumRegisteredSignals != 0) return; // Create an alternate stack for signal handling. This is necessary for us to // be able to reliably handle signals due to stack overflow. CreateSigAltStack(); auto registerHandler = [&](int Signal) { - unsigned Index = NumRegisteredSignals.load(); - assert(Index < array_lengthof(RegisteredSignalInfo) && + assert(NumRegisteredSignals < array_lengthof(RegisteredSignalInfo) && "Out of space for signal handlers!"); struct sigaction NewHandler; @@ -286,8 +153,9 @@ static void RegisterHandlers() { // Not signal-safe. sigemptyset(&NewHandler.sa_mask); // Install the new handler, save the old one in RegisteredSignalInfo. - sigaction(Signal, &NewHandler, &RegisteredSignalInfo[Index].SA); - RegisteredSignalInfo[Index].SigNo = Signal; + sigaction(Signal, &NewHandler, + &RegisteredSignalInfo[NumRegisteredSignals].SA); + RegisteredSignalInfo[NumRegisteredSignals].SigNo = Signal; ++NumRegisteredSignals; }; @@ -299,16 +167,44 @@ static void RegisterHandlers() { // Not signal-safe. static void UnregisterHandlers() { // Restore all of the signal handlers to how they were before we showed up. - for (unsigned i = 0, e = NumRegisteredSignals.load(); i != e; ++i) { + for (unsigned i = 0, e = NumRegisteredSignals; i != e; ++i) sigaction(RegisteredSignalInfo[i].SigNo, &RegisteredSignalInfo[i].SA, nullptr); - --NumRegisteredSignals; - } + NumRegisteredSignals = 0; } -/// Process the FilesToRemove list. + +/// Process the FilesToRemove list. This function should be called with the +/// SignalsMutex lock held. NB: This must be an async signal safe function. It +/// cannot allocate or free memory, even in debug builds. static void RemoveFilesToRemove() { - FileToRemoveList::removeAllFiles(FilesToRemove); + // Avoid constructing ManagedStatic in the signal handler. + // If FilesToRemove is not constructed, there are no files to remove. + if (!FilesToRemove.isConstructed()) + return; + + // We avoid iterators in case of debug iterators that allocate or release + // memory. + std::vector<std::string>& FilesToRemoveRef = *FilesToRemove; + for (unsigned i = 0, e = FilesToRemoveRef.size(); i != e; ++i) { + const char *path = FilesToRemoveRef[i].c_str(); + + // Get the status so we can determine if it's a file or directory. If we + // can't stat the file, ignore it. + struct stat buf; + if (stat(path, &buf) != 0) + continue; + + // If this is not a regular file, ignore it. We want to prevent removal of + // special files like /dev/null, even if the compiler is being run with the + // super-user permissions. + if (!S_ISREG(buf.st_mode)) + continue; + + // Otherwise, remove the file. We ignore any errors here as there is nothing + // else we can do. + unlink(path); + } } // The signal handler that runs. @@ -325,13 +221,20 @@ static RETSIGTYPE SignalHandler(int Sig) { sigprocmask(SIG_UNBLOCK, &SigMask, nullptr); { + unique_lock<sys::SmartMutex<true>> Guard(*SignalsMutex); RemoveFilesToRemove(); if (std::find(std::begin(IntSigs), std::end(IntSigs), Sig) != std::end(IntSigs)) { - if (auto OldInterruptFunction = InterruptFunction.exchange(nullptr)) - return OldInterruptFunction(); + if (InterruptFunction) { + void (*IF)() = InterruptFunction; + Guard.unlock(); + InterruptFunction = nullptr; + IF(); // run the interrupt function. + return; + } + Guard.unlock(); raise(Sig); // Execute the default handler. return; } @@ -351,36 +254,45 @@ static RETSIGTYPE SignalHandler(int Sig) { } void llvm::sys::RunInterruptHandlers() { + sys::SmartScopedLock<true> Guard(*SignalsMutex); RemoveFilesToRemove(); } void llvm::sys::SetInterruptFunction(void (*IF)()) { - InterruptFunction.exchange(IF); + { + sys::SmartScopedLock<true> Guard(*SignalsMutex); + InterruptFunction = IF; + } RegisterHandlers(); } // The public API bool llvm::sys::RemoveFileOnSignal(StringRef Filename, std::string* ErrMsg) { - // Ensure that cleanup will occur as soon as one file is added. - static ManagedStatic<FilesToRemoveCleanup> FilesToRemoveCleanup; - *FilesToRemoveCleanup; - FileToRemoveList::insert(FilesToRemove, Filename.str()); + { + sys::SmartScopedLock<true> Guard(*SignalsMutex); + FilesToRemove->push_back(Filename); + } + RegisterHandlers(); return false; } // The public API void llvm::sys::DontRemoveFileOnSignal(StringRef Filename) { - FileToRemoveList::erase(FilesToRemove, Filename.str()); + sys::SmartScopedLock<true> Guard(*SignalsMutex); + std::vector<std::string>::reverse_iterator RI = + find(reverse(*FilesToRemove), Filename); + std::vector<std::string>::iterator I = FilesToRemove->end(); + if (RI != FilesToRemove->rend()) + I = FilesToRemove->erase(RI.base()-1); } /// 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 /// handler it is. -void llvm::sys::AddSignalHandler(sys::SignalHandlerCallback FnPtr, - void *Cookie) { // Signal-safe. - insertSignalHandler(FnPtr, Cookie); +void llvm::sys::AddSignalHandler(void (*FnPtr)(void *), void *Cookie) { + CallBacksToRun->push_back(std::make_pair(FnPtr, Cookie)); RegisterHandlers(); } diff --git a/llvm/lib/Support/Windows/Signals.inc b/llvm/lib/Support/Windows/Signals.inc index cc225d9824d..959376b52f7 100644 --- a/llvm/lib/Support/Windows/Signals.inc +++ b/llvm/lib/Support/Windows/Signals.inc @@ -560,9 +560,8 @@ void llvm::sys::SetInterruptFunction(void (*IF)()) { /// 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 /// handler it is. -void llvm::sys::AddSignalHandler(sys::SignalHandlerCallback FnPtr, - void *Cookie) { - insertSignalHandler(FnPtr, Cookie); +void llvm::sys::AddSignalHandler(void (*FnPtr)(void *), void *Cookie) { + CallBacksToRun->push_back(std::make_pair(FnPtr, Cookie)); RegisterHandler(); LeaveCriticalSection(&CriticalSection); } |