PageRenderTime 33ms CodeModel.GetById 28ms app.highlight 2ms RepoModel.GetById 0ms app.codeStats 1ms

/thirdparty/breakpad/client/mac/crash_generation/Inspector.mm

http://github.com/tomahawk-player/tomahawk
Objective C++ | 431 lines | 259 code | 76 blank | 96 comment | 52 complexity | c9077187aeaad584668952596498fe6a MD5 | raw file
  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/SimpleStringDictionary.h"
 45#import "common/mac/MachIPC.h"
 46#include "common/mac/bootstrap_compat.h"
 47
 48#import "GTMDefines.h"
 49
 50#import <Foundation/Foundation.h>
 51
 52namespace google_breakpad {
 53
 54//=============================================================================
 55void 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(config_file_.GetFilePath());
 81      } else {
 82        fprintf(stderr, "Inspection of crashed process failed\n");
 83      }
 84
 85      // Now that we're done reading messages, cleanup the service, but only
 86      // if there was an actual exception
 87      // Otherwise, it means the dump was generated on demand and the process
 88      // lives on, and we might be needed again in the future.
 89      if (exception_code_) {
 90        ServiceCheckOut(receive_port_name);
 91      }
 92    } else {
 93        PRINT_MACH_RESULT(result, "Inspector: WaitForMessage()");
 94    }
 95  }
 96}
 97
 98//=============================================================================
 99kern_return_t Inspector::ResetBootstrapPort() {
100  // A reasonable default, in case anything fails.
101  bootstrap_subset_port_ = bootstrap_port;
102
103  mach_port_t self_task = mach_task_self();
104
105  kern_return_t kr = task_get_bootstrap_port(self_task,
106                                             &bootstrap_subset_port_);
107  if (kr != KERN_SUCCESS) {
108    NSLog(@"ResetBootstrapPort: task_get_bootstrap_port failed: %s (%d)",
109          mach_error_string(kr), kr);
110    return kr;
111  }
112
113  mach_port_t bootstrap_parent_port;
114  kr = bootstrap_look_up(bootstrap_subset_port_,
115                         const_cast<char*>(BREAKPAD_BOOTSTRAP_PARENT_PORT),
116                         &bootstrap_parent_port);
117  if (kr != BOOTSTRAP_SUCCESS) {
118    NSLog(@"ResetBootstrapPort: bootstrap_look_up failed: %s (%d)",
119#if defined(MAC_OS_X_VERSION_10_5) && \
120    MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
121          bootstrap_strerror(kr),
122#else
123          mach_error_string(kr),
124#endif
125          kr);
126    return kr;
127  }
128
129  kr = task_set_bootstrap_port(self_task, bootstrap_parent_port);
130  if (kr != KERN_SUCCESS) {
131    NSLog(@"ResetBootstrapPort: task_set_bootstrap_port failed: %s (%d)",
132          mach_error_string(kr), kr);
133    return kr;
134  }
135
136  // Some things access the bootstrap port through this global variable
137  // instead of calling task_get_bootstrap_port.
138  bootstrap_port = bootstrap_parent_port;
139
140  return KERN_SUCCESS;
141}
142
143//=============================================================================
144kern_return_t Inspector::ServiceCheckIn(const char *receive_port_name) {
145  // We need to get the mach port representing this service, so we can
146  // get information from the crashed process.
147  kern_return_t kr = bootstrap_check_in(bootstrap_subset_port_,
148                                        (char*)receive_port_name,
149                                        &service_rcv_port_);
150
151  if (kr != KERN_SUCCESS) {
152#if VERBOSE
153    PRINT_MACH_RESULT(kr, "Inspector: bootstrap_check_in()");
154#endif
155  }
156
157  return kr;
158}
159
160//=============================================================================
161kern_return_t Inspector::ServiceCheckOut(const char *receive_port_name) {
162  // We're done receiving mach messages from the crashed process,
163  // so clean up a bit.
164  kern_return_t kr;
165
166  // DO NOT use mach_port_deallocate() here -- it will fail and the
167  // following bootstrap_register() will also fail leaving our service
168  // name hanging around forever (until reboot)
169  kr = mach_port_destroy(mach_task_self(), service_rcv_port_);
170
171  if (kr != KERN_SUCCESS) {
172    PRINT_MACH_RESULT(kr,
173      "Inspector: UNREGISTERING: service_rcv_port mach_port_deallocate()");
174    return kr;
175  }
176
177  // Unregister the service associated with the receive port.
178  kr = breakpad::BootstrapRegister(bootstrap_subset_port_,
179                                   (char*)receive_port_name,
180                                   MACH_PORT_NULL);
181
182  if (kr != KERN_SUCCESS) {
183    PRINT_MACH_RESULT(kr, "Inspector: UNREGISTERING: bootstrap_register()");
184  }
185
186  return kr;
187}
188
189//=============================================================================
190kern_return_t Inspector::ReadMessages() {
191  // Wait for an initial message from the crashed process containing basic
192  // information about the crash.
193  ReceivePort receive_port(service_rcv_port_);
194
195  MachReceiveMessage message;
196  kern_return_t result = receive_port.WaitForMessage(&message, 1000);
197
198  if (result == KERN_SUCCESS) {
199    InspectorInfo &info = (InspectorInfo &)*message.GetData();
200    exception_type_ = info.exception_type;
201    exception_code_ = info.exception_code;
202    exception_subcode_ = info.exception_subcode;
203
204#if VERBOSE
205    printf("message ID = %d\n", message.GetMessageID());
206#endif
207
208    remote_task_ = message.GetTranslatedPort(0);
209    crashing_thread_ = message.GetTranslatedPort(1);
210    handler_thread_ = message.GetTranslatedPort(2);
211    ack_port_ = message.GetTranslatedPort(3);
212
213#if VERBOSE
214    printf("exception_type = %d\n", exception_type_);
215    printf("exception_code = %d\n", exception_code_);
216    printf("exception_subcode = %d\n", exception_subcode_);
217    printf("remote_task = %d\n", remote_task_);
218    printf("crashing_thread = %d\n", crashing_thread_);
219    printf("handler_thread = %d\n", handler_thread_);
220    printf("ack_port_ = %d\n", ack_port_);
221    printf("parameter count = %d\n", info.parameter_count);
222#endif
223
224    // In certain situations where multiple crash requests come
225    // through quickly, we can end up with the mach IPC messages not
226    // coming through correctly.  Since we don't know what parameters
227    // we've missed, we can't do much besides abort the crash dump
228    // situation in this case.
229    unsigned int parameters_read = 0;
230    // The initial message contains the number of key value pairs that
231    // we are expected to read.
232    // Read each key/value pair, one mach message per key/value pair.
233    for (unsigned int i = 0; i < info.parameter_count; ++i) {
234      MachReceiveMessage parameter_message;
235      result = receive_port.WaitForMessage(&parameter_message, 1000);
236
237      if(result == KERN_SUCCESS) {
238        KeyValueMessageData &key_value_data =
239          (KeyValueMessageData&)*parameter_message.GetData();
240        // If we get a blank key, make sure we don't increment the
241        // parameter count; in some cases (notably on-demand generation
242        // many times in a short period of time) caused the Mach IPC
243        // messages to not come through correctly.
244        if (strlen(key_value_data.key) == 0) {
245          continue;
246        }
247        parameters_read++;
248
249        config_params_.SetKeyValue(key_value_data.key, key_value_data.value);
250      } else {
251        PRINT_MACH_RESULT(result, "Inspector: key/value message");
252        break;
253      }
254    }
255    if (parameters_read != info.parameter_count) {
256      DEBUGLOG(stderr, "Only read %d parameters instead of %d, aborting crash "
257               "dump generation.", parameters_read, info.parameter_count);
258      return KERN_FAILURE;
259    }
260  }
261
262  return result;
263}
264
265//=============================================================================
266bool Inspector::InspectTask() {
267  // keep the task quiet while we're looking at it
268  task_suspend(remote_task_);
269  DEBUGLOG(stderr, "Suspended Remote task\n");
270
271  NSString *minidumpDir;
272
273  const char *minidumpDirectory =
274    config_params_.GetValueForKey(BREAKPAD_DUMP_DIRECTORY);
275
276  // If the client app has not specified a minidump directory,
277  // use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name>
278  if (!minidumpDirectory || 0 == strlen(minidumpDirectory)) {
279    NSArray *libraryDirectories =
280      NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
281                                          NSUserDomainMask,
282                                          YES);
283
284    NSString *applicationSupportDirectory =
285        [libraryDirectories objectAtIndex:0];
286    NSString *library_subdirectory = [NSString 
287        stringWithUTF8String:kDefaultLibrarySubdirectory];
288    NSString *breakpad_product = [NSString 
289        stringWithUTF8String:config_params_.GetValueForKey(BREAKPAD_PRODUCT)];
290        
291    NSArray *path_components = [NSArray
292        arrayWithObjects:applicationSupportDirectory,
293                         library_subdirectory,
294                         breakpad_product,
295                         nil];
296
297    minidumpDir = [NSString pathWithComponents:path_components];
298  } else {
299    minidumpDir = [[NSString stringWithUTF8String:minidumpDirectory]
300                    stringByExpandingTildeInPath];
301  }
302  DEBUGLOG(stderr, 
303           "Writing minidump to directory (%s)\n",
304           [minidumpDir UTF8String]);
305
306  MinidumpLocation minidumpLocation(minidumpDir);
307
308  // Obscure bug alert:
309  // Don't use [NSString stringWithFormat] to build up the path here since it
310  // assumes system encoding and in RTL locales will prepend an LTR override
311  // character for paths beginning with '/' which fileSystemRepresentation does
312  // not remove. Filed as rdar://6889706 .
313  NSString *path_ns = [NSString
314      stringWithUTF8String:minidumpLocation.GetPath()];
315  NSString *pathid_ns = [NSString
316      stringWithUTF8String:minidumpLocation.GetID()];
317  NSString *minidumpPath = [path_ns stringByAppendingPathComponent:pathid_ns];
318  minidumpPath = [minidumpPath 
319      stringByAppendingPathExtension:@"dmp"];
320  
321  DEBUGLOG(stderr, 
322           "minidump path (%s)\n",
323           [minidumpPath UTF8String]);
324
325
326  config_file_.WriteFile( 0,
327                          &config_params_,
328                          minidumpLocation.GetPath(),
329                          minidumpLocation.GetID());
330
331
332  MinidumpGenerator generator(remote_task_, handler_thread_);
333
334  if (exception_type_ && exception_code_) {
335    generator.SetExceptionInformation(exception_type_,
336                                      exception_code_,
337                                      exception_subcode_,
338                                      crashing_thread_);
339  }
340
341
342  bool result = generator.Write([minidumpPath fileSystemRepresentation]);
343
344  if (result) {
345    DEBUGLOG(stderr, "Wrote minidump - OK\n");
346  } else {
347    DEBUGLOG(stderr, "Error writing minidump - errno=%s\n",  strerror(errno));
348  }
349
350  // let the task continue
351  task_resume(remote_task_);
352  DEBUGLOG(stderr, "Resumed remote task\n");
353
354  return result;
355}
356
357//=============================================================================
358// The crashed task needs to be told that the inspection has finished.
359// It will wait on a mach port (with timeout) until we send acknowledgement.
360kern_return_t Inspector::SendAcknowledgement() {
361  if (ack_port_ != MACH_PORT_DEAD) {
362    MachPortSender sender(ack_port_);
363    MachSendMessage ack_message(kMsgType_InspectorAcknowledgement);
364
365    DEBUGLOG(stderr, "Inspector: trying to send acknowledgement to port %d\n",
366      ack_port_);
367
368    kern_return_t result = sender.SendMessage(ack_message, 2000);
369
370#if VERBOSE
371    PRINT_MACH_RESULT(result, "Inspector: sent acknowledgement");
372#endif
373
374    return result;
375  }
376
377  DEBUGLOG(stderr, "Inspector: port translation failure!\n");
378  return KERN_INVALID_NAME;
379}
380
381//=============================================================================
382void Inspector::LaunchReporter(const char *inConfigFilePath) {
383  // Extract the path to the reporter executable.
384  const char *reporterExecutablePath =
385          config_params_.GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION);
386  DEBUGLOG(stderr, "reporter path = %s\n", reporterExecutablePath);
387
388  // Setup and launch the crash dump sender.
389  const char *argv[3];
390  argv[0] = reporterExecutablePath;
391  argv[1] = inConfigFilePath;
392  argv[2] = NULL;
393
394  // Launch the reporter
395  pid_t pid = fork();
396
397  // If we're in the child, load in our new executable and run.
398  // The parent will not wait for the child to complete.
399  if (pid == 0) {
400    execv(argv[0], (char * const *)argv);
401    config_file_.Unlink();  // launch failed - get rid of config file
402    DEBUGLOG(stderr, "Inspector: unable to launch reporter app\n");
403    _exit(1);
404  }
405
406  // Wait until the Reporter child process exits.
407  //
408
409  // We'll use a timeout of one minute.
410  int timeoutCount = 60;   // 60 seconds
411
412  while (timeoutCount-- > 0) {
413    int status;
414    pid_t result = waitpid(pid, &status, WNOHANG);
415
416    if (result == 0) {
417      // The child has not yet finished.
418      sleep(1);
419    } else if (result == -1) {
420      DEBUGLOG(stderr, "Inspector: waitpid error (%d) waiting for reporter app\n",
421        errno);
422      break;
423    } else {
424      // child has finished
425      break;
426    }
427  }
428}
429
430} // namespace google_breakpad
431