1// Copyright (c) 2006, Google Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10//     * Redistributions in binary form must reproduce the above
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14//     * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30#include <mach/exc.h>
31#include <mach/mig.h>
32#include <pthread.h>
33#include <signal.h>
34#include <TargetConditionals.h>
35
36#include <map>
37
38#include "client/mac/handler/exception_handler.h"
39#include "client/mac/handler/minidump_generator.h"
40#include "common/mac/macho_utilities.h"
41#include "common/mac/scoped_task_suspend-inl.h"
42#include "google_breakpad/common/minidump_exception_mac.h"
43
44#ifndef __EXCEPTIONS
45// This file uses C++ try/catch (but shouldn't). Duplicate the macros from
46// <c++/4.2.1/exception_defines.h> allowing this file to work properly with
47// exceptions disabled even when other C++ libraries are used. #undef the try
48// and catch macros first in case libstdc++ is in use and has already provided
49// its own definitions.
50#undef try
51#define try       if (true)
52#undef catch
53#define catch(X)  if (false)
54#endif  // __EXCEPTIONS
55
56#ifndef USE_PROTECTED_ALLOCATIONS
57#if TARGET_OS_IPHONE
58#define USE_PROTECTED_ALLOCATIONS 1
59#else
60#define USE_PROTECTED_ALLOCATIONS 0
61#endif
62#endif
63
64// If USE_PROTECTED_ALLOCATIONS is activated then the
65// gBreakpadAllocator needs to be setup in other code
66// ahead of time.  Please see ProtectedMemoryAllocator.h
67// for more details.
68#if USE_PROTECTED_ALLOCATIONS
69  #include "protected_memory_allocator.h"
70  extern ProtectedMemoryAllocator *gBreakpadAllocator;
71#endif
72
73namespace google_breakpad {
74
75static union {
76#if USE_PROTECTED_ALLOCATIONS
77#if defined PAGE_MAX_SIZE
78  char protected_buffer[PAGE_MAX_SIZE] __attribute__((aligned(PAGE_MAX_SIZE)));
79#else
80  char protected_buffer[PAGE_SIZE] __attribute__((aligned(PAGE_SIZE)));
81#endif  // defined PAGE_MAX_SIZE
82#endif  // USE_PROTECTED_ALLOCATIONS
83  google_breakpad::ExceptionHandler *handler;
84} gProtectedData;
85
86using std::map;
87
88// These structures and techniques are illustrated in
89// Mac OS X Internals, Amit Singh, ch 9.7
90struct ExceptionMessage {
91  mach_msg_header_t           header;
92  mach_msg_body_t             body;
93  mach_msg_port_descriptor_t  thread;
94  mach_msg_port_descriptor_t  task;
95  NDR_record_t                ndr;
96  exception_type_t            exception;
97  mach_msg_type_number_t      code_count;
98  integer_t                   code[EXCEPTION_CODE_MAX];
99  char                        padding[512];
100};
101
102struct ExceptionParameters {
103  ExceptionParameters() : count(0) {}
104  mach_msg_type_number_t count;
105  exception_mask_t masks[EXC_TYPES_COUNT];
106  mach_port_t ports[EXC_TYPES_COUNT];
107  exception_behavior_t behaviors[EXC_TYPES_COUNT];
108  thread_state_flavor_t flavors[EXC_TYPES_COUNT];
109};
110
111struct ExceptionReplyMessage {
112  mach_msg_header_t  header;
113  NDR_record_t       ndr;
114  kern_return_t      return_code;
115};
116
117// Only catch these three exceptions.  The other ones are nebulously defined
118// and may result in treating a non-fatal exception as fatal.
119exception_mask_t s_exception_mask = EXC_MASK_BAD_ACCESS |
120EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT;
121
122#if !TARGET_OS_IPHONE
123extern "C" {
124  // Forward declarations for functions that need "C" style compilation
125  boolean_t exc_server(mach_msg_header_t* request,
126                       mach_msg_header_t* reply);
127
128  // This symbol must be visible to dlsym() - see
129  // http://code.google.com/p/google-breakpad/issues/detail?id=345 for details.
130  kern_return_t catch_exception_raise(mach_port_t target_port,
131                                      mach_port_t failed_thread,
132                                      mach_port_t task,
133                                      exception_type_t exception,
134                                      exception_data_t code,
135                                      mach_msg_type_number_t code_count)
136      __attribute__((visibility("default")));
137}
138#endif
139
140kern_return_t ForwardException(mach_port_t task,
141                               mach_port_t failed_thread,
142                               exception_type_t exception,
143                               exception_data_t code,
144                               mach_msg_type_number_t code_count);
145
146#if TARGET_OS_IPHONE
147// Implementation is based on the implementation generated by mig.
148boolean_t breakpad_exc_server(mach_msg_header_t* InHeadP,
149                              mach_msg_header_t* OutHeadP) {
150  OutHeadP->msgh_bits =
151      MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(InHeadP->msgh_bits), 0);
152  OutHeadP->msgh_remote_port = InHeadP->msgh_remote_port;
153  /* Minimal size: routine() will update it if different */
154  OutHeadP->msgh_size = (mach_msg_size_t)sizeof(mig_reply_error_t);
155  OutHeadP->msgh_local_port = MACH_PORT_NULL;
156  OutHeadP->msgh_id = InHeadP->msgh_id + 100;
157
158  if (InHeadP->msgh_id != 2401) {
159    ((mig_reply_error_t*)OutHeadP)->NDR = NDR_record;
160    ((mig_reply_error_t*)OutHeadP)->RetCode = MIG_BAD_ID;
161    return FALSE;
162  }
163
164#ifdef  __MigPackStructs
165#pragma pack(4)
166#endif
167  typedef struct {
168    mach_msg_header_t Head;
169    /* start of the kernel processed data */
170    mach_msg_body_t msgh_body;
171    mach_msg_port_descriptor_t thread;
172    mach_msg_port_descriptor_t task;
173    /* end of the kernel processed data */
174    NDR_record_t NDR;
175    exception_type_t exception;
176    mach_msg_type_number_t codeCnt;
177    integer_t code[2];
178    mach_msg_trailer_t trailer;
179  } Request;
180
181  typedef struct {
182    mach_msg_header_t Head;
183    NDR_record_t NDR;
184    kern_return_t RetCode;
185  } Reply;
186#ifdef  __MigPackStructs
187#pragma pack()
188#endif
189
190  Request* In0P = (Request*)InHeadP;
191  Reply* OutP = (Reply*)OutHeadP;
192
193  if (In0P->task.name != mach_task_self()) {
194    return FALSE;
195  }
196  OutP->RetCode = ForwardException(In0P->task.name,
197                                   In0P->thread.name,
198                                   In0P->exception,
199                                   In0P->code,
200                                   In0P->codeCnt);
201  OutP->NDR = NDR_record;
202  return TRUE;
203}
204#else
205boolean_t breakpad_exc_server(mach_msg_header_t* request,
206                              mach_msg_header_t* reply) {
207  return exc_server(request, reply);
208}
209
210// Callback from exc_server()
211kern_return_t catch_exception_raise(mach_port_t port, mach_port_t failed_thread,
212                                    mach_port_t task,
213                                    exception_type_t exception,
214                                    exception_data_t code,
215                                    mach_msg_type_number_t code_count) {
216  if (task != mach_task_self()) {
217    return KERN_FAILURE;
218  }
219  return ForwardException(task, failed_thread, exception, code, code_count);
220}
221#endif
222
223ExceptionHandler::ExceptionHandler(const string &dump_path,
224                                   FilterCallback filter,
225                                   MinidumpCallback callback,
226                                   void* callback_context,
227                                   bool install_handler,
228                                   const char* port_name)
229    : dump_path_(),
230      filter_(filter),
231      callback_(callback),
232      callback_context_(callback_context),
233      directCallback_(NULL),
234      handler_thread_(NULL),
235      handler_port_(MACH_PORT_NULL),
236      previous_(NULL),
237      installed_exception_handler_(false),
238      is_in_teardown_(false),
239      last_minidump_write_result_(false),
240      use_minidump_write_mutex_(false) {
241  // This will update to the ID and C-string pointers
242  set_dump_path(dump_path);
243  MinidumpGenerator::GatherSystemInformation();
244#if !TARGET_OS_IPHONE
245  if (port_name)
246    crash_generation_client_.reset(new CrashGenerationClient(port_name));
247#endif
248  Setup(install_handler);
249}
250
251// special constructor if we want to bypass minidump writing and
252// simply get a callback with the exception information
253ExceptionHandler::ExceptionHandler(DirectCallback callback,
254                                   void* callback_context,
255                                   bool install_handler)
256    : dump_path_(),
257      filter_(NULL),
258      callback_(NULL),
259      callback_context_(callback_context),
260      directCallback_(callback),
261      handler_thread_(NULL),
262      handler_port_(MACH_PORT_NULL),
263      previous_(NULL),
264      installed_exception_handler_(false),
265      is_in_teardown_(false),
266      last_minidump_write_result_(false),
267      use_minidump_write_mutex_(false) {
268  MinidumpGenerator::GatherSystemInformation();
269  Setup(install_handler);
270}
271
272ExceptionHandler::~ExceptionHandler() {
273  Teardown();
274}
275
276bool ExceptionHandler::WriteMinidump(bool write_exception_stream) {
277  // If we're currently writing, just return
278  if (use_minidump_write_mutex_)
279    return false;
280
281  use_minidump_write_mutex_ = true;
282  last_minidump_write_result_ = false;
283
284  // Lock the mutex.  Since we just created it, this will return immediately.
285  if (pthread_mutex_lock(&minidump_write_mutex_) == 0) {
286    // Send an empty message to the handle port so that a minidump will
287    // be written
288    bool result = SendMessageToHandlerThread(write_exception_stream ?
289                                               kWriteDumpWithExceptionMessage :
290                                               kWriteDumpMessage);
291    if (!result) {
292      pthread_mutex_unlock(&minidump_write_mutex_);
293      return false;
294    }
295
296    // Wait for the minidump writer to complete its writing.  It will unlock
297    // the mutex when completed
298    pthread_mutex_lock(&minidump_write_mutex_);
299  }
300
301  use_minidump_write_mutex_ = false;
302  UpdateNextID();
303  return last_minidump_write_result_;
304}
305
306// static
307bool ExceptionHandler::WriteMinidump(const string &dump_path,
308                                     bool write_exception_stream,
309                                     MinidumpCallback callback,
310                                     void* callback_context) {
311  ExceptionHandler handler(dump_path, NULL, callback, callback_context, false,
312                           NULL);
313  return handler.WriteMinidump(write_exception_stream);
314}
315
316// static
317bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child,
318                                             mach_port_t child_blamed_thread,
319                                             const string &dump_path,
320                                             MinidumpCallback callback,
321                                             void* callback_context) {
322  ScopedTaskSuspend suspend(child);
323
324  MinidumpGenerator generator(child, MACH_PORT_NULL);
325  string dump_id;
326  string dump_filename = generator.UniqueNameInDirectory(dump_path, &dump_id);
327
328  generator.SetExceptionInformation(EXC_BREAKPOINT,
329#if defined(__i386__) || defined(__x86_64__)
330                                    EXC_I386_BPT,
331#elif defined(__ppc__) || defined(__ppc64__)
332                                    EXC_PPC_BREAKPOINT,
333#elif defined(__arm__) || defined(__aarch64__)
334                                    EXC_ARM_BREAKPOINT,
335#else
336#error architecture not supported
337#endif
338                                    0,
339                                    child_blamed_thread);
340  bool result = generator.Write(dump_filename.c_str());
341
342  if (callback) {
343    return callback(dump_path.c_str(), dump_id.c_str(),
344                    callback_context, result);
345  }
346  return result;
347}
348
349bool ExceptionHandler::WriteMinidumpWithException(
350    int exception_type,
351    int exception_code,
352    int exception_subcode,
353    breakpad_ucontext_t* task_context,
354    mach_port_t thread_name,
355    bool exit_after_write,
356    bool report_current_thread) {
357  bool result = false;
358
359  if (directCallback_) {
360    if (directCallback_(callback_context_,
361                        exception_type,
362                        exception_code,
363                        exception_subcode,
364                        thread_name) ) {
365      if (exit_after_write)
366        _exit(exception_type);
367    }
368#if !TARGET_OS_IPHONE
369  } else if (IsOutOfProcess()) {
370    if (exception_type && exception_code) {
371      // If this is a real exception, give the filter (if any) a chance to
372      // decide if this should be sent.
373      if (filter_ && !filter_(callback_context_))
374        return false;
375      result = crash_generation_client_->RequestDumpForException(
376          exception_type,
377          exception_code,
378          exception_subcode,
379          thread_name);
380      if (result && exit_after_write) {
381        _exit(exception_type);
382      }
383    }
384#endif
385  } else {
386    string minidump_id;
387
388    // Putting the MinidumpGenerator in its own context will ensure that the
389    // destructor is executed, closing the newly created minidump file.
390    if (!dump_path_.empty()) {
391      MinidumpGenerator md(mach_task_self(),
392                           report_current_thread ? MACH_PORT_NULL :
393                                                   mach_thread_self());
394      md.SetTaskContext(task_context);
395      if (exception_type && exception_code) {
396        // If this is a real exception, give the filter (if any) a chance to
397        // decide if this should be sent.
398        if (filter_ && !filter_(callback_context_))
399          return false;
400
401        md.SetExceptionInformation(exception_type, exception_code,
402                                   exception_subcode, thread_name);
403      }
404
405      result = md.Write(next_minidump_path_c_);
406    }
407
408    // Call user specified callback (if any)
409    if (callback_) {
410      // If the user callback returned true and we're handling an exception
411      // (rather than just writing out the file), then we should exit without
412      // forwarding the exception to the next handler.
413      if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
414                    result)) {
415        if (exit_after_write)
416          _exit(exception_type);
417      }
418    }
419  }
420
421  return result;
422}
423
424kern_return_t ForwardException(mach_port_t task, mach_port_t failed_thread,
425                               exception_type_t exception,
426                               exception_data_t code,
427                               mach_msg_type_number_t code_count) {
428  // At this time, we should have called Uninstall() on the exception handler
429  // so that the current exception ports are the ones that we should be
430  // forwarding to.
431  ExceptionParameters current;
432
433  current.count = EXC_TYPES_COUNT;
434  mach_port_t current_task = mach_task_self();
435  task_get_exception_ports(current_task,
436                           s_exception_mask,
437                           current.masks,
438                           &current.count,
439                           current.ports,
440                           current.behaviors,
441                           current.flavors);
442
443  // Find the first exception handler that matches the exception
444  unsigned int found;
445  for (found = 0; found < current.count; ++found) {
446    if (current.masks[found] & (1 << exception)) {
447      break;
448    }
449  }
450
451  // Nothing to forward
452  if (found == current.count) {
453    fprintf(stderr, "** No previous ports for forwarding!! \n");
454    exit(KERN_FAILURE);
455  }
456
457  mach_port_t target_port = current.ports[found];
458  exception_behavior_t target_behavior = current.behaviors[found];
459
460  kern_return_t result;
461  // TODO: Handle the case where |target_behavior| has MACH_EXCEPTION_CODES
462  // set. https://code.google.com/p/google-breakpad/issues/detail?id=551
463  switch (target_behavior) {
464    case EXCEPTION_DEFAULT:
465      result = exception_raise(target_port, failed_thread, task, exception,
466                               code, code_count);
467      break;
468    default:
469      fprintf(stderr, "** Unknown exception behavior: %d\n", target_behavior);
470      result = KERN_FAILURE;
471      break;
472  }
473
474  return result;
475}
476
477// static
478void* ExceptionHandler::WaitForMessage(void* exception_handler_class) {
479  ExceptionHandler* self =
480    reinterpret_cast<ExceptionHandler*>(exception_handler_class);
481  ExceptionMessage receive;
482
483  // Wait for the exception info
484  while (1) {
485    receive.header.msgh_local_port = self->handler_port_;
486    receive.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(receive));
487    kern_return_t result = mach_msg(&(receive.header),
488                                    MACH_RCV_MSG | MACH_RCV_LARGE, 0,
489                                    receive.header.msgh_size,
490                                    self->handler_port_,
491                                    MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
492
493
494    if (result == KERN_SUCCESS) {
495      // Uninstall our handler so that we don't get in a loop if the process of
496      // writing out a minidump causes an exception.  However, if the exception
497      // was caused by a fork'd process, don't uninstall things
498
499      // If the actual exception code is zero, then we're calling this handler
500      // in a way that indicates that we want to either exit this thread or
501      // generate a minidump
502      //
503      // While reporting, all threads (except this one) must be suspended
504      // to avoid misleading stacks.  If appropriate they will be resumed
505      // afterwards.
506      if (!receive.exception) {
507        // Don't touch self, since this message could have been sent
508        // from its destructor.
509        if (receive.header.msgh_id == kShutdownMessage)
510          return NULL;
511
512        self->SuspendThreads();
513
514#if USE_PROTECTED_ALLOCATIONS
515        if (gBreakpadAllocator)
516          gBreakpadAllocator->Unprotect();
517#endif
518
519        mach_port_t thread = MACH_PORT_NULL;
520        int exception_type = 0;
521        int exception_code = 0;
522        if (receive.header.msgh_id == kWriteDumpWithExceptionMessage) {
523          thread = receive.thread.name;
524          exception_type = EXC_BREAKPOINT;
525#if defined(__i386__) || defined(__x86_64__)
526          exception_code = EXC_I386_BPT;
527#elif defined(__ppc__) || defined(__ppc64__)
528          exception_code = EXC_PPC_BREAKPOINT;
529#elif defined(__arm__) || defined(__aarch64__)
530          exception_code = EXC_ARM_BREAKPOINT;
531#else
532#error architecture not supported
533#endif
534        }
535
536        // Write out the dump and save the result for later retrieval
537        self->last_minidump_write_result_ =
538          self->WriteMinidumpWithException(exception_type, exception_code,
539                                           0, NULL, thread,
540                                           false, false);
541
542#if USE_PROTECTED_ALLOCATIONS
543        if (gBreakpadAllocator)
544          gBreakpadAllocator->Protect();
545#endif
546
547        self->ResumeThreads();
548
549        if (self->use_minidump_write_mutex_)
550          pthread_mutex_unlock(&self->minidump_write_mutex_);
551      } else {
552        // When forking a child process with the exception handler installed,
553        // if the child crashes, it will send the exception back to the parent
554        // process.  The check for task == self_task() ensures that only
555        // exceptions that occur in the parent process are caught and
556        // processed.  If the exception was not caused by this task, we
557        // still need to call into the exception server and have it return
558        // KERN_FAILURE (see catch_exception_raise) in order for the kernel
559        // to move onto the host exception handler for the child task
560        if (receive.task.name == mach_task_self()) {
561          self->SuspendThreads();
562
563#if USE_PROTECTED_ALLOCATIONS
564        if (gBreakpadAllocator)
565          gBreakpadAllocator->Unprotect();
566#endif
567
568        int subcode = 0;
569        if (receive.exception == EXC_BAD_ACCESS && receive.code_count > 1)
570          subcode = receive.code[1];
571
572        // Generate the minidump with the exception data.
573        self->WriteMinidumpWithException(receive.exception, receive.code[0],
574                                         subcode, NULL, receive.thread.name,
575                                         true, false);
576
577#if USE_PROTECTED_ALLOCATIONS
578        // This may have become protected again within
579        // WriteMinidumpWithException, but it needs to be unprotected for
580        // UninstallHandler.
581        if (gBreakpadAllocator)
582          gBreakpadAllocator->Unprotect();
583#endif
584
585        self->UninstallHandler(true);
586
587#if USE_PROTECTED_ALLOCATIONS
588        if (gBreakpadAllocator)
589          gBreakpadAllocator->Protect();
590#endif
591        }
592        // Pass along the exception to the server, which will setup the
593        // message and call catch_exception_raise() and put the return
594        // code into the reply.
595        ExceptionReplyMessage reply;
596        if (!breakpad_exc_server(&receive.header, &reply.header))
597          exit(1);
598
599        // Send a reply and exit
600        mach_msg(&(reply.header), MACH_SEND_MSG,
601                 reply.header.msgh_size, 0, MACH_PORT_NULL,
602                 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
603      }
604    }
605  }
606
607  return NULL;
608}
609
610// static
611void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
612#if USE_PROTECTED_ALLOCATIONS
613  if (gBreakpadAllocator)
614    gBreakpadAllocator->Unprotect();
615#endif
616  gProtectedData.handler->WriteMinidumpWithException(
617      EXC_SOFTWARE,
618      MD_EXCEPTION_CODE_MAC_ABORT,
619      0,
620      static_cast<breakpad_ucontext_t*>(uc),
621      mach_thread_self(),
622      true,
623      true);
624#if USE_PROTECTED_ALLOCATIONS
625  if (gBreakpadAllocator)
626    gBreakpadAllocator->Protect();
627#endif
628}
629
630bool ExceptionHandler::InstallHandler() {
631  // If a handler is already installed, something is really wrong.
632  if (gProtectedData.handler != NULL) {
633    return false;
634  }
635  if (!IsOutOfProcess()) {
636    struct sigaction sa;
637    memset(&sa, 0, sizeof(sa));
638    sigemptyset(&sa.sa_mask);
639    sigaddset(&sa.sa_mask, SIGABRT);
640    sa.sa_sigaction = ExceptionHandler::SignalHandler;
641    sa.sa_flags = SA_SIGINFO;
642
643    scoped_ptr<struct sigaction> old(new struct sigaction);
644    if (sigaction(SIGABRT, &sa, old.get()) == -1) {
645      return false;
646    }
647    old_handler_.swap(old);
648    gProtectedData.handler = this;
649#if USE_PROTECTED_ALLOCATIONS
650    assert(((size_t)(gProtectedData.protected_buffer) & PAGE_MASK) == 0);
651    mprotect(gProtectedData.protected_buffer, PAGE_SIZE, PROT_READ);
652#endif
653  }
654
655  try {
656#if USE_PROTECTED_ALLOCATIONS
657    previous_ = new (gBreakpadAllocator->Allocate(sizeof(ExceptionParameters)) )
658      ExceptionParameters();
659#else
660    previous_ = new ExceptionParameters();
661#endif
662  }
663  catch (std::bad_alloc) {
664    return false;
665  }
666
667  // Save the current exception ports so that we can forward to them
668  previous_->count = EXC_TYPES_COUNT;
669  mach_port_t current_task = mach_task_self();
670  kern_return_t result = task_get_exception_ports(current_task,
671                                                  s_exception_mask,
672                                                  previous_->masks,
673                                                  &previous_->count,
674                                                  previous_->ports,
675                                                  previous_->behaviors,
676                                                  previous_->flavors);
677
678  // Setup the exception ports on this task
679  if (result == KERN_SUCCESS)
680    result = task_set_exception_ports(current_task, s_exception_mask,
681                                      handler_port_, EXCEPTION_DEFAULT,
682                                      THREAD_STATE_NONE);
683
684  installed_exception_handler_ = (result == KERN_SUCCESS);
685
686  return installed_exception_handler_;
687}
688
689bool ExceptionHandler::UninstallHandler(bool in_exception) {
690  kern_return_t result = KERN_SUCCESS;
691
692  if (old_handler_.get()) {
693    sigaction(SIGABRT, old_handler_.get(), NULL);
694#if USE_PROTECTED_ALLOCATIONS
695    mprotect(gProtectedData.protected_buffer, PAGE_SIZE,
696        PROT_READ | PROT_WRITE);
697#endif
698    old_handler_.reset();
699    gProtectedData.handler = NULL;
700  }
701
702  if (installed_exception_handler_) {
703    mach_port_t current_task = mach_task_self();
704
705    // Restore the previous ports
706    for (unsigned int i = 0; i < previous_->count; ++i) {
707       result = task_set_exception_ports(current_task, previous_->masks[i],
708                                        previous_->ports[i],
709                                        previous_->behaviors[i],
710                                        previous_->flavors[i]);
711      if (result != KERN_SUCCESS)
712        return false;
713    }
714
715    // this delete should NOT happen if an exception just occurred!
716    if (!in_exception) {
717#if USE_PROTECTED_ALLOCATIONS
718      previous_->~ExceptionParameters();
719#else
720      delete previous_;
721#endif
722    }
723
724    previous_ = NULL;
725    installed_exception_handler_ = false;
726  }
727
728  return result == KERN_SUCCESS;
729}
730
731bool ExceptionHandler::Setup(bool install_handler) {
732  if (pthread_mutex_init(&minidump_write_mutex_, NULL))
733    return false;
734
735  // Create a receive right
736  mach_port_t current_task = mach_task_self();
737  kern_return_t result = mach_port_allocate(current_task,
738                                            MACH_PORT_RIGHT_RECEIVE,
739                                            &handler_port_);
740  // Add send right
741  if (result == KERN_SUCCESS)
742    result = mach_port_insert_right(current_task, handler_port_, handler_port_,
743                                    MACH_MSG_TYPE_MAKE_SEND);
744
745  if (install_handler && result == KERN_SUCCESS)
746    if (!InstallHandler())
747      return false;
748
749  if (result == KERN_SUCCESS) {
750    // Install the handler in its own thread, detached as we won't be joining.
751    pthread_attr_t attr;
752    pthread_attr_init(&attr);
753    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
754    int thread_create_result = pthread_create(&handler_thread_, &attr,
755                                              &WaitForMessage, this);
756    pthread_attr_destroy(&attr);
757    result = thread_create_result ? KERN_FAILURE : KERN_SUCCESS;
758  }
759
760  return result == KERN_SUCCESS;
761}
762
763bool ExceptionHandler::Teardown() {
764  kern_return_t result = KERN_SUCCESS;
765  is_in_teardown_ = true;
766
767  if (!UninstallHandler(false))
768    return false;
769
770  // Send an empty message so that the handler_thread exits
771  if (SendMessageToHandlerThread(kShutdownMessage)) {
772    mach_port_t current_task = mach_task_self();
773    result = mach_port_deallocate(current_task, handler_port_);
774    if (result != KERN_SUCCESS)
775      return false;
776  } else {
777    return false;
778  }
779
780  handler_thread_ = NULL;
781  handler_port_ = MACH_PORT_NULL;
782  pthread_mutex_destroy(&minidump_write_mutex_);
783
784  return result == KERN_SUCCESS;
785}
786
787bool ExceptionHandler::SendMessageToHandlerThread(
788    HandlerThreadMessage message_id) {
789  ExceptionMessage msg;
790  memset(&msg, 0, sizeof(msg));
791  msg.header.msgh_id = message_id;
792  if (message_id == kWriteDumpMessage ||
793      message_id == kWriteDumpWithExceptionMessage) {
794    // Include this thread's port.
795    msg.thread.name = mach_thread_self();
796    msg.thread.disposition = MACH_MSG_TYPE_PORT_SEND;
797    msg.thread.type = MACH_MSG_PORT_DESCRIPTOR;
798  }
799  msg.header.msgh_size = sizeof(msg) - sizeof(msg.padding);
800  msg.header.msgh_remote_port = handler_port_;
801  msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,
802                                          MACH_MSG_TYPE_MAKE_SEND_ONCE);
803  kern_return_t result = mach_msg(&(msg.header),
804                                  MACH_SEND_MSG | MACH_SEND_TIMEOUT,
805                                  msg.header.msgh_size, 0, 0,
806                                  MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
807
808  return result == KERN_SUCCESS;
809}
810
811void ExceptionHandler::UpdateNextID() {
812  next_minidump_path_ =
813    (MinidumpGenerator::UniqueNameInDirectory(dump_path_, &next_minidump_id_));
814
815  next_minidump_path_c_ = next_minidump_path_.c_str();
816  next_minidump_id_c_ = next_minidump_id_.c_str();
817}
818
819bool ExceptionHandler::SuspendThreads() {
820  thread_act_port_array_t   threads_for_task;
821  mach_msg_type_number_t    thread_count;
822
823  if (task_threads(mach_task_self(), &threads_for_task, &thread_count))
824    return false;
825
826  // suspend all of the threads except for this one
827  for (unsigned int i = 0; i < thread_count; ++i) {
828    if (threads_for_task[i] != mach_thread_self()) {
829      if (thread_suspend(threads_for_task[i]))
830        return false;
831    }
832  }
833
834  return true;
835}
836
837bool ExceptionHandler::ResumeThreads() {
838  thread_act_port_array_t   threads_for_task;
839  mach_msg_type_number_t    thread_count;
840
841  if (task_threads(mach_task_self(), &threads_for_task, &thread_count))
842    return false;
843
844  // resume all of the threads except for this one
845  for (unsigned int i = 0; i < thread_count; ++i) {
846    if (threads_for_task[i] != mach_thread_self()) {
847      if (thread_resume(threads_for_task[i]))
848        return false;
849    }
850  }
851
852  return true;
853}
854
855}  // namespace google_breakpad
856