aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/mac/handler/exception_handler.cc497
-rw-r--r--src/client/mac/handler/exception_handler.h188
-rw-r--r--src/client/mac/handler/exception_handler_test.cc107
-rw-r--r--src/client/mac/handler/minidump_generator.cc694
-rw-r--r--src/client/mac/handler/minidump_generator.h122
-rw-r--r--src/client/mac/handler/minidump_generator_test.cc79
-rw-r--r--src/client/mac/handler/minidump_test.xcodeproj/project.pbxproj447
7 files changed, 2134 insertions, 0 deletions
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 <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
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 <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__
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 <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;
+}
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 <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
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 <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__
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 <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;
+}
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 = "<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 */;
+}