/ src / client / mac / crash_generation / Inspector.mm
Inspector.mm
  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  
 51  namespace google_breakpad {
 52  
 53  //=============================================================================
 54  void 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  //=============================================================================
100  kern_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  //=============================================================================
145  kern_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  //=============================================================================
162  kern_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  //=============================================================================
191  kern_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(&parameter_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  //=============================================================================
265  bool 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.
343  kern_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