exception_handler.cc revision 983264848d5372d8e64d62eb67f672c71e4b6470
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 <map> 31#include <pthread.h> 32 33#include "client/mac/handler/exception_handler.h" 34#include "client/mac/handler/minidump_generator.h" 35 36namespace google_airbag { 37 38using std::map; 39 40// These structures and techniques are illustrated in 41// Mac OS X Internals, Amit Singh, ch 9.7 42struct ExceptionMessage { 43 mach_msg_header_t header; 44 mach_msg_body_t body; 45 mach_msg_port_descriptor_t thread; 46 mach_msg_port_descriptor_t task; 47 NDR_record_t ndr; 48 exception_type_t exception; 49 mach_msg_type_number_t code_count; 50 integer_t code[EXCEPTION_CODE_MAX]; 51 char padding[512]; 52}; 53 54struct ExceptionParameters { 55 ExceptionParameters() : count(0) {} 56 mach_msg_type_number_t count; 57 exception_mask_t masks[EXC_TYPES_COUNT]; 58 mach_port_t ports[EXC_TYPES_COUNT]; 59 exception_behavior_t behaviors[EXC_TYPES_COUNT]; 60 thread_state_flavor_t flavors[EXC_TYPES_COUNT]; 61}; 62 63struct ExceptionReplyMessage { 64 mach_msg_header_t header; 65 NDR_record_t ndr; 66 kern_return_t return_code; 67}; 68 69// Only catch these three exceptions. The other ones are nebulously defined 70// and may result in treating a non-fatal exception as fatal. 71exception_mask_t s_exception_mask = EXC_MASK_BAD_ACCESS | 72EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC; 73 74extern "C" 75{ 76 // Forward declarations for functions that need "C" style compilation 77 boolean_t exc_server(mach_msg_header_t *request, 78 mach_msg_header_t *reply); 79 80 kern_return_t catch_exception_raise(mach_port_t target_port, 81 mach_port_t failed_thread, 82 mach_port_t task, 83 exception_type_t exception, 84 exception_data_t code, 85 mach_msg_type_number_t code_count); 86 87 kern_return_t ForwardException(mach_port_t task, 88 mach_port_t failed_thread, 89 exception_type_t exception, 90 exception_data_t code, 91 mach_msg_type_number_t code_count); 92 93 kern_return_t exception_raise(mach_port_t target_port, 94 mach_port_t failed_thread, 95 mach_port_t task, 96 exception_type_t exception, 97 exception_data_t exception_code, 98 mach_msg_type_number_t exception_code_count); 99 100 kern_return_t 101 exception_raise_state(mach_port_t target_port, 102 mach_port_t failed_thread, 103 mach_port_t task, 104 exception_type_t exception, 105 exception_data_t exception_code, 106 mach_msg_type_number_t code_count, 107 thread_state_flavor_t *target_flavor, 108 thread_state_t thread_state, 109 mach_msg_type_number_t thread_state_count, 110 thread_state_t thread_state, 111 mach_msg_type_number_t *thread_state_count); 112 113 kern_return_t 114 exception_raise_state_identity(mach_port_t target_port, 115 mach_port_t failed_thread, 116 mach_port_t task, 117 exception_type_t exception, 118 exception_data_t exception_code, 119 mach_msg_type_number_t exception_code_count, 120 thread_state_flavor_t *target_flavor, 121 thread_state_t thread_state, 122 mach_msg_type_number_t thread_state_count, 123 thread_state_t thread_state, 124 mach_msg_type_number_t *thread_state_count); 125} 126 127ExceptionHandler::ExceptionHandler(const string &dump_path, 128 FilterCallback filter, 129 MinidumpCallback callback, 130 void *callback_context, 131 bool install_handler) 132 : dump_path_(), 133 filter_(filter), 134 callback_(callback), 135 callback_context_(callback_context), 136 handler_thread_(NULL), 137 handler_port_(0), 138 previous_(NULL), 139 installed_exception_handler_(false), 140 is_in_teardown_(false), 141 last_minidump_write_result_(false), 142 use_minidump_write_mutex_(false) { 143 // This will update to the ID and C-string pointers 144 set_dump_path(dump_path); 145 MinidumpGenerator::GatherSystemInformation(); 146 Setup(install_handler); 147} 148 149ExceptionHandler::~ExceptionHandler() { 150 Teardown(); 151} 152 153bool ExceptionHandler::WriteMinidump() { 154 // If we're currently writing, just return 155 if (use_minidump_write_mutex_) 156 return false; 157 158 use_minidump_write_mutex_ = true; 159 last_minidump_write_result_ = false; 160 161 // Lock the mutex. Since we just created it, this will return immediately. 162 if (pthread_mutex_lock(&minidump_write_mutex_) == 0) { 163 // Send an empty message to the handle port so that a minidump will 164 // be written 165 SendEmptyMachMessage(); 166 167 // Wait for the minidump writer to complete its writing. It will unlock 168 // the mutex when completed 169 pthread_mutex_lock(&minidump_write_mutex_); 170 } 171 172 use_minidump_write_mutex_ = false; 173 UpdateNextID(); 174 return last_minidump_write_result_; 175} 176 177// static 178bool ExceptionHandler::WriteMinidump(const string &dump_path, 179 MinidumpCallback callback, 180 void *callback_context) { 181 ExceptionHandler handler(dump_path, NULL, callback, callback_context, false); 182 return handler.WriteMinidump(); 183} 184 185bool ExceptionHandler::WriteMinidumpWithException(int exception_type, 186 int exception_code, 187 mach_port_t thread_name) { 188 bool result = false; 189 string minidump_id; 190 191 // Putting the MinidumpGenerator in its own context will ensure that the 192 // destructor is executed, closing the newly created minidump file. 193 if (!dump_path_.empty()) { 194 MinidumpGenerator md; 195 if (exception_type && exception_code) { 196 // If this is a real exception, give the filter (if any) a chance to 197 // decided if this should be sent 198 if (filter_ && !filter_(callback_context_)) 199 return false; 200 201 md.SetExceptionInformation(exception_type, exception_code, thread_name); 202 } 203 204 result = md.Write(next_minidump_path_c_); 205 } 206 207 // Call user specified callback (if any) 208 if (callback_) { 209 // If the user callback returned true and we're handling an exception 210 // (rather than just writing out the file), then we should exit without 211 // forwarding the exception to the next handler. 212 if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_, 213 result)) { 214 if (exception_type && exception_code) 215 exit(exception_type); 216 } 217 } 218 219 return result; 220} 221 222kern_return_t ForwardException(mach_port_t task, mach_port_t failed_thread, 223 exception_type_t exception, 224 exception_data_t code, 225 mach_msg_type_number_t code_count) { 226 // At this time, we should have called Uninstall() on the exception handler 227 // so that the current exception ports are the ones that we should be 228 // forwarding to. 229 ExceptionParameters current; 230 231 current.count = EXC_TYPES_COUNT; 232 mach_port_t current_task = mach_task_self(); 233 kern_return_t result = task_get_exception_ports(current_task, 234 s_exception_mask, 235 current.masks, 236 ¤t.count, 237 current.ports, 238 current.behaviors, 239 current.flavors); 240 241 // Find the first exception handler that matches the exception 242 unsigned int found; 243 for (found = 0; found < current.count; ++found) { 244 if (current.masks[found] & (1 << exception)) { 245 break; 246 } 247 } 248 249 // Nothing to forward 250 if (found == current.count) { 251 fprintf(stderr, "** No previous ports for forwarding!! \n"); 252 exit(KERN_FAILURE); 253 } 254 255 mach_port_t target_port = current.ports[found]; 256 exception_behavior_t target_behavior = current.behaviors[found]; 257 thread_state_flavor_t target_flavor = current.flavors[found]; 258 259 mach_msg_type_number_t thread_state_count = THREAD_STATE_MAX; 260 thread_state_data_t thread_state; 261 switch (target_behavior) { 262 case EXCEPTION_DEFAULT: 263 result = exception_raise(target_port, failed_thread, task, exception, 264 code, code_count); 265 break; 266 267 case EXCEPTION_STATE: 268 result = thread_get_state(failed_thread, target_flavor, thread_state, 269 &thread_state_count); 270 if (result == KERN_SUCCESS) 271 result = exception_raise_state(target_port, failed_thread, task, 272 exception, code, 273 code_count, &target_flavor, 274 thread_state, thread_state_count, 275 thread_state, &thread_state_count); 276 if (result == KERN_SUCCESS) 277 result = thread_set_state(failed_thread, target_flavor, thread_state, 278 thread_state_count); 279 break; 280 281 case EXCEPTION_STATE_IDENTITY: 282 result = thread_get_state(failed_thread, target_flavor, thread_state, 283 &thread_state_count); 284 if (result == KERN_SUCCESS) 285 result = exception_raise_state_identity(target_port, failed_thread, 286 task, exception, code, 287 code_count, &target_flavor, 288 thread_state, 289 thread_state_count, 290 thread_state, 291 &thread_state_count); 292 if (result == KERN_SUCCESS) 293 result = thread_set_state(failed_thread, target_flavor, thread_state, 294 thread_state_count); 295 break; 296 297 default: 298 fprintf(stderr, "** Unknown exception behavior\n"); 299 result = KERN_FAILURE; 300 break; 301 } 302 303 return result; 304} 305 306// Callback from exc_server() 307kern_return_t catch_exception_raise(mach_port_t port, mach_port_t failed_thread, 308 mach_port_t task, 309 exception_type_t exception, 310 exception_data_t code, 311 mach_msg_type_number_t code_count) { 312 return ForwardException(task, failed_thread, exception, code, code_count); 313} 314 315// static 316void *ExceptionHandler::WaitForMessage(void *exception_handler_class) { 317 ExceptionHandler *self = 318 reinterpret_cast<ExceptionHandler *>(exception_handler_class); 319 ExceptionMessage receive; 320 321 // Wait for the exception info 322 while (1) { 323 receive.header.msgh_local_port = self->handler_port_; 324 receive.header.msgh_size = sizeof(receive); 325 kern_return_t result = mach_msg(&(receive.header), 326 MACH_RCV_MSG | MACH_RCV_LARGE, 0, 327 sizeof(receive), self->handler_port_, 328 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); 329 330 if (result == KERN_SUCCESS) { 331 // Uninstall our handler so that we don't get in a loop if the process of 332 // writing out a minidump causes an exception. However, if the exception 333 // was caused by a fork'd process, don't uninstall things 334 if (receive.task.name == mach_task_self()) 335 self->UninstallHandler(); 336 337 // If the actual exception code is zero, then we're calling this handler 338 // in a way that indicates that we want to either exit this thread or 339 // generate a minidump 340 if (!receive.exception) { 341 if (self->is_in_teardown_) 342 return NULL; 343 344 // Write out the dump and save the result for later retrieval 345 self->last_minidump_write_result_ = 346 self->WriteMinidumpWithException(0, 0, 0); 347 if (self->use_minidump_write_mutex_) 348 pthread_mutex_unlock(&self->minidump_write_mutex_); 349 } else { 350 // When forking a child process with the exception handler installed, 351 // if the child crashes, it will send the exception back to the parent 352 // process. The check for task == self_task() ensures that only 353 // exceptions that occur in the parent process are caught and 354 // processed. 355 if (receive.task.name == mach_task_self()) { 356 357 // Generate the minidump with the exception data. 358 self->WriteMinidumpWithException(receive.exception, receive.code[0], 359 receive.thread.name); 360 361 // Pass along the exception to the server, which will setup the 362 // message and call catch_exception_raise() and put the KERN_SUCCESS 363 // into the reply. 364 ExceptionReplyMessage reply; 365 if (!exc_server(&receive.header, &reply.header)) 366 exit(1); 367 368 // Send a reply and exit 369 result = mach_msg(&(reply.header), MACH_SEND_MSG, 370 reply.header.msgh_size, 0, MACH_PORT_NULL, 371 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); 372 } else { 373 // An exception occurred in a child process 374 } 375 } 376 } 377 } 378 379 return NULL; 380} 381 382bool ExceptionHandler::InstallHandler() { 383 try { 384 previous_ = new ExceptionParameters(); 385 } 386 catch (std::bad_alloc) { 387 return false; 388 } 389 390 // Save the current exception ports so that we can forward to them 391 previous_->count = EXC_TYPES_COUNT; 392 mach_port_t current_task = mach_task_self(); 393 kern_return_t result = task_get_exception_ports(current_task, 394 s_exception_mask, 395 previous_->masks, 396 &previous_->count, 397 previous_->ports, 398 previous_->behaviors, 399 previous_->flavors); 400 401 // Setup the exception ports on this task 402 if (result == KERN_SUCCESS) 403 result = task_set_exception_ports(current_task, s_exception_mask, 404 handler_port_, EXCEPTION_DEFAULT, 405 THREAD_STATE_NONE); 406 407 installed_exception_handler_ = (result == KERN_SUCCESS); 408 409 return installed_exception_handler_; 410} 411 412bool ExceptionHandler::UninstallHandler() { 413 kern_return_t result = KERN_SUCCESS; 414 415 if (installed_exception_handler_) { 416 mach_port_t current_task = mach_task_self(); 417 418 // Restore the previous ports 419 for (unsigned int i = 0; i < previous_->count; ++i) { 420 result = task_set_exception_ports(current_task, previous_->masks[i], 421 previous_->ports[i], 422 previous_->behaviors[i], 423 previous_->flavors[i]); 424 if (result != KERN_SUCCESS) 425 return false; 426 } 427 428 delete previous_; 429 previous_ = NULL; 430 installed_exception_handler_ = false; 431 } 432 433 return result == KERN_SUCCESS; 434} 435 436bool ExceptionHandler::Setup(bool install_handler) { 437 if (pthread_mutex_init(&minidump_write_mutex_, NULL)) 438 return false; 439 440 // Create a receive right 441 mach_port_t current_task = mach_task_self(); 442 kern_return_t result = mach_port_allocate(current_task, 443 MACH_PORT_RIGHT_RECEIVE, 444 &handler_port_); 445 // Add send right 446 if (result == KERN_SUCCESS) 447 result = mach_port_insert_right(current_task, handler_port_, handler_port_, 448 MACH_MSG_TYPE_MAKE_SEND); 449 450 if (install_handler && result == KERN_SUCCESS) 451 if (!InstallHandler()) 452 return false; 453 454 if (result == KERN_SUCCESS) { 455 // Install the handler in its own thread, detached as we won't be joining. 456 pthread_attr_t attr; 457 pthread_attr_init(&attr); 458 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 459 int thread_create_result = pthread_create(&handler_thread_, &attr, 460 &WaitForMessage, this); 461 pthread_attr_destroy(&attr); 462 result = thread_create_result ? KERN_FAILURE : KERN_SUCCESS; 463 } 464 465 return result == KERN_SUCCESS ? true : false; 466} 467 468bool ExceptionHandler::Teardown() { 469 kern_return_t result = KERN_SUCCESS; 470 is_in_teardown_ = true; 471 472 if (!UninstallHandler()) 473 return false; 474 475 // Send an empty message so that the handler_thread exits 476 if (SendEmptyMachMessage()) { 477 mach_port_t current_task = mach_task_self(); 478 result = mach_port_deallocate(current_task, handler_port_); 479 if (result != KERN_SUCCESS) 480 return false; 481 } else { 482 return false; 483 } 484 485 handler_thread_ = NULL; 486 handler_port_ = NULL; 487 pthread_mutex_destroy(&minidump_write_mutex_); 488 489 return result == KERN_SUCCESS; 490} 491 492bool ExceptionHandler::SendEmptyMachMessage() { 493 ExceptionMessage empty; 494 memset(&empty, 0, sizeof(empty)); 495 empty.header.msgh_size = sizeof(empty) - sizeof(empty.padding); 496 empty.header.msgh_remote_port = handler_port_; 497 empty.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 498 MACH_MSG_TYPE_MAKE_SEND_ONCE); 499 kern_return_t result = mach_msg(&(empty.header), 500 MACH_SEND_MSG | MACH_SEND_TIMEOUT, 501 empty.header.msgh_size, 0, 0, 502 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); 503 504 return result == KERN_SUCCESS; 505} 506 507void ExceptionHandler::UpdateNextID() { 508 next_minidump_path_ = 509 (MinidumpGenerator::UniqueNameInDirectory(dump_path_, &next_minidump_id_)); 510 511 next_minidump_path_c_ = next_minidump_path_.c_str(); 512 next_minidump_id_c_ = next_minidump_id_.c_str(); 513} 514 515} // namespace google_airbag 516