exception_handler.cc 29.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860
// 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 <mach/exc.h>
#include <mach/mig.h>
#include <pthread.h>
#include <signal.h>
#include <TargetConditionals.h>

#include <map>

#include "client/mac/handler/exception_handler.h"
#include "client/mac/handler/minidump_generator.h"
#include "common/mac/macho_utilities.h"
#include "common/mac/scoped_task_suspend-inl.h"
#include "google_breakpad/common/minidump_exception_mac.h"

#ifndef __EXCEPTIONS
// This file uses C++ try/catch (but shouldn't). Duplicate the macros from
// <c++/4.2.1/exception_defines.h> allowing this file to work properly with
// exceptions disabled even when other C++ libraries are used. #undef the try
// and catch macros first in case libstdc++ is in use and has already provided
// its own definitions.
#undef try
#define try       if (true)
#undef catch
#define catch(X)  if (false)
#endif  // __EXCEPTIONS

#ifndef USE_PROTECTED_ALLOCATIONS
#if TARGET_OS_IPHONE
#define USE_PROTECTED_ALLOCATIONS 1
#else
#define USE_PROTECTED_ALLOCATIONS 0
#endif
#endif

// If USE_PROTECTED_ALLOCATIONS is activated then the
// gBreakpadAllocator needs to be setup in other code
// ahead of time.  Please see ProtectedMemoryAllocator.h
// for more details.
#if USE_PROTECTED_ALLOCATIONS
  #include "protected_memory_allocator.h"
  extern ProtectedMemoryAllocator *gBreakpadAllocator;
#endif

namespace google_breakpad {

static union {
#if USE_PROTECTED_ALLOCATIONS
#if defined PAGE_MAX_SIZE
  char protected_buffer[PAGE_MAX_SIZE] __attribute__((aligned(PAGE_MAX_SIZE)));
#else
  char protected_buffer[PAGE_SIZE] __attribute__((aligned(PAGE_SIZE)));
#endif  // defined PAGE_MAX_SIZE
#endif  // USE_PROTECTED_ALLOCATIONS
  google_breakpad::ExceptionHandler *handler;
} gProtectedData;

using std::map;

// These structures and techniques are illustrated in
// Mac OS X Internals, Amit Singh, ch 9.7
struct ExceptionMessage {
  mach_msg_header_t           header;
  mach_msg_body_t             body;
  mach_msg_port_descriptor_t  thread;
  mach_msg_port_descriptor_t  task;
  NDR_record_t                ndr;
  exception_type_t            exception;
  mach_msg_type_number_t      code_count;
  integer_t                   code[EXCEPTION_CODE_MAX];
  char                        padding[512];
};

struct ExceptionParameters {
  ExceptionParameters() : count(0) {}
  mach_msg_type_number_t count;
  exception_mask_t masks[EXC_TYPES_COUNT];
  mach_port_t ports[EXC_TYPES_COUNT];
  exception_behavior_t behaviors[EXC_TYPES_COUNT];
  thread_state_flavor_t flavors[EXC_TYPES_COUNT];
};

struct ExceptionReplyMessage {
  mach_msg_header_t  header;
  NDR_record_t       ndr;
  kern_return_t      return_code;
};

// Only catch these three exceptions.  The other ones are nebulously defined
// and may result in treating a non-fatal exception as fatal.
exception_mask_t s_exception_mask = EXC_MASK_BAD_ACCESS |
EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT;

#if !TARGET_OS_IPHONE
extern "C" {
  // Forward declarations for functions that need "C" style compilation
  boolean_t exc_server(mach_msg_header_t* request,
                       mach_msg_header_t* reply);

  // This symbol must be visible to dlsym() - see
  // https://bugs.chromium.org/p/google-breakpad/issues/detail?id=345 for details.
  kern_return_t catch_exception_raise(mach_port_t target_port,
                                      mach_port_t failed_thread,
                                      mach_port_t task,
                                      exception_type_t exception,
                                      exception_data_t code,
                                      mach_msg_type_number_t code_count)
      __attribute__((visibility("default")));
}
#endif

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);

