Inspector.mm revision be368a3d4a5ab67d9bafc24e46635fb2d3fda7ea
1// Copyright (c) 2007, 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// Utility that can inspect another process and write a crash dump 31 32#include <cstdio> 33#include <iostream> 34#include <servers/bootstrap.h> 35#include <stdio.h> 36#include <string.h> 37#include <string> 38#include <sys/time.h> 39 40#import "client/mac/crash_generation/Inspector.h" 41 42#import "client/mac/Framework/Breakpad.h" 43#import "client/mac/handler/minidump_generator.h" 44 45#import "common/mac/SimpleStringDictionary.h" 46#import "common/mac/MachIPC.h" 47 48#import "GTMDefines.h" 49 50#import <Foundation/Foundation.h> 51 52#if VERBOSE 53 bool gDebugLog = true; 54#else 55 bool gDebugLog = false; 56#endif 57 58namespace google_breakpad { 59 60//============================================================================= 61BOOL EnsureDirectoryPathExists(NSString *dirPath) { 62 NSFileManager *mgr = [NSFileManager defaultManager]; 63 64 // If we got a relative path, prepend the current directory 65 if (![dirPath isAbsolutePath]) 66 dirPath = [[mgr currentDirectoryPath] stringByAppendingPathComponent:dirPath]; 67 68 NSString *path = dirPath; 69 70 // Ensure that no file exists within the path which would block creation 71 while (1) { 72 BOOL isDir; 73 if ([mgr fileExistsAtPath:path isDirectory:&isDir]) { 74 if (isDir) 75 break; 76 77 return NO; 78 } 79 80 path = [path stringByDeletingLastPathComponent]; 81 } 82 83 // Path now contains the first valid directory (or is empty) 84 if (![path length]) 85 return NO; 86 87 NSString *common = 88 [dirPath commonPrefixWithString:path options:NSLiteralSearch]; 89 90 // If everything is good 91 if ([common isEqualToString:dirPath]) 92 return YES; 93 94 // Break up the difference into components 95 NSString *diff = [dirPath substringFromIndex:[common length] + 1]; 96 NSArray *components = [diff pathComponents]; 97 NSUInteger count = [components count]; 98 99 // Rebuild the path one component at a time 100 NSDictionary *attrs = 101 [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:0750] 102 forKey:NSFilePosixPermissions]; 103 path = common; 104 for (NSUInteger i = 0; i < count; ++i) { 105 path = [path stringByAppendingPathComponent:[components objectAtIndex:i]]; 106 107 if (![mgr createDirectoryAtPath:path attributes:attrs]) 108 return NO; 109 } 110 111 return YES; 112} 113 114//============================================================================= 115BOOL ConfigFile::WriteData(const void *data, size_t length) { 116 size_t result = write(config_file_, data, length); 117 118 return result == length; 119} 120 121//============================================================================= 122BOOL ConfigFile::AppendConfigData(const char *key, 123 const void *data, size_t length) { 124 assert(config_file_ != -1); 125 126 if (!key) { 127 DEBUGLOG(stderr, "Breakpad: Missing Key\n"); 128 return NO; 129 } 130 131 if (!data) { 132 DEBUGLOG(stderr, "Breakpad: Missing data for key: %s\n", key ? key : 133 "<Unknown Key>"); 134 return NO; 135 } 136 137 // Write the key, \n, length of data (ascii integer), \n, data 138 char buffer[16]; 139 char nl = '\n'; 140 BOOL result = WriteData(key, strlen(key)); 141 142 snprintf(buffer, sizeof(buffer) - 1, "\n%lu\n", length); 143 result &= WriteData(buffer, strlen(buffer)); 144 result &= WriteData(data, length); 145 result &= WriteData(&nl, 1); 146 return result; 147} 148 149//============================================================================= 150BOOL ConfigFile::AppendConfigString(const char *key, 151 const char *value) { 152 return AppendConfigData(key, value, strlen(value)); 153} 154 155//============================================================================= 156void ConfigFile::WriteFile(const SimpleStringDictionary *configurationParameters, 157 const char *dump_dir, 158 const char *minidump_id) { 159 160 assert(config_file_ == -1); 161 162 // Open and write out configuration file preamble 163 strlcpy(config_file_path_, "/tmp/Config-XXXXXX", 164 sizeof(config_file_path_)); 165 config_file_ = mkstemp(config_file_path_); 166 167 if (config_file_ == -1) { 168 DEBUGLOG(stderr, 169 "mkstemp(config_file_path_) == -1 (%s)\n", 170 strerror(errno)); 171 return; 172 } 173 else { 174 DEBUGLOG(stderr, "Writing config file to (%s)\n", config_file_path_); 175 } 176 177 has_created_file_ = true; 178 179 // Add the minidump dir 180 AppendConfigString(kReporterMinidumpDirectoryKey, dump_dir); 181 AppendConfigString(kReporterMinidumpIDKey, minidump_id); 182 183 // Write out the configuration parameters 184 BOOL result = YES; 185 const SimpleStringDictionary &dictionary = *configurationParameters; 186 187 const KeyValueEntry *entry = NULL; 188 SimpleStringDictionaryIterator iter(dictionary); 189 190 while ((entry = iter.Next())) { 191 DEBUGLOG(stderr, 192 "config: (%s) -> (%s)\n", 193 entry->GetKey(), 194 entry->GetValue()); 195 result = AppendConfigString(entry->GetKey(), entry->GetValue()); 196 197 if (!result) 198 break; 199 } 200 201 close(config_file_); 202 config_file_ = -1; 203} 204 205//============================================================================= 206void Inspector::Inspect(const char *receive_port_name) { 207 kern_return_t result = ResetBootstrapPort(); 208 if (result != KERN_SUCCESS) { 209 return; 210 } 211 212 result = ServiceCheckIn(receive_port_name); 213 214 if (result == KERN_SUCCESS) { 215 result = ReadMessages(); 216 217 if (result == KERN_SUCCESS) { 218 // Inspect the task and write a minidump file. 219 bool wrote_minidump = InspectTask(); 220 221 // Send acknowledgement to the crashed process that the inspection 222 // has finished. It will then be able to cleanly exit. 223 // The return value is ignored because failure isn't fatal. If the process 224 // didn't get the message there's nothing we can do, and we still want to 225 // send the report. 226 SendAcknowledgement(); 227 228 if (wrote_minidump) { 229 // Ask the user if he wants to upload the crash report to a server, 230 // and do so if he agrees. 231 LaunchReporter(config_file_.GetFilePath()); 232 } else { 233 fprintf(stderr, "Inspection of crashed process failed\n"); 234 } 235 236 // Now that we're done reading messages, cleanup the service, but only 237 // if there was an actual exception 238 // Otherwise, it means the dump was generated on demand and the process 239 // lives on, and we might be needed again in the future. 240 if (exception_code_) { 241 ServiceCheckOut(receive_port_name); 242 } 243 } else { 244 PRINT_MACH_RESULT(result, "Inspector: WaitForMessage()"); 245 } 246 } 247} 248 249//============================================================================= 250kern_return_t Inspector::ResetBootstrapPort() { 251 // A reasonable default, in case anything fails. 252 bootstrap_subset_port_ = bootstrap_port; 253 254 mach_port_t self_task = mach_task_self(); 255 256 kern_return_t kr = task_get_bootstrap_port(self_task, 257 &bootstrap_subset_port_); 258 if (kr != KERN_SUCCESS) { 259 NSLog(@"ResetBootstrapPort: task_get_bootstrap_port failed: %s (%d)", 260 mach_error_string(kr), kr); 261 return kr; 262 } 263 264 mach_port_t bootstrap_parent_port; 265 kr = bootstrap_look_up(bootstrap_subset_port_, 266 const_cast<char*>(BREAKPAD_BOOTSTRAP_PARENT_PORT), 267 &bootstrap_parent_port); 268 if (kr != BOOTSTRAP_SUCCESS) { 269 NSLog(@"ResetBootstrapPort: bootstrap_look_up failed: %s (%d)", 270#if defined(MAC_OS_X_VERSION_10_5) && \ 271 MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 272 bootstrap_strerror(kr), 273#else 274 mach_error_string(kr), 275#endif 276 kr); 277 return kr; 278 } 279 280 kr = task_set_bootstrap_port(self_task, bootstrap_parent_port); 281 if (kr != KERN_SUCCESS) { 282 NSLog(@"ResetBootstrapPort: task_set_bootstrap_port failed: %s (%d)", 283 mach_error_string(kr), kr); 284 return kr; 285 } 286 287 // Some things access the bootstrap port through this global variable 288 // instead of calling task_get_bootstrap_port. 289 bootstrap_port = bootstrap_parent_port; 290 291 return KERN_SUCCESS; 292} 293 294//============================================================================= 295kern_return_t Inspector::ServiceCheckIn(const char *receive_port_name) { 296 // We need to get the mach port representing this service, so we can 297 // get information from the crashed process. 298 kern_return_t kr = bootstrap_check_in(bootstrap_subset_port_, 299 (char*)receive_port_name, 300 &service_rcv_port_); 301 302 if (kr != KERN_SUCCESS) { 303#if VERBOSE 304 PRINT_MACH_RESULT(kr, "Inspector: bootstrap_check_in()"); 305#endif 306 } 307 308 return kr; 309} 310 311//============================================================================= 312kern_return_t Inspector::ServiceCheckOut(const char *receive_port_name) { 313 // We're done receiving mach messages from the crashed process, 314 // so clean up a bit. 315 kern_return_t kr; 316 317 // DO NOT use mach_port_deallocate() here -- it will fail and the 318 // following bootstrap_register() will also fail leaving our service 319 // name hanging around forever (until reboot) 320 kr = mach_port_destroy(mach_task_self(), service_rcv_port_); 321 322 if (kr != KERN_SUCCESS) { 323 PRINT_MACH_RESULT(kr, 324 "Inspector: UNREGISTERING: service_rcv_port mach_port_deallocate()"); 325 return kr; 326 } 327 328 // Unregister the service associated with the receive port. 329 kr = bootstrap_register(bootstrap_subset_port_, 330 (char*)receive_port_name, 331 MACH_PORT_NULL); 332 333 if (kr != KERN_SUCCESS) { 334 PRINT_MACH_RESULT(kr, "Inspector: UNREGISTERING: bootstrap_register()"); 335 } 336 337 return kr; 338} 339 340//============================================================================= 341kern_return_t Inspector::ReadMessages() { 342 // Wait for an initial message from the crashed process containing basic 343 // information about the crash. 344 ReceivePort receive_port(service_rcv_port_); 345 346 MachReceiveMessage message; 347 kern_return_t result = receive_port.WaitForMessage(&message, 1000); 348 349 if (result == KERN_SUCCESS) { 350 InspectorInfo &info = (InspectorInfo &)*message.GetData(); 351 exception_type_ = info.exception_type; 352 exception_code_ = info.exception_code; 353 exception_subcode_ = info.exception_subcode; 354 355#if VERBOSE 356 printf("message ID = %d\n", message.GetMessageID()); 357#endif 358 359 remote_task_ = message.GetTranslatedPort(0); 360 crashing_thread_ = message.GetTranslatedPort(1); 361 handler_thread_ = message.GetTranslatedPort(2); 362 ack_port_ = message.GetTranslatedPort(3); 363 364#if VERBOSE 365 printf("exception_type = %d\n", exception_type_); 366 printf("exception_code = %d\n", exception_code_); 367 printf("exception_subcode = %d\n", exception_subcode_); 368 printf("remote_task = %d\n", remote_task_); 369 printf("crashing_thread = %d\n", crashing_thread_); 370 printf("handler_thread = %d\n", handler_thread_); 371 printf("ack_port_ = %d\n", ack_port_); 372 printf("parameter count = %d\n", info.parameter_count); 373#endif 374 375 // In certain situations where multiple crash requests come 376 // through quickly, we can end up with the mach IPC messages not 377 // coming through correctly. Since we don't know what parameters 378 // we've missed, we can't do much besides abort the crash dump 379 // situation in this case. 380 unsigned int parameters_read = 0; 381 // The initial message contains the number of key value pairs that 382 // we are expected to read. 383 // Read each key/value pair, one mach message per key/value pair. 384 for (unsigned int i = 0; i < info.parameter_count; ++i) { 385 MachReceiveMessage parameter_message; 386 result = receive_port.WaitForMessage(¶meter_message, 1000); 387 388 if(result == KERN_SUCCESS) { 389 KeyValueMessageData &key_value_data = 390 (KeyValueMessageData&)*parameter_message.GetData(); 391 // If we get a blank key, make sure we don't increment the 392 // parameter count; in some cases (notably on-demand generation 393 // many times in a short period of time) caused the Mach IPC 394 // messages to not come through correctly. 395 if (strlen(key_value_data.key) == 0) { 396 continue; 397 } 398 parameters_read++; 399 400 config_params_.SetKeyValue(key_value_data.key, key_value_data.value); 401 } else { 402 PRINT_MACH_RESULT(result, "Inspector: key/value message"); 403 break; 404 } 405 } 406 if (parameters_read != info.parameter_count) { 407 DEBUGLOG(stderr, "Only read %d parameters instead of %d, aborting crash " 408 "dump generation.", parameters_read, info.parameter_count); 409 return KERN_FAILURE; 410 } 411 } 412 413 return result; 414} 415 416//============================================================================= 417// Sets keys in the parameters dictionary that are specific to process uptime. 418// The two we set are process up time, and process crash time. 419void Inspector::SetCrashTimeParameters() { 420 // Set process uptime parameter 421 struct timeval tv; 422 gettimeofday(&tv, NULL); 423 424 char processUptimeString[32], processCrashtimeString[32]; 425 const char *processStartTimeString = 426 config_params_.GetValueForKey(BREAKPAD_PROCESS_START_TIME); 427 428 // Set up time if we've received the start time. 429 if (processStartTimeString) { 430 time_t processStartTime = strtol(processStartTimeString, NULL, 10); 431 time_t processUptime = tv.tv_sec - processStartTime; 432 sprintf(processUptimeString, "%zd", processUptime); 433 config_params_.SetKeyValue(BREAKPAD_PROCESS_UP_TIME, processUptimeString); 434 } 435 436 sprintf(processCrashtimeString, "%zd", tv.tv_sec); 437 config_params_.SetKeyValue(BREAKPAD_PROCESS_CRASH_TIME, 438 processCrashtimeString); 439} 440 441bool Inspector::InspectTask() { 442 // keep the task quiet while we're looking at it 443 task_suspend(remote_task_); 444 DEBUGLOG(stderr, "Suspended Remote task\n"); 445 446 NSString *minidumpDir; 447 448 const char *minidumpDirectory = 449 config_params_.GetValueForKey(BREAKPAD_DUMP_DIRECTORY); 450 451 SetCrashTimeParameters(); 452 // If the client app has not specified a minidump directory, 453 // use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name> 454 if (!minidumpDirectory || 0 == strlen(minidumpDirectory)) { 455 NSArray *libraryDirectories = 456 NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, 457 NSUserDomainMask, 458 YES); 459 460 NSString *applicationSupportDirectory = 461 [libraryDirectories objectAtIndex:0]; 462 NSString *library_subdirectory = [NSString 463 stringWithUTF8String:kDefaultLibrarySubdirectory]; 464 NSString *breakpad_product = [NSString 465 stringWithUTF8String:config_params_.GetValueForKey(BREAKPAD_PRODUCT)]; 466 467 NSArray *path_components = [NSArray 468 arrayWithObjects:applicationSupportDirectory, 469 library_subdirectory, 470 breakpad_product, 471 nil]; 472 473 minidumpDir = [NSString pathWithComponents:path_components]; 474 } else { 475 minidumpDir = [[NSString stringWithUTF8String:minidumpDirectory] 476 stringByExpandingTildeInPath]; 477 } 478 DEBUGLOG(stderr, 479 "Writing minidump to directory (%s)\n", 480 [minidumpDir UTF8String]); 481 482 MinidumpLocation minidumpLocation(minidumpDir); 483 484 // Obscure bug alert: 485 // Don't use [NSString stringWithFormat] to build up the path here since it 486 // assumes system encoding and in RTL locales will prepend an LTR override 487 // character for paths beginning with '/' which fileSystemRepresentation does 488 // not remove. Filed as rdar://6889706 . 489 NSString *path_ns = [NSString 490 stringWithUTF8String:minidumpLocation.GetPath()]; 491 NSString *pathid_ns = [NSString 492 stringWithUTF8String:minidumpLocation.GetID()]; 493 NSString *minidumpPath = [path_ns stringByAppendingPathComponent:pathid_ns]; 494 minidumpPath = [minidumpPath 495 stringByAppendingPathExtension:@"dmp"]; 496 497 DEBUGLOG(stderr, 498 "minidump path (%s)\n", 499 [minidumpPath UTF8String]); 500 501 502 config_file_.WriteFile( &config_params_, 503 minidumpLocation.GetPath(), 504 minidumpLocation.GetID()); 505 506 507 MinidumpGenerator generator(remote_task_, handler_thread_); 508 509 if (exception_type_ && exception_code_) { 510 generator.SetExceptionInformation(exception_type_, 511 exception_code_, 512 exception_subcode_, 513 crashing_thread_); 514 } 515 516 517 bool result = generator.Write([minidumpPath fileSystemRepresentation]); 518 519 if (result) { 520 DEBUGLOG(stderr, "Wrote minidump - OK\n"); 521 } else { 522 DEBUGLOG(stderr, "Error writing minidump - errno=%s\n", strerror(errno)); 523 } 524 525 // let the task continue 526 task_resume(remote_task_); 527 DEBUGLOG(stderr, "Resumed remote task\n"); 528 529 return result; 530} 531 532//============================================================================= 533// The crashed task needs to be told that the inspection has finished. 534// It will wait on a mach port (with timeout) until we send acknowledgement. 535kern_return_t Inspector::SendAcknowledgement() { 536 if (ack_port_ != MACH_PORT_DEAD) { 537 MachPortSender sender(ack_port_); 538 MachSendMessage ack_message(kMsgType_InspectorAcknowledgement); 539 540 DEBUGLOG(stderr, "Inspector: trying to send acknowledgement to port %d\n", 541 ack_port_); 542 543 kern_return_t result = sender.SendMessage(ack_message, 2000); 544 545#if VERBOSE 546 PRINT_MACH_RESULT(result, "Inspector: sent acknowledgement"); 547#endif 548 549 return result; 550 } 551 552 DEBUGLOG(stderr, "Inspector: port translation failure!\n"); 553 return KERN_INVALID_NAME; 554} 555 556//============================================================================= 557void Inspector::LaunchReporter(const char *inConfigFilePath) { 558 // Extract the path to the reporter executable. 559 const char *reporterExecutablePath = 560 config_params_.GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION); 561 DEBUGLOG(stderr, "reporter path = %s\n", reporterExecutablePath); 562 563 // Setup and launch the crash dump sender. 564 const char *argv[3]; 565 argv[0] = reporterExecutablePath; 566 argv[1] = inConfigFilePath; 567 argv[2] = NULL; 568 569 // Launch the reporter 570 pid_t pid = fork(); 571 572 // If we're in the child, load in our new executable and run. 573 // The parent will not wait for the child to complete. 574 if (pid == 0) { 575 execv(argv[0], (char * const *)argv); 576 config_file_.Unlink(); // launch failed - get rid of config file 577 DEBUGLOG(stderr, "Inspector: unable to launch reporter app\n"); 578 _exit(1); 579 } 580 581 // Wait until the Reporter child process exits. 582 // 583 584 // We'll use a timeout of one minute. 585 int timeoutCount = 60; // 60 seconds 586 587 while (timeoutCount-- > 0) { 588 int status; 589 pid_t result = waitpid(pid, &status, WNOHANG); 590 591 if (result == 0) { 592 // The child has not yet finished. 593 sleep(1); 594 } else if (result == -1) { 595 DEBUGLOG(stderr, "Inspector: waitpid error (%d) waiting for reporter app\n", 596 errno); 597 break; 598 } else { 599 // child has finished 600 break; 601 } 602 } 603} 604 605} // namespace google_breakpad 606 607