Inspector.mm revision be368a3d4a5ab67d9bafc24e46635fb2d3fda7ea
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#include <sys/time.h>
39
40#import "client/mac/crash_generation/Inspector.h"
41
42#import "client/mac/Framework/Breakpad.h"
43#import "client/mac/handler/minidump_generator.h"
44
45#import "common/mac/SimpleStringDictionary.h"
46#import "common/mac/MachIPC.h"
47
48#import "GTMDefines.h"
49
50#import <Foundation/Foundation.h>
51
52#if VERBOSE
53  bool gDebugLog = true;
54#else
55  bool gDebugLog = false;
56#endif
57
58namespace google_breakpad {
59
60//=============================================================================
61BOOL EnsureDirectoryPathExists(NSString *dirPath) {
62  NSFileManager *mgr = [NSFileManager defaultManager];
63
64  // If we got a relative path, prepend the current directory
65  if (![dirPath isAbsolutePath])
66    dirPath = [[mgr currentDirectoryPath] stringByAppendingPathComponent:dirPath];
67
68  NSString *path = dirPath;
69
70  // Ensure that no file exists within the path which would block creation
71  while (1) {
72    BOOL isDir;
73    if ([mgr fileExistsAtPath:path isDirectory:&isDir]) {
74      if (isDir)
75        break;
76
77      return NO;
78    }
79
80    path = [path stringByDeletingLastPathComponent];
81  }
82
83  // Path now contains the first valid directory (or is empty)
84  if (![path length])
85    return NO;
86
87  NSString *common =
88    [dirPath commonPrefixWithString:path options:NSLiteralSearch];
89
90  // If everything is good
91  if ([common isEqualToString:dirPath])
92    return YES;
93
94  // Break up the difference into components
95  NSString *diff = [dirPath substringFromIndex:[common length] + 1];
96  NSArray *components = [diff pathComponents];
97  NSUInteger count = [components count];
98
99  // Rebuild the path one component at a time
100  NSDictionary *attrs =
101    [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:0750]
102                                forKey:NSFilePosixPermissions];
103  path = common;
104  for (NSUInteger i = 0; i < count; ++i) {
105    path = [path stringByAppendingPathComponent:[components objectAtIndex:i]];
106
107    if (![mgr createDirectoryAtPath:path attributes:attrs])
108      return NO;
109  }
110
111  return YES;
112}
113
114//=============================================================================
115BOOL ConfigFile::WriteData(const void *data, size_t length) {
116  size_t result = write(config_file_, data, length);
117
118  return result == length;
119}
120
121//=============================================================================
122BOOL ConfigFile::AppendConfigData(const char *key,
123                                  const void *data, size_t length) {
124  assert(config_file_ != -1);
125
126  if (!key) {
127    DEBUGLOG(stderr, "Breakpad: Missing Key\n");
128    return NO;
129  }
130
131  if (!data) {
132    DEBUGLOG(stderr, "Breakpad: Missing data for key: %s\n", key ? key :
133            "<Unknown Key>");
134    return NO;
135  }
136
137  // Write the key, \n, length of data (ascii integer), \n, data
138  char buffer[16];
139  char nl = '\n';
140  BOOL result = WriteData(key, strlen(key));
141
142  snprintf(buffer, sizeof(buffer) - 1, "\n%lu\n", length);
143  result &= WriteData(buffer, strlen(buffer));
144  result &= WriteData(data, length);
145  result &= WriteData(&nl, 1);
146  return result;
147}
148
149//=============================================================================
150BOOL ConfigFile::AppendConfigString(const char *key,
151                                    const char *value) {
152  return AppendConfigData(key, value, strlen(value));
153}
154
155//=============================================================================
156void ConfigFile::WriteFile(const SimpleStringDictionary *configurationParameters,
157                           const char *dump_dir,
158                           const char *minidump_id) {
159
160  assert(config_file_ == -1);
161
162  // Open and write out configuration file preamble
163  strlcpy(config_file_path_, "/tmp/Config-XXXXXX",
164          sizeof(config_file_path_));
165  config_file_ = mkstemp(config_file_path_);
166
167  if (config_file_ == -1) {
168    DEBUGLOG(stderr,
169             "mkstemp(config_file_path_) == -1 (%s)\n",
170             strerror(errno));
171    return;
172  }
173  else {
174    DEBUGLOG(stderr, "Writing config file to (%s)\n", config_file_path_);
175  }
176
177  has_created_file_ = true;
178
179  // Add the minidump dir
180  AppendConfigString(kReporterMinidumpDirectoryKey, dump_dir);
181  AppendConfigString(kReporterMinidumpIDKey, minidump_id);
182
183  // Write out the configuration parameters
184  BOOL result = YES;
185  const SimpleStringDictionary &dictionary = *configurationParameters;
186
187  const KeyValueEntry *entry = NULL;
188  SimpleStringDictionaryIterator iter(dictionary);
189
190  while ((entry = iter.Next())) {
191    DEBUGLOG(stderr,
192             "config: (%s) -> (%s)\n",
193             entry->GetKey(),
194             entry->GetValue());
195    result = AppendConfigString(entry->GetKey(), entry->GetValue());
196
197    if (!result)
198      break;
199  }
200
201  close(config_file_);
202  config_file_ = -1;
203}
204
205//=============================================================================
206void Inspector::Inspect(const char *receive_port_name) {
207  kern_return_t result = ResetBootstrapPort();
208  if (result != KERN_SUCCESS) {
209    return;
210  }
211
212  result = ServiceCheckIn(receive_port_name);
213
214  if (result == KERN_SUCCESS) {
215    result = ReadMessages();
216
217    if (result == KERN_SUCCESS) {
218      // Inspect the task and write a minidump file.
219      bool wrote_minidump = InspectTask();
220
221      // Send acknowledgement to the crashed process that the inspection
222      // has finished.  It will then be able to cleanly exit.
223      // The return value is ignored because failure isn't fatal. If the process
224      // didn't get the message there's nothing we can do, and we still want to
225      // send the report.
226      SendAcknowledgement();
227
228      if (wrote_minidump) {
229        // Ask the user if he wants to upload the crash report to a server,
230        // and do so if he agrees.
231        LaunchReporter(config_file_.GetFilePath());
232      } else {
233        fprintf(stderr, "Inspection of crashed process failed\n");
234      }
235
236      // Now that we're done reading messages, cleanup the service, but only
237      // if there was an actual exception
238      // Otherwise, it means the dump was generated on demand and the process
239      // lives on, and we might be needed again in the future.
240      if (exception_code_) {
241        ServiceCheckOut(receive_port_name);
242      }
243    } else {
244        PRINT_MACH_RESULT(result, "Inspector: WaitForMessage()");
245    }
246  }
247}
248
249//=============================================================================
250kern_return_t Inspector::ResetBootstrapPort() {
251  // A reasonable default, in case anything fails.
252  bootstrap_subset_port_ = bootstrap_port;
253
254  mach_port_t self_task = mach_task_self();
255
256  kern_return_t kr = task_get_bootstrap_port(self_task,
257                                             &bootstrap_subset_port_);
258  if (kr != KERN_SUCCESS) {
259    NSLog(@"ResetBootstrapPort: task_get_bootstrap_port failed: %s (%d)",
260          mach_error_string(kr), kr);
261    return kr;
262  }
263
264  mach_port_t bootstrap_parent_port;
265  kr = bootstrap_look_up(bootstrap_subset_port_,
266                         const_cast<char*>(BREAKPAD_BOOTSTRAP_PARENT_PORT),
267                         &bootstrap_parent_port);
268  if (kr != BOOTSTRAP_SUCCESS) {
269    NSLog(@"ResetBootstrapPort: bootstrap_look_up failed: %s (%d)",
270#if defined(MAC_OS_X_VERSION_10_5) && \
271    MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
272          bootstrap_strerror(kr),
273#else
274          mach_error_string(kr),
275#endif
276          kr);
277    return kr;
278  }
279
280  kr = task_set_bootstrap_port(self_task, bootstrap_parent_port);
281  if (kr != KERN_SUCCESS) {
282    NSLog(@"ResetBootstrapPort: task_set_bootstrap_port failed: %s (%d)",
283          mach_error_string(kr), kr);
284    return kr;
285  }
286
287  // Some things access the bootstrap port through this global variable
288  // instead of calling task_get_bootstrap_port.
289  bootstrap_port = bootstrap_parent_port;
290
291  return KERN_SUCCESS;
292}
293
294//=============================================================================
295kern_return_t Inspector::ServiceCheckIn(const char *receive_port_name) {
296  // We need to get the mach port representing this service, so we can
297  // get information from the crashed process.
298  kern_return_t kr = bootstrap_check_in(bootstrap_subset_port_,
299                                        (char*)receive_port_name,
300                                        &service_rcv_port_);
301
302  if (kr != KERN_SUCCESS) {
303#if VERBOSE
304    PRINT_MACH_RESULT(kr, "Inspector: bootstrap_check_in()");
305#endif
306  }
307
308  return kr;
309}
310
311//=============================================================================
312kern_return_t Inspector::ServiceCheckOut(const char *receive_port_name) {
313  // We're done receiving mach messages from the crashed process,
314  // so clean up a bit.
315  kern_return_t kr;
316
317  // DO NOT use mach_port_deallocate() here -- it will fail and the
318  // following bootstrap_register() will also fail leaving our service
319  // name hanging around forever (until reboot)
320  kr = mach_port_destroy(mach_task_self(), service_rcv_port_);
321
322  if (kr != KERN_SUCCESS) {
323    PRINT_MACH_RESULT(kr,
324      "Inspector: UNREGISTERING: service_rcv_port mach_port_deallocate()");
325    return kr;
326  }
327
328  // Unregister the service associated with the receive port.
329  kr = bootstrap_register(bootstrap_subset_port_,
330                          (char*)receive_port_name,
331                          MACH_PORT_NULL);
332
333  if (kr != KERN_SUCCESS) {
334    PRINT_MACH_RESULT(kr, "Inspector: UNREGISTERING: bootstrap_register()");
335  }
336
337  return kr;
338}
339
340//=============================================================================
341kern_return_t Inspector::ReadMessages() {
342  // Wait for an initial message from the crashed process containing basic
343  // information about the crash.
344  ReceivePort receive_port(service_rcv_port_);
345
346  MachReceiveMessage message;
347  kern_return_t result = receive_port.WaitForMessage(&message, 1000);
348
349  if (result == KERN_SUCCESS) {
350    InspectorInfo &info = (InspectorInfo &)*message.GetData();
351    exception_type_ = info.exception_type;
352    exception_code_ = info.exception_code;
353    exception_subcode_ = info.exception_subcode;
354
355#if VERBOSE
356    printf("message ID = %d\n", message.GetMessageID());
357#endif
358
359    remote_task_ = message.GetTranslatedPort(0);
360    crashing_thread_ = message.GetTranslatedPort(1);
361    handler_thread_ = message.GetTranslatedPort(2);
362    ack_port_ = message.GetTranslatedPort(3);
363
364#if VERBOSE
365    printf("exception_type = %d\n", exception_type_);
366    printf("exception_code = %d\n", exception_code_);
367    printf("exception_subcode = %d\n", exception_subcode_);
368    printf("remote_task = %d\n", remote_task_);
369    printf("crashing_thread = %d\n", crashing_thread_);
370    printf("handler_thread = %d\n", handler_thread_);
371    printf("ack_port_ = %d\n", ack_port_);
372    printf("parameter count = %d\n", info.parameter_count);
373#endif
374
375    // In certain situations where multiple crash requests come
376    // through quickly, we can end up with the mach IPC messages not
377    // coming through correctly.  Since we don't know what parameters
378    // we've missed, we can't do much besides abort the crash dump
379    // situation in this case.
380    unsigned int parameters_read = 0;
381    // The initial message contains the number of key value pairs that
382    // we are expected to read.
383    // Read each key/value pair, one mach message per key/value pair.
384    for (unsigned int i = 0; i < info.parameter_count; ++i) {
385      MachReceiveMessage parameter_message;
386      result = receive_port.WaitForMessage(&parameter_message, 1000);
387
388      if(result == KERN_SUCCESS) {
389        KeyValueMessageData &key_value_data =
390          (KeyValueMessageData&)*parameter_message.GetData();
391        // If we get a blank key, make sure we don't increment the
392        // parameter count; in some cases (notably on-demand generation
393        // many times in a short period of time) caused the Mach IPC
394        // messages to not come through correctly.
395        if (strlen(key_value_data.key) == 0) {
396          continue;
397        }
398        parameters_read++;
399
400        config_params_.SetKeyValue(key_value_data.key, key_value_data.value);
401      } else {
402        PRINT_MACH_RESULT(result, "Inspector: key/value message");
403        break;
404      }
405    }
406    if (parameters_read != info.parameter_count) {
407      DEBUGLOG(stderr, "Only read %d parameters instead of %d, aborting crash "
408               "dump generation.", parameters_read, info.parameter_count);
409      return KERN_FAILURE;
410    }
411  }
412
413  return result;
414}
415
416//=============================================================================
417// Sets keys in the parameters dictionary that are specific to process uptime.
418// The two we set are process up time, and process crash time.
419void Inspector::SetCrashTimeParameters() {
420  // Set process uptime parameter
421  struct timeval tv;
422  gettimeofday(&tv, NULL);
423
424  char processUptimeString[32], processCrashtimeString[32];
425  const char *processStartTimeString =
426    config_params_.GetValueForKey(BREAKPAD_PROCESS_START_TIME);
427
428  // Set up time if we've received the start time.
429  if (processStartTimeString) {
430    time_t processStartTime = strtol(processStartTimeString, NULL, 10);
431    time_t processUptime = tv.tv_sec - processStartTime;
432    sprintf(processUptimeString, "%zd", processUptime);
433    config_params_.SetKeyValue(BREAKPAD_PROCESS_UP_TIME, processUptimeString);
434  }
435
436  sprintf(processCrashtimeString, "%zd", tv.tv_sec);
437  config_params_.SetKeyValue(BREAKPAD_PROCESS_CRASH_TIME,
438                             processCrashtimeString);
439}
440
441bool Inspector::InspectTask() {
442  // keep the task quiet while we're looking at it
443  task_suspend(remote_task_);
444  DEBUGLOG(stderr, "Suspended Remote task\n");
445
446  NSString *minidumpDir;
447
448  const char *minidumpDirectory =
449    config_params_.GetValueForKey(BREAKPAD_DUMP_DIRECTORY);
450
451  SetCrashTimeParameters();
452  // If the client app has not specified a minidump directory,
453  // use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name>
454  if (!minidumpDirectory || 0 == strlen(minidumpDirectory)) {
455    NSArray *libraryDirectories =
456      NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
457                                          NSUserDomainMask,
458                                          YES);
459
460    NSString *applicationSupportDirectory =
461        [libraryDirectories objectAtIndex:0];
462    NSString *library_subdirectory = [NSString 
463        stringWithUTF8String:kDefaultLibrarySubdirectory];
464    NSString *breakpad_product = [NSString 
465        stringWithUTF8String:config_params_.GetValueForKey(BREAKPAD_PRODUCT)];
466        
467    NSArray *path_components = [NSArray
468        arrayWithObjects:applicationSupportDirectory,
469                         library_subdirectory,
470                         breakpad_product,
471                         nil];
472
473    minidumpDir = [NSString pathWithComponents:path_components];
474  } else {
475    minidumpDir = [[NSString stringWithUTF8String:minidumpDirectory]
476                    stringByExpandingTildeInPath];
477  }
478  DEBUGLOG(stderr, 
479           "Writing minidump to directory (%s)\n",
480           [minidumpDir UTF8String]);
481
482  MinidumpLocation minidumpLocation(minidumpDir);
483
484  // Obscure bug alert:
485  // Don't use [NSString stringWithFormat] to build up the path here since it
486  // assumes system encoding and in RTL locales will prepend an LTR override
487  // character for paths beginning with '/' which fileSystemRepresentation does
488  // not remove. Filed as rdar://6889706 .
489  NSString *path_ns = [NSString
490      stringWithUTF8String:minidumpLocation.GetPath()];
491  NSString *pathid_ns = [NSString
492      stringWithUTF8String:minidumpLocation.GetID()];
493  NSString *minidumpPath = [path_ns stringByAppendingPathComponent:pathid_ns];
494  minidumpPath = [minidumpPath 
495      stringByAppendingPathExtension:@"dmp"];
496  
497  DEBUGLOG(stderr, 
498           "minidump path (%s)\n",
499           [minidumpPath UTF8String]);
500
501
502  config_file_.WriteFile( &config_params_,
503                          minidumpLocation.GetPath(),
504                          minidumpLocation.GetID());
505
506
507  MinidumpGenerator generator(remote_task_, handler_thread_);
508
509  if (exception_type_ && exception_code_) {
510    generator.SetExceptionInformation(exception_type_,
511                                      exception_code_,
512                                      exception_subcode_,
513                                      crashing_thread_);
514  }
515
516
517  bool result = generator.Write([minidumpPath fileSystemRepresentation]);
518
519  if (result) {
520    DEBUGLOG(stderr, "Wrote minidump - OK\n");
521  } else {
522    DEBUGLOG(stderr, "Error writing minidump - errno=%s\n",  strerror(errno));
523  }
524
525  // let the task continue
526  task_resume(remote_task_);
527  DEBUGLOG(stderr, "Resumed remote task\n");
528
529  return result;
530}
531
532//=============================================================================
533// The crashed task needs to be told that the inspection has finished.
534// It will wait on a mach port (with timeout) until we send acknowledgement.
535kern_return_t Inspector::SendAcknowledgement() {
536  if (ack_port_ != MACH_PORT_DEAD) {
537    MachPortSender sender(ack_port_);
538    MachSendMessage ack_message(kMsgType_InspectorAcknowledgement);
539
540    DEBUGLOG(stderr, "Inspector: trying to send acknowledgement to port %d\n",
541      ack_port_);
542
543    kern_return_t result = sender.SendMessage(ack_message, 2000);
544
545#if VERBOSE
546    PRINT_MACH_RESULT(result, "Inspector: sent acknowledgement");
547#endif
548
549    return result;
550  }
551
552  DEBUGLOG(stderr, "Inspector: port translation failure!\n");
553  return KERN_INVALID_NAME;
554}
555
556//=============================================================================
557void Inspector::LaunchReporter(const char *inConfigFilePath) {
558  // Extract the path to the reporter executable.
559  const char *reporterExecutablePath =
560          config_params_.GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION);
561  DEBUGLOG(stderr, "reporter path = %s\n", reporterExecutablePath);
562
563  // Setup and launch the crash dump sender.
564  const char *argv[3];
565  argv[0] = reporterExecutablePath;
566  argv[1] = inConfigFilePath;
567  argv[2] = NULL;
568
569  // Launch the reporter
570  pid_t pid = fork();
571
572  // If we're in the child, load in our new executable and run.
573  // The parent will not wait for the child to complete.
574  if (pid == 0) {
575    execv(argv[0], (char * const *)argv);
576    config_file_.Unlink();  // launch failed - get rid of config file
577    DEBUGLOG(stderr, "Inspector: unable to launch reporter app\n");
578    _exit(1);
579  }
580
581  // Wait until the Reporter child process exits.
582  //
583
584  // We'll use a timeout of one minute.
585  int timeoutCount = 60;   // 60 seconds
586
587  while (timeoutCount-- > 0) {
588    int status;
589    pid_t result = waitpid(pid, &status, WNOHANG);
590
591    if (result == 0) {
592      // The child has not yet finished.
593      sleep(1);
594    } else if (result == -1) {
595      DEBUGLOG(stderr, "Inspector: waitpid error (%d) waiting for reporter app\n",
596        errno);
597      break;
598    } else {
599      // child has finished
600      break;
601    }
602  }
603}
604
605} // namespace google_breakpad
606
607