• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
73 namespace google_breakpad {
74 
75 static 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 
86 using std::map;
87 
88 // These structures and techniques are illustrated in
89 // Mac OS X Internals, Amit Singh, ch 9.7
90 struct 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 
102 struct ExceptionParameters {
ExceptionParametersgoogle_breakpad::ExceptionParameters103   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 
111 struct 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.
119 exception_mask_t s_exception_mask = EXC_MASK_BAD_ACCESS |
120 EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT;
121 
122 #if !TARGET_OS_IPHONE
123 extern "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   // https://bugs.chromium.org/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 
140 kern_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.
breakpad_exc_server(mach_msg_header_t * InHeadP,mach_msg_header_t * OutHeadP)148 boolean_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
breakpad_exc_server(mach_msg_header_t * request,mach_msg_header_t * reply)205 boolean_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()
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)211 kern_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 
ExceptionHandler(const string & dump_path,FilterCallback filter,MinidumpCallback callback,void * callback_context,bool install_handler,const char * port_name)223 ExceptionHandler::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
ExceptionHandler(DirectCallback callback,void * callback_context,bool install_handler)253 ExceptionHandler::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 
~ExceptionHandler()272 ExceptionHandler::~ExceptionHandler() {
273   Teardown();
274 }
275 
WriteMinidump(bool write_exception_stream)276 bool 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
WriteMinidump(const string & dump_path,bool write_exception_stream,MinidumpCallback callback,void * callback_context)307 bool 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
WriteMinidumpForChild(mach_port_t child,mach_port_t child_blamed_thread,const string & dump_path,MinidumpCallback callback,void * callback_context)317 bool 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 
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)349 bool 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 TARGET_OS_IPHONE
360   // _exit() should never be called on iOS.
361   exit_after_write = false;
362 #endif
363 
364   if (directCallback_) {
365     if (directCallback_(callback_context_,
366                         exception_type,
367                         exception_code,
368                         exception_subcode,
369                         thread_name) ) {
370       if (exit_after_write)
371         _exit(exception_type);
372     }
373 #if !TARGET_OS_IPHONE
374   } else if (IsOutOfProcess()) {
375     if (exception_type && exception_code) {
376       // If this is a real exception, give the filter (if any) a chance to
377       // decide if this should be sent.
378       if (filter_ && !filter_(callback_context_))
379         return false;
380       result = crash_generation_client_->RequestDumpForException(
381           exception_type,
382           exception_code,
383           exception_subcode,
384           thread_name);
385       if (result && exit_after_write) {
386         _exit(exception_type);
387       }
388     }
389 #endif
390   } else {
391     string minidump_id;
392 
393     // Putting the MinidumpGenerator in its own context will ensure that the
394     // destructor is executed, closing the newly created minidump file.
395     if (!dump_path_.empty()) {
396       MinidumpGenerator md(mach_task_self(),
397                            report_current_thread ? MACH_PORT_NULL :
398                                                    mach_thread_self());
399       md.SetTaskContext(task_context);
400       if (exception_type && exception_code) {
401         // If this is a real exception, give the filter (if any) a chance to
402         // decide if this should be sent.
403         if (filter_ && !filter_(callback_context_))
404           return false;
405 
406         md.SetExceptionInformation(exception_type, exception_code,
407                                    exception_subcode, thread_name);
408       }
409 
410       result = md.Write(next_minidump_path_c_);
411     }
412 
413     // Call user specified callback (if any)
414     if (callback_) {
415       // If the user callback returned true and we're handling an exception
416       // (rather than just writing out the file), then we should exit without
417       // forwarding the exception to the next handler.
418       if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
419                     result)) {
420         if (exit_after_write)
421           _exit(exception_type);
422       }
423     }
424   }
425 
426   return result;
427 }
428 
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)429 kern_return_t ForwardException(mach_port_t task, mach_port_t failed_thread,
430                                exception_type_t exception,
431                                exception_data_t code,
432                                mach_msg_type_number_t code_count) {
433   // At this time, we should have called Uninstall() on the exception handler
434   // so that the current exception ports are the ones that we should be
435   // forwarding to.
436   ExceptionParameters current;
437 
438   current.count = EXC_TYPES_COUNT;
439   mach_port_t current_task = mach_task_self();
440   task_get_exception_ports(current_task,
441                            s_exception_mask,
442                            current.masks,
443                            &current.count,
444                            current.ports,
445                            current.behaviors,
446                            current.flavors);
447 
448   // Find the first exception handler that matches the exception
449   unsigned int found;
450   for (found = 0; found < current.count; ++found) {
451     if (current.masks[found] & (1 << exception)) {
452       break;
453     }
454   }
455 
456   // Nothing to forward
457   if (found == current.count) {
458     fprintf(stderr, "** No previous ports for forwarding!! \n");
459     exit(KERN_FAILURE);
460   }
461 
462   mach_port_t target_port = current.ports[found];
463   exception_behavior_t target_behavior = current.behaviors[found];
464 
465   kern_return_t result;
466   // TODO: Handle the case where |target_behavior| has MACH_EXCEPTION_CODES
467   // set. https://bugs.chromium.org/p/google-breakpad/issues/detail?id=551
468   switch (target_behavior) {
469     case EXCEPTION_DEFAULT:
470       result = exception_raise(target_port, failed_thread, task, exception,
471                                code, code_count);
472       break;
473     default:
474       fprintf(stderr, "** Unknown exception behavior: %d\n", target_behavior);
475       result = KERN_FAILURE;
476       break;
477   }
478 
479   return result;
480 }
481 
482 // static
WaitForMessage(void * exception_handler_class)483 void* ExceptionHandler::WaitForMessage(void* exception_handler_class) {
484   ExceptionHandler* self =
485     reinterpret_cast<ExceptionHandler*>(exception_handler_class);
486   ExceptionMessage receive;
487 
488   // Wait for the exception info
489   while (1) {
490     receive.header.msgh_local_port = self->handler_port_;
491     receive.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(receive));
492     kern_return_t result = mach_msg(&(receive.header),
493                                     MACH_RCV_MSG | MACH_RCV_LARGE, 0,
494                                     receive.header.msgh_size,
495                                     self->handler_port_,
496                                     MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
497 
498 
499     if (result == KERN_SUCCESS) {
500       // Uninstall our handler so that we don't get in a loop if the process of
501       // writing out a minidump causes an exception.  However, if the exception
502       // was caused by a fork'd process, don't uninstall things
503 
504       // If the actual exception code is zero, then we're calling this handler
505       // in a way that indicates that we want to either exit this thread or
506       // generate a minidump
507       //
508       // While reporting, all threads (except this one) must be suspended
509       // to avoid misleading stacks.  If appropriate they will be resumed
510       // afterwards.
511       if (!receive.exception) {
512         // Don't touch self, since this message could have been sent
513         // from its destructor.
514         if (receive.header.msgh_id == kShutdownMessage)
515           return NULL;
516 
517         self->SuspendThreads();
518 
519 #if USE_PROTECTED_ALLOCATIONS
520         if (gBreakpadAllocator)
521           gBreakpadAllocator->Unprotect();
522 #endif
523 
524         mach_port_t thread = MACH_PORT_NULL;
525         int exception_type = 0;
526         int exception_code = 0;
527         if (receive.header.msgh_id == kWriteDumpWithExceptionMessage) {
528           thread = receive.thread.name;
529           exception_type = EXC_BREAKPOINT;
530 #if defined(__i386__) || defined(__x86_64__)
531           exception_code = EXC_I386_BPT;
532 #elif defined(__ppc__) || defined(__ppc64__)
533           exception_code = EXC_PPC_BREAKPOINT;
534 #elif defined(__arm__) || defined(__aarch64__)
535           exception_code = EXC_ARM_BREAKPOINT;
536 #else
537 #error architecture not supported
538 #endif
539         }
540 
541         // Write out the dump and save the result for later retrieval
542         self->last_minidump_write_result_ =
543           self->WriteMinidumpWithException(exception_type, exception_code,
544                                            0, NULL, thread,
545                                            false, false);
546 
547 #if USE_PROTECTED_ALLOCATIONS
548         if (gBreakpadAllocator)
549           gBreakpadAllocator->Protect();
550 #endif
551 
552         self->ResumeThreads();
553 
554         if (self->use_minidump_write_mutex_)
555           pthread_mutex_unlock(&self->minidump_write_mutex_);
556       } else {
557         // When forking a child process with the exception handler installed,
558         // if the child crashes, it will send the exception back to the parent
559         // process.  The check for task == self_task() ensures that only
560         // exceptions that occur in the parent process are caught and
561         // processed.  If the exception was not caused by this task, we
562         // still need to call into the exception server and have it return
563         // KERN_FAILURE (see catch_exception_raise) in order for the kernel
564         // to move onto the host exception handler for the child task
565         if (receive.task.name == mach_task_self()) {
566           self->SuspendThreads();
567 
568 #if USE_PROTECTED_ALLOCATIONS
569         if (gBreakpadAllocator)
570           gBreakpadAllocator->Unprotect();
571 #endif
572 
573         int subcode = 0;
574         if (receive.exception == EXC_BAD_ACCESS && receive.code_count > 1)
575           subcode = receive.code[1];
576 
577         // Generate the minidump with the exception data.
578         self->WriteMinidumpWithException(receive.exception, receive.code[0],
579                                          subcode, NULL, receive.thread.name,
580                                          true, false);
581 
582 #if USE_PROTECTED_ALLOCATIONS
583         // This may have become protected again within
584         // WriteMinidumpWithException, but it needs to be unprotected for
585         // UninstallHandler.
586         if (gBreakpadAllocator)
587           gBreakpadAllocator->Unprotect();
588 #endif
589 
590         self->UninstallHandler(true);
591 
592 #if USE_PROTECTED_ALLOCATIONS
593         if (gBreakpadAllocator)
594           gBreakpadAllocator->Protect();
595 #endif
596         }
597         // Pass along the exception to the server, which will setup the
598         // message and call catch_exception_raise() and put the return
599         // code into the reply.
600         ExceptionReplyMessage reply;
601         if (!breakpad_exc_server(&receive.header, &reply.header))
602           exit(1);
603 
604         // Send a reply and exit
605         mach_msg(&(reply.header), MACH_SEND_MSG,
606                  reply.header.msgh_size, 0, MACH_PORT_NULL,
607                  MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
608       }
609     }
610   }
611 
612   return NULL;
613 }
614 
615 // static
SignalHandler(int sig,siginfo_t * info,void * uc)616 void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
617 #if USE_PROTECTED_ALLOCATIONS
618   if (gBreakpadAllocator)
619     gBreakpadAllocator->Unprotect();
620 #endif
621   gProtectedData.handler->WriteMinidumpWithException(
622       EXC_SOFTWARE,
623       MD_EXCEPTION_CODE_MAC_ABORT,
624       0,
625       static_cast<breakpad_ucontext_t*>(uc),
626       mach_thread_self(),
627       true,
628       true);
629 #if USE_PROTECTED_ALLOCATIONS
630   if (gBreakpadAllocator)
631     gBreakpadAllocator->Protect();
632 #endif
633 }
634 
InstallHandler()635 bool ExceptionHandler::InstallHandler() {
636   // If a handler is already installed, something is really wrong.
637   if (gProtectedData.handler != NULL) {
638     return false;
639   }
640   if (!IsOutOfProcess()) {
641     struct sigaction sa;
642     memset(&sa, 0, sizeof(sa));
643     sigemptyset(&sa.sa_mask);
644     sigaddset(&sa.sa_mask, SIGABRT);
645     sa.sa_sigaction = ExceptionHandler::SignalHandler;
646     sa.sa_flags = SA_SIGINFO;
647 
648     scoped_ptr<struct sigaction> old(new struct sigaction);
649     if (sigaction(SIGABRT, &sa, old.get()) == -1) {
650       return false;
651     }
652     old_handler_.swap(old);
653     gProtectedData.handler = this;
654 #if USE_PROTECTED_ALLOCATIONS
655     assert(((size_t)(gProtectedData.protected_buffer) & PAGE_MASK) == 0);
656     mprotect(gProtectedData.protected_buffer, PAGE_SIZE, PROT_READ);
657 #endif
658   }
659 
660   try {
661 #if USE_PROTECTED_ALLOCATIONS
662     previous_ = new (gBreakpadAllocator->Allocate(sizeof(ExceptionParameters)) )
663       ExceptionParameters();
664 #else
665     previous_ = new ExceptionParameters();
666 #endif
667   }
668   catch (std::bad_alloc) {
669     return false;
670   }
671 
672   // Save the current exception ports so that we can forward to them
673   previous_->count = EXC_TYPES_COUNT;
674   mach_port_t current_task = mach_task_self();
675   kern_return_t result = task_get_exception_ports(current_task,
676                                                   s_exception_mask,
677                                                   previous_->masks,
678                                                   &previous_->count,
679                                                   previous_->ports,
680                                                   previous_->behaviors,
681                                                   previous_->flavors);
682 
683   // Setup the exception ports on this task
684   if (result == KERN_SUCCESS)
685     result = task_set_exception_ports(current_task, s_exception_mask,
686                                       handler_port_, EXCEPTION_DEFAULT,
687                                       THREAD_STATE_NONE);
688 
689   installed_exception_handler_ = (result == KERN_SUCCESS);
690 
691   return installed_exception_handler_;
692 }
693 
UninstallHandler(bool in_exception)694 bool ExceptionHandler::UninstallHandler(bool in_exception) {
695   kern_return_t result = KERN_SUCCESS;
696 
697   if (old_handler_.get()) {
698     sigaction(SIGABRT, old_handler_.get(), NULL);
699 #if USE_PROTECTED_ALLOCATIONS
700     mprotect(gProtectedData.protected_buffer, PAGE_SIZE,
701         PROT_READ | PROT_WRITE);
702 #endif
703     old_handler_.reset();
704     gProtectedData.handler = NULL;
705   }
706 
707   if (installed_exception_handler_) {
708     mach_port_t current_task = mach_task_self();
709 
710     // Restore the previous ports
711     for (unsigned int i = 0; i < previous_->count; ++i) {
712        result = task_set_exception_ports(current_task, previous_->masks[i],
713                                         previous_->ports[i],
714                                         previous_->behaviors[i],
715                                         previous_->flavors[i]);
716       if (result != KERN_SUCCESS)
717         return false;
718     }
719 
720     // this delete should NOT happen if an exception just occurred!
721     if (!in_exception) {
722 #if USE_PROTECTED_ALLOCATIONS
723       previous_->~ExceptionParameters();
724 #else
725       delete previous_;
726 #endif
727     }
728 
729     previous_ = NULL;
730     installed_exception_handler_ = false;
731   }
732 
733   return result == KERN_SUCCESS;
734 }
735 
Setup(bool install_handler)736 bool ExceptionHandler::Setup(bool install_handler) {
737   if (pthread_mutex_init(&minidump_write_mutex_, NULL))
738     return false;
739 
740   // Create a receive right
741   mach_port_t current_task = mach_task_self();
742   kern_return_t result = mach_port_allocate(current_task,
743                                             MACH_PORT_RIGHT_RECEIVE,
744                                             &handler_port_);
745   // Add send right
746   if (result == KERN_SUCCESS)
747     result = mach_port_insert_right(current_task, handler_port_, handler_port_,
748                                     MACH_MSG_TYPE_MAKE_SEND);
749 
750   if (install_handler && result == KERN_SUCCESS)
751     if (!InstallHandler())
752       return false;
753 
754   if (result == KERN_SUCCESS) {
755     // Install the handler in its own thread, detached as we won't be joining.
756     pthread_attr_t attr;
757     pthread_attr_init(&attr);
758     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
759     int thread_create_result = pthread_create(&handler_thread_, &attr,
760                                               &WaitForMessage, this);
761     pthread_attr_destroy(&attr);
762     result = thread_create_result ? KERN_FAILURE : KERN_SUCCESS;
763   }
764 
765   return result == KERN_SUCCESS;
766 }
767 
Teardown()768 bool ExceptionHandler::Teardown() {
769   kern_return_t result = KERN_SUCCESS;
770   is_in_teardown_ = true;
771 
772   if (!UninstallHandler(false))
773     return false;
774 
775   // Send an empty message so that the handler_thread exits
776   if (SendMessageToHandlerThread(kShutdownMessage)) {
777     mach_port_t current_task = mach_task_self();
778     result = mach_port_deallocate(current_task, handler_port_);
779     if (result != KERN_SUCCESS)
780       return false;
781   } else {
782     return false;
783   }
784 
785   handler_thread_ = NULL;
786   handler_port_ = MACH_PORT_NULL;
787   pthread_mutex_destroy(&minidump_write_mutex_);
788 
789   return result == KERN_SUCCESS;
790 }
791 
SendMessageToHandlerThread(HandlerThreadMessage message_id)792 bool ExceptionHandler::SendMessageToHandlerThread(
793     HandlerThreadMessage message_id) {
794   ExceptionMessage msg;
795   memset(&msg, 0, sizeof(msg));
796   msg.header.msgh_id = message_id;
797   if (message_id == kWriteDumpMessage ||
798       message_id == kWriteDumpWithExceptionMessage) {
799     // Include this thread's port.
800     msg.thread.name = mach_thread_self();
801     msg.thread.disposition = MACH_MSG_TYPE_PORT_SEND;
802     msg.thread.type = MACH_MSG_PORT_DESCRIPTOR;
803   }
804   msg.header.msgh_size = sizeof(msg) - sizeof(msg.padding);
805   msg.header.msgh_remote_port = handler_port_;
806   msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,
807                                           MACH_MSG_TYPE_MAKE_SEND_ONCE);
808   kern_return_t result = mach_msg(&(msg.header),
809                                   MACH_SEND_MSG | MACH_SEND_TIMEOUT,
810                                   msg.header.msgh_size, 0, 0,
811                                   MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
812 
813   return result == KERN_SUCCESS;
814 }
815 
UpdateNextID()816 void ExceptionHandler::UpdateNextID() {
817   next_minidump_path_ =
818     (MinidumpGenerator::UniqueNameInDirectory(dump_path_, &next_minidump_id_));
819 
820   next_minidump_path_c_ = next_minidump_path_.c_str();
821   next_minidump_id_c_ = next_minidump_id_.c_str();
822 }
823 
SuspendThreads()824 bool ExceptionHandler::SuspendThreads() {
825   thread_act_port_array_t   threads_for_task;
826   mach_msg_type_number_t    thread_count;
827 
828   if (task_threads(mach_task_self(), &threads_for_task, &thread_count))
829     return false;
830 
831   // suspend all of the threads except for this one
832   for (unsigned int i = 0; i < thread_count; ++i) {
833     if (threads_for_task[i] != mach_thread_self()) {
834       if (thread_suspend(threads_for_task[i]))
835         return false;
836     }
837   }
838 
839   return true;
840 }
841 
ResumeThreads()842 bool ExceptionHandler::ResumeThreads() {
843   thread_act_port_array_t   threads_for_task;
844   mach_msg_type_number_t    thread_count;
845 
846   if (task_threads(mach_task_self(), &threads_for_task, &thread_count))
847     return false;
848 
849   // resume all of the threads except for this one
850   for (unsigned int i = 0; i < thread_count; ++i) {
851     if (threads_for_task[i] != mach_thread_self()) {
852       if (thread_resume(threads_for_task[i]))
853         return false;
854     }
855   }
856 
857   return true;
858 }
859 
860 }  // namespace google_breakpad
861