From 5ac2b9a569890f165478f91670dcdd553ce2d10e Mon Sep 17 00:00:00 2001 From: waylonis Date: Wed, 20 Dec 2006 17:41:55 +0000 Subject: [PATCH] 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 --- src/client/mac/handler/exception_handler.cc | 497 +++++++++++++ src/client/mac/handler/exception_handler.h | 188 +++++ .../mac/handler/exception_handler_test.cc | 107 +++ src/client/mac/handler/minidump_generator.cc | 694 ++++++++++++++++++ src/client/mac/handler/minidump_generator.h | 122 +++ .../mac/handler/minidump_generator_test.cc | 79 ++ .../minidump_test.xcodeproj/project.pbxproj | 447 +++++++++++ 7 files changed, 2134 insertions(+) create mode 100644 src/client/mac/handler/exception_handler.cc create mode 100644 src/client/mac/handler/exception_handler.h create mode 100644 src/client/mac/handler/exception_handler_test.cc create mode 100644 src/client/mac/handler/minidump_generator.cc create mode 100644 src/client/mac/handler/minidump_generator.h create mode 100644 src/client/mac/handler/minidump_generator_test.cc create mode 100644 src/client/mac/handler/minidump_test.xcodeproj/project.pbxproj diff --git a/src/client/mac/handler/exception_handler.cc b/src/client/mac/handler/exception_handler.cc new file mode 100644 index 00000000..fb8dd499 --- /dev/null +++ b/src/client/mac/handler/exception_handler.cc @@ -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 +#include + +#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 *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(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; + + (*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 diff --git a/src/client/mac/handler/exception_handler.h b/src/client/mac/handler/exception_handler.h new file mode 100644 index 00000000..0d1232aa --- /dev/null +++ b/src/client/mac/handler/exception_handler.h @@ -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 + +#include + +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 /.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__ diff --git a/src/client/mac/handler/exception_handler_test.cc b/src/client/mac/handler/exception_handler_test.cc new file mode 100644 index 00000000..0fbe1d82 --- /dev/null +++ b/src/client/mac/handler/exception_handler_test.cc @@ -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 +#include +#include + +#include + +#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; +} diff --git a/src/client/mac/handler/minidump_generator.cc b/src/client/mac/handler/minidump_generator.cc new file mode 100644 index 00000000..65b4e6f2 --- /dev/null +++ b/src/client/mac/handler/minidump_generator.cc @@ -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 + +#include +#include +#include +#include +#include + +#include + +#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 + (CFPropertyListCreateFromXMLData(NULL, data, kCFPropertyListImmutable, + NULL)); + if (!list) + return; + + CFStringRef build_version = static_cast + (CFDictionaryGetValue(list, CFSTR("ProductBuildVersion"))); + CFStringRef product_version = static_cast + (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 header(&writer_); + TypedMDRVA 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(&(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(&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(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(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(state); + + return machine_state->srr0; +} + +bool MinidumpGenerator::WriteContext(thread_state_data_t state, + MDLocationDescriptor *register_location) { + TypedMDRVA context(&writer_); + ppc_thread_state_t *machine_state = + reinterpret_cast(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(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(state); + + return machine_state->uts.ts32.eip; +} + +bool MinidumpGenerator::WriteContext(thread_state_data_t state, + MDLocationDescriptor *register_location) { + TypedMDRVA context(&writer_); + x86_thread_state_t *machine_state = + reinterpret_cast(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 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 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 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(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(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((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 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 = ""; + + 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 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 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 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 diff --git a/src/client/mac/handler/minidump_generator.h b/src/client/mac/handler/minidump_generator.h new file mode 100644 index 00000000..53c76041 --- /dev/null +++ b/src/client/mac/handler/minidump_generator.h @@ -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 + +#include + +#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 /.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__ diff --git a/src/client/mac/handler/minidump_generator_test.cc b/src/client/mac/handler/minidump_generator_test.cc new file mode 100644 index 00000000..465cf52e --- /dev/null +++ b/src/client/mac/handler/minidump_generator_test.cc @@ -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 + +#include +#include + +#include + +#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; +} diff --git a/src/client/mac/handler/minidump_test.xcodeproj/project.pbxproj b/src/client/mac/handler/minidump_test.xcodeproj/project.pbxproj new file mode 100644 index 00000000..45444f9d --- /dev/null +++ b/src/client/mac/handler/minidump_test.xcodeproj/project.pbxproj @@ -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 = ""; }; + 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 = ""; }; + 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 = ""; + }; + 08FB7795FE84155DC02AAC07 /* Source */ = { + isa = PBXGroup; + children = ( + 9BD82BFD0B01333D0055103E /* exception_handler_test.cc */, + 9BD82BFE0B01333D0055103E /* minidump_generator_test.cc */, + 9B7CA8530B12989000CD3A1D /* minidump_file_writer_unittest.cc */, + ); + name = Source; + sourceTree = ""; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 8DD76F6C0486A84900D96B5E /* generator_test */, + 9BD82A9B0B00267E0055103E /* handler_test */, + 9B7CA84E0B1297F200CD3A1D /* unit_test */, + ); + name = Products; + sourceTree = ""; + }; + 9B37CEEA0AF98EB600FA4BD4 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 9B37CEEB0AF98ECD00FA4BD4 /* CoreFoundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 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 = ""; + }; +/* 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 */; +}