michael@0: // Copyright (c) 2006, Google Inc. michael@0: // All rights reserved. michael@0: // michael@0: // Redistribution and use in source and binary forms, with or without michael@0: // modification, are permitted provided that the following conditions are michael@0: // met: michael@0: // michael@0: // * Redistributions of source code must retain the above copyright michael@0: // notice, this list of conditions and the following disclaimer. michael@0: // * Redistributions in binary form must reproduce the above michael@0: // copyright notice, this list of conditions and the following disclaimer michael@0: // in the documentation and/or other materials provided with the michael@0: // distribution. michael@0: // * Neither the name of Google Inc. nor the names of its michael@0: // contributors may be used to endorse or promote products derived from michael@0: // this software without specific prior written permission. michael@0: // michael@0: // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include michael@0: michael@0: #include "client/mac/handler/exception_handler.h" michael@0: #include "client/mac/handler/minidump_generator.h" michael@0: #include "common/mac/macho_utilities.h" michael@0: #include "common/mac/scoped_task_suspend-inl.h" michael@0: #include "google_breakpad/common/minidump_exception_mac.h" michael@0: michael@0: #ifndef __EXCEPTIONS michael@0: // This file uses C++ try/catch (but shouldn't). Duplicate the macros from michael@0: // allowing this file to work properly with michael@0: // exceptions disabled even when other C++ libraries are used. #undef the try michael@0: // and catch macros first in case libstdc++ is in use and has already provided michael@0: // its own definitions. michael@0: #undef try michael@0: #define try if (true) michael@0: #undef catch michael@0: #define catch(X) if (false) michael@0: #endif // __EXCEPTIONS michael@0: michael@0: #ifndef USE_PROTECTED_ALLOCATIONS michael@0: #if TARGET_OS_IPHONE michael@0: #define USE_PROTECTED_ALLOCATIONS 1 michael@0: #else michael@0: #define USE_PROTECTED_ALLOCATIONS 0 michael@0: #endif michael@0: #endif michael@0: michael@0: // If USE_PROTECTED_ALLOCATIONS is activated then the michael@0: // gBreakpadAllocator needs to be setup in other code michael@0: // ahead of time. Please see ProtectedMemoryAllocator.h michael@0: // for more details. michael@0: #if USE_PROTECTED_ALLOCATIONS michael@0: #include "protected_memory_allocator.h" michael@0: extern ProtectedMemoryAllocator *gBreakpadAllocator; michael@0: #endif michael@0: michael@0: namespace google_breakpad { michael@0: michael@0: static union { michael@0: #if USE_PROTECTED_ALLOCATIONS michael@0: char protected_buffer[PAGE_SIZE] __attribute__((aligned(PAGE_SIZE))); michael@0: #endif michael@0: google_breakpad::ExceptionHandler *handler; michael@0: } gProtectedData; michael@0: michael@0: using std::map; michael@0: michael@0: // These structures and techniques are illustrated in michael@0: // Mac OS X Internals, Amit Singh, ch 9.7 michael@0: struct ExceptionMessage { michael@0: mach_msg_header_t header; michael@0: mach_msg_body_t body; michael@0: mach_msg_port_descriptor_t thread; michael@0: mach_msg_port_descriptor_t task; michael@0: NDR_record_t ndr; michael@0: exception_type_t exception; michael@0: mach_msg_type_number_t code_count; michael@0: integer_t code[EXCEPTION_CODE_MAX]; michael@0: char padding[512]; michael@0: }; michael@0: michael@0: struct ExceptionParameters { michael@0: ExceptionParameters() : count(0) {} michael@0: mach_msg_type_number_t count; michael@0: exception_mask_t masks[EXC_TYPES_COUNT]; michael@0: mach_port_t ports[EXC_TYPES_COUNT]; michael@0: exception_behavior_t behaviors[EXC_TYPES_COUNT]; michael@0: thread_state_flavor_t flavors[EXC_TYPES_COUNT]; michael@0: }; michael@0: michael@0: struct ExceptionReplyMessage { michael@0: mach_msg_header_t header; michael@0: NDR_record_t ndr; michael@0: kern_return_t return_code; michael@0: }; michael@0: michael@0: // Only catch these three exceptions. The other ones are nebulously defined michael@0: // and may result in treating a non-fatal exception as fatal. michael@0: exception_mask_t s_exception_mask = EXC_MASK_BAD_ACCESS | michael@0: EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT; michael@0: michael@0: #if !TARGET_OS_IPHONE michael@0: extern "C" { michael@0: // Forward declarations for functions that need "C" style compilation michael@0: boolean_t exc_server(mach_msg_header_t* request, michael@0: mach_msg_header_t* reply); michael@0: michael@0: // This symbol must be visible to dlsym() - see michael@0: // http://code.google.com/p/google-breakpad/issues/detail?id=345 for details. michael@0: kern_return_t catch_exception_raise(mach_port_t target_port, michael@0: mach_port_t failed_thread, michael@0: mach_port_t task, michael@0: exception_type_t exception, michael@0: exception_data_t code, michael@0: mach_msg_type_number_t code_count) michael@0: __attribute__((visibility("default"))); michael@0: } michael@0: #endif michael@0: michael@0: kern_return_t ForwardException(mach_port_t task, michael@0: mach_port_t failed_thread, michael@0: exception_type_t exception, michael@0: exception_data_t code, michael@0: mach_msg_type_number_t code_count); michael@0: michael@0: #if TARGET_OS_IPHONE michael@0: // Implementation is based on the implementation generated by mig. michael@0: boolean_t breakpad_exc_server(mach_msg_header_t* InHeadP, michael@0: mach_msg_header_t* OutHeadP) { michael@0: OutHeadP->msgh_bits = michael@0: MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(InHeadP->msgh_bits), 0); michael@0: OutHeadP->msgh_remote_port = InHeadP->msgh_remote_port; michael@0: /* Minimal size: routine() will update it if different */ michael@0: OutHeadP->msgh_size = (mach_msg_size_t)sizeof(mig_reply_error_t); michael@0: OutHeadP->msgh_local_port = MACH_PORT_NULL; michael@0: OutHeadP->msgh_id = InHeadP->msgh_id + 100; michael@0: michael@0: if (InHeadP->msgh_id != 2401) { michael@0: ((mig_reply_error_t*)OutHeadP)->NDR = NDR_record; michael@0: ((mig_reply_error_t*)OutHeadP)->RetCode = MIG_BAD_ID; michael@0: return FALSE; michael@0: } michael@0: michael@0: #ifdef __MigPackStructs michael@0: #pragma pack(4) michael@0: #endif michael@0: typedef struct { michael@0: mach_msg_header_t Head; michael@0: /* start of the kernel processed data */ michael@0: mach_msg_body_t msgh_body; michael@0: mach_msg_port_descriptor_t thread; michael@0: mach_msg_port_descriptor_t task; michael@0: /* end of the kernel processed data */ michael@0: NDR_record_t NDR; michael@0: exception_type_t exception; michael@0: mach_msg_type_number_t codeCnt; michael@0: integer_t code[2]; michael@0: mach_msg_trailer_t trailer; michael@0: } Request; michael@0: michael@0: typedef struct { michael@0: mach_msg_header_t Head; michael@0: NDR_record_t NDR; michael@0: kern_return_t RetCode; michael@0: } Reply; michael@0: #ifdef __MigPackStructs michael@0: #pragma pack() michael@0: #endif michael@0: michael@0: Request* In0P = (Request*)InHeadP; michael@0: Reply* OutP = (Reply*)OutHeadP; michael@0: michael@0: if (In0P->task.name != mach_task_self()) { michael@0: return FALSE; michael@0: } michael@0: OutP->RetCode = ForwardException(In0P->task.name, michael@0: In0P->thread.name, michael@0: In0P->exception, michael@0: In0P->code, michael@0: In0P->codeCnt); michael@0: OutP->NDR = NDR_record; michael@0: return TRUE; michael@0: } michael@0: #else michael@0: boolean_t breakpad_exc_server(mach_msg_header_t* request, michael@0: mach_msg_header_t* reply) { michael@0: return exc_server(request, reply); michael@0: } michael@0: michael@0: // Callback from exc_server() michael@0: kern_return_t catch_exception_raise(mach_port_t port, mach_port_t failed_thread, michael@0: mach_port_t task, michael@0: exception_type_t exception, michael@0: exception_data_t code, michael@0: mach_msg_type_number_t code_count) { michael@0: if (task != mach_task_self()) { michael@0: return KERN_FAILURE; michael@0: } michael@0: return ForwardException(task, failed_thread, exception, code, code_count); michael@0: } michael@0: #endif michael@0: michael@0: ExceptionHandler::ExceptionHandler(const string &dump_path, michael@0: FilterCallback filter, michael@0: MinidumpCallback callback, michael@0: void* callback_context, michael@0: bool install_handler, michael@0: const char* port_name) michael@0: : dump_path_(), michael@0: filter_(filter), michael@0: callback_(callback), michael@0: callback_context_(callback_context), michael@0: directCallback_(NULL), michael@0: handler_thread_(NULL), michael@0: handler_port_(MACH_PORT_NULL), michael@0: previous_(NULL), michael@0: installed_exception_handler_(false), michael@0: is_in_teardown_(false), michael@0: last_minidump_write_result_(false), michael@0: use_minidump_write_mutex_(false) { michael@0: // This will update to the ID and C-string pointers michael@0: set_dump_path(dump_path); michael@0: MinidumpGenerator::GatherSystemInformation(); michael@0: #if !TARGET_OS_IPHONE michael@0: if (port_name) michael@0: crash_generation_client_.reset(new CrashGenerationClient(port_name)); michael@0: #endif michael@0: Setup(install_handler); michael@0: } michael@0: michael@0: // special constructor if we want to bypass minidump writing and michael@0: // simply get a callback with the exception information michael@0: ExceptionHandler::ExceptionHandler(DirectCallback callback, michael@0: void* callback_context, michael@0: bool install_handler) michael@0: : dump_path_(), michael@0: filter_(NULL), michael@0: callback_(NULL), michael@0: callback_context_(callback_context), michael@0: directCallback_(callback), michael@0: handler_thread_(NULL), michael@0: handler_port_(MACH_PORT_NULL), michael@0: previous_(NULL), michael@0: installed_exception_handler_(false), michael@0: is_in_teardown_(false), michael@0: last_minidump_write_result_(false), michael@0: use_minidump_write_mutex_(false) { michael@0: MinidumpGenerator::GatherSystemInformation(); michael@0: Setup(install_handler); michael@0: } michael@0: michael@0: ExceptionHandler::~ExceptionHandler() { michael@0: Teardown(); michael@0: } michael@0: michael@0: bool ExceptionHandler::WriteMinidump(bool write_exception_stream) { michael@0: // If we're currently writing, just return michael@0: if (use_minidump_write_mutex_) michael@0: return false; michael@0: michael@0: use_minidump_write_mutex_ = true; michael@0: last_minidump_write_result_ = false; michael@0: michael@0: // Lock the mutex. Since we just created it, this will return immediately. michael@0: if (pthread_mutex_lock(&minidump_write_mutex_) == 0) { michael@0: // Send an empty message to the handle port so that a minidump will michael@0: // be written michael@0: bool result = SendMessageToHandlerThread(write_exception_stream ? michael@0: kWriteDumpWithExceptionMessage : michael@0: kWriteDumpMessage); michael@0: if (!result) { michael@0: pthread_mutex_unlock(&minidump_write_mutex_); michael@0: return false; michael@0: } michael@0: michael@0: // Wait for the minidump writer to complete its writing. It will unlock michael@0: // the mutex when completed michael@0: pthread_mutex_lock(&minidump_write_mutex_); michael@0: } michael@0: michael@0: use_minidump_write_mutex_ = false; michael@0: UpdateNextID(); michael@0: return last_minidump_write_result_; michael@0: } michael@0: michael@0: // static michael@0: bool ExceptionHandler::WriteMinidump(const string &dump_path, michael@0: bool write_exception_stream, michael@0: MinidumpCallback callback, michael@0: void* callback_context) { michael@0: ExceptionHandler handler(dump_path, NULL, callback, callback_context, false, michael@0: NULL); michael@0: return handler.WriteMinidump(write_exception_stream); michael@0: } michael@0: michael@0: // static michael@0: bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child, michael@0: mach_port_t child_blamed_thread, michael@0: const string &dump_path, michael@0: MinidumpCallback callback, michael@0: void* callback_context) { michael@0: ScopedTaskSuspend suspend(child); michael@0: michael@0: MinidumpGenerator generator(child, MACH_PORT_NULL); michael@0: string dump_id; michael@0: string dump_filename = generator.UniqueNameInDirectory(dump_path, &dump_id); michael@0: michael@0: generator.SetExceptionInformation(EXC_BREAKPOINT, michael@0: #if defined(__i386__) || defined(__x86_64__) michael@0: EXC_I386_BPT, michael@0: #elif defined(__ppc__) || defined(__ppc64__) michael@0: EXC_PPC_BREAKPOINT, michael@0: #elif defined(__arm__) michael@0: EXC_ARM_BREAKPOINT, michael@0: #else michael@0: #error architecture not supported michael@0: #endif michael@0: 0, michael@0: child_blamed_thread); michael@0: bool result = generator.Write(dump_filename.c_str()); michael@0: michael@0: if (callback) { michael@0: return callback(dump_path.c_str(), dump_id.c_str(), michael@0: callback_context, result); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: bool ExceptionHandler::WriteMinidumpWithException(int exception_type, michael@0: int exception_code, michael@0: int exception_subcode, michael@0: ucontext_t* task_context, michael@0: mach_port_t thread_name, michael@0: bool exit_after_write, michael@0: bool report_current_thread) { michael@0: bool result = false; michael@0: michael@0: if (directCallback_) { michael@0: if (directCallback_(callback_context_, michael@0: exception_type, michael@0: exception_code, michael@0: exception_subcode, michael@0: thread_name) ) { michael@0: if (exit_after_write) michael@0: _exit(exception_type); michael@0: } michael@0: #if !TARGET_OS_IPHONE michael@0: } else if (IsOutOfProcess()) { michael@0: if (exception_type && exception_code) { michael@0: // If this is a real exception, give the filter (if any) a chance to michael@0: // decide if this should be sent. michael@0: if (filter_ && !filter_(callback_context_)) michael@0: return false; michael@0: result = crash_generation_client_->RequestDumpForException( michael@0: exception_type, michael@0: exception_code, michael@0: exception_subcode, michael@0: thread_name); michael@0: if (result && exit_after_write) { michael@0: _exit(exception_type); michael@0: } michael@0: } michael@0: #endif michael@0: } else { michael@0: string minidump_id; michael@0: michael@0: // Putting the MinidumpGenerator in its own context will ensure that the michael@0: // destructor is executed, closing the newly created minidump file. michael@0: if (!dump_path_.empty()) { michael@0: MinidumpGenerator md(mach_task_self(), michael@0: report_current_thread ? MACH_PORT_NULL : michael@0: mach_thread_self()); michael@0: md.SetTaskContext(task_context); michael@0: if (exception_type && exception_code) { michael@0: // If this is a real exception, give the filter (if any) a chance to michael@0: // decide if this should be sent. michael@0: if (filter_ && !filter_(callback_context_)) michael@0: return false; michael@0: michael@0: md.SetExceptionInformation(exception_type, exception_code, michael@0: exception_subcode, thread_name); michael@0: } michael@0: michael@0: result = md.Write(next_minidump_path_c_); michael@0: } michael@0: michael@0: // Call user specified callback (if any) michael@0: if (callback_) { michael@0: // If the user callback returned true and we're handling an exception michael@0: // (rather than just writing out the file), then we should exit without michael@0: // forwarding the exception to the next handler. michael@0: if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_, michael@0: result)) { michael@0: if (exit_after_write) michael@0: _exit(exception_type); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: kern_return_t ForwardException(mach_port_t task, mach_port_t failed_thread, michael@0: exception_type_t exception, michael@0: exception_data_t code, michael@0: mach_msg_type_number_t code_count) { michael@0: // At this time, we should have called Uninstall() on the exception handler michael@0: // so that the current exception ports are the ones that we should be michael@0: // forwarding to. michael@0: ExceptionParameters current; michael@0: michael@0: current.count = EXC_TYPES_COUNT; michael@0: mach_port_t current_task = mach_task_self(); michael@0: task_get_exception_ports(current_task, michael@0: s_exception_mask, michael@0: current.masks, michael@0: ¤t.count, michael@0: current.ports, michael@0: current.behaviors, michael@0: current.flavors); michael@0: michael@0: // Find the first exception handler that matches the exception michael@0: unsigned int found; michael@0: for (found = 0; found < current.count; ++found) { michael@0: if (current.masks[found] & (1 << exception)) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Nothing to forward michael@0: if (found == current.count) { michael@0: fprintf(stderr, "** No previous ports for forwarding!! \n"); michael@0: exit(KERN_FAILURE); michael@0: } michael@0: michael@0: mach_port_t target_port = current.ports[found]; michael@0: exception_behavior_t target_behavior = current.behaviors[found]; michael@0: michael@0: kern_return_t result; michael@0: switch (target_behavior) { michael@0: case EXCEPTION_DEFAULT: michael@0: result = exception_raise(target_port, failed_thread, task, exception, michael@0: code, code_count); michael@0: break; michael@0: michael@0: default: michael@0: fprintf(stderr, "** Unknown exception behavior: %d\n", target_behavior); michael@0: result = KERN_FAILURE; michael@0: break; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: // static michael@0: void* ExceptionHandler::WaitForMessage(void* exception_handler_class) { michael@0: ExceptionHandler* self = michael@0: reinterpret_cast(exception_handler_class); michael@0: ExceptionMessage receive; michael@0: michael@0: // Wait for the exception info michael@0: while (1) { michael@0: receive.header.msgh_local_port = self->handler_port_; michael@0: receive.header.msgh_size = static_cast(sizeof(receive)); michael@0: kern_return_t result = mach_msg(&(receive.header), michael@0: MACH_RCV_MSG | MACH_RCV_LARGE, 0, michael@0: receive.header.msgh_size, michael@0: self->handler_port_, michael@0: MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); michael@0: michael@0: michael@0: if (result == KERN_SUCCESS) { michael@0: // Uninstall our handler so that we don't get in a loop if the process of michael@0: // writing out a minidump causes an exception. However, if the exception michael@0: // was caused by a fork'd process, don't uninstall things michael@0: michael@0: // If the actual exception code is zero, then we're calling this handler michael@0: // in a way that indicates that we want to either exit this thread or michael@0: // generate a minidump michael@0: // michael@0: // While reporting, all threads (except this one) must be suspended michael@0: // to avoid misleading stacks. If appropriate they will be resumed michael@0: // afterwards. michael@0: if (!receive.exception) { michael@0: // Don't touch self, since this message could have been sent michael@0: // from its destructor. michael@0: if (receive.header.msgh_id == kShutdownMessage) michael@0: return NULL; michael@0: michael@0: self->SuspendThreads(); michael@0: michael@0: #if USE_PROTECTED_ALLOCATIONS michael@0: if (gBreakpadAllocator) michael@0: gBreakpadAllocator->Unprotect(); michael@0: #endif michael@0: michael@0: mach_port_t thread = MACH_PORT_NULL; michael@0: int exception_type = 0; michael@0: int exception_code = 0; michael@0: if (receive.header.msgh_id == kWriteDumpWithExceptionMessage) { michael@0: thread = receive.thread.name; michael@0: exception_type = EXC_BREAKPOINT; michael@0: #if defined(__i386__) || defined(__x86_64__) michael@0: exception_code = EXC_I386_BPT; michael@0: #elif defined(__ppc__) || defined(__ppc64__) michael@0: exception_code = EXC_PPC_BREAKPOINT; michael@0: #elif defined(__arm__) michael@0: exception_code = EXC_ARM_BREAKPOINT; michael@0: #else michael@0: #error architecture not supported michael@0: #endif michael@0: } michael@0: michael@0: // Write out the dump and save the result for later retrieval michael@0: self->last_minidump_write_result_ = michael@0: self->WriteMinidumpWithException(exception_type, exception_code, michael@0: 0, NULL, thread, michael@0: false, false); michael@0: michael@0: #if USE_PROTECTED_ALLOCATIONS michael@0: if (gBreakpadAllocator) michael@0: gBreakpadAllocator->Protect(); michael@0: #endif michael@0: michael@0: self->ResumeThreads(); michael@0: michael@0: if (self->use_minidump_write_mutex_) michael@0: pthread_mutex_unlock(&self->minidump_write_mutex_); michael@0: } else { michael@0: // When forking a child process with the exception handler installed, michael@0: // if the child crashes, it will send the exception back to the parent michael@0: // process. The check for task == self_task() ensures that only michael@0: // exceptions that occur in the parent process are caught and michael@0: // processed. If the exception was not caused by this task, we michael@0: // still need to call into the exception server and have it return michael@0: // KERN_FAILURE (see catch_exception_raise) in order for the kernel michael@0: // to move onto the host exception handler for the child task michael@0: if (receive.task.name == mach_task_self()) { michael@0: self->SuspendThreads(); michael@0: michael@0: #if USE_PROTECTED_ALLOCATIONS michael@0: if (gBreakpadAllocator) michael@0: gBreakpadAllocator->Unprotect(); michael@0: #endif michael@0: michael@0: int subcode = 0; michael@0: if (receive.exception == EXC_BAD_ACCESS && receive.code_count > 1) michael@0: subcode = receive.code[1]; michael@0: michael@0: // Generate the minidump with the exception data. michael@0: self->WriteMinidumpWithException(receive.exception, receive.code[0], michael@0: subcode, NULL, receive.thread.name, michael@0: true, false); michael@0: michael@0: #if USE_PROTECTED_ALLOCATIONS michael@0: // This may have become protected again within michael@0: // WriteMinidumpWithException, but it needs to be unprotected for michael@0: // UninstallHandler. michael@0: if (gBreakpadAllocator) michael@0: gBreakpadAllocator->Unprotect(); michael@0: #endif michael@0: michael@0: self->UninstallHandler(true); michael@0: michael@0: #if USE_PROTECTED_ALLOCATIONS michael@0: if (gBreakpadAllocator) michael@0: gBreakpadAllocator->Protect(); michael@0: #endif michael@0: } michael@0: // Pass along the exception to the server, which will setup the michael@0: // message and call catch_exception_raise() and put the return michael@0: // code into the reply. michael@0: ExceptionReplyMessage reply; michael@0: if (!breakpad_exc_server(&receive.header, &reply.header)) michael@0: exit(1); michael@0: michael@0: // Send a reply and exit michael@0: mach_msg(&(reply.header), MACH_SEND_MSG, michael@0: reply.header.msgh_size, 0, MACH_PORT_NULL, michael@0: MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NULL; michael@0: } michael@0: michael@0: // static michael@0: void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) { michael@0: #if USE_PROTECTED_ALLOCATIONS michael@0: if (gBreakpadAllocator) michael@0: gBreakpadAllocator->Unprotect(); michael@0: #endif michael@0: gProtectedData.handler->WriteMinidumpWithException( michael@0: EXC_SOFTWARE, michael@0: MD_EXCEPTION_CODE_MAC_ABORT, michael@0: 0, michael@0: static_cast(uc), michael@0: mach_thread_self(), michael@0: true, michael@0: true); michael@0: #if USE_PROTECTED_ALLOCATIONS michael@0: if (gBreakpadAllocator) michael@0: gBreakpadAllocator->Protect(); michael@0: #endif michael@0: } michael@0: michael@0: bool ExceptionHandler::InstallHandler() { michael@0: // If a handler is already installed, something is really wrong. michael@0: if (gProtectedData.handler != NULL) { michael@0: return false; michael@0: } michael@0: if (!IsOutOfProcess()) { michael@0: struct sigaction sa; michael@0: memset(&sa, 0, sizeof(sa)); michael@0: sigemptyset(&sa.sa_mask); michael@0: sigaddset(&sa.sa_mask, SIGABRT); michael@0: sa.sa_sigaction = ExceptionHandler::SignalHandler; michael@0: sa.sa_flags = SA_SIGINFO; michael@0: michael@0: scoped_ptr old(new struct sigaction); michael@0: if (sigaction(SIGABRT, &sa, old.get()) == -1) { michael@0: return false; michael@0: } michael@0: old_handler_.swap(old); michael@0: gProtectedData.handler = this; michael@0: #if USE_PROTECTED_ALLOCATIONS michael@0: assert(((size_t)(gProtectedData.protected_buffer) & PAGE_MASK) == 0); michael@0: mprotect(gProtectedData.protected_buffer, PAGE_SIZE, PROT_READ); michael@0: #endif michael@0: } michael@0: michael@0: try { michael@0: #if USE_PROTECTED_ALLOCATIONS michael@0: previous_ = new (gBreakpadAllocator->Allocate(sizeof(ExceptionParameters)) ) michael@0: ExceptionParameters(); michael@0: #else michael@0: previous_ = new ExceptionParameters(); michael@0: #endif michael@0: } michael@0: catch (std::bad_alloc) { michael@0: return false; michael@0: } michael@0: michael@0: // Save the current exception ports so that we can forward to them michael@0: previous_->count = EXC_TYPES_COUNT; michael@0: mach_port_t current_task = mach_task_self(); michael@0: kern_return_t result = task_get_exception_ports(current_task, michael@0: s_exception_mask, michael@0: previous_->masks, michael@0: &previous_->count, michael@0: previous_->ports, michael@0: previous_->behaviors, michael@0: previous_->flavors); michael@0: michael@0: // Setup the exception ports on this task michael@0: if (result == KERN_SUCCESS) michael@0: result = task_set_exception_ports(current_task, s_exception_mask, michael@0: handler_port_, EXCEPTION_DEFAULT, michael@0: THREAD_STATE_NONE); michael@0: michael@0: installed_exception_handler_ = (result == KERN_SUCCESS); michael@0: michael@0: return installed_exception_handler_; michael@0: } michael@0: michael@0: bool ExceptionHandler::UninstallHandler(bool in_exception) { michael@0: kern_return_t result = KERN_SUCCESS; michael@0: michael@0: if (old_handler_.get()) { michael@0: sigaction(SIGABRT, old_handler_.get(), NULL); michael@0: #if USE_PROTECTED_ALLOCATIONS michael@0: mprotect(gProtectedData.protected_buffer, PAGE_SIZE, michael@0: PROT_READ | PROT_WRITE); michael@0: #endif michael@0: old_handler_.reset(); michael@0: gProtectedData.handler = NULL; michael@0: } michael@0: michael@0: if (installed_exception_handler_) { michael@0: mach_port_t current_task = mach_task_self(); michael@0: michael@0: // Restore the previous ports michael@0: for (unsigned int i = 0; i < previous_->count; ++i) { michael@0: result = task_set_exception_ports(current_task, previous_->masks[i], michael@0: previous_->ports[i], michael@0: previous_->behaviors[i], michael@0: previous_->flavors[i]); michael@0: if (result != KERN_SUCCESS) michael@0: return false; michael@0: } michael@0: michael@0: // this delete should NOT happen if an exception just occurred! michael@0: if (!in_exception) { michael@0: #if USE_PROTECTED_ALLOCATIONS michael@0: previous_->~ExceptionParameters(); michael@0: #else michael@0: delete previous_; michael@0: #endif michael@0: } michael@0: michael@0: previous_ = NULL; michael@0: installed_exception_handler_ = false; michael@0: } michael@0: michael@0: return result == KERN_SUCCESS; michael@0: } michael@0: michael@0: bool ExceptionHandler::Setup(bool install_handler) { michael@0: if (pthread_mutex_init(&minidump_write_mutex_, NULL)) michael@0: return false; michael@0: michael@0: // Create a receive right michael@0: mach_port_t current_task = mach_task_self(); michael@0: kern_return_t result = mach_port_allocate(current_task, michael@0: MACH_PORT_RIGHT_RECEIVE, michael@0: &handler_port_); michael@0: // Add send right michael@0: if (result == KERN_SUCCESS) michael@0: result = mach_port_insert_right(current_task, handler_port_, handler_port_, michael@0: MACH_MSG_TYPE_MAKE_SEND); michael@0: michael@0: if (install_handler && result == KERN_SUCCESS) michael@0: if (!InstallHandler()) michael@0: return false; michael@0: michael@0: if (result == KERN_SUCCESS) { michael@0: // Install the handler in its own thread, detached as we won't be joining. michael@0: pthread_attr_t attr; michael@0: pthread_attr_init(&attr); michael@0: pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); michael@0: int thread_create_result = pthread_create(&handler_thread_, &attr, michael@0: &WaitForMessage, this); michael@0: pthread_attr_destroy(&attr); michael@0: result = thread_create_result ? KERN_FAILURE : KERN_SUCCESS; michael@0: } michael@0: michael@0: return result == KERN_SUCCESS; michael@0: } michael@0: michael@0: bool ExceptionHandler::Teardown() { michael@0: kern_return_t result = KERN_SUCCESS; michael@0: is_in_teardown_ = true; michael@0: michael@0: if (!UninstallHandler(false)) michael@0: return false; michael@0: michael@0: // Send an empty message so that the handler_thread exits michael@0: if (SendMessageToHandlerThread(kShutdownMessage)) { michael@0: mach_port_t current_task = mach_task_self(); michael@0: result = mach_port_deallocate(current_task, handler_port_); michael@0: if (result != KERN_SUCCESS) michael@0: return false; michael@0: } else { michael@0: return false; michael@0: } michael@0: michael@0: handler_thread_ = NULL; michael@0: handler_port_ = MACH_PORT_NULL; michael@0: pthread_mutex_destroy(&minidump_write_mutex_); michael@0: michael@0: return result == KERN_SUCCESS; michael@0: } michael@0: michael@0: bool ExceptionHandler::SendMessageToHandlerThread( michael@0: HandlerThreadMessage message_id) { michael@0: ExceptionMessage msg; michael@0: memset(&msg, 0, sizeof(msg)); michael@0: msg.header.msgh_id = message_id; michael@0: if (message_id == kWriteDumpMessage || michael@0: message_id == kWriteDumpWithExceptionMessage) { michael@0: // Include this thread's port. michael@0: msg.thread.name = mach_thread_self(); michael@0: msg.thread.disposition = MACH_MSG_TYPE_PORT_SEND; michael@0: msg.thread.type = MACH_MSG_PORT_DESCRIPTOR; michael@0: } michael@0: msg.header.msgh_size = sizeof(msg) - sizeof(msg.padding); michael@0: msg.header.msgh_remote_port = handler_port_; michael@0: msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, michael@0: MACH_MSG_TYPE_MAKE_SEND_ONCE); michael@0: kern_return_t result = mach_msg(&(msg.header), michael@0: MACH_SEND_MSG | MACH_SEND_TIMEOUT, michael@0: msg.header.msgh_size, 0, 0, michael@0: MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); michael@0: michael@0: return result == KERN_SUCCESS; michael@0: } michael@0: michael@0: void ExceptionHandler::UpdateNextID() { michael@0: next_minidump_path_ = michael@0: (MinidumpGenerator::UniqueNameInDirectory(dump_path_, &next_minidump_id_)); michael@0: michael@0: next_minidump_path_c_ = next_minidump_path_.c_str(); michael@0: next_minidump_id_c_ = next_minidump_id_.c_str(); michael@0: } michael@0: michael@0: bool ExceptionHandler::SuspendThreads() { michael@0: thread_act_port_array_t threads_for_task; michael@0: mach_msg_type_number_t thread_count; michael@0: michael@0: if (task_threads(mach_task_self(), &threads_for_task, &thread_count)) michael@0: return false; michael@0: michael@0: // suspend all of the threads except for this one michael@0: for (unsigned int i = 0; i < thread_count; ++i) { michael@0: if (threads_for_task[i] != mach_thread_self()) { michael@0: if (thread_suspend(threads_for_task[i])) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool ExceptionHandler::ResumeThreads() { michael@0: thread_act_port_array_t threads_for_task; michael@0: mach_msg_type_number_t thread_count; michael@0: michael@0: if (task_threads(mach_task_self(), &threads_for_task, &thread_count)) michael@0: return false; michael@0: michael@0: // resume all of the threads except for this one michael@0: for (unsigned int i = 0; i < thread_count; ++i) { michael@0: if (threads_for_task[i] != mach_thread_self()) { michael@0: if (thread_resume(threads_for_task[i])) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: } // namespace google_breakpad