summaryrefslogtreecommitdiff
path: root/runtime/fault_handler.cc
diff options
context:
space:
mode:
author Josh Gao <jmgao@google.com> 2017-02-28 16:53:59 -0800
committer Josh Gao <jmgao@google.com> 2017-03-08 15:07:49 -0800
commitefd20cb2f63cf647c7d947d00e8987affefeb177 (patch)
tree666ea53832f1085378bcc75620f314ba1f46a330 /runtime/fault_handler.cc
parentcaa348cee1312150a957bd308ce77ad406f3eb64 (diff)
Catch signals that happen inside a fault handler.
Unblock some signals (SIGABRT, SIGBUS, SIGSEGV) that could happen inside of the ART internal fault handlers, to report crashes inside of the signal handler. Because we can't use sigaction to change the handler when this happens, because it modifies global state, add a new member variable in Thread to track whether a call to the fault handler is reentrant or not. Remove the old nested signal implementation that attempted to do this. Bug: http://b/35853436 Test: changed the #if 0 to #if 1, ran a dummy process that threw a NullPointerException, inspected logcat Change-Id: I04bb4a09433c6817933d64ec681ec433b528f2a5
Diffstat (limited to 'runtime/fault_handler.cc')
-rw-r--r--runtime/fault_handler.cc217
1 files changed, 66 insertions, 151 deletions
diff --git a/runtime/fault_handler.cc b/runtime/fault_handler.cc
index f9345b64a8..64128cc61e 100644
--- a/runtime/fault_handler.cc
+++ b/runtime/fault_handler.cc
@@ -28,47 +28,6 @@
#include "thread-inl.h"
#include "verify_object-inl.h"
-// Note on nested signal support
-// -----------------------------
-//
-// Typically a signal handler should not need to deal with signals that occur within it.
-// However, when a SIGSEGV occurs that is in generated code and is not one of the
-// handled signals (implicit checks), we call a function to try to dump the stack
-// to the log. This enhances the debugging experience but may have the side effect
-// that it may not work. If the cause of the original SIGSEGV is a corrupted stack or other
-// memory region, the stack backtrace code may run into trouble and may either crash
-// or fail with an abort (SIGABRT). In either case we don't want that (new) signal to
-// mask the original signal and thus prevent useful debug output from being presented.
-//
-// In order to handle this situation, before we call the stack tracer we do the following:
-//
-// 1. shutdown the fault manager so that we are talking to the real signal management
-// functions rather than those in sigchain.
-// 2. use pthread_sigmask to allow SIGSEGV and SIGABRT signals to be delivered to the
-// thread running the signal handler.
-// 3. set the handler for SIGSEGV and SIGABRT to a secondary signal handler.
-// 4. save the thread's state to the TLS of the current thread using 'setjmp'
-//
-// We then call the stack tracer and one of two things may happen:
-// a. it completes successfully
-// b. it crashes and a signal is raised.
-//
-// In the former case, we fall through and everything is fine. In the latter case
-// our secondary signal handler gets called in a signal context. This results in
-// a call to FaultManager::HandledNestedSignal(), an archirecture specific function
-// whose purpose is to call 'longjmp' on the jmp_buf saved in the TLS of the current
-// thread. This results in a return with a non-zero value from 'setjmp'. We detect this
-// and write something to the log to tell the user that it happened.
-//
-// Regardless of how we got there, we reach the code after the stack tracer and we
-// restore the signal states to their original values, reinstate the fault manager (thus
-// reestablishing the signal chain) and continue.
-
-// This is difficult to test with a runtime test. To invoke the nested signal code
-// on any signal, uncomment the following line and run something that throws a
-// NullPointerException.
-// #define TEST_NESTED_SIGNAL
-
namespace art {
// Static fault manger object accessed by signal handler.
FaultManager fault_manager;
@@ -83,11 +42,6 @@ static void art_fault_handler(int sig, siginfo_t* info, void* context) {
fault_manager.HandleFault(sig, info, context);
}
-// Signal handler for dealing with a nested signal.
-static void art_nested_signal_handler(int sig, siginfo_t* info, void* context) {
- fault_manager.HandleNestedSignal(sig, info, context);
-}
-
FaultManager::FaultManager() : initialized_(false) {
sigaction(SIGSEGV, nullptr, &oldaction_);
}
@@ -156,122 +110,93 @@ bool FaultManager::HandleFaultByOtherHandlers(int sig, siginfo_t* info, void* co
DCHECK(self != nullptr);
DCHECK(Runtime::Current() != nullptr);
DCHECK(Runtime::Current()->IsStarted());
-
- // Now set up the nested signal handler.
-
- // TODO: add SIGSEGV back to the nested signals when we can handle running out stack gracefully.
- static const int handled_nested_signals[] = {SIGABRT};
- constexpr size_t num_handled_nested_signals = arraysize(handled_nested_signals);
-
- // Release the fault manager so that it will remove the signal chain for
- // SIGSEGV and we call the real sigaction.
- fault_manager.Release();
-
- // The action for SIGSEGV should be the default handler now.
-
- // Unblock the signals we allow so that they can be delivered in the signal handler.
- sigset_t sigset;
- sigemptyset(&sigset);
- for (int signal : handled_nested_signals) {
- sigaddset(&sigset, signal);
+ for (const auto& handler : other_handlers_) {
+ if (handler->Action(sig, info, context)) {
+ return true;
+ }
}
- pthread_sigmask(SIG_UNBLOCK, &sigset, nullptr);
+ return false;
+}
- // If we get a signal in this code we want to invoke our nested signal
- // handler.
- struct sigaction action;
- struct sigaction oldactions[num_handled_nested_signals];
- action.sa_sigaction = art_nested_signal_handler;
-
- // Explicitly mask out SIGSEGV and SIGABRT from the nested signal handler. This
- // should be the default but we definitely don't want these happening in our
- // nested signal handler.
- sigemptyset(&action.sa_mask);
- for (int signal : handled_nested_signals) {
- sigaddset(&action.sa_mask, signal);
+class ScopedSignalUnblocker {
+ public:
+ explicit ScopedSignalUnblocker(const std::initializer_list<int>& signals) {
+ sigset_t new_mask;
+ sigemptyset(&new_mask);
+ for (int signal : signals) {
+ sigaddset(&new_mask, signal);
+ }
+ if (sigprocmask(SIG_UNBLOCK, &new_mask, &previous_mask_) != 0) {
+ PLOG(FATAL) << "failed to unblock signals";
+ }
}
- action.sa_flags = SA_SIGINFO | SA_ONSTACK;
-#if !defined(__APPLE__) && !defined(__mips__)
- action.sa_restorer = nullptr;
-#endif
-
- // Catch handled signals to invoke our nested handler.
- bool success = true;
- for (size_t i = 0; i < num_handled_nested_signals; ++i) {
- success = sigaction(handled_nested_signals[i], &action, &oldactions[i]) == 0;
- if (!success) {
- PLOG(ERROR) << "Unable to set up nested signal handler";
- break;
+ ~ScopedSignalUnblocker() {
+ if (sigprocmask(SIG_SETMASK, &previous_mask_, nullptr) != 0) {
+ PLOG(FATAL) << "failed to unblock signals";
}
}
- if (success) {
- // Save the current state and call the handlers. If anything causes a signal
- // our nested signal handler will be invoked and this will longjmp to the saved
- // state.
- if (setjmp(*self->GetNestedSignalState()) == 0) {
- for (const auto& handler : other_handlers_) {
- if (handler->Action(sig, info, context)) {
- // Restore the signal handlers, reinit the fault manager and return. Signal was
- // handled.
- for (size_t i = 0; i < num_handled_nested_signals; ++i) {
- success = sigaction(handled_nested_signals[i], &oldactions[i], nullptr) == 0;
- if (!success) {
- PLOG(ERROR) << "Unable to restore signal handler";
- }
- }
- fault_manager.Init();
- return true;
- }
- }
- } else {
- LOG(ERROR) << "Nested signal detected - original signal being reported";
- }
+ private:
+ sigset_t previous_mask_;
+};
- // Restore the signal handlers.
- for (size_t i = 0; i < num_handled_nested_signals; ++i) {
- success = sigaction(handled_nested_signals[i], &oldactions[i], nullptr) == 0;
- if (!success) {
- PLOG(ERROR) << "Unable to restore signal handler";
- }
- }
+class ScopedHandlingSignalSetter {
+ public:
+ explicit ScopedHandlingSignalSetter(Thread* thread) : thread_(thread) {
+ CHECK(!thread->HandlingSignal());
+ thread_->SetHandlingSignal(true);
}
- // Now put the fault manager back in place.
- fault_manager.Init();
- return false;
-}
+ ~ScopedHandlingSignalSetter() {
+ CHECK(thread_->HandlingSignal());
+ thread_->SetHandlingSignal(false);
+ }
+
+ private:
+ Thread* thread_;
+};
void FaultManager::HandleFault(int sig, siginfo_t* info, void* context) {
// BE CAREFUL ALLOCATING HERE INCLUDING USING LOG(...)
//
// If malloc calls abort, it will be holding its lock.
// If the handler tries to call malloc, it will deadlock.
- VLOG(signals) << "Handling fault";
- if (IsInGeneratedCode(info, context, true)) {
- VLOG(signals) << "in generated code, looking for handler";
- for (const auto& handler : generated_code_handlers_) {
- VLOG(signals) << "invoking Action on handler " << handler;
- if (handler->Action(sig, info, context)) {
+
+ // Use a thread local field to track whether we're recursing, and fall back.
+ // (e.g.. if one of our handlers crashed)
+ Thread* thread = Thread::Current();
+
+ if (thread != nullptr && !thread->HandlingSignal()) {
+ // Unblock some signals and set thread->handling_signal_ to true,
+ // so that we can catch crashes in our signal handler.
+ ScopedHandlingSignalSetter setter(thread);
+ ScopedSignalUnblocker unblocker { SIGABRT, SIGBUS, SIGSEGV }; // NOLINT
+
+ VLOG(signals) << "Handling fault";
+
#ifdef TEST_NESTED_SIGNAL
- // In test mode we want to fall through to stack trace handler
- // on every signal (in reality this will cause a crash on the first
- // signal).
- break;
-#else
- // We have handled a signal so it's time to return from the
- // signal handler to the appropriate place.
- return;
+ // Simulate a crash in a handler.
+ raise(SIGSEGV);
#endif
+
+ if (IsInGeneratedCode(info, context, true)) {
+ VLOG(signals) << "in generated code, looking for handler";
+ for (const auto& handler : generated_code_handlers_) {
+ VLOG(signals) << "invoking Action on handler " << handler;
+ if (handler->Action(sig, info, context)) {
+ // We have handled a signal so it's time to return from the
+ // signal handler to the appropriate place.
+ return;
+ }
}
- }
- // We hit a signal we didn't handle. This might be something for which
- // we can give more information about so call all registered handlers to see
- // if it is.
- if (HandleFaultByOtherHandlers(sig, info, context)) {
+ // We hit a signal we didn't handle. This might be something for which
+ // we can give more information about so call all registered handlers to
+ // see if it is.
+ if (HandleFaultByOtherHandlers(sig, info, context)) {
return;
+ }
}
}
@@ -417,11 +342,7 @@ JavaStackTraceHandler::JavaStackTraceHandler(FaultManager* manager) : FaultHandl
bool JavaStackTraceHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* siginfo, void* context) {
// Make sure that we are in the generated code, but we may not have a dex pc.
-#ifdef TEST_NESTED_SIGNAL
- bool in_generated_code = true;
-#else
bool in_generated_code = manager_->IsInGeneratedCode(siginfo, context, false);
-#endif
if (in_generated_code) {
LOG(ERROR) << "Dumping java stack trace for crash in generated code";
ArtMethod* method = nullptr;
@@ -432,12 +353,6 @@ bool JavaStackTraceHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* siginfo,
manager_->GetMethodAndReturnPcAndSp(siginfo, context, &method, &return_pc, &sp);
// Inside of generated code, sp[0] is the method, so sp is the frame.
self->SetTopOfStack(reinterpret_cast<ArtMethod**>(sp));
-#ifdef TEST_NESTED_SIGNAL
- // To test the nested signal handler we raise a signal here. This will cause the
- // nested signal handler to be called and perform a longjmp back to the setjmp
- // above.
- abort();
-#endif
self->DumpJavaStack(LOG_STREAM(ERROR));
}