814 lines
30 KiB
C++
814 lines
30 KiB
C++
// 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 <map>
|
|
#include <pthread.h>
|
|
|
|
#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"
|
|
|
|
#ifndef USE_PROTECTED_ALLOCATIONS
|
|
#define USE_PROTECTED_ALLOCATIONS 0
|
|
#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 {
|
|
|
|
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;
|
|
|
|
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
|
|
// http://code.google.com/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")));
|
|
|
|
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);
|
|
|
|
kern_return_t exception_raise(mach_port_t target_port,
|
|
mach_port_t failed_thread,
|
|
mach_port_t task,
|
|
exception_type_t exception,
|
|
exception_data_t exception_code,
|
|
mach_msg_type_number_t exception_code_count);
|
|
|
|
kern_return_t
|
|
exception_raise_state(mach_port_t target_port,
|
|
mach_port_t failed_thread,
|
|
mach_port_t task,
|
|
exception_type_t exception,
|
|
exception_data_t exception_code,
|
|
mach_msg_type_number_t code_count,
|
|
thread_state_flavor_t *target_flavor,
|
|
thread_state_t in_thread_state,
|
|
mach_msg_type_number_t in_thread_state_count,
|
|
thread_state_t out_thread_state,
|
|
mach_msg_type_number_t *out_thread_state_count);
|
|
|
|
kern_return_t
|
|
exception_raise_state_identity(mach_port_t target_port,
|
|
mach_port_t failed_thread,
|
|
mach_port_t task,
|
|
exception_type_t exception,
|
|
exception_data_t exception_code,
|
|
mach_msg_type_number_t exception_code_count,
|
|
thread_state_flavor_t *target_flavor,
|
|
thread_state_t in_thread_state,
|
|
mach_msg_type_number_t in_thread_state_count,
|
|
thread_state_t out_thread_state,
|
|
mach_msg_type_number_t *out_thread_state_count);
|
|
|
|
kern_return_t breakpad_exception_raise_state(mach_port_t exception_port,
|
|
exception_type_t exception,
|
|
const exception_data_t code,
|
|
mach_msg_type_number_t codeCnt,
|
|
int *flavor,
|
|
const thread_state_t old_state,
|
|
mach_msg_type_number_t old_stateCnt,
|
|
thread_state_t new_state,
|
|
mach_msg_type_number_t *new_stateCnt
|
|
);
|
|
|
|
kern_return_t breakpad_exception_raise_state_identity(mach_port_t exception_port,
|
|
mach_port_t thread,
|
|
mach_port_t task,
|
|
exception_type_t exception,
|
|
exception_data_t code,
|
|
mach_msg_type_number_t codeCnt,
|
|
int *flavor,
|
|
thread_state_t old_state,
|
|
mach_msg_type_number_t old_stateCnt,
|
|
thread_state_t new_state,
|
|
mach_msg_type_number_t *new_stateCnt
|
|
);
|
|
|
|
kern_return_t breakpad_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);
|
|
}
|
|
|
|
|
|
|
|
kern_return_t breakpad_exception_raise_state(mach_port_t exception_port,
|
|
exception_type_t exception,
|
|
const exception_data_t code,
|
|
mach_msg_type_number_t codeCnt,
|
|
int *flavor,
|
|
const thread_state_t old_state,
|
|
mach_msg_type_number_t old_stateCnt,
|
|
thread_state_t new_state,
|
|
mach_msg_type_number_t *new_stateCnt
|
|
)
|
|
{
|
|
return KERN_SUCCESS;
|
|
}
|
|
|
|
kern_return_t breakpad_exception_raise_state_identity(mach_port_t exception_port,
|
|
mach_port_t thread,
|
|
mach_port_t task,
|
|
exception_type_t exception,
|
|
exception_data_t code,
|
|
mach_msg_type_number_t codeCnt,
|
|
int *flavor,
|
|
thread_state_t old_state,
|
|
mach_msg_type_number_t old_stateCnt,
|
|
thread_state_t new_state,
|
|
mach_msg_type_number_t *new_stateCnt
|
|
)
|
|
{
|
|
return KERN_SUCCESS;
|
|
}
|
|
|
|
kern_return_t breakpad_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);
|
|
}
|
|
|
|
|
|
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 (port_name)
|
|
crash_generation_client_.reset(new CrashGenerationClient(port_name));
|
|
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
|
|
SendMessageToHandlerThread(write_exception_stream ?
|
|
kWriteDumpWithExceptionMessage :
|
|
kWriteDumpMessage);
|
|
|
|
// 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,
|
|
#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,
|
|
mach_port_t thread_name,
|
|
bool exit_after_write) {
|
|
bool result = false;
|
|
|
|
if (directCallback_) {
|
|
if (directCallback_(callback_context_,
|
|
exception_type,
|
|
exception_code,
|
|
exception_subcode,
|
|
thread_name) ) {
|
|
if (exit_after_write)
|
|
_exit(exception_type);
|
|
}
|
|
} 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;
|
|
return crash_generation_client_->RequestDumpForException(
|
|
exception_type,
|
|
exception_code,
|
|
exception_subcode,
|
|
thread_name);
|
|
}
|
|
} 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;
|
|
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();
|
|
kern_return_t result = 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];
|
|
thread_state_flavor_t target_flavor = current.flavors[found];
|
|
|
|
mach_msg_type_number_t thread_state_count = THREAD_STATE_MAX;
|
|
breakpad_thread_state_data_t thread_state;
|
|
switch (target_behavior) {
|
|
case EXCEPTION_DEFAULT:
|
|
result = exception_raise(target_port, failed_thread, task, exception,
|
|
code, code_count);
|
|
break;
|
|
|
|
case EXCEPTION_STATE:
|
|
result = thread_get_state(failed_thread, target_flavor, thread_state,
|
|
&thread_state_count);
|
|
if (result == KERN_SUCCESS)
|
|
result = exception_raise_state(target_port, failed_thread, task,
|
|
exception, code,
|
|
code_count, &target_flavor,
|
|
thread_state, thread_state_count,
|
|
thread_state, &thread_state_count);
|
|
if (result == KERN_SUCCESS)
|
|
result = thread_set_state(failed_thread, target_flavor, thread_state,
|
|
thread_state_count);
|
|
break;
|
|
|
|
case EXCEPTION_STATE_IDENTITY:
|
|
result = thread_get_state(failed_thread, target_flavor, thread_state,
|
|
&thread_state_count);
|
|
if (result == KERN_SUCCESS)
|
|
result = exception_raise_state_identity(target_port, failed_thread,
|
|
task, exception, code,
|
|
code_count, &target_flavor,
|
|
thread_state,
|
|
thread_state_count,
|
|
thread_state,
|
|
&thread_state_count);
|
|
if (result == KERN_SUCCESS)
|
|
result = thread_set_state(failed_thread, target_flavor, thread_state,
|
|
thread_state_count);
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "** Unknown exception behavior\n");
|
|
result = KERN_FAILURE;
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// 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;
|
|
#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, thread,
|
|
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 breakpad_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, receive.thread.name, true);
|
|
|
|
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 breakpad_exception_raise() and put the return
|
|
// code into the reply.
|
|
ExceptionReplyMessage reply;
|
|
if (!exc_server(&receive.header, &reply.header))
|
|
exit(1);
|
|
|
|
// Send a reply and exit
|
|
result = mach_msg(&(reply.header), MACH_SEND_MSG,
|
|
reply.header.msgh_size, 0, MACH_PORT_NULL,
|
|
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool ExceptionHandler::InstallHandler() {
|
|
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 (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 ? true : false;
|
|
}
|
|
|
|
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_ = 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
|