From bc8fb886486bb1c47cda103d75d86f21d6857920 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 11 Oct 2017 16:01:06 -0700 Subject: ios: Adds a no-Mach exception handler This exception_handler_no_mach does not use Mach for exception handling so that clients such as tvOS and watchOS that do not support mach messages can handle POSIX signals. Change-Id: I4a4574e58834bc590e110e6ecd1825f8af1437a2 Reviewed-on: https://chromium-review.googlesource.com/714276 Reviewed-by: Mark Mentovai --- src/client/ios/Breakpad.mm | 7 + src/client/ios/exception_handler_no_mach.cc | 261 ++++++++++++++++++++++++++++ src/client/ios/exception_handler_no_mach.h | 179 +++++++++++++++++++ 3 files changed, 447 insertions(+) create mode 100644 src/client/ios/exception_handler_no_mach.cc create mode 100644 src/client/ios/exception_handler_no_mach.h (limited to 'src/client') diff --git a/src/client/ios/Breakpad.mm b/src/client/ios/Breakpad.mm index ce635bd2..88dd2870 100644 --- a/src/client/ios/Breakpad.mm +++ b/src/client/ios/Breakpad.mm @@ -36,6 +36,7 @@ #include #include #include +#include #import "client/ios/handler/ios_exception_minidump_generator.h" #import "client/mac/crash_generation/ConfigFile.h" @@ -45,6 +46,12 @@ #import "client/mac/handler/protected_memory_allocator.h" #import "common/simple_string_dictionary.h" +#if !TARGET_OS_TV && !TARGET_OS_WATCH +#import "client/mac/handler/exception_handler.h" +#else +#import "client/ios/handler/exception_handler_no_mach.h" +#endif // !TARGET_OS_TV && !TARGET_OS_WATCH + #if !defined(__EXCEPTIONS) || (__clang__ && !__has_feature(cxx_exceptions)) // This file uses C++ try/catch (but shouldn't). Duplicate the macros from // allowing this file to work properly with diff --git a/src/client/ios/exception_handler_no_mach.cc b/src/client/ios/exception_handler_no_mach.cc new file mode 100644 index 00000000..32723fc3 --- /dev/null +++ b/src/client/ios/exception_handler_no_mach.cc @@ -0,0 +1,261 @@ +// Copyright (c) 2006, 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. + +#include +#include + +#include "client/mac/handler/minidump_generator.h" +#include "client/ios/handler/exception_handler_no_mach.h" + +#ifndef USE_PROTECTED_ALLOCATIONS +#if TARGET_OS_TV +#define USE_PROTECTED_ALLOCATIONS 1 +#else +#define USE_PROTECTED_ALLOCATIONS 0 +#endif +#endif + +// If USE_PROTECTED_ALLOCATIONS is activated then the +// gBreakpadAllocator needs to be setup in other code +// ahead of time. Please see ProtectedMemoryAllocator.h +// for more details. +#if USE_PROTECTED_ALLOCATIONS + #include "client/mac/handler/protected_memory_allocator.h" + extern ProtectedMemoryAllocator *gBreakpadAllocator; +#endif + +namespace google_breakpad { + +const int kExceptionSignals[] = { + // Core-generating signals. + SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGQUIT, SIGSEGV, SIGSYS, SIGTRAP, SIGEMT, + SIGXCPU, SIGXFSZ, + // Non-core-generating but terminating signals. + SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGPROF, SIGTERM, SIGUSR1, SIGUSR2, + SIGVTALRM, SIGXCPU, SIGXFSZ, SIGIO, +}; +const int kNumHandledSignals = + sizeof(kExceptionSignals) / sizeof(kExceptionSignals[0]); +struct scoped_ptr old_handlers[kNumHandledSignals]; + +static union { +#if USE_PROTECTED_ALLOCATIONS +#if defined PAGE_MAX_SIZE + char protected_buffer[PAGE_MAX_SIZE] __attribute__((aligned(PAGE_MAX_SIZE))); +#else + char protected_buffer[PAGE_SIZE] __attribute__((aligned(PAGE_SIZE))); +#endif // defined PAGE_MAX_SIZE +#endif // USE_PROTECTED_ALLOCATIONS + google_breakpad::ExceptionHandler *handler; +} gProtectedData; + +ExceptionHandler::ExceptionHandler(const string &dump_path, + FilterCallback filter, + MinidumpCallback callback, + void* callback_context, + bool install_handler, + const char* port_name) + : dump_path_(), + filter_(filter), + callback_(callback), + callback_context_(callback_context), + directCallback_(NULL), + installed_exception_handler_(false), + is_in_teardown_(false) { + // This will update to the ID and C-string pointers + set_dump_path(dump_path); + MinidumpGenerator::GatherSystemInformation(); + Setup(); +} + +// special constructor if we want to bypass minidump writing and +// simply get a callback with the exception information +ExceptionHandler::ExceptionHandler(DirectCallback callback, + void* callback_context, + bool install_handler) + : dump_path_(), + filter_(NULL), + callback_(NULL), + callback_context_(callback_context), + directCallback_(callback), + installed_exception_handler_(false), + is_in_teardown_(false) { + MinidumpGenerator::GatherSystemInformation(); + Setup(); +} + +ExceptionHandler::~ExceptionHandler() { + Teardown(); +} + +bool ExceptionHandler::WriteMinidumpWithException( + int exception_type, + int exception_code, + int exception_subcode, + breakpad_ucontext_t* task_context, + mach_port_t thread_name, + bool exit_after_write, + bool report_current_thread) { + bool result = false; + + exit_after_write = false; + + if (directCallback_) { + if (directCallback_(callback_context_, + exception_type, + exception_code, + exception_subcode, + thread_name) ) { + if (exit_after_write) + _exit(exception_type); + } + } else { + string minidump_id; + + // Putting the MinidumpGenerator in its own context will ensure that the + // destructor is executed, closing the newly created minidump file. + if (!dump_path_.empty()) { + MinidumpGenerator md(mach_task_self(), + report_current_thread ? MACH_PORT_NULL : + mach_thread_self()); + md.SetTaskContext(task_context); + if (exception_type && exception_code) { + // If this is a real exception, give the filter (if any) a chance to + // decide if this should be sent. + if (filter_ && !filter_(callback_context_)) + return false; + + md.SetExceptionInformation(exception_type, exception_code, + exception_subcode, thread_name); + } + + result = md.Write(next_minidump_path_c_); + } + + // Call user specified callback (if any) + if (callback_) { + // If the user callback returned true and we're handling an exception + // (rather than just writing out the file), then we should exit without + // forwarding the exception to the next handler. + if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_, + result)) { + if (exit_after_write) + _exit(exception_type); + } + } + } + + return result; +} + +// static +void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) { +#if USE_PROTECTED_ALLOCATIONS + if (gBreakpadAllocator) + gBreakpadAllocator->Unprotect(); +#endif + gProtectedData.handler->WriteMinidumpWithException( + EXC_SOFTWARE, + MD_EXCEPTION_CODE_MAC_ABORT, + 0, + static_cast(uc), + mach_thread_self(), + true, + true); +#if USE_PROTECTED_ALLOCATIONS + if (gBreakpadAllocator) + gBreakpadAllocator->Protect(); +#endif +} + +bool ExceptionHandler::InstallHandlers() { + // If a handler is already installed, something is really wrong. + if (gProtectedData.handler != NULL) { + return false; + } + gProtectedData.handler = this; + for (int i = 0; i < kNumHandledSignals; ++i) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, kExceptionSignals[i]); + sa.sa_sigaction = ExceptionHandler::SignalHandler; + sa.sa_flags = SA_ONSTACK | SA_SIGINFO; + + if (sigaction(kExceptionSignals[i], &sa, old_handlers[i].get()) == -1) { + return false; + } +#if USE_PROTECTED_ALLOCATIONS + assert(((size_t)(gProtectedData.protected_buffer) & PAGE_MASK) == 0); + mprotect(gProtectedData.protected_buffer, PAGE_SIZE, PROT_READ); +#endif // USE_PROTECTED_ALLOCATIONS + } + installed_exception_handler_ = true; + return true; +} + +bool ExceptionHandler::UninstallHandlers() { + for (int i = 0; i < kNumHandledSignals; ++i) { + if (old_handlers[i].get()) { + sigaction(kExceptionSignals[i], old_handlers[i].get(), NULL); +#if USE_PROTECTED_ALLOCATIONS + mprotect(gProtectedData.protected_buffer, PAGE_SIZE, PROT_READ | PROT_WRITE); +#endif // USE_PROTECTED_ALLOCATIONS + old_handlers[i].reset(); + } + gProtectedData.handler = NULL; + } + installed_exception_handler_ = false; + return true; +} + +bool ExceptionHandler::Setup() { + if (!InstallHandlers()) + return false; + return true; +} + +bool ExceptionHandler::Teardown() { + is_in_teardown_ = true; + + if (!UninstallHandlers()) + return false; + + return true; +} + +void ExceptionHandler::UpdateNextID() { + next_minidump_path_ = + (MinidumpGenerator::UniqueNameInDirectory(dump_path_, &next_minidump_id_)); + + next_minidump_path_c_ = next_minidump_path_.c_str(); + next_minidump_id_c_ = next_minidump_id_.c_str(); +} + +} // namespace google_breakpad diff --git a/src/client/ios/exception_handler_no_mach.h b/src/client/ios/exception_handler_no_mach.h new file mode 100644 index 00000000..6d99565b --- /dev/null +++ b/src/client/ios/exception_handler_no_mach.h @@ -0,0 +1,179 @@ +// Copyright (c) 2006, 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. + +#ifndef CLIENT_IOS_HANDLER_EXCEPTION_HANDLER_NO_MACH_H__ +#define CLIENT_IOS_HANDLER_EXCEPTION_HANDLER_NO_MACH_H__ + +#include +#include + +#include + +#include "client/mac/handler/ucontext_compat.h" +#include "common/scoped_ptr.h" + +namespace google_breakpad { + +using std::string; + +class ExceptionHandler { + public: + // A callback function to run before Breakpad performs any substantial + // processing of an exception. A FilterCallback is called before writing + // a minidump. context is the parameter supplied by the user as + // callback_context when the handler was created. + // + // If a FilterCallback returns true, Breakpad will continue processing, + // attempting to write a minidump. If a FilterCallback returns false, Breakpad + // will immediately report the exception as unhandled without writing a + // minidump, allowing another handler the opportunity to handle it. + typedef bool (*FilterCallback)(void *context); + + // A callback function to run after the minidump has been written. + // |minidump_id| is a unique id for the dump, so the minidump + // file is /.dmp. + // |context| is the value passed into the constructor. + // |succeeded| indicates whether a minidump file was successfully written. + // Return true if the exception was fully handled and breakpad should exit. + // Return false to allow any other exception handlers to process the + // exception. + typedef bool (*MinidumpCallback)(const char *dump_dir, + const char *minidump_id, + void *context, bool succeeded); + + // A callback function which will be called directly if an exception occurs. + // This bypasses the minidump file writing and simply gives the client + // the exception information. + typedef bool (*DirectCallback)( void *context, + int exception_type, + int exception_code, + int exception_subcode, + mach_port_t thread_name); + + // Creates a new ExceptionHandler instance to handle writing minidumps. + // Minidump files will be written to dump_path, and the optional callback + // is called after writing the dump file, as described above. + // If install_handler is true, then a minidump will be written whenever + // an unhandled exception occurs. If it is false, minidumps will only + // be written when WriteMinidump is called. + // If port_name is non-NULL, attempt to perform out-of-process dump generation + // If port_name is NULL, in-process dump generation will be used. + ExceptionHandler(const string &dump_path, + FilterCallback filter, MinidumpCallback callback, + void *callback_context, bool install_handler, + const char *port_name); + + // A special constructor if we want to bypass minidump writing and + // simply get a callback with the exception information. + ExceptionHandler(DirectCallback callback, + void *callback_context, + bool install_handler); + + ~ExceptionHandler(); + + // Get and set the minidump path. + string dump_path() const { return dump_path_; } + void set_dump_path(const string &dump_path) { + dump_path_ = dump_path; + dump_path_c_ = dump_path_.c_str(); + UpdateNextID(); // Necessary to put dump_path_ in next_minidump_path_. + } + + private: + // Install the SIG exception handlers. + bool InstallHandlers(); + + // Uninstall the SIG exception handlers. + bool UninstallHandlers(); + + // Setup the handler thread, and if |install_handler| is true, install the + // mach exception port handler + bool Setup(); + + // Uninstall the mach exception handler (if any) and terminate the helper + // thread + bool Teardown(); + + // All minidump writing goes through this one routine. + // |task_context| can be NULL. If not, it will be used to retrieve the + // context of the current thread, instead of using |thread_get_state|. + bool WriteMinidumpWithException(int exception_type, + int exception_code, + int exception_subcode, + breakpad_ucontext_t *task_context, + mach_port_t thread_name, + bool exit_after_write, + bool report_current_thread); + + // Signal handler for SIG exceptions. + static void SignalHandler(int sig, siginfo_t* info, void* uc); + + // disallow copy ctor and operator= + explicit ExceptionHandler(const ExceptionHandler &); + void operator=(const ExceptionHandler &); + + // Generates a new ID and stores it in next_minidump_id_, and stores the + // path of the next minidump to be written in next_minidump_path_. + void UpdateNextID(); + + // The destination directory for the minidump + string dump_path_; + + // The basename of the next minidump w/o extension + string next_minidump_id_; + + // The full path to the next minidump to be written, including extension + string next_minidump_path_; + + // Pointers to the UTF-8 versions of above + const char *dump_path_c_; + const char *next_minidump_id_c_; + const char *next_minidump_path_c_; + + // The callback function and pointer to be passed back after the minidump + // has been written + FilterCallback filter_; + MinidumpCallback callback_; + void *callback_context_; + + // The callback function to be passed back when we don't want a minidump + // file to be written + DirectCallback directCallback_; + + // True, if we've installed the exception handler + bool installed_exception_handler_; + + // True, if we're in the process of uninstalling the exception handler and + // the thread. + bool is_in_teardown_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_IOS_HANDLER_EXCEPTION_HANDLER_NO_MACH_H__ -- cgit v1.2.1