1// Copyright 2007 Google LLC 2// 3// Redistribution and use in source and binary forms, with or without 4// modification, are permitted provided that the following conditions are 5// met: 6// 7// * Redistributions of source code must retain the above copyright 8// notice, this list of conditions and the following disclaimer. 9// * Redistributions in binary form must reproduce the above 10// copyright notice, this list of conditions and the following disclaimer 11// in the documentation and/or other materials provided with the 12// distribution. 13// * Neither the name of Google LLC nor the names of its 14// contributors may be used to endorse or promote products derived from 15// this software without specific prior written permission. 16// 17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28// 29// Utility that can inspect another process and write a crash dump 30 31#include <cstdio> 32#include <iostream> 33#include <servers/bootstrap.h> 34#include <stdio.h> 35#include <string.h> 36#include <string> 37 38#import "client/mac/crash_generation/Inspector.h" 39 40#import "client/mac/Framework/Breakpad.h" 41#import "client/mac/handler/minidump_generator.h" 42 43#import "common/mac/MachIPC.h" 44#include "common/mac/bootstrap_compat.h" 45#include "common/mac/launch_reporter.h" 46 47#import "GTMDefines.h" 48 49#import <Foundation/Foundation.h> 50 51namespace google_breakpad { 52 53//============================================================================= 54void Inspector::Inspect(const char* receive_port_name) { 55 kern_return_t result = ResetBootstrapPort(); 56 if (result != KERN_SUCCESS) { 57 return; 58 } 59 60 result = ServiceCheckIn(receive_port_name); 61 62 if (result == KERN_SUCCESS) { 63 result = ReadMessages(); 64 65 if (result == KERN_SUCCESS) { 66 // Inspect the task and write a minidump file. 67 bool wrote_minidump = InspectTask(); 68 69 // Send acknowledgement to the crashed process that the inspection 70 // has finished. It will then be able to cleanly exit. 71 // The return value is ignored because failure isn't fatal. If the process 72 // didn't get the message there's nothing we can do, and we still want to 73 // send the report. 74 SendAcknowledgement(); 75 76 if (wrote_minidump) { 77 // Ask the user if he wants to upload the crash report to a server, 78 // and do so if he agrees. 79 LaunchReporter( 80 config_params_.GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION), 81 config_file_.GetFilePath()); 82 } else { 83 fprintf(stderr, "Inspection of crashed process failed\n"); 84 } 85 86 // Now that we're done reading messages, cleanup the service, but only 87 // if there was an actual exception 88 // Otherwise, it means the dump was generated on demand and the process 89 // lives on, and we might be needed again in the future. 90 if (exception_code_) { 91 ServiceCheckOut(receive_port_name); 92 } 93 } else { 94 PRINT_MACH_RESULT(result, "Inspector: WaitForMessage()"); 95 } 96 } 97} 98 99//============================================================================= 100kern_return_t Inspector::ResetBootstrapPort() { 101 // A reasonable default, in case anything fails. 102 bootstrap_subset_port_ = bootstrap_port; 103 104 mach_port_t self_task = mach_task_self(); 105 106 kern_return_t kr = task_get_bootstrap_port(self_task, 107 &bootstrap_subset_port_); 108 if (kr != KERN_SUCCESS) { 109 NSLog(@"ResetBootstrapPort: task_get_bootstrap_port failed: %s (%d)", 110 mach_error_string(kr), kr); 111 return kr; 112 } 113 114 mach_port_t bootstrap_parent_port; 115 kr = bootstrap_look_up(bootstrap_subset_port_, 116 const_cast<char*>(BREAKPAD_BOOTSTRAP_PARENT_PORT), 117 &bootstrap_parent_port); 118 if (kr != BOOTSTRAP_SUCCESS) { 119 NSLog(@"ResetBootstrapPort: bootstrap_look_up failed: %s (%d)", 120#if defined(MAC_OS_X_VERSION_10_5) && \ 121 MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 122 bootstrap_strerror(kr), 123#else 124 mach_error_string(kr), 125#endif 126 kr); 127 return kr; 128 } 129 130 kr = task_set_bootstrap_port(self_task, bootstrap_parent_port); 131 if (kr != KERN_SUCCESS) { 132 NSLog(@"ResetBootstrapPort: task_set_bootstrap_port failed: %s (%d)", 133 mach_error_string(kr), kr); 134 return kr; 135 } 136 137 // Some things access the bootstrap port through this global variable 138 // instead of calling task_get_bootstrap_port. 139 bootstrap_port = bootstrap_parent_port; 140 141 return KERN_SUCCESS; 142} 143 144//============================================================================= 145kern_return_t Inspector::ServiceCheckIn(const char* receive_port_name) { 146 // We need to get the mach port representing this service, so we can 147 // get information from the crashed process. 148 kern_return_t kr = bootstrap_check_in(bootstrap_subset_port_, 149 (char*)receive_port_name, 150 &service_rcv_port_); 151 152 if (kr != KERN_SUCCESS) { 153#if VERBOSE 154 PRINT_MACH_RESULT(kr, "Inspector: bootstrap_check_in()"); 155#endif 156 } 157 158 return kr; 159} 160 161//============================================================================= 162kern_return_t Inspector::ServiceCheckOut(const char* receive_port_name) { 163 // We're done receiving mach messages from the crashed process, 164 // so clean up a bit. 165 kern_return_t kr; 166 167 // DO NOT use mach_port_deallocate() here -- it will fail and the 168 // following bootstrap_register() will also fail leaving our service 169 // name hanging around forever (until reboot) 170 kr = mach_port_destroy(mach_task_self(), service_rcv_port_); 171 172 if (kr != KERN_SUCCESS) { 173 PRINT_MACH_RESULT(kr, 174 "Inspector: UNREGISTERING: service_rcv_port mach_port_deallocate()"); 175 return kr; 176 } 177 178 // Unregister the service associated with the receive port. 179 kr = breakpad::BootstrapRegister(bootstrap_subset_port_, 180 (char*)receive_port_name, 181 MACH_PORT_NULL); 182 183 if (kr != KERN_SUCCESS) { 184 PRINT_MACH_RESULT(kr, "Inspector: UNREGISTERING: bootstrap_register()"); 185 } 186 187 return kr; 188} 189 190//============================================================================= 191kern_return_t Inspector::ReadMessages() { 192 // Wait for an initial message from the crashed process containing basic 193 // information about the crash. 194 ReceivePort receive_port(service_rcv_port_); 195 196 MachReceiveMessage message; 197 kern_return_t result = receive_port.WaitForMessage(&message, 1000); 198 199 if (result == KERN_SUCCESS) { 200 InspectorInfo& info = (InspectorInfo&)*message.GetData(); 201 exception_type_ = info.exception_type; 202 exception_code_ = info.exception_code; 203 exception_subcode_ = info.exception_subcode; 204 205#if VERBOSE 206 printf("message ID = %d\n", message.GetMessageID()); 207#endif 208 209 remote_task_ = message.GetTranslatedPort(0); 210 crashing_thread_ = message.GetTranslatedPort(1); 211 handler_thread_ = message.GetTranslatedPort(2); 212 ack_port_ = message.GetTranslatedPort(3); 213 214#if VERBOSE 215 printf("exception_type = %d\n", exception_type_); 216 printf("exception_code = %d\n", exception_code_); 217 printf("exception_subcode = %d\n", exception_subcode_); 218 printf("remote_task = %d\n", remote_task_); 219 printf("crashing_thread = %d\n", crashing_thread_); 220 printf("handler_thread = %d\n", handler_thread_); 221 printf("ack_port_ = %d\n", ack_port_); 222 printf("parameter count = %d\n", info.parameter_count); 223#endif 224 225 // In certain situations where multiple crash requests come 226 // through quickly, we can end up with the mach IPC messages not 227 // coming through correctly. Since we don't know what parameters 228 // we've missed, we can't do much besides abort the crash dump 229 // situation in this case. 230 unsigned int parameters_read = 0; 231 // The initial message contains the number of key value pairs that 232 // we are expected to read. 233 // Read each key/value pair, one mach message per key/value pair. 234 for (unsigned int i = 0; i < info.parameter_count; ++i) { 235 MachReceiveMessage parameter_message; 236 result = receive_port.WaitForMessage(¶meter_message, 1000); 237 238 if(result == KERN_SUCCESS) { 239 KeyValueMessageData& key_value_data = 240 (KeyValueMessageData&)*parameter_message.GetData(); 241 // If we get a blank key, make sure we don't increment the 242 // parameter count; in some cases (notably on-demand generation 243 // many times in a short period of time) caused the Mach IPC 244 // messages to not come through correctly. 245 if (strlen(key_value_data.key) == 0) { 246 continue; 247 } 248 parameters_read++; 249 250 config_params_.SetKeyValue(key_value_data.key, key_value_data.value); 251 } else { 252 PRINT_MACH_RESULT(result, "Inspector: key/value message"); 253 break; 254 } 255 } 256 if (parameters_read != info.parameter_count) { 257 return KERN_FAILURE; 258 } 259 } 260 261 return result; 262} 263 264//============================================================================= 265bool Inspector::InspectTask() { 266 // keep the task quiet while we're looking at it 267 task_suspend(remote_task_); 268 269 NSString* minidumpDir; 270 271 const char* minidumpDirectory = 272 config_params_.GetValueForKey(BREAKPAD_DUMP_DIRECTORY); 273 274 // If the client app has not specified a minidump directory, 275 // use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name> 276 if (!minidumpDirectory || 0 == strlen(minidumpDirectory)) { 277 NSArray* libraryDirectories = 278 NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, 279 NSUserDomainMask, 280 YES); 281 282 NSString* applicationSupportDirectory = 283 [libraryDirectories objectAtIndex:0]; 284 NSString* library_subdirectory = [NSString 285 stringWithUTF8String:kDefaultLibrarySubdirectory]; 286 NSString* breakpad_product = [NSString 287 stringWithUTF8String:config_params_.GetValueForKey(BREAKPAD_PRODUCT)]; 288 289 NSArray* path_components = [NSArray 290 arrayWithObjects:applicationSupportDirectory, 291 library_subdirectory, 292 breakpad_product, 293 nil]; 294 295 minidumpDir = [NSString pathWithComponents:path_components]; 296 } else { 297 minidumpDir = [[NSString stringWithUTF8String:minidumpDirectory] 298 stringByExpandingTildeInPath]; 299 } 300 301 MinidumpLocation minidumpLocation(minidumpDir); 302 303 // Obscure bug alert: 304 // Don't use [NSString stringWithFormat] to build up the path here since it 305 // assumes system encoding and in RTL locales will prepend an LTR override 306 // character for paths beginning with '/' which fileSystemRepresentation does 307 // not remove. Filed as rdar://6889706 . 308 NSString* path_ns = [NSString 309 stringWithUTF8String:minidumpLocation.GetPath()]; 310 NSString* pathid_ns = [NSString 311 stringWithUTF8String:minidumpLocation.GetID()]; 312 NSString* minidumpPath = [path_ns stringByAppendingPathComponent:pathid_ns]; 313 minidumpPath = [minidumpPath 314 stringByAppendingPathExtension:@"dmp"]; 315 316 config_file_.WriteFile( 0, 317 &config_params_, 318 minidumpLocation.GetPath(), 319 minidumpLocation.GetID()); 320 321 322 MinidumpGenerator generator(remote_task_, handler_thread_); 323 324 if (exception_type_ && exception_code_) { 325 generator.SetExceptionInformation(exception_type_, 326 exception_code_, 327 exception_subcode_, 328 crashing_thread_); 329 } 330 331 332 bool result = generator.Write([minidumpPath fileSystemRepresentation]); 333 334 // let the task continue 335 task_resume(remote_task_); 336 337 return result; 338} 339 340//============================================================================= 341// The crashed task needs to be told that the inspection has finished. 342// It will wait on a mach port (with timeout) until we send acknowledgement. 343kern_return_t Inspector::SendAcknowledgement() { 344 if (ack_port_ != MACH_PORT_DEAD) { 345 MachPortSender sender(ack_port_); 346 MachSendMessage ack_message(kMsgType_InspectorAcknowledgement); 347 348 kern_return_t result = sender.SendMessage(ack_message, 2000); 349 350#if VERBOSE 351 PRINT_MACH_RESULT(result, "Inspector: sent acknowledgement"); 352#endif 353 354 return result; 355 } 356 357 return KERN_INVALID_NAME; 358} 359 360} // namespace google_breakpad 361 362