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