• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
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(
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//=============================================================================
101kern_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//=============================================================================
146kern_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//=============================================================================
163kern_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//=============================================================================
192kern_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//=============================================================================
266bool 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.
344kern_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