forked from OSchip/llvm-project
Revert "Signal handling should be signal-safe"
Some bots don't have double-pointer width compare-and-exchange. Revert for now.q llvm-svn: 332429
This commit is contained in:
parent
253aa8b099
commit
b8931c1cf4
|
@ -56,12 +56,10 @@ namespace sys {
|
|||
// Run all registered signal handlers.
|
||||
void RunSignalHandlers();
|
||||
|
||||
using SignalHandlerCallback = void (*)(void *);
|
||||
|
||||
/// Add a function to be called when an abort/kill 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 AddSignalHandler(SignalHandlerCallback FnPtr, void *Cookie);
|
||||
void AddSignalHandler(void (*FnPtr)(void *), void *Cookie);
|
||||
|
||||
/// This function registers a function to be called when the user "interrupts"
|
||||
/// the program (typically by pressing ctrl-c). When the user interrupts the
|
||||
|
|
|
@ -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);
|
||||
cl::init(false), 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");
|
||||
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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
static ManagedStatic<sys::SmartMutex<true> > SignalsMutex;
|
||||
|
||||
/// The function to call if ctrl-c is pressed.
|
||||
using InterruptFunctionType = void (*)();
|
||||
static std::atomic<InterruptFunctionType> InterruptFunction =
|
||||
ATOMIC_VAR_INIT(nullptr);
|
||||
static void (*InterruptFunction)() = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue