blob: d226ca38d806f574ecc1bb13255b01c0ff7eaeaa [file] [log] [blame]
// Copyright (c) 2007, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Utility that can inspect another process and write a crash dump
#include <cstdio>
#include <iostream>
#include <servers/bootstrap.h>
#include <stdio.h>
#include <string.h>
#include <string>
#import "client/mac/crash_generation/Inspector.h"
#import "client/mac/Framework/Breakpad.h"
#import "client/mac/handler/minidump_generator.h"
#import "common/mac/MachIPC.h"
#include "common/mac/bootstrap_compat.h"
#import "GTMDefines.h"
#import <Foundation/Foundation.h>
namespace google_breakpad {
//=============================================================================
void Inspector::Inspect(const char *receive_port_name) {
kern_return_t result = ResetBootstrapPort();
if (result != KERN_SUCCESS) {
return;
}
result = ServiceCheckIn(receive_port_name);
if (result == KERN_SUCCESS) {
result = ReadMessages();
if (result == KERN_SUCCESS) {
// Inspect the task and write a minidump file.
bool wrote_minidump = InspectTask();
// Send acknowledgement to the crashed process that the inspection
// has finished. It will then be able to cleanly exit.
// The return value is ignored because failure isn't fatal. If the process
// didn't get the message there's nothing we can do, and we still want to
// send the report.
SendAcknowledgement();
if (wrote_minidump) {
// Ask the user if he wants to upload the crash report to a server,
// and do so if he agrees.
LaunchReporter(config_file_.GetFilePath());
} else {
fprintf(stderr, "Inspection of crashed process failed\n");
}
// Now that we're done reading messages, cleanup the service, but only
// if there was an actual exception
// Otherwise, it means the dump was generated on demand and the process
// lives on, and we might be needed again in the future.
if (exception_code_) {
ServiceCheckOut(receive_port_name);
}
} else {
PRINT_MACH_RESULT(result, "Inspector: WaitForMessage()");
}
}
}
//=============================================================================
kern_return_t Inspector::ResetBootstrapPort() {
// A reasonable default, in case anything fails.
bootstrap_subset_port_ = bootstrap_port;
mach_port_t self_task = mach_task_self();
kern_return_t kr = task_get_bootstrap_port(self_task,
&bootstrap_subset_port_);
if (kr != KERN_SUCCESS) {
NSLog(@"ResetBootstrapPort: task_get_bootstrap_port failed: %s (%d)",
mach_error_string(kr), kr);
return kr;
}
mach_port_t bootstrap_parent_port;
kr = bootstrap_look_up(bootstrap_subset_port_,
const_cast<char*>(BREAKPAD_BOOTSTRAP_PARENT_PORT),
&bootstrap_parent_port);
if (kr != BOOTSTRAP_SUCCESS) {
NSLog(@"ResetBootstrapPort: bootstrap_look_up failed: %s (%d)",
#if defined(MAC_OS_X_VERSION_10_5) && \
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
bootstrap_strerror(kr),
#else
mach_error_string(kr),
#endif
kr);
return kr;
}
kr = task_set_bootstrap_port(self_task, bootstrap_parent_port);
if (kr != KERN_SUCCESS) {
NSLog(@"ResetBootstrapPort: task_set_bootstrap_port failed: %s (%d)",
mach_error_string(kr), kr);
return kr;
}
// Some things access the bootstrap port through this global variable
// instead of calling task_get_bootstrap_port.
bootstrap_port = bootstrap_parent_port;
return KERN_SUCCESS;
}
//=============================================================================
kern_return_t Inspector::ServiceCheckIn(const char *receive_port_name) {
// We need to get the mach port representing this service, so we can
// get information from the crashed process.
kern_return_t kr = bootstrap_check_in(bootstrap_subset_port_,
(char*)receive_port_name,
&service_rcv_port_);
if (kr != KERN_SUCCESS) {
#if VERBOSE
PRINT_MACH_RESULT(kr, "Inspector: bootstrap_check_in()");
#endif
}
return kr;
}
//=============================================================================
kern_return_t Inspector::ServiceCheckOut(const char *receive_port_name) {
// We're done receiving mach messages from the crashed process,
// so clean up a bit.
kern_return_t kr;
// DO NOT use mach_port_deallocate() here -- it will fail and the
// following bootstrap_register() will also fail leaving our service
// name hanging around forever (until reboot)
kr = mach_port_destroy(mach_task_self(), service_rcv_port_);
if (kr != KERN_SUCCESS) {
PRINT_MACH_RESULT(kr,
"Inspector: UNREGISTERING: service_rcv_port mach_port_deallocate()");
return kr;
}
// Unregister the service associated with the receive port.
kr = breakpad::BootstrapRegister(bootstrap_subset_port_,
(char*)receive_port_name,
MACH_PORT_NULL);
if (kr != KERN_SUCCESS) {
PRINT_MACH_RESULT(kr, "Inspector: UNREGISTERING: bootstrap_register()");
}
return kr;
}
//=============================================================================
kern_return_t Inspector::ReadMessages() {
// Wait for an initial message from the crashed process containing basic
// information about the crash.
ReceivePort receive_port(service_rcv_port_);
MachReceiveMessage message;
kern_return_t result = receive_port.WaitForMessage(&message, 1000);
if (result == KERN_SUCCESS) {
InspectorInfo &info = (InspectorInfo &)*message.GetData();
exception_type_ = info.exception_type;
exception_code_ = info.exception_code;
exception_subcode_ = info.exception_subcode;
#if VERBOSE
printf("message ID = %d\n", message.GetMessageID());
#endif
remote_task_ = message.GetTranslatedPort(0);
crashing_thread_ = message.GetTranslatedPort(1);
handler_thread_ = message.GetTranslatedPort(2);
ack_port_ = message.GetTranslatedPort(3);
#if VERBOSE
printf("exception_type = %d\n", exception_type_);
printf("exception_code = %d\n", exception_code_);
printf("exception_subcode = %d\n", exception_subcode_);
printf("remote_task = %d\n", remote_task_);
printf("crashing_thread = %d\n", crashing_thread_);
printf("handler_thread = %d\n", handler_thread_);
printf("ack_port_ = %d\n", ack_port_);
printf("parameter count = %d\n", info.parameter_count);
#endif
// In certain situations where multiple crash requests come
// through quickly, we can end up with the mach IPC messages not
// coming through correctly. Since we don't know what parameters
// we've missed, we can't do much besides abort the crash dump
// situation in this case.
unsigned int parameters_read = 0;
// The initial message contains the number of key value pairs that
// we are expected to read.
// Read each key/value pair, one mach message per key/value pair.
for (unsigned int i = 0; i < info.parameter_count; ++i) {
MachReceiveMessage parameter_message;
result = receive_port.WaitForMessage(&parameter_message, 1000);
if(result == KERN_SUCCESS) {
KeyValueMessageData &key_value_data =
(KeyValueMessageData&)*parameter_message.GetData();
// If we get a blank key, make sure we don't increment the
// parameter count; in some cases (notably on-demand generation
// many times in a short period of time) caused the Mach IPC
// messages to not come through correctly.
if (strlen(key_value_data.key) == 0) {
continue;
}
parameters_read++;
config_params_.SetKeyValue(key_value_data.key, key_value_data.value);
} else {
PRINT_MACH_RESULT(result, "Inspector: key/value message");
break;
}
}
if (parameters_read != info.parameter_count) {
return KERN_FAILURE;
}
}
return result;
}
//=============================================================================
bool Inspector::InspectTask() {
// keep the task quiet while we're looking at it
task_suspend(remote_task_);
NSString *minidumpDir;
const char *minidumpDirectory =
config_params_.GetValueForKey(BREAKPAD_DUMP_DIRECTORY);
// If the client app has not specified a minidump directory,
// use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name>
if (!minidumpDirectory || 0 == strlen(minidumpDirectory)) {
NSArray *libraryDirectories =
NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
NSUserDomainMask,
YES);
NSString *applicationSupportDirectory =
[libraryDirectories objectAtIndex:0];
NSString *library_subdirectory = [NSString
stringWithUTF8String:kDefaultLibrarySubdirectory];
NSString *breakpad_product = [NSString
stringWithUTF8String:config_params_.GetValueForKey(BREAKPAD_PRODUCT)];
NSArray *path_components = [NSArray
arrayWithObjects:applicationSupportDirectory,
library_subdirectory,
breakpad_product,
nil];
minidumpDir = [NSString pathWithComponents:path_components];
} else {
minidumpDir = [[NSString stringWithUTF8String:minidumpDirectory]
stringByExpandingTildeInPath];
}
MinidumpLocation minidumpLocation(minidumpDir);
// Obscure bug alert:
// Don't use [NSString stringWithFormat] to build up the path here since it
// assumes system encoding and in RTL locales will prepend an LTR override
// character for paths beginning with '/' which fileSystemRepresentation does
// not remove. Filed as rdar://6889706 .
NSString *path_ns = [NSString
stringWithUTF8String:minidumpLocation.GetPath()];
NSString *pathid_ns = [NSString
stringWithUTF8String:minidumpLocation.GetID()];
NSString *minidumpPath = [path_ns stringByAppendingPathComponent:pathid_ns];
minidumpPath = [minidumpPath
stringByAppendingPathExtension:@"dmp"];
config_file_.WriteFile( 0,
&config_params_,
minidumpLocation.GetPath(),
minidumpLocation.GetID());
MinidumpGenerator generator(remote_task_, handler_thread_);
if (exception_type_ && exception_code_) {
generator.SetExceptionInformation(exception_type_,
exception_code_,
exception_subcode_,
crashing_thread_);
}
bool result = generator.Write([minidumpPath fileSystemRepresentation]);
// let the task continue
task_resume(remote_task_);
return result;
}
//=============================================================================
// The crashed task needs to be told that the inspection has finished.
// It will wait on a mach port (with timeout) until we send acknowledgement.
kern_return_t Inspector::SendAcknowledgement() {
if (ack_port_ != MACH_PORT_DEAD) {
MachPortSender sender(ack_port_);
MachSendMessage ack_message(kMsgType_InspectorAcknowledgement);
kern_return_t result = sender.SendMessage(ack_message, 2000);
#if VERBOSE
PRINT_MACH_RESULT(result, "Inspector: sent acknowledgement");
#endif
return result;
}
return KERN_INVALID_NAME;
}
//=============================================================================
void Inspector::LaunchReporter(const char *inConfigFilePath) {
// Extract the path to the reporter executable.
const char *reporterExecutablePath =
config_params_.GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION);
// Setup and launch the crash dump sender.
const char *argv[3];
argv[0] = reporterExecutablePath;
argv[1] = inConfigFilePath;
argv[2] = NULL;
// Launch the reporter
pid_t pid = fork();
// If we're in the child, load in our new executable and run.
// The parent will not wait for the child to complete.
if (pid == 0) {
execv(argv[0], (char * const *)argv);
config_file_.Unlink(); // launch failed - get rid of config file
_exit(1);
}
// Wait until the Reporter child process exits.
//
// We'll use a timeout of one minute.
int timeoutCount = 60; // 60 seconds
while (timeoutCount-- > 0) {
int status;
pid_t result = waitpid(pid, &status, WNOHANG);
if (result == 0) {
// The child has not yet finished.
sleep(1);
} else if (result == -1) {
// error occurred.
break;
} else {
// child has finished
break;
}
}
}
} // namespace google_breakpad