xref: /aosp_15_r20/external/google-breakpad/src/client/mac/crash_generation/Inspector.mm (revision 9712c20fc9bbfbac4935993a2ca0b3958c5adad2)
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
51namespace google_breakpad {
52
53//=============================================================================
54void 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//=============================================================================
100kern_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//=============================================================================
145kern_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//=============================================================================
162kern_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//=============================================================================
191kern_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//=============================================================================
265bool 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.
343kern_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
362