Inspector.mm revision 42b7811fdc27fa1ec2a780c117f7279e699c5984
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 39#import "client/mac/crash_generation/Inspector.h" 40 41#import "client/mac/Framework/Breakpad.h" 42#import "client/mac/handler/minidump_generator.h" 43 44#import "common/mac/SimpleStringDictionary.h" 45#import "common/mac/MachIPC.h" 46#include "common/mac/bootstrap_compat.h" 47 48#import "GTMDefines.h" 49 50#import <Foundation/Foundation.h> 51 52namespace google_breakpad { 53 54//============================================================================= 55void Inspector::Inspect(const char *receive_port_name) { 56 kern_return_t result = ResetBootstrapPort(); 57 if (result != KERN_SUCCESS) { 58 return; 59 } 60 61 result = ServiceCheckIn(receive_port_name); 62 63 if (result == KERN_SUCCESS) { 64 result = ReadMessages(); 65 66 if (result == KERN_SUCCESS) { 67 // Inspect the task and write a minidump file. 68 bool wrote_minidump = InspectTask(); 69 70 // Send acknowledgement to the crashed process that the inspection 71 // has finished. It will then be able to cleanly exit. 72 // The return value is ignored because failure isn't fatal. If the process 73 // didn't get the message there's nothing we can do, and we still want to 74 // send the report. 75 SendAcknowledgement(); 76 77 if (wrote_minidump) { 78 // Ask the user if he wants to upload the crash report to a server, 79 // and do so if he agrees. 80 LaunchReporter(config_file_.GetFilePath()); 81 } else { 82 fprintf(stderr, "Inspection of crashed process failed\n"); 83 } 84 85 // Now that we're done reading messages, cleanup the service, but only 86 // if there was an actual exception 87 // Otherwise, it means the dump was generated on demand and the process 88 // lives on, and we might be needed again in the future. 89 if (exception_code_) { 90 ServiceCheckOut(receive_port_name); 91 } 92 } else { 93 PRINT_MACH_RESULT(result, "Inspector: WaitForMessage()"); 94 } 95 } 96} 97 98//============================================================================= 99kern_return_t Inspector::ResetBootstrapPort() { 100 // A reasonable default, in case anything fails. 101 bootstrap_subset_port_ = bootstrap_port; 102 103 mach_port_t self_task = mach_task_self(); 104 105 kern_return_t kr = task_get_bootstrap_port(self_task, 106 &bootstrap_subset_port_); 107 if (kr != KERN_SUCCESS) { 108 NSLog(@"ResetBootstrapPort: task_get_bootstrap_port failed: %s (%d)", 109 mach_error_string(kr), kr); 110 return kr; 111 } 112 113 mach_port_t bootstrap_parent_port; 114 kr = bootstrap_look_up(bootstrap_subset_port_, 115 const_cast<char*>(BREAKPAD_BOOTSTRAP_PARENT_PORT), 116 &bootstrap_parent_port); 117 if (kr != BOOTSTRAP_SUCCESS) { 118 NSLog(@"ResetBootstrapPort: bootstrap_look_up failed: %s (%d)", 119#if defined(MAC_OS_X_VERSION_10_5) && \ 120 MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 121 bootstrap_strerror(kr), 122#else 123 mach_error_string(kr), 124#endif 125 kr); 126 return kr; 127 } 128 129 kr = task_set_bootstrap_port(self_task, bootstrap_parent_port); 130 if (kr != KERN_SUCCESS) { 131 NSLog(@"ResetBootstrapPort: task_set_bootstrap_port failed: %s (%d)", 132 mach_error_string(kr), kr); 133 return kr; 134 } 135 136 // Some things access the bootstrap port through this global variable 137 // instead of calling task_get_bootstrap_port. 138 bootstrap_port = bootstrap_parent_port; 139 140 return KERN_SUCCESS; 141} 142 143//============================================================================= 144kern_return_t Inspector::ServiceCheckIn(const char *receive_port_name) { 145 // We need to get the mach port representing this service, so we can 146 // get information from the crashed process. 147 kern_return_t kr = bootstrap_check_in(bootstrap_subset_port_, 148 (char*)receive_port_name, 149 &service_rcv_port_); 150 151 if (kr != KERN_SUCCESS) { 152#if VERBOSE 153 PRINT_MACH_RESULT(kr, "Inspector: bootstrap_check_in()"); 154#endif 155 } 156 157 return kr; 158} 159 160//============================================================================= 161kern_return_t Inspector::ServiceCheckOut(const char *receive_port_name) { 162 // We're done receiving mach messages from the crashed process, 163 // so clean up a bit. 164 kern_return_t kr; 165 166 // DO NOT use mach_port_deallocate() here -- it will fail and the 167 // following bootstrap_register() will also fail leaving our service 168 // name hanging around forever (until reboot) 169 kr = mach_port_destroy(mach_task_self(), service_rcv_port_); 170 171 if (kr != KERN_SUCCESS) { 172 PRINT_MACH_RESULT(kr, 173 "Inspector: UNREGISTERING: service_rcv_port mach_port_deallocate()"); 174 return kr; 175 } 176 177 // Unregister the service associated with the receive port. 178 kr = breakpad::BootstrapRegister(bootstrap_subset_port_, 179 (char*)receive_port_name, 180 MACH_PORT_NULL); 181 182 if (kr != KERN_SUCCESS) { 183 PRINT_MACH_RESULT(kr, "Inspector: UNREGISTERING: bootstrap_register()"); 184 } 185 186 return kr; 187} 188 189//============================================================================= 190kern_return_t Inspector::ReadMessages() { 191 // Wait for an initial message from the crashed process containing basic 192 // information about the crash. 193 ReceivePort receive_port(service_rcv_port_); 194 195 MachReceiveMessage message; 196 kern_return_t result = receive_port.WaitForMessage(&message, 1000); 197 198 if (result == KERN_SUCCESS) { 199 InspectorInfo &info = (InspectorInfo &)*message.GetData(); 200 exception_type_ = info.exception_type; 201 exception_code_ = info.exception_code; 202 exception_subcode_ = info.exception_subcode; 203 204#if VERBOSE 205 printf("message ID = %d\n", message.GetMessageID()); 206#endif 207 208 remote_task_ = message.GetTranslatedPort(0); 209 crashing_thread_ = message.GetTranslatedPort(1); 210 handler_thread_ = message.GetTranslatedPort(2); 211 ack_port_ = message.GetTranslatedPort(3); 212 213#if VERBOSE 214 printf("exception_type = %d\n", exception_type_); 215 printf("exception_code = %d\n", exception_code_); 216 printf("exception_subcode = %d\n", exception_subcode_); 217 printf("remote_task = %d\n", remote_task_); 218 printf("crashing_thread = %d\n", crashing_thread_); 219 printf("handler_thread = %d\n", handler_thread_); 220 printf("ack_port_ = %d\n", ack_port_); 221 printf("parameter count = %d\n", info.parameter_count); 222#endif 223 224 // In certain situations where multiple crash requests come 225 // through quickly, we can end up with the mach IPC messages not 226 // coming through correctly. Since we don't know what parameters 227 // we've missed, we can't do much besides abort the crash dump 228 // situation in this case. 229 unsigned int parameters_read = 0; 230 // The initial message contains the number of key value pairs that 231 // we are expected to read. 232 // Read each key/value pair, one mach message per key/value pair. 233 for (unsigned int i = 0; i < info.parameter_count; ++i) { 234 MachReceiveMessage parameter_message; 235 result = receive_port.WaitForMessage(¶meter_message, 1000); 236 237 if(result == KERN_SUCCESS) { 238 KeyValueMessageData &key_value_data = 239 (KeyValueMessageData&)*parameter_message.GetData(); 240 // If we get a blank key, make sure we don't increment the 241 // parameter count; in some cases (notably on-demand generation 242 // many times in a short period of time) caused the Mach IPC 243 // messages to not come through correctly. 244 if (strlen(key_value_data.key) == 0) { 245 continue; 246 } 247 parameters_read++; 248 249 config_params_.SetKeyValue(key_value_data.key, key_value_data.value); 250 } else { 251 PRINT_MACH_RESULT(result, "Inspector: key/value message"); 252 break; 253 } 254 } 255 if (parameters_read != info.parameter_count) { 256 DEBUGLOG(stderr, "Only read %d parameters instead of %d, aborting crash " 257 "dump generation.", parameters_read, info.parameter_count); 258 return KERN_FAILURE; 259 } 260 } 261 262 return result; 263} 264 265//============================================================================= 266bool Inspector::InspectTask() { 267 // keep the task quiet while we're looking at it 268 task_suspend(remote_task_); 269 DEBUGLOG(stderr, "Suspended Remote task\n"); 270 271 NSString *minidumpDir; 272 273 const char *minidumpDirectory = 274 config_params_.GetValueForKey(BREAKPAD_DUMP_DIRECTORY); 275 276 // If the client app has not specified a minidump directory, 277 // use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name> 278 if (!minidumpDirectory || 0 == strlen(minidumpDirectory)) { 279 NSArray *libraryDirectories = 280 NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, 281 NSUserDomainMask, 282 YES); 283 284 NSString *applicationSupportDirectory = 285 [libraryDirectories objectAtIndex:0]; 286 NSString *library_subdirectory = [NSString 287 stringWithUTF8String:kDefaultLibrarySubdirectory]; 288 NSString *breakpad_product = [NSString 289 stringWithUTF8String:config_params_.GetValueForKey(BREAKPAD_PRODUCT)]; 290 291 NSArray *path_components = [NSArray 292 arrayWithObjects:applicationSupportDirectory, 293 library_subdirectory, 294 breakpad_product, 295 nil]; 296 297 minidumpDir = [NSString pathWithComponents:path_components]; 298 } else { 299 minidumpDir = [[NSString stringWithUTF8String:minidumpDirectory] 300 stringByExpandingTildeInPath]; 301 } 302 DEBUGLOG(stderr, 303 "Writing minidump to directory (%s)\n", 304 [minidumpDir UTF8String]); 305 306 MinidumpLocation minidumpLocation(minidumpDir); 307 308 // Obscure bug alert: 309 // Don't use [NSString stringWithFormat] to build up the path here since it 310 // assumes system encoding and in RTL locales will prepend an LTR override 311 // character for paths beginning with '/' which fileSystemRepresentation does 312 // not remove. Filed as rdar://6889706 . 313 NSString *path_ns = [NSString 314 stringWithUTF8String:minidumpLocation.GetPath()]; 315 NSString *pathid_ns = [NSString 316 stringWithUTF8String:minidumpLocation.GetID()]; 317 NSString *minidumpPath = [path_ns stringByAppendingPathComponent:pathid_ns]; 318 minidumpPath = [minidumpPath 319 stringByAppendingPathExtension:@"dmp"]; 320 321 DEBUGLOG(stderr, 322 "minidump path (%s)\n", 323 [minidumpPath UTF8String]); 324 325 326 config_file_.WriteFile( 0, 327 &config_params_, 328 minidumpLocation.GetPath(), 329 minidumpLocation.GetID()); 330 331 332 MinidumpGenerator generator(remote_task_, handler_thread_); 333 334 if (exception_type_ && exception_code_) { 335 generator.SetExceptionInformation(exception_type_, 336 exception_code_, 337 exception_subcode_, 338 crashing_thread_); 339 } 340 341 342 bool result = generator.Write([minidumpPath fileSystemRepresentation]); 343 344 if (result) { 345 DEBUGLOG(stderr, "Wrote minidump - OK\n"); 346 } else { 347 DEBUGLOG(stderr, "Error writing minidump - errno=%s\n", strerror(errno)); 348 } 349 350 // let the task continue 351 task_resume(remote_task_); 352 DEBUGLOG(stderr, "Resumed remote task\n"); 353 354 return result; 355} 356 357//============================================================================= 358// The crashed task needs to be told that the inspection has finished. 359// It will wait on a mach port (with timeout) until we send acknowledgement. 360kern_return_t Inspector::SendAcknowledgement() { 361 if (ack_port_ != MACH_PORT_DEAD) { 362 MachPortSender sender(ack_port_); 363 MachSendMessage ack_message(kMsgType_InspectorAcknowledgement); 364 365 DEBUGLOG(stderr, "Inspector: trying to send acknowledgement to port %d\n", 366 ack_port_); 367 368 kern_return_t result = sender.SendMessage(ack_message, 2000); 369 370#if VERBOSE 371 PRINT_MACH_RESULT(result, "Inspector: sent acknowledgement"); 372#endif 373 374 return result; 375 } 376 377 DEBUGLOG(stderr, "Inspector: port translation failure!\n"); 378 return KERN_INVALID_NAME; 379} 380 381//============================================================================= 382void Inspector::LaunchReporter(const char *inConfigFilePath) { 383 // Extract the path to the reporter executable. 384 const char *reporterExecutablePath = 385 config_params_.GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION); 386 DEBUGLOG(stderr, "reporter path = %s\n", reporterExecutablePath); 387 388 // Setup and launch the crash dump sender. 389 const char *argv[3]; 390 argv[0] = reporterExecutablePath; 391 argv[1] = inConfigFilePath; 392 argv[2] = NULL; 393 394 // Launch the reporter 395 pid_t pid = fork(); 396 397 // If we're in the child, load in our new executable and run. 398 // The parent will not wait for the child to complete. 399 if (pid == 0) { 400 execv(argv[0], (char * const *)argv); 401 config_file_.Unlink(); // launch failed - get rid of config file 402 DEBUGLOG(stderr, "Inspector: unable to launch reporter app\n"); 403 _exit(1); 404 } 405 406 // Wait until the Reporter child process exits. 407 // 408 409 // We'll use a timeout of one minute. 410 int timeoutCount = 60; // 60 seconds 411 412 while (timeoutCount-- > 0) { 413 int status; 414 pid_t result = waitpid(pid, &status, WNOHANG); 415 416 if (result == 0) { 417 // The child has not yet finished. 418 sleep(1); 419 } else if (result == -1) { 420 DEBUGLOG(stderr, "Inspector: waitpid error (%d) waiting for reporter app\n", 421 errno); 422 break; 423 } else { 424 // child has finished 425 break; 426 } 427 } 428} 429 430} // namespace google_breakpad 431 432