#if TARGET_OS_IPHONE
// Implementation is based on the implementation generated by mig.
boolean_t breakpad_exc_server(mach_msg_header_t* InHeadP,
                              mach_msg_header_t* OutHeadP) {
  OutHeadP->msgh_bits =
      MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(InHeadP->msgh_bits), 0);
  OutHeadP->msgh_remote_port = InHeadP->msgh_remote_port;
  /* Minimal size: routine() will update it if different */
  OutHeadP->msgh_size = (mach_msg_size_t)sizeof(mig_reply_error_t);
  OutHeadP->msgh_local_port = MACH_PORT_NULL;
  OutHeadP->msgh_id = InHeadP->msgh_id + 100;

  if (InHeadP->msgh_id != 2401) {
    ((mig_reply_error_t*)OutHeadP)->NDR = NDR_record;
    ((mig_reply_error_t*)OutHeadP)->RetCode = MIG_BAD_ID;
    return FALSE;
  }

#ifdef  __MigPackStructs
#pragma pack(4)
#endif
  typedef struct {
    mach_msg_header_t Head;
    /* start of the kernel processed data */
    mach_msg_body_t msgh_body;
    mach_msg_port_descriptor_t thread;
    mach_msg_port_descriptor_t task;
    /* end of the kernel processed data */
    NDR_record_t NDR;
    exception_type_t exception;
    mach_msg_type_number_t codeCnt;
    integer_t code[2];
    mach_msg_trailer_t trailer;
  } Request;

  typedef struct {
    mach_msg_header_t Head;
    NDR_record_t NDR;
    kern_return_t RetCode;
  } Reply;
#ifdef  __MigPackStructs
#pragma pack()
#endif

  Request* In0P = (Request*)InHeadP;
  Reply* OutP = (Reply*)OutHeadP;

  if (In0P->task.name != mach_task_self()) {
    return FALSE;
  }
  OutP->RetCode = ForwardException(In0P->task.name,
                                   In0P->thread.name,
                                   In0P->exception,
                                   In0P->code,
                                   In0P->codeCnt);
  OutP->NDR = NDR_record;
  return TRUE;
}
#else
boolean_t breakpad_exc_server(mach_msg_header_t* request,
                              mach_msg_header_t* reply) {
  return exc_server(request, reply);
}

// Callback from exc_server()
kern_return_t catch_exception_raise(mach_port_t port, mach_port_t failed_thread,
                                    mach_port_t task,
                                    exception_type_t exception,
                                    exception_data_t code,
                                    mach_msg_type_number_t code_count) {
  if (task != mach_task_self()) {
    return KERN_FAILURE;
  }
  return ForwardException(task, failed_thread, exception, code, code_count);
}
#endif

ExceptionHandler::ExceptionHandler(const string &dump_path,
                                   FilterCallback filter,
                                   MinidumpCallback callback,
                                   void* callback_context,
                                   bool install_handler,
                                   const char* port_name)
    : dump_path_(),
      filter_(filter),
      callback_(callback),
      callback_context_(callback_context),
      directCallback_(NULL),
      handler_thread_(NULL),
      handler_port_(MACH_PORT_NULL),
      previous_(NULL),
      installed_exception_handler_(false),
      is_in_teardown_(false),
      last_minidump_write_result_(false),
      use_minidump_write_mutex_(false) {
  // This will update to the ID and C-string pointers
  set_dump_path(dump_path);
  MinidumpGenerator::GatherSystemInformation();
#if !TARGET_OS_IPHONE
  if (port_name)
    crash_generation_client_.reset(new CrashGenerationClient(port_name));
#endif
  Setup(install_handler);
}

// special constructor if we want to bypass minidump writing and
// simply get a callback with the exception information
ExceptionHandler::ExceptionHandler(DirectCallback callback,
                                   void* callback_context,
                                   bool install_handler)
    : dump_path_(),
      filter_(NULL),
      callback_(NULL),
      callback_context_(callback_context),
      directCallback_(callback),
      handler_thread_(NULL),
      handler_port_(MACH_PORT_NULL),
      previous_(NULL),
      installed_exception_handler_(false),
      is_in_teardown_(false),
      last_minidump_write_result_(false),
      use_minidump_write_mutex_(false) {
  MinidumpGenerator::GatherSystemInformation();
  Setup(install_handler);
}

ExceptionHandler::~ExceptionHandler() {
  Teardown();
}

bool ExceptionHandler::WriteMinidump(bool write_exception_stream) {
  // If we're currently writing, just return
  if (use_minidump_write_mutex_)
    return false;

  use_minidump_write_mutex_ = true;
  last_minidump_write_result_ = false;

  // Lock the mutex.  Since we just created it, this will return immediately.
  if (pthread_mutex_lock(&minidump_write_mutex_) == 0) {
    // Send an empty message to the handle port so that a minidump will
    // be written
    bool result = SendMessageToHandlerThread(write_exception_stream ?
                                               kWriteDumpWithExceptionMessage :
                                               kWriteDumpMessage);
    if (!result) {
      pthread_mutex_unlock(&minidump_write_mutex_);
      return false;
    }

    // Wait for the minidump writer to complete its writing.  It will unlock
    // the mutex when completed
    pthread_mutex_lock(&minidump_write_mutex_);
  }

  use_minidump_write_mutex_ = false;
  UpdateNextID();
  return last_minidump_write_result_;
}

// static
bool ExceptionHandler::WriteMinidump(const string &dump_path,
                                     bool write_exception_stream,
                                     MinidumpCallback callback,
                                     void* callback_context) {
  ExceptionHandler handler(dump_path, NULL, callback, callback_context, false,
                           NULL);
  return handler.WriteMinidump(write_exception_stream);
}

// static
bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child,
                                             mach_port_t child_blamed_thread,
                                             const string &dump_path,
                                             MinidumpCallback callback,
                                             void* callback_context) {
  ScopedTaskSuspend suspend(child);

  MinidumpGenerator generator(child, MACH_PORT_NULL);
  string dump_id;
  string dump_filename = generator.UniqueNameInDirectory(dump_path, &dump_id);

  generator.SetExceptionInformation(EXC_BREAKPOINT,
#if defined(__i386__) || defined(__x86_64__)
                                    EXC_I386_BPT,
#elif defined(__ppc__) || defined(__ppc64__)
                                    EXC_PPC_BREAKPOINT,
#elif defined(__arm__) || defined(__aarch64__)
                                    EXC_ARM_BREAKPOINT,
#else
#error architecture not supported
#endif
                                    0,
                                    child_blamed_thread);
  bool result = generator.Write(dump_filename.c_str());

  if (callback) {
    return callback(dump_path.c_str(), dump_id.c_str(),
                    callback_context, result);
  }
  return result;
}

bool ExceptionHandler::WriteMinidumpWithException(
    int exception_type,
    int exception_code,
    int exception_subcode,
    breakpad_ucontext_t* task_context,
    mach_port_t thread_name,
    bool exit_after_write,
    bool report_current_thread) {
  bool result = false;

#if TARGET_OS_IPHONE
  // _exit() should never be called on iOS.
  exit_after_write = false;
#endif

  if (directCallback_) {
    if (directCallback_(callback_context_,
                        exception_type,
                        exception_code,
                        exception_subcode,
                        thread_name) ) {
      if (exit_after_write)
        _exit(exception_type);
    }
#if !TARGET_OS_IPHONE
  } else if (IsOutOfProcess()) {
    if (exception_type && exception_code) {
      // If this is a real exception, give the filter (if any) a chance to
      // decide if this should be sent.
      if (filter_ && !filter_(callback_context_))
        return false;
      result = crash_generation_client_->RequestDumpForException(
          exception_type,
          exception_code,
          exception_subcode,
          thread_name);
      if (result && exit_after_write) {
        _exit(exception_type);
      }
    }
#endif
  } else {
    string minidump_id;

    // Putting the MinidumpGenerator in its own context will ensure that the
    // destructor is executed, closing the newly created minidump file.
    if (!dump_path_.empty()) {
      MinidumpGenerator md(mach_task_self(),
                           report_current_thread ? MACH_PORT_NULL :
                                                   mach_thread_self());
      md.SetTaskContext(task_context);
      if (exception_type && exception_code) {
        // If this is a real exception, give the filter (if any) a chance to
        // decide if this should be sent.
        if (filter_ && !filter_(callback_context_))
          return false;

        md.SetExceptionInformation(exception_type, exception_code,
                                   exception_subcode, thread_name);
      }

      result = md.Write(next_minidump_path_c_);
    }

    // Call user specified callback (if any)
    if (callback_) {
      // If the user callback returned true and we're handling an exception
      // (rather than just writing out the file), then we should exit without
      // forwarding the exception to the next handler.
      if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
                    result)) {
        if (exit_after_write)
          _exit(exception_type);
      }
    }
  }

  return result;
}

kern_return_t ForwardException(mach_port_t task, mach_port_t failed_thread,
                               exception_type_t exception,
                               exception_data_t code,
                               mach_msg_type_number_t code_count) {
  // At this time, we should have called Uninstall() on the exception handler
  // so that the current exception ports are the ones that we should be
  // forwarding to.
  ExceptionParameters current;

  current.count = EXC_TYPES_COUNT;
  mach_port_t current_task = mach_task_self();
  task_get_exception_ports(current_task,
                           s_exception_mask,
                           current.masks,
                           &current.count,
                           current.ports,
                           current.behaviors,
                           current.flavors);

  // Find the first exception handler that matches the exception
  unsigned int found;
  for (found = 0; found < current.count; ++found) {
    if (current.masks[found] & (1 << exception)) {
      break;
    }
  }

  // Nothing to forward
  if (found == current.count) {
    fprintf(stderr, "** No previous ports for forwarding!! \n");
    exit(KERN_FAILURE);
  }

  mach_port_t target_port = current.ports[found];
  exception_behavior_t target_behavior = current.behaviors[found];

  kern_return_t result;
  // TODO: Handle the case where |target_behavior| has MACH_EXCEPTION_CODES
  // set. https://bugs.chromium.org/p/google-breakpad/issues/detail?id=551
  switch (target_behavior) {
    case EXCEPTION_DEFAULT:
      result = exception_raise(target_port, failed_thread, task, exception,
                               code, code_count);
      break;
    default:
      fprintf(stderr, "** Unknown exception behavior: %d\n", target_behavior);
      result = KERN_FAILURE;
      break;
  }

  return result;
}

// static
void* ExceptionHandler::WaitForMessage(void* exception_handler_class) {
  ExceptionHandler* self =
    reinterpret_cast<ExceptionHandler*>(exception_handler_class);
  ExceptionMessage receive;

  // Wait for the exception info
  while (1) {
    receive.header.msgh_local_port = self->handler_port_;
    receive.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(receive));
    kern_return_t result = mach_msg(&(receive.header),
                                    MACH_RCV_MSG | MACH_RCV_LARGE, 0,
                                    receive.header.msgh_size,
                                    self->handler_port_,
                                    MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);


    if (result == KERN_SUCCESS) {
      // Uninstall our handler so that we don't get in a loop if the process of
      // writing out a minidump causes an exception.  However, if the exception
      // was caused by a fork'd process, don't uninstall things

      // If the actual exception code is zero, then we're calling this handler
      // in a way that indicates that we want to either exit this thread or
      // generate a minidump
      //
      // While reporting, all threads (except this one) must be suspended
      // to avoid misleading stacks.  If appropriate they will be resumed
      // afterwards.
      if (!receive.exception) {
        // Don't touch self, since this message could have been sent
        // from its destructor.
        if (receive.header.msgh_id == kShutdownMessage)
          return NULL;

        self->SuspendThreads();

#if USE_PROTECTED_ALLOCATIONS
        if (gBreakpadAllocator)
          gBreakpadAllocator->Unprotect();
#endif

        mach_port_t thread = MACH_PORT_NULL;
        int exception_type = 0;
        int exception_code = 0;
        if (receive.header.msgh_id == kWriteDumpWithExceptionMessage) {
          thread = receive.thread.name;
          exception_type = EXC_BREAKPOINT;
#if defined(__i386__) || defined(__x86_64__)
          exception_code = EXC_I386_BPT;
#elif defined(__ppc__) || defined(__ppc64__)
          exception_code = EXC_PPC_BREAKPOINT;
#elif defined(__arm__) || defined(__aarch64__)
          exception_code = EXC_ARM_BREAKPOINT;
#else
#error architecture not supported
#endif
        }

        // Write out the dump and save the result for later retrieval
        self->last_minidump_write_result_ =
          self->WriteMinidumpWithException(exception_type, exception_code,
                                           0, NULL, thread,
                                           false, false);

#if USE_PROTECTED_ALLOCATIONS
        if (gBreakpadAllocator)
          gBreakpadAllocator->Protect();
#endif

        self->ResumeThreads();

        if (self->use_minidump_write_mutex_)
          pthread_mutex_unlock(&self->minidump_write_mutex_);
      } else {
        // When forking a child process with the exception handler installed,
        // if the child crashes, it will send the exception back to the parent
        // process.  The check for task == self_task() ensures that only
        // exceptions that occur in the parent process are caught and
        // processed.  If the exception was not caused by this task, we
        // still need to call into the exception server and have it return
        // KERN_FAILURE (see catch_exception_raise) in order for the kernel
        // to move onto the host exception handler for the child task
        if (receive.task.name == mach_task_self()) {
          self->SuspendThreads();

#if USE_PROTECTED_ALLOCATIONS
        if (gBreakpadAllocator)
          gBreakpadAllocator->Unprotect();
#endif

        int subcode = 0;
        if (receive.exception == EXC_BAD_ACCESS && receive.code_count > 1)
          subcode = receive.code[1];

        // Generate the minidump with the exception data.
        self->WriteMinidumpWithException(receive.exception, receive.code[0],
                                         subcode, NULL, receive.thread.name,
                                         true, false);

#if USE_PROTECTED_ALLOCATIONS
        // This may have become protected again within
        // WriteMinidumpWithException, but it needs to be unprotected for
        // UninstallHandler.
        if (gBreakpadAllocator)
          gBreakpadAllocator->Unprotect();
#endif

        self->UninstallHandler(true);

#if USE_PROTECTED_ALLOCATIONS
        if (gBreakpadAllocator)
          gBreakpadAllocator->Protect();
#endif
        }
        // Pass along the exception to the server, which will setup the
        // message and call catch_exception_raise() and put the return
        // code into the reply.
        ExceptionReplyMessage reply;
        if (!breakpad_exc_server(&receive.header, &reply.header))
          exit(1);

        // Send a reply and exit
        mach_msg(&(reply.header), MACH_SEND_MSG,
                 reply.header.msgh_size, 0, MACH_PORT_NULL,
                 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
      }
    }
  }

  return NULL;
}

// static
void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
#if USE_PROTECTED_ALLOCATIONS
  if (gBreakpadAllocator)
    gBreakpadAllocator->Unprotect();
