| // 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" |
| #include "common/mac/launch_reporter.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_params_.GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION), |
| 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(¶meter_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; |
| } |
| |
| } // namespace google_breakpad |
| |