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 "mac/handler/exception_handler.h"
39 #include "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 #include "mozilla/Assertions.h"
44 
45 #ifdef MOZ_PHC
46 #include "replace_malloc_bridge.h"
47 #endif
48 
49 #ifndef __EXCEPTIONS
50 // This file uses C++ try/catch (but shouldn't). Duplicate the macros from
51 // <c++/4.2.1/exception_defines.h> allowing this file to work properly with
52 // exceptions disabled even when other C++ libraries are used. #undef the try
53 // and catch macros first in case libstdc++ is in use and has already provided
54 // its own definitions.
55 #undef try
56 #define try       if (true)
57 #undef catch
58 #define catch(X)  if (false)
59 #endif  // __EXCEPTIONS
60 
61 #ifndef USE_PROTECTED_ALLOCATIONS
62 #if TARGET_OS_IPHONE
63 #define USE_PROTECTED_ALLOCATIONS 1
64 #else
65 #define USE_PROTECTED_ALLOCATIONS 0
66 #endif
67 #endif
68 
69 // If USE_PROTECTED_ALLOCATIONS is activated then the
70 // gBreakpadAllocator needs to be setup in other code
71 // ahead of time.  Please see ProtectedMemoryAllocator.h
72 // for more details.
73 #if USE_PROTECTED_ALLOCATIONS
74   #include "protected_memory_allocator.h"
75   extern ProtectedMemoryAllocator *gBreakpadAllocator;
76 #endif
77 
78 namespace google_breakpad {
79 
80 static union {
81 #if USE_PROTECTED_ALLOCATIONS
82 #if defined PAGE_MAX_SIZE
83   char protected_buffer[PAGE_MAX_SIZE] __attribute__((aligned(PAGE_MAX_SIZE)));
84 #else
85   char protected_buffer[PAGE_SIZE] __attribute__((aligned(PAGE_SIZE)));
86 #endif  // defined PAGE_MAX_SIZE
87 #endif  // USE_PROTECTED_ALLOCATIONS
88   google_breakpad::ExceptionHandler *handler;
89 } gProtectedData;
90 
91 using std::map;
92 
93 // These structures and techniques are illustrated in
94 // Mac OS X Internals, Amit Singh, ch 9.7
95 #pragma pack(push, 4)
96 struct ExceptionMessage {
97   mach_msg_header_t           header;
98   mach_msg_body_t             body;
99   mach_msg_port_descriptor_t  thread;
100   mach_msg_port_descriptor_t  task;
101   NDR_record_t                ndr;
102   exception_type_t            exception;
103   mach_msg_type_number_t      code_count;
104   mach_exception_data_type_t  code[EXCEPTION_CODE_MAX];
105   char                        padding[512];
106 };
107 #pragma pack(pop)
108 
109 struct ExceptionParameters {
ExceptionParametersgoogle_breakpad::ExceptionParameters110   ExceptionParameters() : count(0) {}
111   mach_msg_type_number_t count;
112   exception_mask_t masks[EXC_TYPES_COUNT];
113   mach_port_t ports[EXC_TYPES_COUNT];
114   exception_behavior_t behaviors[EXC_TYPES_COUNT];
115   thread_state_flavor_t flavors[EXC_TYPES_COUNT];
116 };
117 
118 #pragma pack(push, 4)
119 struct ExceptionReplyMessage {
120   mach_msg_header_t  header;
121   NDR_record_t       ndr;
122   kern_return_t      return_code;
123 };
124 #pragma pack(pop)
125 
126 // Only catch these three exceptions.  The other ones are nebulously defined
127 // and may result in treating a non-fatal exception as fatal.
128 exception_mask_t s_exception_mask = EXC_MASK_BAD_ACCESS |
129 EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT |
130 EXC_MASK_CRASH | EXC_MASK_RESOURCE | EXC_MASK_GUARD;
131 
132 kern_return_t ForwardException(mach_port_t task,
133                                mach_port_t failed_thread,
134                                exception_type_t exception,
135                                mach_exception_data_t code,
136                                mach_msg_type_number_t code_count);
137 
138 // The contents of mach_exc_server() and mach_exception_raise() are derived
139 // from /usr/include/mach/mach_exc.defs, as follows:
140 //
141 // 1) Run 'mig mach_exc.defs' which creates the following files:
142 //    mach_exc.h
143 //    mach_excServer.c
144 //    mach_excUser.c
145 // 2) The relevant code for mach_exc_server() comes from the following methods
146 //    in mach_excServer.c:
147 //      mach_exc_server()
148 //      _Xmach_exception_raise()
149 // 3) The relevant code for mach_exception_raise() comes from the following
150 //    method in mach_excUser.c:
151 //      mach_exception_raise()
mach_exc_server(mach_msg_header_t * InHeadP,mach_msg_header_t * OutHeadP)152 boolean_t mach_exc_server(mach_msg_header_t* InHeadP,
153                           mach_msg_header_t* OutHeadP)
154 {
155   OutHeadP->msgh_bits =
156     MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(InHeadP->msgh_bits), 0);
157   OutHeadP->msgh_remote_port = InHeadP->msgh_remote_port;
158   /* Minimal size: mach_exception_raise() will update it if different */
159   OutHeadP->msgh_size = (mach_msg_size_t)sizeof(mig_reply_error_t);
160   OutHeadP->msgh_local_port = MACH_PORT_NULL;
161   OutHeadP->msgh_id = InHeadP->msgh_id + 100;
162   OutHeadP->msgh_reserved = 0;
163 
164   if (InHeadP->msgh_id != 2405) {
165     ((mig_reply_error_t*)OutHeadP)->NDR = NDR_record;
166     ((mig_reply_error_t*)OutHeadP)->RetCode = MIG_BAD_ID;
167     return FALSE;
168   }
169 
170 #pragma pack(push, 4)
171   typedef struct {
172     mach_msg_header_t Head;
173     /* start of the kernel processed data */
174     mach_msg_body_t msgh_body;
175     mach_msg_port_descriptor_t thread;
176     mach_msg_port_descriptor_t task;
177     /* end of the kernel processed data */
178     NDR_record_t NDR;
179     exception_type_t exception;
180     mach_msg_type_number_t codeCnt;
181     int64_t code[2];
182     mach_msg_trailer_t trailer;
183   } Request;
184 
185   typedef struct {
186     mach_msg_header_t Head;
187     NDR_record_t NDR;
188     kern_return_t RetCode;
189   } Reply;
190 #pragma pack(pop)
191 
192   Request* In0P = (Request*)InHeadP;
193   Reply* OutP = (Reply*)OutHeadP;
194 
195   if (In0P->task.name != mach_task_self()) {
196     // This exception was not meant for us, we avoid forwarding it (because it
197     // could cause a loop in the exception handler) and simply ignore it
198     // instead.
199     return TRUE;
200   }
201 
202   OutP->RetCode = ForwardException(In0P->task.name,
203                                    In0P->thread.name,
204                                    In0P->exception,
205                                    In0P->code,
206                                    In0P->codeCnt);
207   OutP->NDR = NDR_record;
208   return TRUE;
209 }
210 
mach_exception_raise(mach_port_t exception_port,mach_port_t thread,mach_port_t task,exception_type_t exception,mach_exception_data_t code,mach_msg_type_number_t codeCnt)211 kern_return_t mach_exception_raise(mach_port_t exception_port,
212                                    mach_port_t thread,
213                                    mach_port_t task,
214                                    exception_type_t exception,
215                                    mach_exception_data_t code,
216                                    mach_msg_type_number_t codeCnt)
217 {
218 #pragma pack(push, 4)
219   typedef struct {
220     mach_msg_header_t Head;
221     /* start of the kernel processed data */
222     mach_msg_body_t msgh_body;
223     mach_msg_port_descriptor_t thread;
224     mach_msg_port_descriptor_t task;
225     /* end of the kernel processed data */
226     NDR_record_t NDR;
227     exception_type_t exception;
228     mach_msg_type_number_t codeCnt;
229     int64_t code[2];
230   } Request;
231 
232   typedef struct {
233     mach_msg_header_t Head;
234     NDR_record_t NDR;
235     kern_return_t RetCode;
236     mach_msg_trailer_t trailer;
237   } Reply;
238 #pragma pack(pop)
239 
240   Request In;
241   Request *InP = &In;
242 
243   mach_msg_return_t msg_result;
244   unsigned int msgh_size;
245 
246   InP->msgh_body.msgh_descriptor_count = 2;
247   InP->thread.name = thread;
248   InP->thread.disposition = 19;
249   InP->thread.type = MACH_MSG_PORT_DESCRIPTOR;
250   InP->task.name = task;
251   InP->task.disposition = 19;
252   InP->task.type = MACH_MSG_PORT_DESCRIPTOR;
253 
254   InP->NDR = NDR_record;
255 
256   InP->exception = exception;
257 
258   if (codeCnt > 2) {
259     { return MIG_ARRAY_TOO_LARGE; }
260   }
261   (void)memcpy((char *) InP->code, (const char *) code, 8 * codeCnt);
262 
263   InP->codeCnt = codeCnt;
264 
265   msgh_size = (mach_msg_size_t)(sizeof(Request) - 16) + ((8 * codeCnt));
266   InP->Head.msgh_bits = MACH_MSGH_BITS_COMPLEX |
267     MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE);
268   /* msgh_size passed as argument */
269   InP->Head.msgh_remote_port = exception_port;
270   InP->Head.msgh_local_port = mig_get_reply_port();
271   InP->Head.msgh_id = 2405;
272   InP->Head.msgh_reserved = 0;
273 
274   msg_result = mach_msg(&InP->Head,
275                         MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE,
276                         msgh_size,
277                         (mach_msg_size_t)sizeof(Reply),
278                         InP->Head.msgh_local_port,
279                         MACH_MSG_TIMEOUT_NONE,
280                         MACH_PORT_NULL);
281 
282   if (msg_result != MACH_MSG_SUCCESS) {
283     fprintf(stderr, "** mach_msg() error forwarding exception!!\n");
284     return msg_result;
285   }
286 
287   return KERN_SUCCESS;
288 }
289 
ExceptionHandler(const string & dump_path,FilterCallback filter,MinidumpCallback callback,void * callback_context,bool install_handler,const char * port_name)290 ExceptionHandler::ExceptionHandler(const string &dump_path,
291                                    FilterCallback filter,
292                                    MinidumpCallback callback,
293                                    void* callback_context,
294                                    bool install_handler,
295                                    const char* port_name)
296     : dump_path_(),
297       filter_(filter),
298       callback_(callback),
299       callback_context_(callback_context),
300       directCallback_(NULL),
301       handler_thread_(NULL),
302       handler_port_(MACH_PORT_NULL),
303       previous_(NULL),
304       installed_exception_handler_(false),
305       is_in_teardown_(false),
306       last_minidump_write_result_(false),
307       use_minidump_write_mutex_(false) {
308   // This will update to the ID and C-string pointers
309   set_dump_path(dump_path);
310   MinidumpGenerator::GatherSystemInformation();
311 #if !TARGET_OS_IPHONE
312   if (port_name)
313     crash_generation_client_.reset(new CrashGenerationClient(port_name));
314 #endif
315   Setup(install_handler);
316 }
317 
318 // special constructor if we want to bypass minidump writing and
319 // simply get a callback with the exception information
ExceptionHandler(DirectCallback callback,void * callback_context,bool install_handler)320 ExceptionHandler::ExceptionHandler(DirectCallback callback,
321                                    void* callback_context,
322                                    bool install_handler)
323     : dump_path_(),
324       filter_(NULL),
325       callback_(NULL),
326       callback_context_(callback_context),
327       directCallback_(callback),
328       handler_thread_(NULL),
329       handler_port_(MACH_PORT_NULL),
330       previous_(NULL),
331       installed_exception_handler_(false),
332       is_in_teardown_(false),
333       last_minidump_write_result_(false),
334       use_minidump_write_mutex_(false) {
335   MinidumpGenerator::GatherSystemInformation();
336   Setup(install_handler);
337 }
338 
~ExceptionHandler()339 ExceptionHandler::~ExceptionHandler() {
340   Teardown();
341 }
342 
WriteMinidump(bool write_exception_stream)343 bool ExceptionHandler::WriteMinidump(bool write_exception_stream) {
344   // If we're currently writing, just return
345   if (use_minidump_write_mutex_)
346     return false;
347 
348   use_minidump_write_mutex_ = true;
349   last_minidump_write_result_ = false;
350 
351   // Lock the mutex.  Since we just created it, this will return immediately.
352   if (pthread_mutex_lock(&minidump_write_mutex_) == 0) {
353     // Send an empty message to the handle port so that a minidump will
354     // be written
355     bool result = SendMessageToHandlerThread(write_exception_stream ?
356                                                kWriteDumpWithExceptionMessage :
357                                                kWriteDumpMessage);
358     if (!result) {
359       pthread_mutex_unlock(&minidump_write_mutex_);
360       return false;
361     }
362 
363     // Wait for the minidump writer to complete its writing.  It will unlock
364     // the mutex when completed
365     pthread_mutex_lock(&minidump_write_mutex_);
366   }
367 
368   use_minidump_write_mutex_ = false;
369   UpdateNextID();
370   return last_minidump_write_result_;
371 }
372 
373 // static
WriteMinidump(const string & dump_path,bool write_exception_stream,MinidumpCallback callback,void * callback_context)374 bool ExceptionHandler::WriteMinidump(const string &dump_path,
375                                      bool write_exception_stream,
376                                      MinidumpCallback callback,
377                                      void* callback_context) {
378   ExceptionHandler handler(dump_path, NULL, callback, callback_context, false,
379                            NULL);
380   return handler.WriteMinidump(write_exception_stream);
381 }
382 
383 // static
WriteMinidumpForChild(mach_port_t child,mach_port_t child_blamed_thread,const string & dump_path,MinidumpCallback callback,void * callback_context)384 bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child,
385                                              mach_port_t child_blamed_thread,
386                                              const string &dump_path,
387                                              MinidumpCallback callback,
388                                              void* callback_context) {
389   ScopedTaskSuspend suspend(child);
390 
391   MinidumpGenerator generator(child, MACH_PORT_NULL);
392   string dump_id;
393   string dump_filename = generator.UniqueNameInDirectory(dump_path, &dump_id);
394 
395   generator.SetExceptionInformation(EXC_BREAKPOINT,
396 #if defined(__i386__) || defined(__x86_64__)
397                                     EXC_I386_BPT,
398 #elif defined(__ppc__) || defined(__ppc64__)
399                                     EXC_PPC_BREAKPOINT,
400 #elif defined(__arm__) || defined(__aarch64__)
401                                     EXC_ARM_BREAKPOINT,
402 #else
403 #error architecture not supported
404 #endif
405                                     0,
406                                     child_blamed_thread);
407   bool result = generator.Write(dump_filename.c_str());
408 
409   if (callback) {
410     return callback(dump_path.c_str(), dump_id.c_str(),
411                     callback_context, nullptr, result);
412   }
413   return result;
414 }
415 
416 #ifdef MOZ_PHC
GetPHCAddrInfo(int exception_type,int64_t exception_subcode,mozilla::phc::AddrInfo * addr_info)417 static void GetPHCAddrInfo(int exception_type, int64_t exception_subcode,
418                            mozilla::phc::AddrInfo* addr_info) {
419   // Is this a crash involving a PHC allocation?
420   if (exception_type == EXC_BAD_ACCESS) {
421     // `exception_subcode` is only non-zero when it's a bad access, in which
422     // case it holds the address of the bad access.
423     char* addr = reinterpret_cast<char*>(exception_subcode);
424     ReplaceMalloc::IsPHCAllocation(addr, addr_info);
425   }
426 }
427 #endif
428 
WriteMinidumpWithException(int exception_type,int64_t exception_code,int64_t exception_subcode,breakpad_ucontext_t * task_context,mach_port_t thread_name,mach_port_t task_name,bool exit_after_write,bool report_current_thread)429 bool ExceptionHandler::WriteMinidumpWithException(
430     int exception_type,
431     int64_t exception_code,
432     int64_t exception_subcode,
433     breakpad_ucontext_t* task_context,
434     mach_port_t thread_name,
435     mach_port_t task_name,
436     bool exit_after_write,
437     bool report_current_thread) {
438   bool result = false;
439 
440 #if TARGET_OS_IPHONE
441   // _exit() should never be called on iOS.
442   exit_after_write = false;
443 #endif
444 
445     mozilla::phc::AddrInfo addr_info;
446 #ifdef MOZ_PHC
447     GetPHCAddrInfo(exception_type, exception_subcode, &addr_info);
448 #endif
449 
450   if (directCallback_) {
451     if (directCallback_(callback_context_,
452                         exception_type,
453                         exception_code,
454                         exception_subcode,
455                         thread_name) ) {
456       if (exit_after_write)
457         _exit(exception_type);
458     }
459 #if !TARGET_OS_IPHONE
460   } else if (IsOutOfProcess()) {
461     if (exception_type && exception_code) {
462       // If this is a real exception, give the filter (if any) a chance to
463       // decide if this should be sent.
464       if (filter_ && !filter_(callback_context_))
465         return false;
466       result = crash_generation_client_->RequestDumpForException(
467           exception_type,
468           exception_code,
469           exception_subcode,
470           thread_name,
471           task_name);
472 
473       if (callback_) {
474         result = callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
475                            &addr_info, result);
476       }
477 
478       if (result && exit_after_write) {
479         _exit(exception_type);
480       }
481     }
482 #endif
483   } else {
484     string minidump_id;
485 
486     // Putting the MinidumpGenerator in its own context will ensure that the
487     // destructor is executed, closing the newly created minidump file.
488     if (!dump_path_.empty()) {
489       MinidumpGenerator md(mach_task_self(),
490                            report_current_thread ? MACH_PORT_NULL :
491                                                    mach_thread_self());
492       md.SetTaskContext(task_context);
493       if (exception_type && exception_code) {
494         // If this is a real exception, give the filter (if any) a chance to
495         // decide if this should be sent.
496         if (filter_ && !filter_(callback_context_))
497           return false;
498 
499         md.SetExceptionInformation(exception_type, exception_code,
500                                    exception_subcode, thread_name);
501       }
502 
503       result = md.Write(next_minidump_path_c_);
504     }
505 
506     // Call user specified callback (if any)
507     if (callback_) {
508       result = callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
509                          &addr_info, result);
510       // If the user callback returned true and we're handling an exception
511       // (rather than just writing out the file), then we should exit without
512       // forwarding the exception to the next handler.
513       if (result) {
514         if (exit_after_write)
515           _exit(exception_type);
516       }
517     }
518   }
519 
520   return result;
521 }
522 
ForwardException(mach_port_t task,mach_port_t failed_thread,exception_type_t exception,mach_exception_data_t code,mach_msg_type_number_t code_count)523 kern_return_t ForwardException(mach_port_t task, mach_port_t failed_thread,
524                                exception_type_t exception,
525                                mach_exception_data_t code,
526                                mach_msg_type_number_t code_count) {
527   // At this time, we should have called Uninstall() on the exception handler
528   // so that the current exception ports are the ones that we should be
529   // forwarding to.
530   ExceptionParameters current;
531 
532   current.count = EXC_TYPES_COUNT;
533   mach_port_t current_task = mach_task_self();
534   task_get_exception_ports(current_task,
535                            s_exception_mask,
536                            current.masks,
537                            &current.count,
538                            current.ports,
539                            current.behaviors,
540                            current.flavors);
541 
542   // Find the first exception handler that matches the exception
543   unsigned int found;
544   for (found = 0; found < current.count; ++found) {
545     if (current.masks[found] & (1 << exception)) {
546       break;
547     }
548   }
549 
550   // Nothing to forward
551   if (found == current.count) {
552     fprintf(stderr, "** No previous ports for forwarding!! \n");
553     _exit(KERN_FAILURE);
554   }
555 
556   mach_port_t target_port = current.ports[found];
557   exception_behavior_t target_behavior = current.behaviors[found];
558 
559   kern_return_t result;
560   switch (target_behavior & ~MACH_EXCEPTION_CODES) {
561     case EXCEPTION_DEFAULT:
562       result = mach_exception_raise(target_port, failed_thread, task, exception,
563                                     code, code_count);
564       break;
565     default:
566       fprintf(stderr, "** Unknown exception behavior: %d\n", target_behavior);
567       result = KERN_FAILURE;
568       break;
569   }
570 
571   return result;
572 }
573 
574 // static
WaitForMessage(void * exception_handler_class)575 void* ExceptionHandler::WaitForMessage(void* exception_handler_class) {
576   pthread_setname_np("Breakpad ExceptionHandler");
577 
578   ExceptionHandler* self =
579     reinterpret_cast<ExceptionHandler*>(exception_handler_class);
580   ExceptionMessage receive;
581 
582   // Wait for the exception info
583   while (1) {
584     receive.header.msgh_local_port = self->handler_port_;
585     receive.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(receive));
586     kern_return_t result = mach_msg(&(receive.header),
587                                     MACH_RCV_MSG | MACH_RCV_LARGE, 0,
588                                     receive.header.msgh_size,
589                                     self->handler_port_,
590                                     MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
591 
592 
593     if (result == KERN_SUCCESS) {
594       // Uninstall our handler so that we don't get in a loop if the process of
595       // writing out a minidump causes an exception.  However, if the exception
596       // was caused by a fork'd process, don't uninstall things
597 
598       // If the actual exception code is zero, then we're calling this handler
599       // in a way that indicates that we want to either exit this thread or
600       // generate a minidump
601       //
602       // While reporting, all threads (except this one) must be suspended
603       // to avoid misleading stacks.  If appropriate they will be resumed
604       // afterwards.
605       if (!receive.exception) {
606         // Don't touch self, since this message could have been sent
607         // from its destructor.
608         if (receive.header.msgh_id == kShutdownMessage)
609           return NULL;
610 
611         self->SuspendThreads();
612 
613 #if USE_PROTECTED_ALLOCATIONS
614         if (gBreakpadAllocator)
615           gBreakpadAllocator->Unprotect();
616 #endif
617 
618         mach_port_t thread = MACH_PORT_NULL;
619         int exception_type = 0;
620         int64_t exception_code = 0;
621         if (receive.header.msgh_id == kWriteDumpWithExceptionMessage) {
622           thread = receive.thread.name;
623           exception_type = EXC_BREAKPOINT;
624 #if defined(__i386__) || defined(__x86_64__)
625           exception_code = EXC_I386_BPT;
626 #elif defined(__ppc__) || defined(__ppc64__)
627           exception_code = EXC_PPC_BREAKPOINT;
628 #elif defined(__arm__) || defined(__aarch64__)
629           exception_code = EXC_ARM_BREAKPOINT;
630 #else
631 #error architecture not supported
632 #endif
633         }
634 
635         // Write out the dump and save the result for later retrieval
636         self->last_minidump_write_result_ =
637           self->WriteMinidumpWithException(exception_type, exception_code,
638                                            0, NULL, thread, mach_task_self(),
639                                            false, false);
640 
641 #if USE_PROTECTED_ALLOCATIONS
642         if (gBreakpadAllocator)
643           gBreakpadAllocator->Protect();
644 #endif
645 
646         self->ResumeThreads();
647 
648         if (self->use_minidump_write_mutex_)
649           pthread_mutex_unlock(&self->minidump_write_mutex_);
650       } else {
651         bool crash_reported = false;
652 
653         // When forking a child process with the exception handler installed,
654         // if the child crashes, it will send the exception back to the parent
655         // process.  The check for task == self_task() ensures that only
656         // exceptions that occur in the parent process are caught and
657         // processed.
658         if (receive.task.name == mach_task_self()) {
659           self->SuspendThreads();
660 
661 #if USE_PROTECTED_ALLOCATIONS
662           if (gBreakpadAllocator)
663             gBreakpadAllocator->Unprotect();
664 #endif
665 
666           mach_exception_data_type_t subcode = 0;
667           if (receive.code_count > 1) {
668             switch (receive.exception) {
669               case EXC_BAD_ACCESS:
670               case EXC_CRASH:
671               case EXC_RESOURCE:
672               case EXC_GUARD:
673                 subcode = receive.code[1];
674                 break;
675               default:
676                 subcode = 0;
677             }
678           }
679 
680           // Generate the minidump with the exception data.
681           crash_reported =
682             self->WriteMinidumpWithException(receive.exception, receive.code[0],
683                                              subcode, NULL, receive.thread.name,
684                                              mach_task_self(),  true, false);
685 
686 #if USE_PROTECTED_ALLOCATIONS
687           // This may have become protected again within
688           // WriteMinidumpWithException, but it needs to be unprotected for
689           // UninstallHandler.
690           if (gBreakpadAllocator)
691             gBreakpadAllocator->Unprotect();
692 #endif
693 
694           self->UninstallHandler(true);
695 
696 #if USE_PROTECTED_ALLOCATIONS
697           if (gBreakpadAllocator)
698             gBreakpadAllocator->Protect();
699 #endif
700         }
701 
702         ExceptionReplyMessage reply;
703         if (!mach_exc_server(&receive.header, &reply.header)) {
704           MOZ_CRASH_UNSAFE_PRINTF("Mach message id: %d crash reported = %d",
705                                   receive.header.msgh_id, crash_reported);
706         }
707 
708         // Send a reply and exit
709         mach_msg(&(reply.header), MACH_SEND_MSG,
710                  reply.header.msgh_size, 0, MACH_PORT_NULL,
711                  MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
712       }
713     }
714   }
715 
716   return NULL;
717 }
718 
719 // static
SignalHandler(int sig,siginfo_t * info,void * uc)720 void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) {
721 #if USE_PROTECTED_ALLOCATIONS
722   if (gBreakpadAllocator)
723     gBreakpadAllocator->Unprotect();
724 #endif
725   gProtectedData.handler->WriteMinidumpWithException(
726       EXC_SOFTWARE,
727       MD_EXCEPTION_CODE_MAC_ABORT,
728       0,
729       static_cast<breakpad_ucontext_t*>(uc),
730       mach_thread_self(),
731       mach_task_self(),
732       true,
733       true);
734 #if USE_PROTECTED_ALLOCATIONS
735   if (gBreakpadAllocator)
736     gBreakpadAllocator->Protect();
737 #endif
738 }
739 
740 // static
WriteForwardedExceptionMinidump(int exception_type,int64_t exception_code,int64_t exception_subcode,mach_port_t thread,mach_port_t task)741 bool ExceptionHandler::WriteForwardedExceptionMinidump(int exception_type,
742 						       int64_t exception_code,
743 						       int64_t exception_subcode,
744 						       mach_port_t thread,
745 						       mach_port_t task)
746 {
747   if (!gProtectedData.handler) {
748     return false;
749   }
750   return gProtectedData.handler->WriteMinidumpWithException(exception_type, exception_code,
751 							    exception_subcode, NULL, thread, task,
752 							    /* exit_after_write = */ false,
753 							    /* report_current_thread = */ true);
754 }
755 
InstallHandler()756 bool ExceptionHandler::InstallHandler() {
757   // If a handler is already installed, something is really wrong.
758   if (gProtectedData.handler != NULL) {
759     return false;
760   }
761 
762   struct sigaction sa;
763   memset(&sa, 0, sizeof(sa));
764   sigemptyset(&sa.sa_mask);
765   sigaddset(&sa.sa_mask, SIGABRT);
766   sa.sa_sigaction = ExceptionHandler::SignalHandler;
767   sa.sa_flags = SA_SIGINFO;
768 
769   scoped_ptr<struct sigaction> old(new struct sigaction);
770   if (sigaction(SIGABRT, &sa, old.get()) == -1) {
771     return false;
772   }
773   old_handler_.swap(old);
774   gProtectedData.handler = this;
775 #if USE_PROTECTED_ALLOCATIONS
776   assert(((size_t)(gProtectedData.protected_buffer) & PAGE_MASK) == 0);
777   mprotect(gProtectedData.protected_buffer, PAGE_SIZE, PROT_READ);
778 #endif
779 
780   try {
781 #if USE_PROTECTED_ALLOCATIONS
782     previous_ = new (gBreakpadAllocator->Allocate(sizeof(ExceptionParameters)) )
783       ExceptionParameters();
784 #else
785     previous_ = new ExceptionParameters();
786 #endif
787   }
788   catch (std::bad_alloc) {
789     return false;
790   }
791 
792   // Save the current exception ports so that we can forward to them
793   previous_->count = EXC_TYPES_COUNT;
794   mach_port_t current_task = mach_task_self();
795   kern_return_t result = task_get_exception_ports(current_task,
796                                                   s_exception_mask,
797                                                   previous_->masks,
798                                                   &previous_->count,
799                                                   previous_->ports,
800                                                   previous_->behaviors,
801                                                   previous_->flavors);
802 
803   // Setup the exception ports on this task.  Such documentation as exists for
804   // task_set_exception_port() and friends can be found in the source code for
805   // xnu.  Apple's implementation is available at https://opensource.apple.com/.
806   if (result == KERN_SUCCESS)
807     result = task_set_exception_ports(current_task, s_exception_mask,
808                                       handler_port_,
809                                       EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,
810                                       THREAD_STATE_NONE);
811 
812   installed_exception_handler_ = (result == KERN_SUCCESS);
813 
814   return installed_exception_handler_;
815 }
816 
UninstallHandler(bool in_exception)817 bool ExceptionHandler::UninstallHandler(bool in_exception) {
818   kern_return_t result = KERN_SUCCESS;
819 
820   if (old_handler_.get()) {
821     sigaction(SIGABRT, old_handler_.get(), NULL);
822 #if USE_PROTECTED_ALLOCATIONS
823     mprotect(gProtectedData.protected_buffer, PAGE_SIZE,
824         PROT_READ | PROT_WRITE);
825 #endif
826     // If we're handling an exception, leak the sigaction struct
827     // because it is unsafe to delete objects while in exception
828     // handling context.
829     if (in_exception) {
830       old_handler_.release();
831     } else {
832       old_handler_.reset();
833     }
834     gProtectedData.handler = NULL;
835   }
836 
837   if (installed_exception_handler_) {
838     mach_port_t current_task = mach_task_self();
839 
840     // Restore the previous ports
841     for (unsigned int i = 0; i < previous_->count; ++i) {
842        result = task_set_exception_ports(current_task, previous_->masks[i],
843                                         previous_->ports[i],
844                                         previous_->behaviors[i],
845                                         previous_->flavors[i]);
846       if (result != KERN_SUCCESS)
847         return false;
848     }
849 
850     // this delete should NOT happen if an exception just occurred!
851     if (!in_exception) {
852 #if USE_PROTECTED_ALLOCATIONS
853       previous_->~ExceptionParameters();
854 #else
855       delete previous_;
856 #endif
857     }
858 
859     previous_ = NULL;
860     installed_exception_handler_ = false;
861   }
862 
863   return result == KERN_SUCCESS;
864 }
865 
Setup(bool install_handler)866 bool ExceptionHandler::Setup(bool install_handler) {
867   if (pthread_mutex_init(&minidump_write_mutex_, NULL))
868     return false;
869 
870   // Create a receive right
871   mach_port_t current_task = mach_task_self();
872   kern_return_t result = mach_port_allocate(current_task,
873                                             MACH_PORT_RIGHT_RECEIVE,
874                                             &handler_port_);
875   // Add send right
876   if (result == KERN_SUCCESS)
877     result = mach_port_insert_right(current_task, handler_port_, handler_port_,
878                                     MACH_MSG_TYPE_MAKE_SEND);
879 
880   if (install_handler && result == KERN_SUCCESS)
881     if (!InstallHandler())
882       return false;
883 
884   if (result == KERN_SUCCESS) {
885     // Install the handler in its own thread, detached as we won't be joining.
886     pthread_attr_t attr;
887     pthread_attr_init(&attr);
888     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
889     int thread_create_result = pthread_create(&handler_thread_, &attr,
890                                               &WaitForMessage, this);
891     pthread_attr_destroy(&attr);
892     result = thread_create_result ? KERN_FAILURE : KERN_SUCCESS;
893   }
894 
895   return result == KERN_SUCCESS;
896 }
897 
Teardown()898 bool ExceptionHandler::Teardown() {
899   kern_return_t result = KERN_SUCCESS;
900   is_in_teardown_ = true;
901 
902   if (!UninstallHandler(false))
903     return false;
904 
905   // Send an empty message so that the handler_thread exits
906   if (SendMessageToHandlerThread(kShutdownMessage)) {
907     mach_port_t current_task = mach_task_self();
908     result = mach_port_deallocate(current_task, handler_port_);
909     if (result != KERN_SUCCESS)
910       return false;
911   } else {
912     return false;
913   }
914 
915   handler_thread_ = NULL;
916   handler_port_ = MACH_PORT_NULL;
917   pthread_mutex_destroy(&minidump_write_mutex_);
918 
919   return result == KERN_SUCCESS;
920 }
921 
SendMessageToHandlerThread(HandlerThreadMessage message_id)922 bool ExceptionHandler::SendMessageToHandlerThread(
923     HandlerThreadMessage message_id) {
924   ExceptionMessage msg;
925   memset(&msg, 0, sizeof(msg));
926   msg.header.msgh_id = message_id;
927   if (message_id == kWriteDumpMessage ||
928       message_id == kWriteDumpWithExceptionMessage) {
929     // Include this thread's port.
930     msg.thread.name = mach_thread_self();
931     msg.thread.disposition = MACH_MSG_TYPE_PORT_SEND;
932     msg.thread.type = MACH_MSG_PORT_DESCRIPTOR;
933   }
934   msg.header.msgh_size = sizeof(msg) - sizeof(msg.padding);
935   msg.header.msgh_remote_port = handler_port_;
936   msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,
937                                           MACH_MSG_TYPE_MAKE_SEND_ONCE);
938   kern_return_t result = mach_msg(&(msg.header),
939                                   MACH_SEND_MSG | MACH_SEND_TIMEOUT,
940                                   msg.header.msgh_size, 0, 0,
941                                   MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
942 
943   return result == KERN_SUCCESS;
944 }
945 
UpdateNextID()946 void ExceptionHandler::UpdateNextID() {
947   next_minidump_path_ =
948     (MinidumpGenerator::UniqueNameInDirectory(dump_path_, &next_minidump_id_));
949 
950   next_minidump_path_c_ = next_minidump_path_.c_str();
951   next_minidump_id_c_ = next_minidump_id_.c_str();
952 }
953 
SuspendThreads()954 bool ExceptionHandler::SuspendThreads() {
955   thread_act_port_array_t   threads_for_task;
956   mach_msg_type_number_t    thread_count;
957 
958   if (task_threads(mach_task_self(), &threads_for_task, &thread_count))
959     return false;
960 
961   // suspend all of the threads except for this one
962   for (unsigned int i = 0; i < thread_count; ++i) {
963     if (threads_for_task[i] != mach_thread_self()) {
964       if (thread_suspend(threads_for_task[i]))
965         return false;
966     }
967   }
968 
969   return true;
970 }
971 
ResumeThreads()972 bool ExceptionHandler::ResumeThreads() {
973   thread_act_port_array_t   threads_for_task;
974   mach_msg_type_number_t    thread_count;
975 
976   if (task_threads(mach_task_self(), &threads_for_task, &thread_count))
977     return false;
978 
979   // resume all of the threads except for this one
980   for (unsigned int i = 0; i < thread_count; ++i) {
981     if (threads_for_task[i] != mach_thread_self()) {
982       if (thread_resume(threads_for_task[i]))
983         return false;
984     }
985   }
986 
987   return true;
988 }
989 
990 }  // namespace google_breakpad
991