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 ¤t.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