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 <mark@chromium.org>
This commit is contained in:
Adam Harrison 2017-10-11 16:01:06 -07:00 committed by Mark Mentovai
parent 1c3f8d1002
commit bc8fb88648
3 changed files with 447 additions and 0 deletions

View file

@ -36,6 +36,7 @@
#include <pthread.h> #include <pthread.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/sysctl.h> #include <sys/sysctl.h>
#include <TargetConditionals.h>
#import "client/ios/handler/ios_exception_minidump_generator.h" #import "client/ios/handler/ios_exception_minidump_generator.h"
#import "client/mac/crash_generation/ConfigFile.h" #import "client/mac/crash_generation/ConfigFile.h"
@ -45,6 +46,12 @@
#import "client/mac/handler/protected_memory_allocator.h" #import "client/mac/handler/protected_memory_allocator.h"
#import "common/simple_string_dictionary.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)) #if !defined(__EXCEPTIONS) || (__clang__ && !__has_feature(cxx_exceptions))
// This file uses C++ try/catch (but shouldn't). Duplicate the macros from // This file uses C++ try/catch (but shouldn't). Duplicate the macros from
// <c++/4.2.1/exception_defines.h> allowing this file to work properly with // <c++/4.2.1/exception_defines.h> allowing this file to work properly with

View file

@ -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 <signal.h>
#include <TargetConditionals.h>
#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<struct sigaction> 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<breakpad_ucontext_t*>(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

View file

@ -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 <mach/mach.h>
#include <TargetConditionals.h>
#include <string>
#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 <dump_dir>/<minidump_id>.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__