| // 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 <mach/exc.h> |
| #include <mach/mig.h> |
| #include <pthread.h> |
| #include <signal.h> |
| #include <TargetConditionals.h> |
| |
| #include <map> |
| |
| #include "client/mac/handler/exception_handler.h" |
| #include "client/mac/handler/minidump_generator.h" |
| #include "common/mac/macho_utilities.h" |
| #include "common/mac/scoped_task_suspend-inl.h" |
| #include "google_breakpad/common/minidump_exception_mac.h" |
| |
| #ifndef __EXCEPTIONS |
| // 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 |
| // exceptions disabled even when other C++ libraries are used. #undef the try |
| // and catch macros first in case libstdc++ is in use and has already provided |
| // its own definitions. |
| #undef try |
| #define try if (true) |
| #undef catch |
| #define catch(X) if (false) |
| #endif // __EXCEPTIONS |
| |
| #ifndef USE_PROTECTED_ALLOCATIONS |
| #if TARGET_OS_IPHONE |
| #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 "protected_memory_allocator.h" |
| extern ProtectedMemoryAllocator *gBreakpadAllocator; |
| #endif |
| |
| namespace google_breakpad { |
| |
| 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; |
| |
| using std::map; |
| |
| // These structures and techniques are illustrated in |
| // Mac OS X Internals, Amit Singh, ch 9.7 |
| struct ExceptionMessage { |
| mach_msg_header_t header; |
| mach_msg_body_t body; |
| mach_msg_port_descriptor_t thread; |
| mach_msg_port_descriptor_t task; |
| NDR_record_t ndr; |
| exception_type_t exception; |
| mach_msg_type_number_t code_count; |
| integer_t code[EXCEPTION_CODE_MAX]; |
| char padding[512]; |
| }; |
| |
| struct ExceptionParameters { |
| ExceptionParameters() : count(0) {} |
| mach_msg_type_number_t count; |
| exception_mask_t masks[EXC_TYPES_COUNT]; |
| mach_port_t ports[EXC_TYPES_COUNT]; |
| exception_behavior_t behaviors[EXC_TYPES_COUNT]; |
| thread_state_flavor_t flavors[EXC_TYPES_COUNT]; |
| }; |
| |
| struct ExceptionReplyMessage { |
| mach_msg_header_t header; |
| NDR_record_t ndr; |
| kern_return_t return_code; |
| }; |
| |
| // Only catch these three exceptions. The other ones are nebulously defined |
| // and may result in treating a non-fatal exception as fatal. |
| exception_mask_t s_exception_mask = EXC_MASK_BAD_ACCESS | |
| EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT; |
| |
| #if !TARGET_OS_IPHONE |
| extern "C" { |
| // Forward declarations for functions that need "C" style compilation |
| boolean_t exc_server(mach_msg_header_t* request, |
| mach_msg_header_t* reply); |
| |
| // This symbol must be visible to dlsym() - see |
| // https://bugs.chromium.org/p/google-breakpad/issues/detail?id=345 for details. |
| kern_return_t catch_exception_raise(mach_port_t target_port, |
| mach_port_t failed_thread, |
| mach_port_t task, |
| exception_type_t exception, |
| exception_data_t code, |
| mach_msg_type_number_t code_count) |
| __attribute__((visibility("default"))); |
| } |
| #endif |
| |
| kern_return_t ForwardException(mach_port_t task, |
| mach_port_t failed_thread, |
| exception_type_t exception, |
| exception_data_t code, |
| mach_msg_type_number_t code_count); |
| |
| #if TARGET_OS_IPHONE |
| // Implementation is based on the implementation generated by mig. |
| boolean_t breakpad_exc_server(mach_msg_header_t* InHeadP, |
| mach_msg_header_t* OutHeadP) { |
| OutHeadP->msgh_bits = |
| MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(InHeadP->msgh_bits), 0); |
| OutHeadP->msgh_remote_port = InHeadP->msgh_remote_port; |
| /* Minimal size: routine() will update it if different */ |
| OutHeadP->msgh_size = (mach_msg_size_t)sizeof(mig_reply_error_t); |
| OutHeadP->msgh_local_port = MACH_PORT_NULL; |
| OutHeadP->msgh_id = InHeadP->msgh_id + 100; |
| |
| if (InHeadP->msgh_id != 2401) { |
| ((mig_reply_error_t*)OutHeadP)->NDR = NDR_record; |
| ((mig_reply_error_t*)OutHeadP)->RetCode = MIG_BAD_ID; |
| return FALSE; |
| } |
| |
| #ifdef __MigPackStructs |
| #pragma pack(4) |
| #endif |
| typedef struct { |
| mach_msg_header_t Head; |
| /* start of the kernel processed data */ |
| mach_msg_body_t msgh_body; |
| mach_msg_port_descriptor_t thread; |
| mach_msg_port_descriptor_t task; |
| /* end of the kernel processed data */ |
| NDR_record_t NDR; |
| exception_type_t exception; |
| mach_msg_type_number_t codeCnt; |
| integer_t code[2]; |
| mach_msg_trailer_t trailer; |
| } Request; |
| |
| typedef struct { |
| mach_msg_header_t Head; |
| NDR_record_t NDR; |
| kern_return_t RetCode; |
| } Reply; |
| #ifdef __MigPackStructs |
| #pragma pack() |
| #endif |
| |
| Request* In0P = (Request*)InHeadP; |
| Reply* OutP = (Reply*)OutHeadP; |
| |
| if (In0P->task.name != mach_task_self()) { |
| return FALSE; |
| } |
| OutP->RetCode = ForwardException(In0P->task.name, |
| In0P->thread.name, |
| In0P->exception, |
| In0P->code, |
| In0P->codeCnt); |
| OutP->NDR = NDR_record; |
| return TRUE; |
| } |
| #else |
| boolean_t breakpad_exc_server(mach_msg_header_t* request, |
| mach_msg_header_t* reply) { |
| return exc_server(request, reply); |
| } |
| |
| // Callback from exc_server() |
| kern_return_t catch_exception_raise(mach_port_t port, mach_port_t failed_thread, |
| mach_port_t task, |
| exception_type_t exception, |
| exception_data_t code, |
| mach_msg_type_number_t code_count) { |
| if (task != mach_task_self()) { |
| return KERN_FAILURE; |
| } |
| return ForwardException(task, failed_thread, exception, code, code_count); |
| } |
| #endif |
| |
| 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), |
| handler_thread_(NULL), |
| handler_port_(MACH_PORT_NULL), |
| previous_(NULL), |
| installed_exception_handler_(false), |
| is_in_teardown_(false), |
| last_minidump_write_result_(false), |
| use_minidump_write_mutex_(false) { |
| // This will update to the ID and C-string pointers |
| set_dump_path(dump_path); |
| MinidumpGenerator::GatherSystemInformation(); |
| #if !TARGET_OS_IPHONE |
| if (port_name) |
| crash_generation_client_.reset(new CrashGenerationClient(port_name)); |
| #endif |
| Setup(install_handler); |
| } |
| |
| // 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), |
| handler_thread_(NULL), |
| handler_port_(MACH_PORT_NULL), |
| previous_(NULL), |
| installed_exception_handler_(false), |
| is_in_teardown_(false), |
| last_minidump_write_result_(false), |
| use_minidump_write_mutex_(false) { |
| MinidumpGenerator::GatherSystemInformation(); |
| Setup(install_handler); |
| } |
| |
| ExceptionHandler::~ExceptionHandler() { |
| Teardown(); |
| } |
| |
| bool ExceptionHandler::WriteMinidump(bool write_exception_stream) { |
| // If we're currently writing, just return |
| if (use_minidump_write_mutex_) |
| return false; |
| |
| use_minidump_write_mutex_ = true; |
| last_minidump_write_result_ = false; |
| |
| // Lock the mutex. Since we just created it, this will return immediately. |
| if (pthread_mutex_lock(&minidump_write_mutex_) == 0) { |
| // Send an empty message to the handle port so that a minidump will |
| // be written |
| bool result = SendMessageToHandlerThread(write_exception_stream ? |
| kWriteDumpWithExceptionMessage : |
| kWriteDumpMessage); |
| if (!result) { |
| pthread_mutex_unlock(&minidump_write_mutex_); |
| return false; |
| } |
| |
| // Wait for the minidump writer to complete its writing. It will unlock |
| // the mutex when completed |
| pthread_mutex_lock(&minidump_write_mutex_); |
| } |
| |
| use_minidump_write_mutex_ = false; |
| UpdateNextID(); |
| return last_minidump_write_result_; |
| } |
| |
| // static |
| bool ExceptionHandler::WriteMinidump(const string& dump_path, |
| bool write_exception_stream, |
| MinidumpCallback callback, |
| void* callback_context) { |
| ExceptionHandler handler(dump_path, NULL, callback, callback_context, false, |
| NULL); |
| return handler.WriteMinidump(write_exception_stream); |
| } |
| |
| // static |
| bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child, |
| mach_port_t child_blamed_thread, |
| const string& dump_path, |
| MinidumpCallback callback, |
| void* callback_context) { |
| ScopedTaskSuspend suspend(child); |
| |
| MinidumpGenerator generator(child, MACH_PORT_NULL); |
| string dump_id; |
| string dump_filename = generator.UniqueNameInDirectory(dump_path, &dump_id); |
| |
| generator.SetExceptionInformation(EXC_BREAKPOINT, |
| #if defined(__i386__) || defined(__x86_64__) |
| EXC_I386_BPT, |
| #elif defined(__ppc__) || defined(__ppc64__) |
| EXC_PPC_BREAKPOINT, |
| #elif defined(__arm__) || defined(__aarch64__) |
| EXC_ARM_BREAKPOINT, |
| #else |
| #error architecture not supported |
| #endif |
| 0, |
| child_blamed_thread); |
| bool result = generator.Write(dump_filename.c_str()); |
| |
| if (callback) { |
| return callback(dump_path.c_str(), dump_id.c_str(), |
| callback_context, result); |
| } |
| return result; |
| } |
| |
| 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; |
| |
| #if TARGET_OS_IPHONE |
| // _exit() should never be called on iOS. |
| exit_after_write = false; |
| #endif |
| |
| if (directCallback_) { |
| if (directCallback_(callback_context_, |
| exception_type, |
| exception_code, |
| exception_subcode, |
| thread_name) ) { |
| if (exit_after_write) |
| _exit(exception_type); |
| } |
| #if !TARGET_OS_IPHONE |
| } else if (IsOutOfProcess()) { |
| 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; |
| result = crash_generation_client_->RequestDumpForException( |
| exception_type, |
| exception_code, |
| exception_subcode, |
| thread_name); |
| if (result && exit_after_write) { |
| _exit(exception_type); |
| } |
| } |
| #endif |
| } 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; |
| } |
| |
| kern_return_t ForwardException(mach_port_t task, mach_port_t failed_thread, |
| exception_type_t exception, |
| exception_data_t code, |
| mach_msg_type_number_t code_count) { |
| // At this time, we should have called Uninstall() on the exception handler |
| // so that the current exception ports are the ones that we should be |
| // forwarding to. |
| ExceptionParameters current; |
| |
| current.count = EXC_TYPES_COUNT; |
| mach_port_t current_task = mach_task_self(); |
| task_get_exception_ports(current_task, |
| s_exception_mask, |
| current.masks, |
| ¤t.count, |
| current.ports, |
| current.behaviors, |
| current.flavors); |
| |
| // Find the first exception handler that matches the exception |
| unsigned int found; |
| for (found = 0; found < current.count; ++found) { |
| if (current.masks[found] & (1 << exception)) { |
| break; |
| } |
| } |
| |
| // Nothing to forward |
| if (found == current.count) { |
| fprintf(stderr, "** No previous ports for forwarding!! \n"); |
| exit(KERN_FAILURE); |
| } |
| |
| mach_port_t target_port = current.ports[found]; |
| exception_behavior_t target_behavior = current.behaviors[found]; |
| |
| kern_return_t result; |
| // TODO: Handle the case where |target_behavior| has MACH_EXCEPTION_CODES |
| // set. https://bugs.chromium.org/p/google-breakpad/issues/detail?id=551 |
| switch (target_behavior) { |
| case EXCEPTION_DEFAULT: |
| result = exception_raise(target_port, failed_thread, task, exception, |
| code, code_count); |
| break; |
| default: |
| fprintf(stderr, "** Unknown exception behavior: %d\n", target_behavior); |
| result = KERN_FAILURE; |
| break; |
| } |
| |
| return result; |
| } |
| |
| // static |
| void* ExceptionHandler::WaitForMessage(void* exception_handler_class) { |
| ExceptionHandler* self = |
| reinterpret_cast<ExceptionHandler*>(exception_handler_class); |
| ExceptionMessage receive; |
| |
| // Wait for the exception info |
| while (1) { |
| receive.header.msgh_local_port = self->handler_port_; |
| receive.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(receive)); |
| kern_return_t result = mach_msg(&(receive.header), |
| MACH_RCV_MSG | MACH_RCV_LARGE, 0, |
| receive.header.msgh_size, |
| self->handler_port_, |
| MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); |
| |
| |
| if (result == KERN_SUCCESS) { |
| // Uninstall our handler so that we don't get in a loop if the process of |
| // writing out a minidump causes an exception. However, if the exception |
| // was caused by a fork'd process, don't uninstall things |
| |
| // If the actual exception code is zero, then we're calling this handler |
| // in a way that indicates that we want to either exit this thread or |
| // generate a minidump |
| // |
| // While reporting, all threads (except this one) must be suspended |
| // to avoid misleading stacks. If appropriate they will be resumed |
| // afterwards. |
| if (!receive.exception) { |
| // Don't touch self, since this message could have been sent |
| // from its destructor. |
| if (receive.header.msgh_id == kShutdownMessage) |
| return NULL; |
| |
| self->SuspendThreads(); |
| |
| #if USE_PROTECTED_ALLOCATIONS |
| if (gBreakpadAllocator) |
| gBreakpadAllocator->Unprotect(); |
| #endif |
| |
| mach_port_t thread = MACH_PORT_NULL; |
| int exception_type = 0; |
| int exception_code = 0; |
| if (receive.header.msgh_id == kWriteDumpWithExceptionMessage) { |
| thread = receive.thread.name; |
| exception_type = EXC_BREAKPOINT; |
| #if defined(__i386__) || defined(__x86_64__) |
| exception_code = EXC_I386_BPT; |
| #elif defined(__ppc__) || defined(__ppc64__) |
| exception_code = EXC_PPC_BREAKPOINT; |
| #elif defined(__arm__) || defined(__aarch64__) |
| exception_code = EXC_ARM_BREAKPOINT; |
| #else |
| #error architecture not supported |
| #endif |
| } |
| |
| // Write out the dump and save the result for later retrieval |
| self->last_minidump_write_result_ = |
| self->WriteMinidumpWithException(exception_type, exception_code, |
| 0, NULL, thread, |
| false, false); |
| |
| #if USE_PROTECTED_ALLOCATIONS |
| if (gBreakpadAllocator) |
| gBreakpadAllocator->Protect(); |
| #endif |
| |
| self->ResumeThreads(); |
| |
| if (self->use_minidump_write_mutex_) |
| pthread_mutex_unlock(&self->minidump_write_mutex_); |
| } else { |
| // When forking a child process with the exception handler installed, |
| // if the child crashes, it will send the exception back to the parent |
| // process. The check for task == self_task() ensures that only |
| // exceptions that occur in the parent process are caught and |
| // processed. If the exception was not caused by this task, we |
| // still need to call into the exception server and have it return |
| // KERN_FAILURE (see catch_exception_raise) in order for the kernel |
| // to move onto the host exception handler for the child task |
| if (receive.task.name == mach_task_self()) { |
| self->SuspendThreads(); |
| |
| #if USE_PROTECTED_ALLOCATIONS |
| if (gBreakpadAllocator) |
| gBreakpadAllocator->Unprotect(); |
| #endif |
| |
| int subcode = 0; |
| if (receive.exception == EXC_BAD_ACCESS && receive.code_count > 1) |
| subcode = receive.code[1]; |
| |
| // Generate the minidump with the exception data. |
| self->WriteMinidumpWithException(receive.exception, receive.code[0], |
| subcode, NULL, receive.thread.name, |
| true, false); |
| |
| #if USE_PROTECTED_ALLOCATIONS |
| // This may have become protected again within |
| // WriteMinidumpWithException, but it needs to be unprotected for |
| // UninstallHandler. |
| if (gBreakpadAllocator) |
| gBreakpadAllocator->Unprotect(); |
| #endif |
| |
| self->UninstallHandler(true); |
| |
| #if USE_PROTECTED_ALLOCATIONS |
| if (gBreakpadAllocator) |
| gBreakpadAllocator->Protect(); |
| #endif |
| } |
| // Pass along the exception to the server, which will setup the |
| // message and call catch_exception_raise() and put the return |
| // code into the reply. |
| ExceptionReplyMessage reply; |
| if (!breakpad_exc_server(&receive.header, &reply.header)) |
| exit(1); |
| |
| // Send a reply and exit |
| mach_msg(&(reply.header), MACH_SEND_MSG, |
| reply.header.msgh_size, 0, MACH_PORT_NULL, |
| MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| // 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::InstallHandler() { |
| // If a handler is already installed, something is really wrong. |
| if (gProtectedData.handler != NULL) { |
| return false; |
| } |
| if (!IsOutOfProcess()) { |
| struct sigaction sa; |
| memset(&sa, 0, sizeof(sa)); |
| sigemptyset(&sa.sa_mask); |
| sigaddset(&sa.sa_mask, SIGABRT); |
| sa.sa_sigaction = ExceptionHandler::SignalHandler; |
| sa.sa_flags = SA_SIGINFO; |
| |
| scoped_ptr<struct sigaction> old(new struct sigaction); |
| if (sigaction(SIGABRT, &sa, old.get()) == -1) { |
| return false; |
| } |
| old_handler_.swap(old); |
| gProtectedData.handler = this; |
| #if USE_PROTECTED_ALLOCATIONS |
| assert(((size_t)(gProtectedData.protected_buffer) & PAGE_MASK) == 0); |
| mprotect(gProtectedData.protected_buffer, PAGE_SIZE, PROT_READ); |
| #endif |
| } |
| |
| try { |
| #if USE_PROTECTED_ALLOCATIONS |
| previous_ = new (gBreakpadAllocator->Allocate(sizeof(ExceptionParameters)) ) |
| ExceptionParameters(); |
| #else |
| previous_ = new ExceptionParameters(); |
| #endif |
| } |
| catch (std::bad_alloc) { |
| return false; |
| } |
| |
| // Save the current exception ports so that we can forward to them |
| previous_->count = EXC_TYPES_COUNT; |
| mach_port_t current_task = mach_task_self(); |
| kern_return_t result = task_get_exception_ports(current_task, |
| s_exception_mask, |
| previous_->masks, |
| &previous_->count, |
| previous_->ports, |
| previous_->behaviors, |
| previous_->flavors); |
| |
| // Setup the exception ports on this task |
| if (result == KERN_SUCCESS) |
| result = task_set_exception_ports(current_task, s_exception_mask, |
| handler_port_, EXCEPTION_DEFAULT, |
| THREAD_STATE_NONE); |
| |
| installed_exception_handler_ = (result == KERN_SUCCESS); |
| |
| return installed_exception_handler_; |
| } |
| |
| bool ExceptionHandler::UninstallHandler(bool in_exception) { |
| kern_return_t result = KERN_SUCCESS; |
| |
| if (old_handler_.get()) { |
| sigaction(SIGABRT, old_handler_.get(), NULL); |
| #if USE_PROTECTED_ALLOCATIONS |
| mprotect(gProtectedData.protected_buffer, PAGE_SIZE, |
| PROT_READ | PROT_WRITE); |
| #endif |
| old_handler_.reset(); |
| gProtectedData.handler = NULL; |
| } |
| |
| if (installed_exception_handler_) { |
| mach_port_t current_task = mach_task_self(); |
| |
| // Restore the previous ports |
| for (unsigned int i = 0; i < previous_->count; ++i) { |
| result = task_set_exception_ports(current_task, previous_->masks[i], |
| previous_->ports[i], |
| previous_->behaviors[i], |
| previous_->flavors[i]); |
| if (result != KERN_SUCCESS) |
| return false; |
| } |
| |
| // this delete should NOT happen if an exception just occurred! |
| if (!in_exception) { |
| #if USE_PROTECTED_ALLOCATIONS |
| previous_->~ExceptionParameters(); |
| #else |
| delete previous_; |
| #endif |
| } |
| |
| previous_ = NULL; |
| installed_exception_handler_ = false; |
| } |
| |
| return result == KERN_SUCCESS; |
| } |
| |
| bool ExceptionHandler::Setup(bool install_handler) { |
| if (pthread_mutex_init(&minidump_write_mutex_, NULL)) |
| return false; |
| |
| // Create a receive right |
| mach_port_t current_task = mach_task_self(); |
| kern_return_t result = mach_port_allocate(current_task, |
| MACH_PORT_RIGHT_RECEIVE, |
| &handler_port_); |
| // Add send right |
| if (result == KERN_SUCCESS) |
| result = mach_port_insert_right(current_task, handler_port_, handler_port_, |
| MACH_MSG_TYPE_MAKE_SEND); |
| |
| if (install_handler && result == KERN_SUCCESS) |
| if (!InstallHandler()) |
| return false; |
| |
| if (result == KERN_SUCCESS) { |
| // Install the handler in its own thread, detached as we won't be joining. |
| pthread_attr_t attr; |
| pthread_attr_init(&attr); |
| pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); |
| int thread_create_result = pthread_create(&handler_thread_, &attr, |
| &WaitForMessage, this); |
| pthread_attr_destroy(&attr); |
| result = thread_create_result ? KERN_FAILURE : KERN_SUCCESS; |
| } |
| |
| return result == KERN_SUCCESS; |
| } |
| |
| bool ExceptionHandler::Teardown() { |
| kern_return_t result = KERN_SUCCESS; |
| is_in_teardown_ = true; |
| |
| if (!UninstallHandler(false)) |
| return false; |
| |
| // Send an empty message so that the handler_thread exits |
| if (SendMessageToHandlerThread(kShutdownMessage)) { |
| mach_port_t current_task = mach_task_self(); |
| result = mach_port_deallocate(current_task, handler_port_); |
| if (result != KERN_SUCCESS) |
| return false; |
| } else { |
| return false; |
| } |
| |
| handler_thread_ = NULL; |
| handler_port_ = MACH_PORT_NULL; |
| pthread_mutex_destroy(&minidump_write_mutex_); |
| |
| return result == KERN_SUCCESS; |
| } |
| |
| bool ExceptionHandler::SendMessageToHandlerThread( |
| HandlerThreadMessage message_id) { |
| ExceptionMessage msg; |
| memset(&msg, 0, sizeof(msg)); |
| msg.header.msgh_id = message_id; |
| if (message_id == kWriteDumpMessage || |
| message_id == kWriteDumpWithExceptionMessage) { |
| // Include this thread's port. |
| msg.thread.name = mach_thread_self(); |
| msg.thread.disposition = MACH_MSG_TYPE_PORT_SEND; |
| msg.thread.type = MACH_MSG_PORT_DESCRIPTOR; |
| } |
| msg.header.msgh_size = sizeof(msg) - sizeof(msg.padding); |
| msg.header.msgh_remote_port = handler_port_; |
| msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, |
| MACH_MSG_TYPE_MAKE_SEND_ONCE); |
| kern_return_t result = mach_msg(&(msg.header), |
| MACH_SEND_MSG | MACH_SEND_TIMEOUT, |
| msg.header.msgh_size, 0, 0, |
| MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); |
| |
| return result == KERN_SUCCESS; |
| } |
| |
| 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(); |
| } |
| |
| bool ExceptionHandler::SuspendThreads() { |
| thread_act_port_array_t threads_for_task; |
| mach_msg_type_number_t thread_count; |
| |
| if (task_threads(mach_task_self(), &threads_for_task, &thread_count)) |
| return false; |
| |
| // suspend all of the threads except for this one |
| for (unsigned int i = 0; i < thread_count; ++i) { |
| if (threads_for_task[i] != mach_thread_self()) { |
| if (thread_suspend(threads_for_task[i])) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool ExceptionHandler::ResumeThreads() { |
| thread_act_port_array_t threads_for_task; |
| mach_msg_type_number_t thread_count; |
| |
| if (task_threads(mach_task_self(), &threads_for_task, &thread_count)) |
| return false; |
| |
| // resume all of the threads except for this one |
| for (unsigned int i = 0; i < thread_count; ++i) { |
| if (threads_for_task[i] != mach_thread_self()) { |
| if (thread_resume(threads_for_task[i])) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| } // namespace google_breakpad |