#endif
  gProtectedData.handler->WriteMinidumpWithException(
      EXC_SOFTWARE,
      MD_EXCEPTION_CODE_MAC_ABORT,
      0,
      static_cast<breakpad_ucontext_t*>(uc),
      mach_thread_self(),
      true,
      true);
#if USE_PROTECTED_ALLOCATIONS
  if (gBreakpadAllocator)
    gBreakpadAllocator->Protect();
#endif
}

bool ExceptionHandler::InstallHandler() {
  // If a handler is already installed, something is really wrong.
  if (gProtectedData.handler != NULL) {
    return false;
  }
  if (!IsOutOfProcess()) {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask, SIGABRT);
    sa.sa_sigaction = ExceptionHandler::SignalHandler;
    sa.sa_flags = SA_SIGINFO;

    scoped_ptr<struct sigaction> old(new struct sigaction);
    if (sigaction(SIGABRT, &sa, old.get()) == -1) {
      return false;
    }
    old_handler_.swap(old);
    gProtectedData.handler = this;
#if USE_PROTECTED_ALLOCATIONS
    assert(((size_t)(gProtectedData.protected_buffer) & PAGE_MASK) == 0);
    mprotect(gProtectedData.protected_buffer, PAGE_SIZE, PROT_READ);
#endif
  }

  try {
#if USE_PROTECTED_ALLOCATIONS
    previous_ = new (gBreakpadAllocator->Allocate(sizeof(ExceptionParameters)) )
      ExceptionParameters();
#else
    previous_ = new ExceptionParameters();
#endif
  }
  catch (std::bad_alloc) {
    return false;
  }

  // Save the current exception ports so that we can forward to them
  previous_->count = EXC_TYPES_COUNT;
  mach_port_t current_task = mach_task_self();
  kern_return_t result = task_get_exception_ports(current_task,
                                                  s_exception_mask,
                                                  previous_->masks,
                                                  &previous_->count,
                                                  previous_->ports,
                                                  previous_->behaviors,
                                                  previous_->flavors);

  // Setup the exception ports on this task
  if (result == KERN_SUCCESS)
    result = task_set_exception_ports(current_task, s_exception_mask,
                                      handler_port_, EXCEPTION_DEFAULT,
                                      THREAD_STATE_NONE);

  installed_exception_handler_ = (result == KERN_SUCCESS);

  return installed_exception_handler_;
}

bool ExceptionHandler::UninstallHandler(bool in_exception) {
  kern_return_t result = KERN_SUCCESS;

  if (old_handler_.get()) {
    sigaction(SIGABRT, old_handler_.get(), NULL);
#if USE_PROTECTED_ALLOCATIONS
    mprotect(gProtectedData.protected_buffer, PAGE_SIZE,
        PROT_READ | PROT_WRITE);
#endif
    old_handler_.reset();
    gProtectedData.handler = NULL;
  }

  if (installed_exception_handler_) {
    mach_port_t current_task = mach_task_self();

    // Restore the previous ports
    for (unsigned int i = 0; i < previous_->count; ++i) {
       result = task_set_exception_ports(current_task, previous_->masks[i],
                                        previous_->ports[i],
                                        previous_->behaviors[i],
                                        previous_->flavors[i]);
      if (result != KERN_SUCCESS)
        return false;
    }

    // this delete should NOT happen if an exception just occurred!
    if (!in_exception) {
#if USE_PROTECTED_ALLOCATIONS
      previous_->~ExceptionParameters();
#else
      delete previous_;
#endif
    }

    previous_ = NULL;
    installed_exception_handler_ = false;
  }

  return result == KERN_SUCCESS;
}

