toolkit/crashreporter/google-breakpad/src/client/mac/handler/exception_handler.cc

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial