Index: breakpad/linux/exception_handler.cc |
diff --git a/breakpad/linux/exception_handler.cc b/breakpad/linux/exception_handler.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..bbc69109ca7bae2096e7bc5c1e93becd7de8b220 |
--- /dev/null |
+++ b/breakpad/linux/exception_handler.cc |
@@ -0,0 +1,308 @@ |
+// Copyright (c) 2009, Google Inc. |
+// All rights reserved. |
+// |
+// Redistribution and use in source and binary forms, with or without |
+// modification, are permitted provided that the following conditions are |
+// met: |
+// |
+// * Redistributions of source code must retain the above copyright |
+// notice, this list of conditions and the following disclaimer. |
+// * Redistributions in binary form must reproduce the above |
+// copyright notice, this list of conditions and the following disclaimer |
+// in the documentation and/or other materials provided with the |
+// distribution. |
+// * Neither the name of Google Inc. nor the names of its |
+// contributors may be used to endorse or promote products derived from |
+// this software without specific prior written permission. |
+// |
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
+ |
+// The ExceptionHandler object installs signal handlers for a number of |
+// signals. We rely on the signal handler running on the thread which crashed |
+// in order to identify it. This is true of the synchronous signals (SEGV etc), |
+// but not true of ABRT. Thus, if you send ABRT to yourself in a program which |
+// uses ExceptionHandler, you need to use tgkill to direct it to the current |
+// thread. |
+// |
+// The signal flow looks like this: |
+// |
+// SignalHandler (uses a global stack of ExceptionHandler objects to find |
+// | one to handle the signal. If the first rejects it, try |
+// | the second etc...) |
+// V |
+// HandleSignal ----------------------------| (clones a new process which |
+// | | shares an address space with |
+// (wait for cloned | the crashed process. This |
+// process) | allows us to ptrace the crashed |
+// | | process) |
+// V V |
+// (set signal handler to ThreadEntry (static function to bounce |
+// SIG_DFL and rethrow, | back into the object) |
+// killing the crashed | |
+// process) V |
+// DoDump (writes minidump) |
+// | |
+// V |
+// sys_exit |
+// |
+ |
+// This code is a little fragmented. Different functions of the ExceptionHandler |
+// class run in a number of different contexts. Some of them run in a normal |
+// context and are easy to code, others run in a compromised context and the |
+// restrictions at the top of minidump_writer.cc apply: no libc and use the |
+// alternative malloc. Each function should have comment above it detailing the |
+// context which it runs in. |
+ |
+#include "breakpad/linux/exception_handler.h" |
+ |
+#include <unistd.h> |
+#include <fcntl.h> |
+#include <sys/mman.h> |
+#include <errno.h> |
+#include <signal.h> |
+#include <sys/signal.h> |
+#include <sys/ucontext.h> |
+#include <sys/user.h> |
+#include <sys/syscall.h> |
+#include <sys/wait.h> |
+#include <sched.h> |
+ |
+#include "breakpad/linux/linux_syscall_support.h" |
+#include "breakpad/linux/memory.h" |
+#include "breakpad/linux/minidump_writer.h" |
+#include "common/linux/guid_creator.h" |
+ |
+// A wrapper for the tgkill syscall: send a signal to a specific thread. |
+static int tgkill(pid_t tgid, pid_t tid, int sig) { |
+ syscall(__NR_tgkill, tgid, tid, sig); |
+} |
+ |
+namespace google_breakpad { |
+ |
+// The list of signals which we consider to be crashes. The default action for |
+// all these signals must be Core (see man 7 signal) because we rethrow the |
+// signal after handling it and expect that it'll be fatal. |
+static const int kExceptionSignals[] = { |
+ SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, -1 |
+}; |
+ |
+// We can stack multiple exception handlers. In that case, this is the global |
+// which holds the stack. |
+std::vector<ExceptionHandler*>* ExceptionHandler::handler_stack_ = NULL; |
+unsigned ExceptionHandler::handler_stack_index_ = 0; |
+pthread_mutex_t ExceptionHandler::handler_stack_mutex_ = |
+ PTHREAD_MUTEX_INITIALIZER; |
+ |
+// Runs before crashing: normal context. |
+ExceptionHandler::ExceptionHandler(const std::string &dump_path, |
+ FilterCallback filter, |
+ MinidumpCallback callback, |
+ void *callback_context, |
+ bool install_handler) |
+ : filter_(filter), |
+ callback_(callback), |
+ callback_context_(callback_context), |
+ dump_path_(), |
+ handler_installed_(install_handler), |
+ crash_handler_(NULL) { |
+ set_dump_path(dump_path); |
+ |
+ if (install_handler) { |
+ InstallHandlers(); |
+ |
+ pthread_mutex_lock(&handler_stack_mutex_); |
+ if (handler_stack_ == NULL) |
+ handler_stack_ = new std::vector<ExceptionHandler *>; |
+ handler_stack_->push_back(this); |
+ pthread_mutex_unlock(&handler_stack_mutex_); |
+ } |
+} |
+ |
+// Runs before crashing: normal context. |
+ExceptionHandler::~ExceptionHandler() { |
+ UninstallHandlers(); |
+} |
+ |
+// Runs before crashing: normal context. |
+bool ExceptionHandler::InstallHandlers() { |
+ // We run the signal handlers on an alternative stack because we might have |
+ // crashed because of a stack overflow. |
+ |
+ // We use this value rather than SIGSTKSZ because we would end up overrunning |
+ // such a small stack. |
+ static const unsigned kSigStackSize = 8192; |
+ |
+ signal_stack = malloc(kSigStackSize); |
+ stack_t stack; |
+ memset(&stack, 0, sizeof(stack)); |
+ stack.ss_sp = signal_stack; |
+ stack.ss_size = kSigStackSize; |
+ |
+ if (sigaltstack(&stack, NULL) == -1) |
+ return false; |
+ |
+ struct sigaction sa; |
+ memset(&sa, 0, sizeof(sa)); |
+ sigemptyset(&sa.sa_mask); |
+ |
+ // mask all exception signals when we're handling one of them. |
+ for (unsigned i = 0; kExceptionSignals[i] != -1; ++i) |
+ sigaddset(&sa.sa_mask, kExceptionSignals[i]); |
+ |
+ sa.sa_sigaction = SignalHandler; |
+ sa.sa_flags = SA_ONSTACK | SA_SIGINFO; |
+ |
+ for (unsigned i = 0; kExceptionSignals[i] != -1; ++i) { |
+ struct sigaction* old = new struct sigaction; |
+ if (sigaction(kExceptionSignals[i], &sa, old) == -1) |
+ return false; |
+ old_handlers_.push_back(std::make_pair(kExceptionSignals[i], old)); |
+ } |
+} |
+ |
+// Runs before crashing: normal context. |
+void ExceptionHandler::UninstallHandlers() { |
+ for (unsigned i = 0; i < old_handlers_.size(); ++i) { |
+ struct sigaction *action = |
+ reinterpret_cast<struct sigaction*>(old_handlers_[i].second); |
+ sigaction(old_handlers_[i].first, action, NULL); |
+ delete action; |
+ } |
+ |
+ old_handlers_.clear(); |
+} |
+ |
+// Runs before crashing: normal context. |
+void ExceptionHandler::UpdateNextID() { |
+ GUID guid; |
+ char guid_str[kGUIDStringLength + 1]; |
+ if (CreateGUID(&guid) && GUIDToString(&guid, guid_str, sizeof(guid_str))) { |
+ next_minidump_id_ = guid_str; |
+ next_minidump_id_c_ = next_minidump_id_.c_str(); |
+ |
+ char minidump_path[PATH_MAX]; |
+ snprintf(minidump_path, sizeof(minidump_path), "%s/%s.dmp", |
+ dump_path_c_, |
+ guid_str); |
+ |
+ next_minidump_path_ = minidump_path; |
+ next_minidump_path_c_ = next_minidump_path_.c_str(); |
+ } |
+} |
+ |
+// This function runs in a compromised context: see the top of the file. |
+// Runs on the crashing thread. |
+// static |
+void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) { |
+ // All the exception signals are blocked at this point. |
+ |
+ pthread_mutex_lock(&handler_stack_mutex_); |
+ |
+ if (!handler_stack_->size()) { |
+ pthread_mutex_unlock(&handler_stack_mutex_); |
+ return; |
+ } |
+ |
+ for (int i = handler_stack_->size() - 1; i >= 0; --i) { |
+ if ((*handler_stack_)[i]->HandleSignal(sig, info, uc)) { |
+ // successfully handled: We are in an invalid state since an exception |
+ // signal has been delivered. We don't call the exit handlers because |
+ // they could end up corrupting on-disk state. |
+ break; |
+ } |
+ } |
+ |
+ pthread_mutex_unlock(&handler_stack_mutex_); |
+ |
+ // Terminate ourselves with the same signal so that our parent knows that we |
+ // crashed. The default action for all the signals which we catch is Core, so |
+ // this is the end of us. |
+ signal(sig, SIG_DFL); |
+ tgkill(getpid(), sys_gettid(), sig); |
+ |
+ // not reached. |
+} |
+ |
+struct ThreadArgument { |
+ pid_t pid; // the crashing process |
+ ExceptionHandler* handler; |
+ const void* context; // a CrashContext structure |
+ size_t context_size; |
+}; |
+ |
+// This is the entry function for the cloned process. We are in a compromised |
+// context here: see the top of the file. |
+// static |
+int ExceptionHandler::ThreadEntry(void *arg) { |
+ const ThreadArgument *thread_arg = reinterpret_cast<ThreadArgument*>(arg); |
+ return thread_arg->handler->DoDump(thread_arg->pid, thread_arg->context, |
+ thread_arg->context_size) == false; |
+} |
+ |
+// This function runs in a compromised context: see the top of the file. |
+// Runs on the crashing thread. |
+bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) { |
+ if (filter_ && !filter_(callback_context_)) |
+ return false; |
+ |
+ CrashContext context; |
+ memcpy(&context.siginfo, info, sizeof(siginfo_t)); |
+ memcpy(&context.context, uc, sizeof(struct ucontext)); |
+ context.tid = sys_gettid(); |
+ |
+ if (crash_handler_ && crash_handler_(&context, sizeof(context), |
+ callback_context_)) |
+ return true; |
+ |
+ PageAllocator allocator; |
+ void* const stack = allocator.Alloc(8000); |
+ |
+ ThreadArgument thread_arg; |
+ thread_arg.handler = this; |
+ thread_arg.pid = getpid(); |
+ thread_arg.context = &context; |
+ thread_arg.context_size = sizeof(context); |
+ |
+ const pid_t child = sys_clone( |
+ ThreadEntry, stack, CLONE_FILES | CLONE_FS | CLONE_UNTRACED, |
+ &thread_arg, NULL, NULL, NULL); |
+ int r, status; |
+ do { |
+ r = sys_waitpid(child, &status, __WALL); |
+ } while (r == -1 && errno == EINTR); |
+ |
+ if (r == -1) { |
+ static const char msg[] = "ExceptionHandler::HandleSignal: waitpid failed:"; |
+ sys_write(2, msg, sizeof(msg) - 1); |
+ sys_write(2, strerror(errno), strlen(strerror(errno))); |
+ sys_write(2, "\n", 1); |
+ } |
+ |
+ bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0; |
+ |
+ if (callback_) |
+ success = callback_(dump_path_c_, next_minidump_id_c_, |
+ callback_context_, success); |
+ |
+ return success; |
+} |
+ |
+// This function runs in a compromised context: see the top of the file. |
+// Runs on the cloned process. |
+bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context, |
+ size_t context_size) { |
+ return google_breakpad::WriteMinidump( |
+ next_minidump_path_c_, crashing_process, context, context_size); |
+} |
+ |
+} // namespace google_breakpad |