bool ExceptionHandler::Setup(bool install_handler) {
  if (pthread_mutex_init(&minidump_write_mutex_, NULL))
    return false;

  // Create a receive right
  mach_port_t current_task = mach_task_self();
  kern_return_t result = mach_port_allocate(current_task,
                                            MACH_PORT_RIGHT_RECEIVE,
                                            &handler_port_);
  // Add send right
  if (result == KERN_SUCCESS)
    result = mach_port_insert_right(current_task, handler_port_, handler_port_,
                                    MACH_MSG_TYPE_MAKE_SEND);

  if (install_handler && result == KERN_SUCCESS)
    if (!InstallHandler())
      return false;

  if (result == KERN_SUCCESS) {
    // Install the handler in its own thread, detached as we won't be joining.
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    int thread_create_result = pthread_create(&handler_thread_, &attr,
                                              &WaitForMessage, this);
    pthread_attr_destroy(&attr);
    result = thread_create_result ? KERN_FAILURE : KERN_SUCCESS;
  }

  return result == KERN_SUCCESS;
}

bool ExceptionHandler::Teardown() {
  kern_return_t result = KERN_SUCCESS;
  is_in_teardown_ = true;

  if (!UninstallHandler(false))
    return false;

  // Send an empty message so that the handler_thread exits
  if (SendMessageToHandlerThread(kShutdownMessage)) {
    mach_port_t current_task = mach_task_self();
    result = mach_port_deallocate(current_task, handler_port_);
    if (result != KERN_SUCCESS)
      return false;
  } else {
    return false;
  }

  handler_thread_ = NULL;
  handler_port_ = MACH_PORT_NULL;
  pthread_mutex_destroy(&minidump_write_mutex_);

  return result == KERN_SUCCESS;
}

bool ExceptionHandler::SendMessageToHandlerThread(
    HandlerThreadMessage message_id) {
  ExceptionMessage msg;
  memset(&msg, 0, sizeof(msg));
  msg.header.msgh_id = message_id;
  if (message_id == kWriteDumpMessage ||
      message_id == kWriteDumpWithExceptionMessage) {
    // Include this thread's port.
    msg.thread.name = mach_thread_self();
    msg.thread.disposition = MACH_MSG_TYPE_PORT_SEND;
    msg.thread.type = MACH_MSG_PORT_DESCRIPTOR;
  }
  msg.header.msgh_size = sizeof(msg) - sizeof(msg.padding);
  msg.header.msgh_remote_port = handler_port_;
  msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,
                                          MACH_MSG_TYPE_MAKE_SEND_ONCE);
  kern_return_t result = mach_msg(&(msg.header),
                                  MACH_SEND_MSG | MACH_SEND_TIMEOUT,
                                  msg.header.msgh_size, 0, 0,
                                  MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);

  return result == KERN_SUCCESS;
}

void ExceptionHandler::UpdateNextID() {
  next_minidump_path_ =
    (MinidumpGenerator::UniqueNameInDirectory(dump_path_, &next_minidump_id_));

  next_minidump_path_c_ = next_minidump_path_.c_str();
  next_minidump_id_c_ = next_minidump_id_.c_str();
}

bool ExceptionHandler::SuspendThreads() {
  thread_act_port_array_t   threads_for_task;
  mach_msg_type_number_t    thread_count;

  if (task_threads(mach_task_self(), &threads_for_task, &thread_count))
    return false;

  // suspend all of the threads except for this one
  for (unsigned int i = 0; i < thread_count; ++i) {
    if (threads_for_task[i] != mach_thread_self()) {
      if (thread_suspend(threads_for_task[i]))
        return false;
    }
  }

  return true;
}

bool ExceptionHandler::ResumeThreads() {
  thread_act_port_array_t   threads_for_task;
  mach_msg_type_number_t    thread_count;

  if (task_threads(mach_task_self(), &threads_for_task, &thread_count))
    return false;

  // resume all of the threads except for this one
  for (unsigned int i = 0; i < thread_count; ++i) {
    if (threads_for_task[i] != mach_thread_self()) {
      if (thread_resume(threads_for_task[i]))
        return false;
    }
  }

  return true;
}

}  // namespace google_breakpad