Skip to content

Commit aa1333a

Browse files
committed
Signal handling should be signal-safe
Summary: Before this patch, signal handling wasn't signal safe. This leads to real-world crashes. It used ManagedStatic inside of signals, this can allocate and can lead to unexpected state when a signal occurs during llvm_shutdown (because llvm_shutdown destroys the ManagedStatic). It also used cl::opt without custom backing storage. Some de-allocation was performed as well. Acquiring a lock in a signal handler is also a great way to deadlock. We can't just disable signals on llvm_shutdown because the signals might do useful work during that shutdown. We also can't just disable llvm_shutdown for programs (instead of library uses of clang) because we'd have to then mark the pointers as not leaked and make sure all the ManagedStatic uses are OK to leak and remain so. Move all of the code to lock-free datastructures instead, and avoid having any of them in an inconsistent state. I'm not trying to be fancy, I'm not using any explicit memory order because this code isn't hot. The only purpose of the atomics is to guarantee that a signal firing on the same or a different thread doesn't see an inconsistent state and crash. In some cases we might miss some state (for example, we might fail to delete a temporary file), but that's fine. Note that I haven't touched any of the backtrace support despite it not technically being totally signal-safe. When that code is called we know something bad is up and we don't expect to continue execution, so calling something that e.g. sets errno is the least of our problems. A similar patch should be applied to lib/Support/Windows/Signals.inc, but that can be done separately. Fix r332428 which I reverted in r332429. I originally used double-wide CAS because I was lazy, but some platforms use a runtime function for that which thankfully failed to link (it would have been bad for signal handlers otherwise). I use a separate flag to guard the data instead. <rdar://problem/28010281> Reviewers: dexonsmith Subscribers: steven_wu, llvm-commits llvm-svn: 332496
1 parent 9228f97 commit aa1333a

File tree

4 files changed

+212
-85
lines changed

4 files changed

+212
-85
lines changed

llvm/include/llvm/Support/Signals.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,12 @@ namespace sys {
5656
// Run all registered signal handlers.
5757
void RunSignalHandlers();
5858

59+
using SignalHandlerCallback = void (*)(void *);
60+
5961
/// Add a function to be called when an abort/kill signal is delivered to the
6062
/// process. The handler can have a cookie passed to it to identify what
6163
/// instance of the handler it is.
62-
void AddSignalHandler(void (*FnPtr)(void *), void *Cookie);
64+
void AddSignalHandler(SignalHandlerCallback FnPtr, void *Cookie);
6365

6466
/// This function registers a function to be called when the user "interrupts"
6567
/// the program (typically by pressing ctrl-c). When the user interrupts the

llvm/lib/Support/Signals.cpp

+45-9
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,55 @@
3636

3737
using namespace llvm;
3838

39-
static cl::opt<bool>
39+
// Use explicit storage to avoid accessing cl::opt in a signal handler.
40+
static bool DisableSymbolicationFlag = false;
41+
static cl::opt<bool, true>
4042
DisableSymbolication("disable-symbolication",
4143
cl::desc("Disable symbolizing crash backtraces."),
42-
cl::init(false), cl::Hidden);
44+
cl::location(DisableSymbolicationFlag), cl::Hidden);
4345

44-
static ManagedStatic<std::vector<std::pair<void (*)(void *), void *>>>
45-
CallBacksToRun;
46+
// Callbacks to run in signal handler must be lock-free because a signal handler
47+
// could be running as we add new callbacks. We don't add unbounded numbers of
48+
// callbacks, an array is therefore sufficient.
49+
struct CallbackAndCookie {
50+
sys::SignalHandlerCallback Callback;
51+
void *Cookie;
52+
enum class Status { Empty, Initializing, Initialized, Executing };
53+
std::atomic<Status> Flag;
54+
};
55+
static constexpr size_t MaxSignalHandlerCallbacks = 8;
56+
static CallbackAndCookie CallBacksToRun[MaxSignalHandlerCallbacks];
57+
58+
// Signal-safe.
4659
void sys::RunSignalHandlers() {
47-
if (!CallBacksToRun.isConstructed())
60+
for (size_t I = 0; I < MaxSignalHandlerCallbacks; ++I) {
61+
auto &RunMe = CallBacksToRun[I];
62+
auto Expected = CallbackAndCookie::Status::Initialized;
63+
auto Desired = CallbackAndCookie::Status::Executing;
64+
if (!RunMe.Flag.compare_exchange_strong(Expected, Desired))
65+
continue;
66+
(*RunMe.Callback)(RunMe.Cookie);
67+
RunMe.Callback = nullptr;
68+
RunMe.Cookie = nullptr;
69+
RunMe.Flag.store(CallbackAndCookie::Status::Empty);
70+
}
71+
}
72+
73+
// Signal-safe.
74+
static void insertSignalHandler(sys::SignalHandlerCallback FnPtr,
75+
void *Cookie) {
76+
for (size_t I = 0; I < MaxSignalHandlerCallbacks; ++I) {
77+
auto &SetMe = CallBacksToRun[I];
78+
auto Expected = CallbackAndCookie::Status::Empty;
79+
auto Desired = CallbackAndCookie::Status::Initializing;
80+
if (!SetMe.Flag.compare_exchange_strong(Expected, Desired))
81+
continue;
82+
SetMe.Callback = FnPtr;
83+
SetMe.Cookie = Cookie;
84+
SetMe.Flag.store(CallbackAndCookie::Status::Initialized);
4885
return;
49-
for (auto &I : *CallBacksToRun)
50-
I.first(I.second);
51-
CallBacksToRun->clear();
86+
}
87+
report_fatal_error("too many signal callbacks already registered");
5288
}
5389

5490
static bool findModulesAndOffsets(void **StackTrace, int Depth,
@@ -68,7 +104,7 @@ static FormattedNumber format_ptr(void *PC) {
68104
LLVM_ATTRIBUTE_USED
69105
static bool printSymbolizedStackTrace(StringRef Argv0, void **StackTrace,
70106
int Depth, llvm::raw_ostream &OS) {
71-
if (DisableSymbolication)
107+
if (DisableSymbolicationFlag)
72108
return false;
73109

74110
// Don't recursively invoke the llvm-symbolizer binary.

0 commit comments

Comments
 (0)