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