From 5ac2b9a569890f165478f91670dcdd553ce2d10e Mon Sep 17 00:00:00 2001
From: waylonis <waylonis@4c0a9323-5329-0410-9bdc-e9ce6186880e>
Date: Wed, 20 Dec 2006 17:41:55 +0000
Subject: 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 ++++++
 src/client/mac/handler/exception_handler_test.cc   | 107 ++++
 src/client/mac/handler/minidump_generator.cc       | 694 +++++++++++++++++++++
 src/client/mac/handler/minidump_generator.h        | 122 ++++
 src/client/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 <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 */;
+}
-- 
cgit v1.2.1