Home | History | Annotate | Download | only in crash_generation
      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 
     52 namespace google_breakpad {
     53 
     54 //=============================================================================
     55 void 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 //=============================================================================
    101 kern_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 //=============================================================================
    146 kern_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 //=============================================================================
    163 kern_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 //=============================================================================
    192 kern_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(&parameter_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 //=============================================================================
    266 bool 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.
    344 kern_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