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/MachIPC.h" 45#include "common/mac/bootstrap_compat.h" 46#include "common/mac/launch_reporter.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( 81 config_params_.GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION), 82 config_file_.GetFilePath()); 83 } else { 84 fprintf(stderr, "Inspection of crashed process failed\n"); 85 } 86 87 // Now that we're done reading messages, cleanup the service, but only 88 // if there was an actual exception 89 // Otherwise, it means the dump was generated on demand and the process 90 // lives on, and we might be needed again in the future. 91 if (exception_code_) { 92 ServiceCheckOut(receive_port_name); 93 } 94 } else { 95 PRINT_MACH_RESULT(result, "Inspector: WaitForMessage()"); 96 } 97 } 98} 99 100//============================================================================= 101kern_return_t Inspector::ResetBootstrapPort() { 102 // A reasonable default, in case anything fails. 103 bootstrap_subset_port_ = bootstrap_port; 104 105 mach_port_t self_task = mach_task_self(); 106 107 kern_return_t kr = task_get_bootstrap_port(self_task, 108 &bootstrap_subset_port_); 109 if (kr != KERN_SUCCESS) { 110 NSLog(@"ResetBootstrapPort: task_get_bootstrap_port failed: %s (%d)", 111 mach_error_string(kr), kr); 112 return kr; 113 } 114 115 mach_port_t bootstrap_parent_port; 116 kr = bootstrap_look_up(bootstrap_subset_port_, 117 const_cast<char*>(BREAKPAD_BOOTSTRAP_PARENT_PORT), 118 &bootstrap_parent_port); 119 if (kr != BOOTSTRAP_SUCCESS) { 120 NSLog(@"ResetBootstrapPort: bootstrap_look_up failed: %s (%d)", 121#if defined(MAC_OS_X_VERSION_10_5) && \ 122 MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 123 bootstrap_strerror(kr), 124#else 125 mach_error_string(kr), 126#endif 127 kr); 128 return kr; 129 } 130 131 kr = task_set_bootstrap_port(self_task, bootstrap_parent_port); 132 if (kr != KERN_SUCCESS) { 133 NSLog(@"ResetBootstrapPort: task_set_bootstrap_port failed: %s (%d)", 134 mach_error_string(kr), kr); 135 return kr; 136 } 137 138 // Some things access the bootstrap port through this global variable 139 // instead of calling task_get_bootstrap_port. 140 bootstrap_port = bootstrap_parent_port; 141 142 return KERN_SUCCESS; 143} 144 145//============================================================================= 146kern_return_t Inspector::ServiceCheckIn(const char *receive_port_name) { 147 // We need to get the mach port representing this service, so we can 148 // get information from the crashed process. 149 kern_return_t kr = bootstrap_check_in(bootstrap_subset_port_, 150 (char*)receive_port_name, 151 &service_rcv_port_); 152 153 if (kr != KERN_SUCCESS) { 154#if VERBOSE 155 PRINT_MACH_RESULT(kr, "Inspector: bootstrap_check_in()"); 156#endif 157 } 158 159 return kr; 160} 161 162//============================================================================= 163kern_return_t Inspector::ServiceCheckOut(const char *receive_port_name) { 164 // We're done receiving mach messages from the crashed process, 165 // so clean up a bit. 166 kern_return_t kr; 167 168 // DO NOT use mach_port_deallocate() here -- it will fail and the 169 // following bootstrap_register() will also fail leaving our service 170 // name hanging around forever (until reboot) 171 kr = mach_port_destroy(mach_task_self(), service_rcv_port_); 172 173 if (kr != KERN_SUCCESS) { 174 PRINT_MACH_RESULT(kr, 175 "Inspector: UNREGISTERING: service_rcv_port mach_port_deallocate()"); 176 return kr; 177 } 178 179 // Unregister the service associated with the receive port. 180 kr = breakpad::BootstrapRegister(bootstrap_subset_port_, 181 (char*)receive_port_name, 182 MACH_PORT_NULL); 183 184 if (kr != KERN_SUCCESS) { 185 PRINT_MACH_RESULT(kr, "Inspector: UNREGISTERING: bootstrap_register()"); 186 } 187 188 return kr; 189} 190 191//============================================================================= 192kern_return_t Inspector::ReadMessages() { 193 // Wait for an initial message from the crashed process containing basic 194 // information about the crash. 195 ReceivePort receive_port(service_rcv_port_); 196 197 MachReceiveMessage message; 198 kern_return_t result = receive_port.WaitForMessage(&message, 1000); 199 200 if (result == KERN_SUCCESS) { 201 InspectorInfo &info = (InspectorInfo &)*message.GetData(); 202 exception_type_ = info.exception_type; 203 exception_code_ = info.exception_code; 204 exception_subcode_ = info.exception_subcode; 205 206#if VERBOSE 207 printf("message ID = %d\n", message.GetMessageID()); 208#endif 209 210 remote_task_ = message.GetTranslatedPort(0); 211 crashing_thread_ = message.GetTranslatedPort(1); 212 handler_thread_ = message.GetTranslatedPort(2); 213 ack_port_ = message.GetTranslatedPort(3); 214 215#if VERBOSE 216 printf("exception_type = %d\n", exception_type_); 217 printf("exception_code = %d\n", exception_code_); 218 printf("exception_subcode = %d\n", exception_subcode_); 219 printf("remote_task = %d\n", remote_task_); 220 printf("crashing_thread = %d\n", crashing_thread_); 221 printf("handler_thread = %d\n", handler_thread_); 222 printf("ack_port_ = %d\n", ack_port_); 223 printf("parameter count = %d\n", info.parameter_count); 224#endif 225 226 // In certain situations where multiple crash requests come 227 // through quickly, we can end up with the mach IPC messages not 228 // coming through correctly. Since we don't know what parameters 229 // we've missed, we can't do much besides abort the crash dump 230 // situation in this case. 231 unsigned int parameters_read = 0; 232 // The initial message contains the number of key value pairs that 233 // we are expected to read. 234 // Read each key/value pair, one mach message per key/value pair. 235 for (unsigned int i = 0; i < info.parameter_count; ++i) { 236 MachReceiveMessage parameter_message; 237 result = receive_port.WaitForMessage(¶meter_message, 1000); 238 239 if(result == KERN_SUCCESS) { 240 KeyValueMessageData &key_value_data = 241 (KeyValueMessageData&)*parameter_message.GetData(); 242 // If we get a blank key, make sure we don't increment the 243 // parameter count; in some cases (notably on-demand generation 244 // many times in a short period of time) caused the Mach IPC 245 // messages to not come through correctly. 246 if (strlen(key_value_data.key) == 0) { 247 continue; 248 } 249 parameters_read++; 250 251 config_params_.SetKeyValue(key_value_data.key, key_value_data.value); 252 } else { 253 PRINT_MACH_RESULT(result, "Inspector: key/value message"); 254 break; 255 } 256 } 257 if (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 270 NSString *minidumpDir; 271 272 const char *minidumpDirectory = 273 config_params_.GetValueForKey(BREAKPAD_DUMP_DIRECTORY); 274 275 // If the client app has not specified a minidump directory, 276 // use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name> 277 if (!minidumpDirectory || 0 == strlen(minidumpDirectory)) { 278 NSArray *libraryDirectories = 279 NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, 280 NSUserDomainMask, 281 YES); 282 283 NSString *applicationSupportDirectory = 284 [libraryDirectories objectAtIndex:0]; 285 NSString *library_subdirectory = [NSString 286 stringWithUTF8String:kDefaultLibrarySubdirectory]; 287 NSString *breakpad_product = [NSString 288 stringWithUTF8String:config_params_.GetValueForKey(BREAKPAD_PRODUCT)]; 289 290 NSArray *path_components = [NSArray 291 arrayWithObjects:applicationSupportDirectory, 292 library_subdirectory, 293 breakpad_product, 294 nil]; 295 296 minidumpDir = [NSString pathWithComponents:path_components]; 297 } else { 298 minidumpDir = [[NSString stringWithUTF8String:minidumpDirectory] 299 stringByExpandingTildeInPath]; 300 } 301 302 MinidumpLocation minidumpLocation(minidumpDir); 303 304 // Obscure bug alert: 305 // Don't use [NSString stringWithFormat] to build up the path here since it 306 // assumes system encoding and in RTL locales will prepend an LTR override 307 // character for paths beginning with '/' which fileSystemRepresentation does 308 // not remove. Filed as rdar://6889706 . 309 NSString *path_ns = [NSString 310 stringWithUTF8String:minidumpLocation.GetPath()]; 311 NSString *pathid_ns = [NSString 312 stringWithUTF8String:minidumpLocation.GetID()]; 313 NSString *minidumpPath = [path_ns stringByAppendingPathComponent:pathid_ns]; 314 minidumpPath = [minidumpPath 315 stringByAppendingPathExtension:@"dmp"]; 316 317 config_file_.WriteFile( 0, 318 &config_params_, 319 minidumpLocation.GetPath(), 320 minidumpLocation.GetID()); 321 322 323 MinidumpGenerator generator(remote_task_, handler_thread_); 324 325 if (exception_type_ && exception_code_) { 326 generator.SetExceptionInformation(exception_type_, 327 exception_code_, 328 exception_subcode_, 329 crashing_thread_); 330 } 331 332 333 bool result = generator.Write([minidumpPath fileSystemRepresentation]); 334 335 // let the task continue 336 task_resume(remote_task_); 337 338 return result; 339} 340 341//============================================================================= 342// The crashed task needs to be told that the inspection has finished. 343// It will wait on a mach port (with timeout) until we send acknowledgement. 344kern_return_t Inspector::SendAcknowledgement() { 345 if (ack_port_ != MACH_PORT_DEAD) { 346 MachPortSender sender(ack_port_); 347 MachSendMessage ack_message(kMsgType_InspectorAcknowledgement); 348 349 kern_return_t result = sender.SendMessage(ack_message, 2000); 350 351#if VERBOSE 352 PRINT_MACH_RESULT(result, "Inspector: sent acknowledgement"); 353#endif 354 355 return result; 356 } 357 358 return KERN_INVALID_NAME; 359} 360 361} // namespace google_breakpad 362 363