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