Add Mac exception handler and generator. Fixes issue #69. Reviewed by mmentovai.
git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@98 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
c4d599912c
commit
5ac2b9a569
497
src/client/mac/handler/exception_handler.cc
Normal file
497
src/client/mac/handler/exception_handler.cc
Normal file
@ -0,0 +1,497 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
namespace google_airbag {
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Each thread needs to keep track of its ExceptionParameters. Since the time
|
||||||
|
// that they are needed is when calling through exc_server(), we have no way
|
||||||
|
// of retrieving the values from the class. Therefore, we'll create a map
|
||||||
|
// that will allows storage per thread.
|
||||||
|
static map<pthread_t, ExceptionParameters *> *s_exception_parameter_map = NULL;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 thread_state,
|
||||||
|
mach_msg_type_number_t thread_state_count,
|
||||||
|
thread_state_t thread_state,
|
||||||
|
mach_msg_type_number_t *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 thread_state,
|
||||||
|
mach_msg_type_number_t thread_state_count,
|
||||||
|
thread_state_t thread_state,
|
||||||
|
mach_msg_type_number_t *thread_state_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExceptionHandler::ExceptionHandler(const string &dump_path,
|
||||||
|
FilterCallback filter,
|
||||||
|
MinidumpCallback callback,
|
||||||
|
void *callback_context,
|
||||||
|
bool install_handler)
|
||||||
|
: dump_path_(),
|
||||||
|
filter_(filter),
|
||||||
|
callback_(callback),
|
||||||
|
callback_context_(callback_context),
|
||||||
|
handler_thread_(NULL),
|
||||||
|
handler_port_(0),
|
||||||
|
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();
|
||||||
|
Setup(install_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExceptionHandler::~ExceptionHandler() {
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExceptionHandler::WriteMinidump() {
|
||||||
|
// 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
|
||||||
|
SendEmptyMachMessage();
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
MinidumpCallback callback,
|
||||||
|
void *callback_context) {
|
||||||
|
ExceptionHandler handler(dump_path, NULL, callback, callback_context, false);
|
||||||
|
return handler.WriteMinidump();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExceptionHandler::WriteMinidumpWithException(int exception_type,
|
||||||
|
int exception_code,
|
||||||
|
mach_port_t thread_name) {
|
||||||
|
bool result = false;
|
||||||
|
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
|
||||||
|
// decided if this should be sent
|
||||||
|
if (filter_ && !filter_(callback_context_))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
md.SetExceptionInformation(exception_type, exception_code, 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 (exception_type && exception_code)
|
||||||
|
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) {
|
||||||
|
ExceptionParameters *previous = (*s_exception_parameter_map)[pthread_self()];
|
||||||
|
|
||||||
|
// If we don't have the previous data, we need to just exit
|
||||||
|
if (!previous)
|
||||||
|
exit(exception);
|
||||||
|
|
||||||
|
// Find the first exception handler that matches the exception
|
||||||
|
unsigned int found;
|
||||||
|
for (found = 0; found < previous->count; ++found) {
|
||||||
|
if (previous->masks[found] & (1 << exception)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing to forward
|
||||||
|
if (found == previous->count) {
|
||||||
|
fprintf(stderr, "** No previous ports for forwarding!! \n");
|
||||||
|
exit(KERN_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
mach_port_t target_port = previous->ports[found];
|
||||||
|
exception_behavior_t target_behavior = previous->behaviors[found];
|
||||||
|
thread_state_flavor_t target_flavor = previous->flavors[found];
|
||||||
|
kern_return_t result;
|
||||||
|
|
||||||
|
mach_msg_type_number_t thread_state_count = THREAD_STATE_MAX;
|
||||||
|
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:
|
||||||
|
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) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Save a pointer to our instance so that it will be available in the
|
||||||
|
// routines that are called from exc_server();
|
||||||
|
if (!s_exception_parameter_map)
|
||||||
|
s_exception_parameter_map = new map<pthread_t, ExceptionParameters *>;
|
||||||
|
|
||||||
|
(*s_exception_parameter_map)[pthread_self()] = self->previous_;
|
||||||
|
|
||||||
|
// Wait for the exception info
|
||||||
|
while (1) {
|
||||||
|
receive.header.msgh_local_port = self->handler_port_;
|
||||||
|
receive.header.msgh_size = sizeof(receive);
|
||||||
|
kern_return_t result = mach_msg(&(receive.header),
|
||||||
|
MACH_RCV_MSG | MACH_RCV_LARGE, 0,
|
||||||
|
sizeof(receive), 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.
|
||||||
|
self->UninstallHandler();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
if (!receive.exception) {
|
||||||
|
if (self->is_in_teardown_)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
// Write out the dump and save the result for later retrieval
|
||||||
|
self->last_minidump_write_result_ =
|
||||||
|
self->WriteMinidumpWithException(0, 0, 0);
|
||||||
|
if (self->use_minidump_write_mutex_)
|
||||||
|
pthread_mutex_unlock(&self->minidump_write_mutex_);
|
||||||
|
} else {
|
||||||
|
// Generate the minidump with the exception data.
|
||||||
|
self->WriteMinidumpWithException(receive.exception, receive.code[0],
|
||||||
|
receive.thread.name);
|
||||||
|
|
||||||
|
// Pass along the exception to the server, which will setup the message
|
||||||
|
// and call catch_exception_raise() and put the KERN_SUCCESS 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() {
|
||||||
|
// Get the actual previous data
|
||||||
|
exception_mask_t exception_mask = EXC_MASK_ALL &
|
||||||
|
~(EXC_MASK_BREAKPOINT | EXC_MASK_MACH_SYSCALL |
|
||||||
|
EXC_MASK_SYSCALL | EXC_MASK_RPC_ALERT);
|
||||||
|
|
||||||
|
previous_ = new ExceptionParameters();
|
||||||
|
previous_->count = EXC_TYPES_COUNT;
|
||||||
|
mach_port_t current_task = mach_task_self();
|
||||||
|
kern_return_t result = task_get_exception_ports(current_task, 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, exception_mask,
|
||||||
|
handler_port_, EXCEPTION_DEFAULT,
|
||||||
|
THREAD_STATE_NONE);
|
||||||
|
|
||||||
|
installed_exception_handler_ = (result == KERN_SUCCESS);
|
||||||
|
|
||||||
|
return installed_exception_handler_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExceptionHandler::UninstallHandler() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete previous_;
|
||||||
|
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
|
||||||
|
if (pthread_create(&handler_thread_, NULL, &WaitForMessage, this) == 0) {
|
||||||
|
pthread_detach(handler_thread_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result == KERN_SUCCESS ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExceptionHandler::Teardown() {
|
||||||
|
kern_return_t result = KERN_SUCCESS;
|
||||||
|
is_in_teardown_ = true;
|
||||||
|
|
||||||
|
if (!UninstallHandler())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Send an empty message so that the handler_thread exits
|
||||||
|
if (SendEmptyMachMessage()) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (s_exception_parameter_map)
|
||||||
|
s_exception_parameter_map->erase(handler_thread_);
|
||||||
|
|
||||||
|
handler_thread_ = NULL;
|
||||||
|
handler_port_ = NULL;
|
||||||
|
pthread_mutex_destroy(&minidump_write_mutex_);
|
||||||
|
|
||||||
|
return result == KERN_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExceptionHandler::SendEmptyMachMessage() {
|
||||||
|
ExceptionMessage empty;
|
||||||
|
memset(&empty, 0, sizeof(empty));
|
||||||
|
empty.header.msgh_size = sizeof(empty) - sizeof(empty.padding);
|
||||||
|
empty.header.msgh_remote_port = handler_port_;
|
||||||
|
empty.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,
|
||||||
|
MACH_MSG_TYPE_MAKE_SEND_ONCE);
|
||||||
|
kern_return_t result = mach_msg(&(empty.header),
|
||||||
|
MACH_SEND_MSG | MACH_SEND_TIMEOUT,
|
||||||
|
empty.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace google_airbag
|
188
src/client/mac/handler/exception_handler.h
Normal file
188
src/client/mac/handler/exception_handler.h
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// exception_handler.h: MacOS exception handler
|
||||||
|
// This class can install a Mach exception port handler to trap most common
|
||||||
|
// programming errors. If an exception occurs, a minidump file will be
|
||||||
|
// generated which contains detailed information about the process and the
|
||||||
|
// exception.
|
||||||
|
|
||||||
|
#ifndef CLIENT_MAC_HANDLER_EXCEPTION_HANDLER_H__
|
||||||
|
#define CLIENT_MAC_HANDLER_EXCEPTION_HANDLER_H__
|
||||||
|
|
||||||
|
#include <mach/mach.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace google_airbag {
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
struct ExceptionParameters;
|
||||||
|
|
||||||
|
class ExceptionHandler {
|
||||||
|
public:
|
||||||
|
// A callback function to run before Airbag 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, Airbag will continue processing,
|
||||||
|
// attempting to write a minidump. If a FilterCallback returns false, Airbag
|
||||||
|
// 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 airbag 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);
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
ExceptionHandler(const string &dump_path,
|
||||||
|
FilterCallback filter, MinidumpCallback 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_.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes a minidump immediately. This can be used to capture the
|
||||||
|
// execution state independently of a crash. Returns true on success.
|
||||||
|
bool WriteMinidump();
|
||||||
|
|
||||||
|
// Convenience form of WriteMinidump which does not require an
|
||||||
|
// ExceptionHandler instance.
|
||||||
|
static bool WriteMinidump(const string &dump_path, MinidumpCallback callback,
|
||||||
|
void *callback_context);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Install the mach exception handler
|
||||||
|
bool InstallHandler();
|
||||||
|
|
||||||
|
// Uninstall the mach exception handler (if any)
|
||||||
|
bool UninstallHandler();
|
||||||
|
|
||||||
|
// Setup the handler thread, and if |install_handler| is true, install the
|
||||||
|
// mach exception port handler
|
||||||
|
bool Setup(bool install_handler);
|
||||||
|
|
||||||
|
// Uninstall the mach exception handler (if any) and terminate the helper
|
||||||
|
// thread
|
||||||
|
bool Teardown();
|
||||||
|
|
||||||
|
// Send an "empty" mach message to the exception handler. Return true on
|
||||||
|
// success, false otherwise
|
||||||
|
bool SendEmptyMachMessage();
|
||||||
|
|
||||||
|
// All minidump writing goes through this one routine
|
||||||
|
bool WriteMinidumpWithException(int exception_type, int exception_code,
|
||||||
|
mach_port_t thread_name);
|
||||||
|
|
||||||
|
// When installed, this static function will be call from a newly created
|
||||||
|
// pthread with |this| as the argument
|
||||||
|
static void *WaitForMessage(void *exception_handler_class);
|
||||||
|
|
||||||
|
// 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 thread that is created for the handler
|
||||||
|
pthread_t handler_thread_;
|
||||||
|
|
||||||
|
// The port that is waiting on an exception message to be sent, if the
|
||||||
|
// handler is installed
|
||||||
|
mach_port_t handler_port_;
|
||||||
|
|
||||||
|
// These variables save the previous exception handler's data so that it
|
||||||
|
// can be re-installed when this handler is uninstalled
|
||||||
|
ExceptionParameters *previous_;
|
||||||
|
|
||||||
|
// 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_;
|
||||||
|
|
||||||
|
// Save the last result of the last minidump
|
||||||
|
bool last_minidump_write_result_;
|
||||||
|
|
||||||
|
// A mutex for use when writing out a minidump that was requested on a
|
||||||
|
// thread other than the exception handler.
|
||||||
|
pthread_mutex_t minidump_write_mutex_;
|
||||||
|
|
||||||
|
// True, if we're using the mutext to indicate when mindump writing occurs
|
||||||
|
bool use_minidump_write_mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace google_airbag
|
||||||
|
|
||||||
|
#endif // CLIENT_MAC_HANDLER_EXCEPTION_HANDLER_H__
|
107
src/client/mac/handler/exception_handler_test.cc
Normal file
107
src/client/mac/handler/exception_handler_test.cc
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
g++ -framework CoreFoundation -I../../.. \
|
||||||
|
../../minidump_file_writer.cc \
|
||||||
|
../../../common/convert_UTF.c \
|
||||||
|
../../../common/string_conversion.cc \
|
||||||
|
../../../common/mac/string_utilities.cc \
|
||||||
|
exception_handler.cc \
|
||||||
|
minidump_generator.cc \
|
||||||
|
exception_handler_test.cc \
|
||||||
|
-o exception_handler_test
|
||||||
|
*/
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
|
||||||
|
#include "exception_handler.h"
|
||||||
|
#include "minidump_generator.h"
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
using google_airbag::ExceptionHandler;
|
||||||
|
|
||||||
|
static void *SleepyFunction(void *) {
|
||||||
|
while (1) {
|
||||||
|
sleep(10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Crasher() {
|
||||||
|
int *a = NULL;
|
||||||
|
|
||||||
|
fprintf(stdout, "Going to crash...\n");
|
||||||
|
fprintf(stdout, "A = %d", *a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SoonToCrash() {
|
||||||
|
Crasher();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MDCallback(const char *dump_dir, const char *file_name,
|
||||||
|
void *context, bool success) {
|
||||||
|
string path(dump_dir);
|
||||||
|
string dest(dump_dir);
|
||||||
|
path.append(file_name);
|
||||||
|
path.append(".dmp");
|
||||||
|
|
||||||
|
fprintf(stdout, "Minidump: %s\n", path.c_str());
|
||||||
|
// Indicate that we've handled the callback
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char * const argv[]) {
|
||||||
|
char buffer[PATH_MAX];
|
||||||
|
struct passwd *user = getpwuid(getuid());
|
||||||
|
|
||||||
|
// Home dir
|
||||||
|
snprintf(buffer, sizeof(buffer), "/Users/%s/Desktop/", user->pw_name);
|
||||||
|
|
||||||
|
string path(buffer);
|
||||||
|
ExceptionHandler eh(path, NULL, MDCallback, NULL, true);
|
||||||
|
pthread_t t;
|
||||||
|
|
||||||
|
if (pthread_create(&t, NULL, SleepyFunction, NULL) == 0) {
|
||||||
|
pthread_detach(t);
|
||||||
|
} else {
|
||||||
|
perror("pthread_create");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump a test
|
||||||
|
eh.WriteMinidump();
|
||||||
|
|
||||||
|
// Test the handler
|
||||||
|
SoonToCrash();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
694
src/client/mac/handler/minidump_generator.cc
Normal file
694
src/client/mac/handler/minidump_generator.cc
Normal file
@ -0,0 +1,694 @@
|
|||||||
|
// 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 <cstdio>
|
||||||
|
|
||||||
|
#include <mach/host_info.h>
|
||||||
|
#include <mach/vm_statistics.h>
|
||||||
|
#include <mach-o/dyld.h>
|
||||||
|
#include <mach-o/loader.h>
|
||||||
|
#include <sys/sysctl.h>
|
||||||
|
|
||||||
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
|
||||||
|
#include "client/mac/handler/minidump_generator.h"
|
||||||
|
#include "client/minidump_file_writer-inl.h"
|
||||||
|
#include "common/mac/string_utilities.h"
|
||||||
|
|
||||||
|
using MacStringUtils::ConvertToString;
|
||||||
|
using MacStringUtils::IntegerValueAtIndex;
|
||||||
|
|
||||||
|
namespace google_airbag {
|
||||||
|
|
||||||
|
MinidumpGenerator::MinidumpGenerator()
|
||||||
|
: exception_type_(0),
|
||||||
|
exception_code_(0),
|
||||||
|
exception_thread_(0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
MinidumpGenerator::~MinidumpGenerator() {
|
||||||
|
}
|
||||||
|
|
||||||
|
char MinidumpGenerator::build_string_[16];
|
||||||
|
int MinidumpGenerator::os_major_version_ = 0;
|
||||||
|
int MinidumpGenerator::os_minor_version_ = 0;
|
||||||
|
int MinidumpGenerator::os_build_number_ = 0;
|
||||||
|
|
||||||
|
// static
|
||||||
|
void MinidumpGenerator::GatherSystemInformation() {
|
||||||
|
// If this is non-zero, then we've already gathered the information
|
||||||
|
if (os_major_version_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// This code extracts the version and build information from the OS
|
||||||
|
CFStringRef vers_path =
|
||||||
|
CFSTR("/System/Library/CoreServices/SystemVersion.plist");
|
||||||
|
CFURLRef sys_vers =
|
||||||
|
CFURLCreateWithFileSystemPath(NULL, vers_path, kCFURLPOSIXPathStyle, false);
|
||||||
|
CFDataRef data;
|
||||||
|
SInt32 error;
|
||||||
|
CFURLCreateDataAndPropertiesFromResource(NULL, sys_vers, &data, NULL, NULL,
|
||||||
|
&error);
|
||||||
|
|
||||||
|
if (!data)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CFDictionaryRef list = static_cast<CFDictionaryRef>
|
||||||
|
(CFPropertyListCreateFromXMLData(NULL, data, kCFPropertyListImmutable,
|
||||||
|
NULL));
|
||||||
|
if (!list)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CFStringRef build_version = static_cast<CFStringRef>
|
||||||
|
(CFDictionaryGetValue(list, CFSTR("ProductBuildVersion")));
|
||||||
|
CFStringRef product_version = static_cast<CFStringRef>
|
||||||
|
(CFDictionaryGetValue(list, CFSTR("ProductVersion")));
|
||||||
|
string build_str = ConvertToString(build_version);
|
||||||
|
string product_str = ConvertToString(product_version);
|
||||||
|
|
||||||
|
CFRelease(list);
|
||||||
|
CFRelease(sys_vers);
|
||||||
|
CFRelease(data);
|
||||||
|
|
||||||
|
strlcpy(build_string_, build_str.c_str(), sizeof(build_string_));
|
||||||
|
|
||||||
|
// Parse the string that looks like "10.4.8"
|
||||||
|
os_major_version_ = IntegerValueAtIndex(product_str, 0);
|
||||||
|
os_minor_version_ = IntegerValueAtIndex(product_str, 1);
|
||||||
|
os_build_number_ = IntegerValueAtIndex(product_str, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
string MinidumpGenerator::UniqueNameInDirectory(const string &dir,
|
||||||
|
string *unique_name) {
|
||||||
|
CFUUIDRef uuid = CFUUIDCreate(NULL);
|
||||||
|
CFStringRef uuid_cfstr = CFUUIDCreateString(NULL, uuid);
|
||||||
|
CFRelease(uuid);
|
||||||
|
string file_name(ConvertToString(uuid_cfstr));
|
||||||
|
CFRelease(uuid_cfstr);
|
||||||
|
string path(dir);
|
||||||
|
|
||||||
|
// Ensure that the directory (if non-empty) has a trailing slash so that
|
||||||
|
// we can append the file name and have a valid pathname.
|
||||||
|
if (!dir.empty()) {
|
||||||
|
if (dir.at(dir.size() - 1) != '/')
|
||||||
|
path.append(1, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
path.append(file_name);
|
||||||
|
path.append(".dmp");
|
||||||
|
|
||||||
|
if (unique_name)
|
||||||
|
*unique_name = file_name;
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MinidumpGenerator::Write(const char *path) {
|
||||||
|
WriteStreamFN writers[] = {
|
||||||
|
&MinidumpGenerator::WriteThreadListStream,
|
||||||
|
&MinidumpGenerator::WriteSystemInfoStream,
|
||||||
|
&MinidumpGenerator::WriteModuleListStream,
|
||||||
|
&MinidumpGenerator::WriteMiscInfoStream,
|
||||||
|
&MinidumpGenerator::WriteAirbagInfoStream,
|
||||||
|
// Exception stream needs to be the last entry in this array as it may
|
||||||
|
// be omitted in the case where the minidump is written without an
|
||||||
|
// exception.
|
||||||
|
&MinidumpGenerator::WriteExceptionStream,
|
||||||
|
};
|
||||||
|
bool result = true;
|
||||||
|
|
||||||
|
// If opening was successful, create the header, directory, and call each
|
||||||
|
// writer. The destructor for the TypedMDRVAs will cause the data to be
|
||||||
|
// flushed. The destructor for the MinidumpFileWriter will close the file.
|
||||||
|
if (writer_.Open(path)) {
|
||||||
|
TypedMDRVA<MDRawHeader> header(&writer_);
|
||||||
|
TypedMDRVA<MDRawDirectory> dir(&writer_);
|
||||||
|
|
||||||
|
if (!header.Allocate())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int writer_count = sizeof(writers) / sizeof(writers[0]);
|
||||||
|
|
||||||
|
// If we don't have exception information, don't write out the
|
||||||
|
// exception stream
|
||||||
|
if (!exception_thread_ && !exception_type_)
|
||||||
|
--writer_count;
|
||||||
|
|
||||||
|
// Add space for all writers
|
||||||
|
if (!dir.AllocateArray(writer_count))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
MDRawHeader *header_ptr = header.get();
|
||||||
|
header_ptr->signature = MD_HEADER_SIGNATURE;
|
||||||
|
header_ptr->version = MD_HEADER_VERSION;
|
||||||
|
time(reinterpret_cast<time_t *>(&(header_ptr->time_date_stamp)));
|
||||||
|
header_ptr->stream_count = writer_count;
|
||||||
|
header_ptr->stream_directory_rva = dir.position();
|
||||||
|
|
||||||
|
MDRawDirectory local_dir;
|
||||||
|
for (int i = 0; (result) && (i < writer_count); ++i) {
|
||||||
|
result = (this->*writers[i])(&local_dir);
|
||||||
|
|
||||||
|
if (result)
|
||||||
|
dir.CopyIndex(i, &local_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(waylonis): This routine works most of the time. However, if a process
|
||||||
|
// fork()s, it might cause the stack so that the current top of stack will
|
||||||
|
// exist on a single page.
|
||||||
|
static size_t CalculateStackSize(vm_address_t start_addr) {
|
||||||
|
vm_address_t stack_region_base = start_addr;
|
||||||
|
vm_size_t stack_region_size;
|
||||||
|
natural_t nesting_level = 0;
|
||||||
|
vm_region_submap_info submap_info;
|
||||||
|
mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT;
|
||||||
|
kern_return_t result =
|
||||||
|
vm_region_recurse(mach_task_self(), &stack_region_base, &stack_region_size,
|
||||||
|
&nesting_level,
|
||||||
|
reinterpret_cast<vm_region_recurse_info_t>(&submap_info),
|
||||||
|
&info_count);
|
||||||
|
|
||||||
|
return result == KERN_SUCCESS ?
|
||||||
|
stack_region_base + stack_region_size - start_addr : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MinidumpGenerator::WriteStackFromStartAddress(
|
||||||
|
vm_address_t start_addr,
|
||||||
|
MDMemoryDescriptor *stack_location) {
|
||||||
|
UntypedMDRVA memory(&writer_);
|
||||||
|
size_t size = CalculateStackSize(start_addr);
|
||||||
|
|
||||||
|
// If there's an error in the calculation, return at least the current
|
||||||
|
// stack information
|
||||||
|
if (size == 0)
|
||||||
|
size = 16;
|
||||||
|
|
||||||
|
if (!memory.Allocate(size))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool result = memory.Copy(reinterpret_cast<const void *>(start_addr), size);
|
||||||
|
stack_location->start_of_memory_range = start_addr;
|
||||||
|
stack_location->memory = memory.location();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if TARGET_CPU_PPC
|
||||||
|
bool MinidumpGenerator::WriteStack(thread_state_data_t state,
|
||||||
|
MDMemoryDescriptor *stack_location) {
|
||||||
|
ppc_thread_state_t *machine_state =
|
||||||
|
reinterpret_cast<ppc_thread_state_t *>(state);
|
||||||
|
vm_address_t start_addr = machine_state->r1;
|
||||||
|
return WriteStackFromStartAddress(start_addr, stack_location);
|
||||||
|
}
|
||||||
|
|
||||||
|
u_int64_t MinidumpGenerator::CurrentPCForStack(thread_state_data_t state) {
|
||||||
|
ppc_thread_state_t *machine_state =
|
||||||
|
reinterpret_cast<ppc_thread_state_t *>(state);
|
||||||
|
|
||||||
|
return machine_state->srr0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MinidumpGenerator::WriteContext(thread_state_data_t state,
|
||||||
|
MDLocationDescriptor *register_location) {
|
||||||
|
TypedMDRVA<MDRawContextPPC> context(&writer_);
|
||||||
|
ppc_thread_state_t *machine_state =
|
||||||
|
reinterpret_cast<ppc_thread_state_t *>(state);
|
||||||
|
|
||||||
|
if (!context.Allocate())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*register_location = context.location();
|
||||||
|
MDRawContextPPC *context_ptr = context.get();
|
||||||
|
context_ptr->context_flags = MD_CONTEXT_PPC_BASE;
|
||||||
|
|
||||||
|
#define AddReg(a) context_ptr->a = machine_state->a
|
||||||
|
#define AddGPR(a) context_ptr->gpr[a] = machine_state->r ## a
|
||||||
|
AddReg(srr0);
|
||||||
|
AddReg(cr);
|
||||||
|
AddReg(xer);
|
||||||
|
AddReg(ctr);
|
||||||
|
AddReg(mq);
|
||||||
|
AddReg(lr);
|
||||||
|
AddReg(vrsave);
|
||||||
|
|
||||||
|
AddGPR(0);
|
||||||
|
AddGPR(1);
|
||||||
|
AddGPR(2);
|
||||||
|
AddGPR(3);
|
||||||
|
AddGPR(4);
|
||||||
|
AddGPR(5);
|
||||||
|
AddGPR(6);
|
||||||
|
AddGPR(7);
|
||||||
|
AddGPR(8);
|
||||||
|
AddGPR(9);
|
||||||
|
AddGPR(10);
|
||||||
|
AddGPR(11);
|
||||||
|
AddGPR(12);
|
||||||
|
AddGPR(13);
|
||||||
|
AddGPR(14);
|
||||||
|
AddGPR(15);
|
||||||
|
AddGPR(16);
|
||||||
|
AddGPR(17);
|
||||||
|
AddGPR(18);
|
||||||
|
AddGPR(19);
|
||||||
|
AddGPR(20);
|
||||||
|
AddGPR(21);
|
||||||
|
AddGPR(22);
|
||||||
|
AddGPR(23);
|
||||||
|
AddGPR(24);
|
||||||
|
AddGPR(25);
|
||||||
|
AddGPR(26);
|
||||||
|
AddGPR(27);
|
||||||
|
AddGPR(28);
|
||||||
|
AddGPR(29);
|
||||||
|
AddGPR(30);
|
||||||
|
AddGPR(31);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif TARGET_CPU_X86
|
||||||
|
bool MinidumpGenerator::WriteStack(thread_state_data_t state,
|
||||||
|
MDMemoryDescriptor *stack_location) {
|
||||||
|
x86_thread_state_t *machine_state =
|
||||||
|
reinterpret_cast<x86_thread_state_t *>(state);
|
||||||
|
vm_address_t start_addr = machine_state->uts.ts32.esp;
|
||||||
|
return WriteStackFromStartAddress(start_addr, stack_location);
|
||||||
|
}
|
||||||
|
|
||||||
|
u_int64_t MinidumpGenerator::CurrentPCForStack(thread_state_data_t state) {
|
||||||
|
x86_thread_state_t *machine_state =
|
||||||
|
reinterpret_cast<x86_thread_state_t *>(state);
|
||||||
|
|
||||||
|
return machine_state->uts.ts32.eip;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MinidumpGenerator::WriteContext(thread_state_data_t state,
|
||||||
|
MDLocationDescriptor *register_location) {
|
||||||
|
TypedMDRVA<MDRawContextX86> context(&writer_);
|
||||||
|
x86_thread_state_t *machine_state =
|
||||||
|
reinterpret_cast<x86_thread_state_t *>(state);
|
||||||
|
|
||||||
|
if (!context.Allocate())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*register_location = context.location();
|
||||||
|
MDRawContextX86 *context_ptr = context.get();
|
||||||
|
context_ptr->context_flags = MD_CONTEXT_X86;
|
||||||
|
#define AddReg(a) context_ptr->a = machine_state->uts.ts32.a
|
||||||
|
AddReg(cs);
|
||||||
|
AddReg(ds);
|
||||||
|
AddReg(ss);
|
||||||
|
AddReg(es);
|
||||||
|
AddReg(fs);
|
||||||
|
AddReg(gs);
|
||||||
|
AddReg(eflags);
|
||||||
|
|
||||||
|
AddReg(eip);
|
||||||
|
AddReg(eax);
|
||||||
|
AddReg(ebx);
|
||||||
|
AddReg(ecx);
|
||||||
|
AddReg(edx);
|
||||||
|
AddReg(esi);
|
||||||
|
AddReg(edi);
|
||||||
|
AddReg(ebp);
|
||||||
|
AddReg(esp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool MinidumpGenerator::WriteThreadStream(mach_port_t thread_id,
|
||||||
|
MDRawThread *thread) {
|
||||||
|
thread_state_data_t state;
|
||||||
|
mach_msg_type_number_t state_count = sizeof(state);
|
||||||
|
|
||||||
|
if (thread_get_state(thread_id, MACHINE_THREAD_STATE, state, &state_count) ==
|
||||||
|
KERN_SUCCESS) {
|
||||||
|
if (!WriteStack(state, &thread->stack))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!WriteContext(state, &thread->thread_context))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
thread->thread_id = thread_id;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MinidumpGenerator::WriteThreadListStream(
|
||||||
|
MDRawDirectory *thread_list_stream) {
|
||||||
|
TypedMDRVA<MDRawThreadList> list(&writer_);
|
||||||
|
thread_act_port_array_t threads_for_task;
|
||||||
|
mach_msg_type_number_t thread_count;
|
||||||
|
int non_generator_thread_count;
|
||||||
|
|
||||||
|
if (task_threads(mach_task_self(), &threads_for_task, &thread_count))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Don't include the generator thread
|
||||||
|
non_generator_thread_count = thread_count - 1;
|
||||||
|
if (!list.AllocateObjectAndArray(non_generator_thread_count,
|
||||||
|
sizeof(MDRawThread)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
thread_list_stream->stream_type = MD_THREAD_LIST_STREAM;
|
||||||
|
thread_list_stream->location = list.location();
|
||||||
|
|
||||||
|
list.get()->number_of_threads = non_generator_thread_count;
|
||||||
|
|
||||||
|
MDRawThread thread;
|
||||||
|
int thread_idx = 0;
|
||||||
|
for (unsigned int i = 0; i < thread_count; ++i) {
|
||||||
|
memset(&thread, 0, sizeof(MDRawThread));
|
||||||
|
|
||||||
|
if (threads_for_task[i] != mach_thread_self()) {
|
||||||
|
if (!WriteThreadStream(threads_for_task[i], &thread))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
list.CopyIndexAfterObject(thread_idx++, &thread, sizeof(MDRawThread));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MinidumpGenerator::WriteExceptionStream(MDRawDirectory *exception_stream) {
|
||||||
|
TypedMDRVA<MDRawExceptionStream> exception(&writer_);
|
||||||
|
|
||||||
|
if (!exception.Allocate())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
exception_stream->stream_type = MD_EXCEPTION_STREAM;
|
||||||
|
exception_stream->location = exception.location();
|
||||||
|
MDRawExceptionStream *exception_ptr = exception.get();
|
||||||
|
exception_ptr->thread_id = exception_thread_;
|
||||||
|
|
||||||
|
// This naming is confusing, but it is the proper translation from
|
||||||
|
// mach naming to minidump naming.
|
||||||
|
exception_ptr->exception_record.exception_code = exception_type_;
|
||||||
|
exception_ptr->exception_record.exception_flags = exception_code_;
|
||||||
|
|
||||||
|
thread_state_data_t state;
|
||||||
|
mach_msg_type_number_t stateCount = sizeof(state);
|
||||||
|
|
||||||
|
if (thread_get_state(exception_thread_, MACHINE_THREAD_STATE, state,
|
||||||
|
&stateCount) != KERN_SUCCESS)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!WriteContext(state, &exception_ptr->thread_context))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
exception_ptr->exception_record.exception_address = CurrentPCForStack(state);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MinidumpGenerator::WriteSystemInfoStream(
|
||||||
|
MDRawDirectory *system_info_stream) {
|
||||||
|
TypedMDRVA<MDRawSystemInfo> info(&writer_);
|
||||||
|
|
||||||
|
if (!info.Allocate())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
system_info_stream->stream_type = MD_SYSTEM_INFO_STREAM;
|
||||||
|
system_info_stream->location = info.location();
|
||||||
|
|
||||||
|
// CPU Information
|
||||||
|
uint32_t cpu_type;
|
||||||
|
size_t len = sizeof(cpu_type);
|
||||||
|
sysctlbyname("hw.cputype", &cpu_type, &len, NULL, 0);
|
||||||
|
uint32_t number_of_processors;
|
||||||
|
len = sizeof(number_of_processors);
|
||||||
|
sysctlbyname("hw.ncpu", &number_of_processors, &len, NULL, 0);
|
||||||
|
MDRawSystemInfo *info_ptr = info.get();
|
||||||
|
|
||||||
|
switch (cpu_type) {
|
||||||
|
case CPU_TYPE_POWERPC:
|
||||||
|
info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_PPC;
|
||||||
|
break;
|
||||||
|
case CPU_TYPE_I386:
|
||||||
|
info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_X86;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_UNKNOWN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
info_ptr->number_of_processors = number_of_processors;
|
||||||
|
info_ptr->platform_id = MD_OS_MAC_OS_X;
|
||||||
|
|
||||||
|
MDLocationDescriptor build_string_loc;
|
||||||
|
|
||||||
|
if (!writer_.WriteString(build_string_, 0,
|
||||||
|
&build_string_loc))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
info_ptr->csd_version_rva = build_string_loc.rva;
|
||||||
|
info_ptr->major_version = os_major_version_;
|
||||||
|
info_ptr->minor_version = os_minor_version_;
|
||||||
|
info_ptr->build_number = os_build_number_;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MinidumpGenerator::WriteModuleStream(unsigned int index,
|
||||||
|
MDRawModule *module) {
|
||||||
|
const struct mach_header *header = _dyld_get_image_header(index);
|
||||||
|
|
||||||
|
if (!header)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
unsigned long slide = _dyld_get_image_vmaddr_slide(index);
|
||||||
|
const char* name = _dyld_get_image_name(index);
|
||||||
|
const struct load_command *cmd =
|
||||||
|
reinterpret_cast<const struct load_command *>(header + 1);
|
||||||
|
|
||||||
|
memset(module, 0, sizeof(MDRawModule));
|
||||||
|
|
||||||
|
for (unsigned int i = 0; cmd && (i < header->ncmds); i++) {
|
||||||
|
if (cmd->cmd == LC_SEGMENT) {
|
||||||
|
const struct segment_command *seg =
|
||||||
|
reinterpret_cast<const struct segment_command *>(cmd);
|
||||||
|
if (!strcmp(seg->segname, "__TEXT")) {
|
||||||
|
MDLocationDescriptor string_location;
|
||||||
|
|
||||||
|
if (!writer_.WriteString(name, 0, &string_location))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
module->base_of_image = seg->vmaddr + slide;
|
||||||
|
module->size_of_image = seg->vmsize;
|
||||||
|
module->module_name_rva = string_location.rva;
|
||||||
|
|
||||||
|
if (!WriteCVRecord(module, name))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = reinterpret_cast<struct load_command *>((char *)cmd + cmd->cmdsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int FindExecutableModule() {
|
||||||
|
int image_count = _dyld_image_count();
|
||||||
|
const struct mach_header *header;
|
||||||
|
|
||||||
|
for (int i = 0; i < image_count; ++i) {
|
||||||
|
header = _dyld_get_image_header(i);
|
||||||
|
|
||||||
|
if (header->filetype == MH_EXECUTE)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MinidumpGenerator::WriteCVRecord(MDRawModule *module,
|
||||||
|
const char *module_path) {
|
||||||
|
TypedMDRVA<MDCVInfoPDB70> cv(&writer_);
|
||||||
|
|
||||||
|
// Only return the last path component of the full module path
|
||||||
|
char *module_name = strrchr(module_path, '/');
|
||||||
|
|
||||||
|
// Increment past the slash
|
||||||
|
if (module_name)
|
||||||
|
++module_name;
|
||||||
|
else
|
||||||
|
module_name = "<Unknown>";
|
||||||
|
|
||||||
|
size_t module_name_length = strlen(module_name);
|
||||||
|
|
||||||
|
if (!cv.AllocateObjectAndArray(module_name_length + 1, sizeof(u_int8_t)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!cv.CopyIndexAfterObject(0, module_name, module_name_length))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
module->cv_record = cv.location();
|
||||||
|
MDCVInfoPDB70 *cv_ptr = cv.get();
|
||||||
|
cv_ptr->cv_signature = MD_CVINFOPDB70_SIGNATURE;
|
||||||
|
cv_ptr->age = 0;
|
||||||
|
|
||||||
|
// Convert the string to MDGUID
|
||||||
|
// TODO(waylonis):
|
||||||
|
// MacOS doesn't currently have a uuid string, so we'll just write a
|
||||||
|
// placeholder here.
|
||||||
|
cv_ptr->signature.data1 = 0;
|
||||||
|
cv_ptr->signature.data2 = 0;
|
||||||
|
cv_ptr->signature.data3 = 0;
|
||||||
|
memset(cv_ptr->signature.data4, 0, sizeof(cv_ptr->signature.data4));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MinidumpGenerator::WriteModuleListStream(
|
||||||
|
MDRawDirectory *module_list_stream) {
|
||||||
|
TypedMDRVA<MDRawModuleList> list(&writer_);
|
||||||
|
|
||||||
|
if (!_dyld_present())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int image_count = _dyld_image_count();
|
||||||
|
|
||||||
|
if (!list.AllocateObjectAndArray(image_count, MD_MODULE_SIZE))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
module_list_stream->stream_type = MD_MODULE_LIST_STREAM;
|
||||||
|
module_list_stream->location = list.location();
|
||||||
|
list.get()->number_of_modules = image_count;
|
||||||
|
|
||||||
|
// Write out the executable module as the first one
|
||||||
|
MDRawModule module;
|
||||||
|
int executableIndex = FindExecutableModule();
|
||||||
|
|
||||||
|
if (!WriteModuleStream(executableIndex, &module))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
list.CopyIndexAfterObject(0, &module, MD_MODULE_SIZE);
|
||||||
|
int destinationIndex = 1; // Write all other modules after this one
|
||||||
|
|
||||||
|
for (int i = 0; i < image_count; ++i) {
|
||||||
|
if (i != executableIndex) {
|
||||||
|
if (!WriteModuleStream(i, &module))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
list.CopyIndexAfterObject(destinationIndex++, &module, MD_MODULE_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MinidumpGenerator::WriteMiscInfoStream(MDRawDirectory *misc_info_stream) {
|
||||||
|
TypedMDRVA<MDRawMiscInfo> info(&writer_);
|
||||||
|
|
||||||
|
if (!info.Allocate())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
misc_info_stream->stream_type = MD_MISC_INFO_STREAM;
|
||||||
|
misc_info_stream->location = info.location();
|
||||||
|
|
||||||
|
MDRawMiscInfo *info_ptr = info.get();
|
||||||
|
info_ptr->size_of_info = sizeof(MDRawMiscInfo);
|
||||||
|
info_ptr->flags1 = MD_MISCINFO_FLAGS1_PROCESS_ID |
|
||||||
|
MD_MISCINFO_FLAGS1_PROCESS_TIMES |
|
||||||
|
MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO;
|
||||||
|
|
||||||
|
// Process ID
|
||||||
|
info_ptr->process_id = getpid();
|
||||||
|
|
||||||
|
// Times
|
||||||
|
struct rusage usage;
|
||||||
|
if (getrusage(RUSAGE_SELF, &usage) != -1) {
|
||||||
|
// Omit the fractional time since the MDRawMiscInfo only wants seconds
|
||||||
|
info_ptr->process_user_time = usage.ru_utime.tv_sec;
|
||||||
|
info_ptr->process_kernel_time = usage.ru_stime.tv_sec;
|
||||||
|
}
|
||||||
|
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, info_ptr->process_id };
|
||||||
|
size_t size;
|
||||||
|
if (!sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &size, NULL, 0)) {
|
||||||
|
vm_address_t addr;
|
||||||
|
if (vm_allocate(mach_task_self(), &addr, size, true) == KERN_SUCCESS) {
|
||||||
|
struct kinfo_proc *proc = (struct kinfo_proc *)addr;
|
||||||
|
if (!sysctl(mib, sizeof(mib) / sizeof(mib[0]), proc, &size, NULL, 0))
|
||||||
|
info_ptr->process_create_time = proc->kp_proc.p_starttime.tv_sec;
|
||||||
|
vm_deallocate(mach_task_self(), addr, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Speed
|
||||||
|
uint64_t speed;
|
||||||
|
size = sizeof(speed);
|
||||||
|
sysctlbyname("hw.cpufrequency_max", &speed, &size, NULL, 0);
|
||||||
|
info_ptr->processor_max_mhz = speed / (1000 * 1000);
|
||||||
|
info_ptr->processor_mhz_limit = speed / (1000 * 1000);
|
||||||
|
size = sizeof(speed);
|
||||||
|
sysctlbyname("hw.cpufrequency", &speed, &size, NULL, 0);
|
||||||
|
info_ptr->processor_current_mhz = speed / (1000 * 1000);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MinidumpGenerator::WriteAirbagInfoStream(
|
||||||
|
MDRawDirectory *airbag_info_stream) {
|
||||||
|
TypedMDRVA<MDRawAirbagInfo> info(&writer_);
|
||||||
|
|
||||||
|
if (!info.Allocate())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
airbag_info_stream->stream_type = MD_AIRBAG_INFO_STREAM;
|
||||||
|
airbag_info_stream->location = info.location();
|
||||||
|
MDRawAirbagInfo *info_ptr = info.get();
|
||||||
|
|
||||||
|
if (exception_thread_ && exception_type_) {
|
||||||
|
info_ptr->validity = MD_AIRBAG_INFO_VALID_DUMP_THREAD_ID |
|
||||||
|
MD_AIRBAG_INFO_VALID_REQUESTING_THREAD_ID;
|
||||||
|
info_ptr->dump_thread_id = mach_thread_self();
|
||||||
|
info_ptr->requesting_thread_id = exception_thread_;
|
||||||
|
} else {
|
||||||
|
info_ptr->validity = MD_AIRBAG_INFO_VALID_DUMP_THREAD_ID;
|
||||||
|
info_ptr->dump_thread_id = mach_thread_self();
|
||||||
|
info_ptr->requesting_thread_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace google_airbag
|
122
src/client/mac/handler/minidump_generator.h
Normal file
122
src/client/mac/handler/minidump_generator.h
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// minidump_generator.h: Create a minidump of the current MacOS process.
|
||||||
|
|
||||||
|
#ifndef CLIENT_MAC_GENERATOR_MINIDUMP_GENERATOR_H__
|
||||||
|
#define CLIENT_MAC_GENERATOR_MINIDUMP_GENERATOR_H__
|
||||||
|
|
||||||
|
#include <mach/mach.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "client/minidump_file_writer.h"
|
||||||
|
#include "google_airbag/common/minidump_format.h"
|
||||||
|
|
||||||
|
namespace google_airbag {
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
// Creates a minidump file of the current process. If there is exception data,
|
||||||
|
// use SetExceptionInformation() to add this to the minidump. The minidump
|
||||||
|
// file is generated by the Write() function.
|
||||||
|
// Usage:
|
||||||
|
// MinidumpGenerator minidump();
|
||||||
|
// minidump.Write("/tmp/minidump");
|
||||||
|
//
|
||||||
|
class MinidumpGenerator {
|
||||||
|
public:
|
||||||
|
MinidumpGenerator();
|
||||||
|
~MinidumpGenerator();
|
||||||
|
|
||||||
|
// Return <dir>/<unique_name>.dmp
|
||||||
|
// Sets |unique_name| (if requested) to the unique name for the minidump
|
||||||
|
static string UniqueNameInDirectory(const string &dir, string *unique_name);
|
||||||
|
|
||||||
|
// Write out the minidump into |path|
|
||||||
|
// All of the components of |path| must exist and be writable
|
||||||
|
// Return true if successful, false otherwise
|
||||||
|
bool Write(const char *path);
|
||||||
|
|
||||||
|
// Specify some exception information, if applicable
|
||||||
|
void SetExceptionInformation(int type, int code, mach_port_t thread_name) {
|
||||||
|
exception_type_ = type;
|
||||||
|
exception_code_ = code;
|
||||||
|
exception_thread_ = thread_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gather system information. This should be call at least once before using
|
||||||
|
// the MinidumpGenerator class.
|
||||||
|
static void GatherSystemInformation();
|
||||||
|
|
||||||
|
private:
|
||||||
|
typedef bool (MinidumpGenerator::*WriteStreamFN)(MDRawDirectory *);
|
||||||
|
|
||||||
|
// Stream writers
|
||||||
|
bool WriteThreadListStream(MDRawDirectory *thread_list_stream);
|
||||||
|
bool WriteExceptionStream(MDRawDirectory *exception_stream);
|
||||||
|
bool WriteSystemInfoStream(MDRawDirectory *system_info_stream);
|
||||||
|
bool WriteModuleListStream(MDRawDirectory *module_list_stream);
|
||||||
|
bool WriteMiscInfoStream(MDRawDirectory *misc_info_stream);
|
||||||
|
bool WriteAirbagInfoStream(MDRawDirectory *airbag_info_stream);
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
u_int64_t CurrentPCForStack(thread_state_data_t state);
|
||||||
|
bool WriteStackFromStartAddress(vm_address_t start_addr,
|
||||||
|
MDMemoryDescriptor *stack_location);
|
||||||
|
bool WriteStack(thread_state_data_t state,
|
||||||
|
MDMemoryDescriptor *stack_location);
|
||||||
|
bool WriteContext(thread_state_data_t state,
|
||||||
|
MDLocationDescriptor *register_location);
|
||||||
|
bool WriteThreadStream(mach_port_t thread_id, MDRawThread *thread);
|
||||||
|
bool WriteCVRecord(MDRawModule *module, const char *module_path);
|
||||||
|
bool WriteModuleStream(unsigned int index, MDRawModule *module);
|
||||||
|
|
||||||
|
// disallow copy ctor and operator=
|
||||||
|
explicit MinidumpGenerator(const MinidumpGenerator &);
|
||||||
|
void operator=(const MinidumpGenerator &);
|
||||||
|
|
||||||
|
// Use this writer to put the data to disk
|
||||||
|
MinidumpFileWriter writer_;
|
||||||
|
|
||||||
|
// Exception information
|
||||||
|
int exception_type_;
|
||||||
|
int exception_code_;
|
||||||
|
mach_port_t exception_thread_;
|
||||||
|
|
||||||
|
// System information
|
||||||
|
static char build_string_[16];
|
||||||
|
static int os_major_version_;
|
||||||
|
static int os_minor_version_;
|
||||||
|
static int os_build_number_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace google_airbag
|
||||||
|
|
||||||
|
#endif // CLIENT_MAC_GENERATOR_MINIDUMP_GENERATOR_H__
|
79
src/client/mac/handler/minidump_generator_test.cc
Normal file
79
src/client/mac/handler/minidump_generator_test.cc
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
//
|
||||||
|
// 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 <unistd.h>
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
|
||||||
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
|
||||||
|
#include "minidump_generator.h"
|
||||||
|
#include "minidump_file_writer.h"
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
using google_airbag::MinidumpGenerator;
|
||||||
|
|
||||||
|
static bool doneWritingReport = false;
|
||||||
|
|
||||||
|
static void *Reporter(void *) {
|
||||||
|
char buffer[PATH_MAX];
|
||||||
|
MinidumpGenerator md;
|
||||||
|
struct passwd *user = getpwuid(getuid());
|
||||||
|
|
||||||
|
// Write it to the desktop
|
||||||
|
snprintf(buffer, sizeof(buffer), "/Users/%s/Desktop/test.dmp", user->pw_name);
|
||||||
|
fprintf(stdout, "Writing %s\n", buffer);
|
||||||
|
|
||||||
|
md.Write(buffer);
|
||||||
|
doneWritingReport = true;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SleepyFunction() {
|
||||||
|
while (!doneWritingReport) {
|
||||||
|
usleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char * const argv[]) {
|
||||||
|
pthread_t reporter_thread;
|
||||||
|
|
||||||
|
if (pthread_create(&reporter_thread, NULL, Reporter, NULL) == 0) {
|
||||||
|
pthread_detach(reporter_thread);
|
||||||
|
} else {
|
||||||
|
perror("pthread_create");
|
||||||
|
}
|
||||||
|
|
||||||
|
SleepyFunction();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
447
src/client/mac/handler/minidump_test.xcodeproj/project.pbxproj
Normal file
447
src/client/mac/handler/minidump_test.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,447 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 42;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
9B35FF5A0B267D5F008DE8C7 /* convert_UTF.c in Sources */ = {isa = PBXBuildFile; fileRef = 9B35FF560B267D5F008DE8C7 /* convert_UTF.c */; };
|
||||||
|
9B35FF5B0B267D5F008DE8C7 /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B35FF580B267D5F008DE8C7 /* string_conversion.cc */; };
|
||||||
|
9B37CEEC0AF98ECD00FA4BD4 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B37CEEB0AF98ECD00FA4BD4 /* CoreFoundation.framework */; };
|
||||||
|
9B7CA7700B12873A00CD3A1D /* minidump_file_writer-inl.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9BE3C01E0B0CE329009892DF /* minidump_file_writer-inl.h */; };
|
||||||
|
9B7CA8540B12989000CD3A1D /* minidump_file_writer_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B7CA8530B12989000CD3A1D /* minidump_file_writer_unittest.cc */; };
|
||||||
|
9B7CA8550B1298A100CD3A1D /* minidump_file_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C230B01344C0055103E /* minidump_file_writer.cc */; };
|
||||||
|
9BC1D2940B336F2300F2A2B4 /* convert_UTF.c in Sources */ = {isa = PBXBuildFile; fileRef = 9B35FF560B267D5F008DE8C7 /* convert_UTF.c */; };
|
||||||
|
9BC1D2950B336F2500F2A2B4 /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B35FF580B267D5F008DE8C7 /* string_conversion.cc */; };
|
||||||
|
9BD82AC10B0029DF0055103E /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B37CEEB0AF98ECD00FA4BD4 /* CoreFoundation.framework */; };
|
||||||
|
9BD82BFF0B01333D0055103E /* exception_handler_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82BFD0B01333D0055103E /* exception_handler_test.cc */; };
|
||||||
|
9BD82C020B01333D0055103E /* minidump_generator_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82BFE0B01333D0055103E /* minidump_generator_test.cc */; };
|
||||||
|
9BD82C0D0B0133520055103E /* exception_handler.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C090B0133520055103E /* exception_handler.cc */; };
|
||||||
|
9BD82C0E0B0133520055103E /* minidump_generator.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C0B0B0133520055103E /* minidump_generator.cc */; };
|
||||||
|
9BD82C0F0B0133520055103E /* exception_handler.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C090B0133520055103E /* exception_handler.cc */; };
|
||||||
|
9BD82C100B0133520055103E /* exception_handler.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9BD82C0A0B0133520055103E /* exception_handler.h */; };
|
||||||
|
9BD82C110B0133520055103E /* minidump_generator.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C0B0B0133520055103E /* minidump_generator.cc */; };
|
||||||
|
9BD82C120B0133520055103E /* minidump_generator.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9BD82C0C0B0133520055103E /* minidump_generator.h */; };
|
||||||
|
9BD82C250B01344C0055103E /* minidump_file_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C230B01344C0055103E /* minidump_file_writer.cc */; };
|
||||||
|
9BD82C260B01344C0055103E /* minidump_file_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C230B01344C0055103E /* minidump_file_writer.cc */; };
|
||||||
|
9BD82C270B01344C0055103E /* minidump_file_writer.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9BD82C240B01344C0055103E /* minidump_file_writer.h */; };
|
||||||
|
9BD82C2D0B01345E0055103E /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C2B0B01345E0055103E /* string_utilities.cc */; };
|
||||||
|
9BD82C2E0B01345E0055103E /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C2B0B01345E0055103E /* string_utilities.cc */; };
|
||||||
|
9BD82C2F0B01345E0055103E /* string_utilities.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9BD82C2C0B01345E0055103E /* string_utilities.h */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
8DD76F690486A84900D96B5E /* CopyFiles */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 8;
|
||||||
|
dstPath = /usr/share/man/man1/;
|
||||||
|
dstSubfolderSpec = 0;
|
||||||
|
files = (
|
||||||
|
9BD82C100B0133520055103E /* exception_handler.h in CopyFiles */,
|
||||||
|
9BD82C120B0133520055103E /* minidump_generator.h in CopyFiles */,
|
||||||
|
9BD82C270B01344C0055103E /* minidump_file_writer.h in CopyFiles */,
|
||||||
|
9BD82C2F0B01345E0055103E /* string_utilities.h in CopyFiles */,
|
||||||
|
9B7CA7700B12873A00CD3A1D /* minidump_file_writer-inl.h in CopyFiles */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 1;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
8DD76F6C0486A84900D96B5E /* generator_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = generator_test; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
9B35FF560B267D5F008DE8C7 /* convert_UTF.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = convert_UTF.c; path = ../../../common/convert_UTF.c; sourceTree = SOURCE_ROOT; };
|
||||||
|
9B35FF570B267D5F008DE8C7 /* convert_UTF.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = convert_UTF.h; path = ../../../common/convert_UTF.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
9B35FF580B267D5F008DE8C7 /* string_conversion.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = string_conversion.cc; path = ../../../common/string_conversion.cc; sourceTree = SOURCE_ROOT; };
|
||||||
|
9B35FF590B267D5F008DE8C7 /* string_conversion.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = string_conversion.h; path = ../../../common/string_conversion.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
9B37CEEB0AF98ECD00FA4BD4 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = /System/Library/Frameworks/CoreFoundation.framework; sourceTree = "<absolute>"; };
|
||||||
|
9B7CA84E0B1297F200CD3A1D /* unit_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = unit_test; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
9B7CA8530B12989000CD3A1D /* minidump_file_writer_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = minidump_file_writer_unittest.cc; path = ../../minidump_file_writer_unittest.cc; sourceTree = "<group>"; };
|
||||||
|
9BD82A9B0B00267E0055103E /* handler_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = handler_test; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
9BD82BFD0B01333D0055103E /* exception_handler_test.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = exception_handler_test.cc; sourceTree = SOURCE_ROOT; };
|
||||||
|
9BD82BFE0B01333D0055103E /* minidump_generator_test.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = minidump_generator_test.cc; sourceTree = SOURCE_ROOT; };
|
||||||
|
9BD82C090B0133520055103E /* exception_handler.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = exception_handler.cc; sourceTree = SOURCE_ROOT; };
|
||||||
|
9BD82C0A0B0133520055103E /* exception_handler.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = exception_handler.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
9BD82C0B0B0133520055103E /* minidump_generator.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = minidump_generator.cc; sourceTree = SOURCE_ROOT; };
|
||||||
|
9BD82C0C0B0133520055103E /* minidump_generator.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = minidump_generator.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
9BD82C230B01344C0055103E /* minidump_file_writer.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = minidump_file_writer.cc; path = ../../minidump_file_writer.cc; sourceTree = SOURCE_ROOT; };
|
||||||
|
9BD82C240B01344C0055103E /* minidump_file_writer.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = minidump_file_writer.h; path = ../../minidump_file_writer.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
9BD82C2B0B01345E0055103E /* string_utilities.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = string_utilities.cc; path = ../../../common/mac/string_utilities.cc; sourceTree = SOURCE_ROOT; };
|
||||||
|
9BD82C2C0B01345E0055103E /* string_utilities.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = string_utilities.h; path = ../../../common/mac/string_utilities.h; sourceTree = SOURCE_ROOT; };
|
||||||
|
9BE3C01E0B0CE329009892DF /* minidump_file_writer-inl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "minidump_file_writer-inl.h"; path = "../../minidump_file_writer-inl.h"; sourceTree = SOURCE_ROOT; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
8DD76F660486A84900D96B5E /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
9B37CEEC0AF98ECD00FA4BD4 /* CoreFoundation.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
9B7CA84C0B1297F200CD3A1D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
9BD82A990B00267E0055103E /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
9BD82AC10B0029DF0055103E /* CoreFoundation.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
08FB7794FE84155DC02AAC07 /* MinidumpWriter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9BD82C040B0133420055103E /* Airbag */,
|
||||||
|
08FB7795FE84155DC02AAC07 /* Source */,
|
||||||
|
9B37CEEA0AF98EB600FA4BD4 /* Frameworks */,
|
||||||
|
1AB674ADFE9D54B511CA2CBB /* Products */,
|
||||||
|
);
|
||||||
|
name = MinidumpWriter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
08FB7795FE84155DC02AAC07 /* Source */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9BD82BFD0B01333D0055103E /* exception_handler_test.cc */,
|
||||||
|
9BD82BFE0B01333D0055103E /* minidump_generator_test.cc */,
|
||||||
|
9B7CA8530B12989000CD3A1D /* minidump_file_writer_unittest.cc */,
|
||||||
|
);
|
||||||
|
name = Source;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
1AB674ADFE9D54B511CA2CBB /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
8DD76F6C0486A84900D96B5E /* generator_test */,
|
||||||
|
9BD82A9B0B00267E0055103E /* handler_test */,
|
||||||
|
9B7CA84E0B1297F200CD3A1D /* unit_test */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9B37CEEA0AF98EB600FA4BD4 /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9B37CEEB0AF98ECD00FA4BD4 /* CoreFoundation.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9BD82C040B0133420055103E /* Airbag */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9B35FF560B267D5F008DE8C7 /* convert_UTF.c */,
|
||||||
|
9B35FF570B267D5F008DE8C7 /* convert_UTF.h */,
|
||||||
|
9B35FF580B267D5F008DE8C7 /* string_conversion.cc */,
|
||||||
|
9B35FF590B267D5F008DE8C7 /* string_conversion.h */,
|
||||||
|
9BD82C090B0133520055103E /* exception_handler.cc */,
|
||||||
|
9BD82C0A0B0133520055103E /* exception_handler.h */,
|
||||||
|
9BD82C0B0B0133520055103E /* minidump_generator.cc */,
|
||||||
|
9BD82C0C0B0133520055103E /* minidump_generator.h */,
|
||||||
|
9BD82C230B01344C0055103E /* minidump_file_writer.cc */,
|
||||||
|
9BE3C01E0B0CE329009892DF /* minidump_file_writer-inl.h */,
|
||||||
|
9BD82C240B01344C0055103E /* minidump_file_writer.h */,
|
||||||
|
9BD82C2B0B01345E0055103E /* string_utilities.cc */,
|
||||||
|
9BD82C2C0B01345E0055103E /* string_utilities.h */,
|
||||||
|
);
|
||||||
|
name = Airbag;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
8DD76F620486A84900D96B5E /* generator_test */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 1DEB923108733DC60010E9CD /* Build configuration list for PBXNativeTarget "generator_test" */;
|
||||||
|
buildPhases = (
|
||||||
|
8DD76F640486A84900D96B5E /* Sources */,
|
||||||
|
8DD76F660486A84900D96B5E /* Frameworks */,
|
||||||
|
8DD76F690486A84900D96B5E /* CopyFiles */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = generator_test;
|
||||||
|
productInstallPath = "$(HOME)/bin";
|
||||||
|
productName = MinidumpWriter;
|
||||||
|
productReference = 8DD76F6C0486A84900D96B5E /* generator_test */;
|
||||||
|
productType = "com.apple.product-type.tool";
|
||||||
|
};
|
||||||
|
9B7CA84D0B1297F200CD3A1D /* unit_test */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 9B7CA8500B12984300CD3A1D /* Build configuration list for PBXNativeTarget "unit_test" */;
|
||||||
|
buildPhases = (
|
||||||
|
9B7CA84B0B1297F200CD3A1D /* Sources */,
|
||||||
|
9B7CA84C0B1297F200CD3A1D /* Frameworks */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = unit_test;
|
||||||
|
productName = "filewriter unit test";
|
||||||
|
productReference = 9B7CA84E0B1297F200CD3A1D /* unit_test */;
|
||||||
|
productType = "com.apple.product-type.tool";
|
||||||
|
};
|
||||||
|
9BD82A9A0B00267E0055103E /* handler_test */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 9BD82AA60B0026BF0055103E /* Build configuration list for PBXNativeTarget "handler_test" */;
|
||||||
|
buildPhases = (
|
||||||
|
9BD82A980B00267E0055103E /* Sources */,
|
||||||
|
9BD82A990B00267E0055103E /* Frameworks */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = handler_test;
|
||||||
|
productName = ExceptionTester;
|
||||||
|
productReference = 9BD82A9B0B00267E0055103E /* handler_test */;
|
||||||
|
productType = "com.apple.product-type.tool";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
08FB7793FE84155DC02AAC07 /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
buildConfigurationList = 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "minidump_test" */;
|
||||||
|
hasScannedForEncodings = 1;
|
||||||
|
mainGroup = 08FB7794FE84155DC02AAC07 /* MinidumpWriter */;
|
||||||
|
projectDirPath = "";
|
||||||
|
targets = (
|
||||||
|
8DD76F620486A84900D96B5E /* generator_test */,
|
||||||
|
9BD82A9A0B00267E0055103E /* handler_test */,
|
||||||
|
9B7CA84D0B1297F200CD3A1D /* unit_test */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
8DD76F640486A84900D96B5E /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
9BD82C020B01333D0055103E /* minidump_generator_test.cc in Sources */,
|
||||||
|
9BD82C0F0B0133520055103E /* exception_handler.cc in Sources */,
|
||||||
|
9BD82C110B0133520055103E /* minidump_generator.cc in Sources */,
|
||||||
|
9BD82C260B01344C0055103E /* minidump_file_writer.cc in Sources */,
|
||||||
|
9BD82C2E0B01345E0055103E /* string_utilities.cc in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
9B7CA84B0B1297F200CD3A1D /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
9B7CA8540B12989000CD3A1D /* minidump_file_writer_unittest.cc in Sources */,
|
||||||
|
9B7CA8550B1298A100CD3A1D /* minidump_file_writer.cc in Sources */,
|
||||||
|
9BC1D2940B336F2300F2A2B4 /* convert_UTF.c in Sources */,
|
||||||
|
9BC1D2950B336F2500F2A2B4 /* string_conversion.cc in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
9BD82A980B00267E0055103E /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
9BD82BFF0B01333D0055103E /* exception_handler_test.cc in Sources */,
|
||||||
|
9BD82C0D0B0133520055103E /* exception_handler.cc in Sources */,
|
||||||
|
9BD82C0E0B0133520055103E /* minidump_generator.cc in Sources */,
|
||||||
|
9BD82C250B01344C0055103E /* minidump_file_writer.cc in Sources */,
|
||||||
|
9BD82C2D0B01345E0055103E /* string_utilities.cc in Sources */,
|
||||||
|
9B35FF5A0B267D5F008DE8C7 /* convert_UTF.c in Sources */,
|
||||||
|
9B35FF5B0B267D5F008DE8C7 /* string_conversion.cc in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
1DEB923208733DC60010E9CD /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
GCC_CW_ASM_SYNTAX = NO;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_ENABLE_FIX_AND_CONTINUE = YES;
|
||||||
|
GCC_ENABLE_PASCAL_STRINGS = NO;
|
||||||
|
GCC_MODEL_TUNING = G5;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_THREADSAFE_STATICS = NO;
|
||||||
|
INSTALL_PATH = "$(HOME)/bin";
|
||||||
|
PRODUCT_NAME = generator_test;
|
||||||
|
USER_HEADER_SEARCH_PATHS = "../../../** $(inherited)";
|
||||||
|
ZERO_LINK = NO;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
1DEB923308733DC60010E9CD /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ARCHS = (
|
||||||
|
ppc,
|
||||||
|
i386,
|
||||||
|
);
|
||||||
|
GCC_CW_ASM_SYNTAX = NO;
|
||||||
|
GCC_ENABLE_PASCAL_STRINGS = NO;
|
||||||
|
GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
|
||||||
|
GCC_MODEL_TUNING = G5;
|
||||||
|
GCC_THREADSAFE_STATICS = NO;
|
||||||
|
INSTALL_PATH = "$(HOME)/bin";
|
||||||
|
PRODUCT_NAME = generator_test;
|
||||||
|
USER_HEADER_SEARCH_PATHS = "../../../** $(inherited)";
|
||||||
|
ZERO_LINK = NO;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
1DEB923608733DC60010E9CD /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
PREBINDING = NO;
|
||||||
|
SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
1DEB923708733DC60010E9CD /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
PREBINDING = NO;
|
||||||
|
SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
9B7CA8510B12984300CD3A1D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_ENABLE_FIX_AND_CONTINUE = YES;
|
||||||
|
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
||||||
|
GCC_MODEL_TUNING = G5;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
INSTALL_PATH = "$(HOME)/bin";
|
||||||
|
PREBINDING = NO;
|
||||||
|
PRODUCT_NAME = unit_test;
|
||||||
|
USER_HEADER_SEARCH_PATHS = "../../../** $(inherited)";
|
||||||
|
ZERO_LINK = NO;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
9B7CA8520B12984300CD3A1D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
COPY_PHASE_STRIP = YES;
|
||||||
|
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||||
|
GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
|
||||||
|
GCC_MODEL_TUNING = G5;
|
||||||
|
INSTALL_PATH = "$(HOME)/bin";
|
||||||
|
PREBINDING = NO;
|
||||||
|
PRODUCT_NAME = unit_test;
|
||||||
|
USER_HEADER_SEARCH_PATHS = "../../../** $(inherited)";
|
||||||
|
ZERO_LINK = NO;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
9BD82AA70B0026BF0055103E /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ARCHS = (
|
||||||
|
"$(NATIVE_ARCH)",
|
||||||
|
i386,
|
||||||
|
);
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_ENABLE_FIX_AND_CONTINUE = YES;
|
||||||
|
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
||||||
|
GCC_MODEL_TUNING = G5;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||||
|
INSTALL_PATH = "$(HOME)/bin";
|
||||||
|
OTHER_CFLAGS = "-Wall";
|
||||||
|
PREBINDING = NO;
|
||||||
|
PRODUCT_NAME = handler_test;
|
||||||
|
USER_HEADER_SEARCH_PATHS = "../../.. $(inherited)";
|
||||||
|
ZERO_LINK = NO;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
9BD82AA80B0026BF0055103E /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ARCHS = (
|
||||||
|
"$(NATIVE_ARCH)",
|
||||||
|
i386,
|
||||||
|
);
|
||||||
|
COPY_PHASE_STRIP = YES;
|
||||||
|
GCC_ENABLE_FIX_AND_CONTINUE = NO;
|
||||||
|
GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
|
||||||
|
GCC_MODEL_TUNING = G5;
|
||||||
|
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||||
|
INSTALL_PATH = "$(HOME)/bin";
|
||||||
|
OTHER_CFLAGS = "-Wall";
|
||||||
|
PREBINDING = NO;
|
||||||
|
PRODUCT_NAME = handler_test;
|
||||||
|
USER_HEADER_SEARCH_PATHS = "../../.. $(inherited)";
|
||||||
|
ZERO_LINK = NO;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
1DEB923108733DC60010E9CD /* Build configuration list for PBXNativeTarget "generator_test" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
1DEB923208733DC60010E9CD /* Debug */,
|
||||||
|
1DEB923308733DC60010E9CD /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "minidump_test" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
1DEB923608733DC60010E9CD /* Debug */,
|
||||||
|
1DEB923708733DC60010E9CD /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
9B7CA8500B12984300CD3A1D /* Build configuration list for PBXNativeTarget "unit_test" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
9B7CA8510B12984300CD3A1D /* Debug */,
|
||||||
|
9B7CA8520B12984300CD3A1D /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
9BD82AA60B0026BF0055103E /* Build configuration list for PBXNativeTarget "handler_test" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
9BD82AA70B0026BF0055103E /* Debug */,
|
||||||
|
9BD82AA80B0026BF0055103E /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 08FB7793FE84155DC02AAC07 /* Project object */;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user