• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "components/crash/app/breakpad_mac.h"
6
7#include <CoreFoundation/CoreFoundation.h>
8#import <Foundation/Foundation.h>
9
10#include "base/auto_reset.h"
11#include "base/base_switches.h"
12#import "base/basictypes.h"
13#include "base/command_line.h"
14#include "base/debug/crash_logging.h"
15#include "base/debug/dump_without_crashing.h"
16#include "base/files/file_path.h"
17#include "base/files/file_util.h"
18#import "base/logging.h"
19#include "base/mac/bundle_locations.h"
20#include "base/mac/mac_util.h"
21#include "base/mac/scoped_cftyperef.h"
22#import "base/mac/scoped_nsautorelease_pool.h"
23#include "base/strings/sys_string_conversions.h"
24#include "base/threading/platform_thread.h"
25#include "base/threading/thread_restrictions.h"
26#import "breakpad/src/client/mac/Framework/Breakpad.h"
27#include "components/crash/app/crash_reporter_client.h"
28
29using crash_reporter::GetCrashReporterClient;
30
31namespace breakpad {
32
33namespace {
34
35BreakpadRef gBreakpadRef = NULL;
36
37void SetCrashKeyValue(NSString* key, NSString* value) {
38  // Comment repeated from header to prevent confusion:
39  // IMPORTANT: On OS X, the key/value pairs are sent to the crash server
40  // out of bounds and not recorded on disk in the minidump, this means
41  // that if you look at the minidump file locally you won't see them!
42  if (gBreakpadRef == NULL) {
43    return;
44  }
45
46  BreakpadAddUploadParameter(gBreakpadRef, key, value);
47}
48
49void ClearCrashKeyValue(NSString* key) {
50  if (gBreakpadRef == NULL) {
51    return;
52  }
53
54  BreakpadRemoveUploadParameter(gBreakpadRef, key);
55}
56
57void SetCrashKeyValueImpl(const base::StringPiece& key,
58                          const base::StringPiece& value) {
59  SetCrashKeyValue(base::SysUTF8ToNSString(key.as_string()),
60                   base::SysUTF8ToNSString(value.as_string()));
61}
62
63void ClearCrashKeyValueImpl(const base::StringPiece& key) {
64  ClearCrashKeyValue(base::SysUTF8ToNSString(key.as_string()));
65}
66
67bool FatalMessageHandler(int severity, const char* file, int line,
68                         size_t message_start, const std::string& str) {
69  // Do not handle non-FATAL.
70  if (severity != logging::LOG_FATAL)
71    return false;
72
73  // In case of OOM condition, this code could be reentered when
74  // constructing and storing the key.  Using a static is not
75  // thread-safe, but if multiple threads are in the process of a
76  // fatal crash at the same time, this should work.
77  static bool guarded = false;
78  if (guarded)
79    return false;
80
81  base::AutoReset<bool> guard(&guarded, true);
82
83  // Only log last path component.  This matches logging.cc.
84  if (file) {
85    const char* slash = strrchr(file, '/');
86    if (slash)
87      file = slash + 1;
88  }
89
90  NSString* fatal_key = @"LOG_FATAL";
91  NSString* fatal_value =
92      [NSString stringWithFormat:@"%s:%d: %s",
93                                 file, line, str.c_str() + message_start];
94  SetCrashKeyValue(fatal_key, fatal_value);
95
96  // Rather than including the code to force the crash here, allow the
97  // caller to do it.
98  return false;
99}
100
101// BreakpadGenerateAndSendReport() does not report the current
102// thread.  This class can be used to spin up a thread to run it.
103class DumpHelper : public base::PlatformThread::Delegate {
104 public:
105  static void DumpWithoutCrashing() {
106    DumpHelper dumper;
107    base::PlatformThreadHandle handle;
108    if (base::PlatformThread::Create(0, &dumper, &handle)) {
109      // The entire point of this is to block so that the correct
110      // stack is logged.
111      base::ThreadRestrictions::ScopedAllowIO allow_io;
112      base::PlatformThread::Join(handle);
113    }
114  }
115
116 private:
117  DumpHelper() {}
118
119  virtual void ThreadMain() OVERRIDE {
120    base::PlatformThread::SetName("CrDumpHelper");
121    BreakpadGenerateAndSendReport(gBreakpadRef);
122  }
123
124  DISALLOW_COPY_AND_ASSIGN(DumpHelper);
125};
126
127void SIGABRTHandler(int signal) {
128  // The OSX abort() (link below) masks all signals for the process,
129  // and all except SIGABRT for the thread.  SIGABRT will be masked
130  // when the SIGABRT is sent, which means at this point only SIGKILL
131  // and SIGSTOP can be delivered.  Unmask others so that the code
132  // below crashes as desired.
133  //
134  // http://www.opensource.apple.com/source/Libc/Libc-825.26/stdlib/FreeBSD/abort.c
135  sigset_t mask;
136  sigemptyset(&mask);
137  sigaddset(&mask, signal);
138  pthread_sigmask(SIG_SETMASK, &mask, NULL);
139
140  // Most interesting operations are not safe in a signal handler, just crash.
141  char* volatile death_ptr = NULL;
142  *death_ptr = '!';
143}
144
145}  // namespace
146
147bool IsCrashReporterEnabled() {
148  return gBreakpadRef != NULL;
149}
150
151// Only called for a branded build of Chrome.app.
152void InitCrashReporter(const std::string& process_type) {
153  DCHECK(!gBreakpadRef);
154  base::mac::ScopedNSAutoreleasePool autorelease_pool;
155
156  // Check whether crash reporting should be enabled. If enterprise
157  // configuration management controls crash reporting, it takes precedence.
158  // Otherwise, check whether the user has consented to stats and crash
159  // reporting. The browser process can make this determination directly.
160  // Helper processes may not have access to the disk or to the same data as
161  // the browser process, so the browser passes the decision to them on the
162  // command line.
163  NSBundle* main_bundle = base::mac::FrameworkBundle();
164  bool is_browser = !base::mac::IsBackgroundOnlyProcess();
165  bool enable_breakpad = false;
166  CommandLine* command_line = CommandLine::ForCurrentProcess();
167
168  if (is_browser) {
169    // Since the configuration management infrastructure is possibly not
170    // initialized when this code runs, read the policy preference directly.
171    if (!GetCrashReporterClient()->ReportingIsEnforcedByPolicy(
172            &enable_breakpad)) {
173      // Controlled by the user. The crash reporter may be enabled by
174      // preference or through an environment variable, but the kDisableBreakpad
175      // switch overrides both.
176      enable_breakpad = GetCrashReporterClient()->GetCollectStatsConsent() ||
177                        GetCrashReporterClient()->IsRunningUnattended();
178      enable_breakpad &= !command_line->HasSwitch(switches::kDisableBreakpad);
179    }
180  } else {
181    // This is a helper process, check the command line switch.
182    enable_breakpad = command_line->HasSwitch(switches::kEnableCrashReporter);
183  }
184
185  if (!enable_breakpad) {
186    VLOG_IF(1, is_browser) << "Breakpad disabled";
187    return;
188  }
189
190  // Tell Breakpad where crash_inspector and crash_report_sender are.
191  NSString* resource_path = [main_bundle resourcePath];
192  NSString *inspector_location =
193      [resource_path stringByAppendingPathComponent:@"crash_inspector"];
194  NSString *reporter_bundle_location =
195      [resource_path stringByAppendingPathComponent:@"crash_report_sender.app"];
196  NSString *reporter_location =
197      [[NSBundle bundleWithPath:reporter_bundle_location] executablePath];
198
199  if (!inspector_location || !reporter_location) {
200    VLOG_IF(1, is_browser && base::mac::AmIBundled()) << "Breakpad disabled";
201    return;
202  }
203
204  NSDictionary* info_dictionary = [main_bundle infoDictionary];
205  NSMutableDictionary *breakpad_config =
206      [[info_dictionary mutableCopy] autorelease];
207  [breakpad_config setObject:inspector_location
208                      forKey:@BREAKPAD_INSPECTOR_LOCATION];
209  [breakpad_config setObject:reporter_location
210                      forKey:@BREAKPAD_REPORTER_EXE_LOCATION];
211
212  // In the main application (the browser process), crashes can be passed to
213  // the system's Crash Reporter.  This allows the system to notify the user
214  // when the application crashes, and provide the user with the option to
215  // restart it.
216  if (is_browser)
217    [breakpad_config setObject:@"NO" forKey:@BREAKPAD_SEND_AND_EXIT];
218
219  base::FilePath dir_crash_dumps;
220  GetCrashReporterClient()->GetCrashDumpLocation(&dir_crash_dumps);
221  [breakpad_config setObject:base::SysUTF8ToNSString(dir_crash_dumps.value())
222                      forKey:@BREAKPAD_DUMP_DIRECTORY];
223
224  // Temporarily run Breakpad in-process on 10.10 and later because APIs that
225  // it depends on got broken (http://crbug.com/386208).
226  // This can catch crashes in the browser process only.
227  if (is_browser && base::mac::IsOSYosemiteOrLater()) {
228    [breakpad_config setObject:[NSNumber numberWithBool:YES]
229                        forKey:@BREAKPAD_IN_PROCESS];
230  }
231
232  // Initialize Breakpad.
233  gBreakpadRef = BreakpadCreate(breakpad_config);
234  if (!gBreakpadRef) {
235    LOG_IF(ERROR, base::mac::AmIBundled()) << "Breakpad initializaiton failed";
236    return;
237  }
238
239  // Initialize the scoped crash key system.
240  base::debug::SetCrashKeyReportingFunctions(&SetCrashKeyValueImpl,
241                                             &ClearCrashKeyValueImpl);
242  GetCrashReporterClient()->RegisterCrashKeys();
243
244  // Set Breakpad metadata values.  These values are added to Info.plist during
245  // the branded Google Chrome.app build.
246  SetCrashKeyValue(@"ver", [info_dictionary objectForKey:@BREAKPAD_VERSION]);
247  SetCrashKeyValue(@"prod", [info_dictionary objectForKey:@BREAKPAD_PRODUCT]);
248  SetCrashKeyValue(@"plat", @"OS X");
249
250  if (!is_browser) {
251    // Get the guid from the command line switch.
252    std::string client_guid =
253        command_line->GetSwitchValueASCII(switches::kEnableCrashReporter);
254    GetCrashReporterClient()->SetCrashReporterClientIdFromGUID(client_guid);
255  }
256
257  logging::SetLogMessageHandler(&FatalMessageHandler);
258  base::debug::SetDumpWithoutCrashingFunction(&DumpHelper::DumpWithoutCrashing);
259
260  // abort() sends SIGABRT, which breakpad does not intercept.
261  // Register a signal handler to crash in a way breakpad will
262  // intercept.
263  struct sigaction sigact;
264  memset(&sigact, 0, sizeof(sigact));
265  sigact.sa_handler = SIGABRTHandler;
266  CHECK(0 == sigaction(SIGABRT, &sigact, NULL));
267}
268
269void InitCrashProcessInfo(const std::string& process_type_switch) {
270  if (gBreakpadRef == NULL) {
271    return;
272  }
273
274  // Determine the process type.
275  NSString* process_type = @"browser";
276  if (!process_type_switch.empty()) {
277    process_type = base::SysUTF8ToNSString(process_type_switch);
278  }
279
280  GetCrashReporterClient()->InstallAdditionalFilters(gBreakpadRef);
281
282  // Store process type in crash dump.
283  SetCrashKeyValue(@"ptype", process_type);
284
285  NSString* pid_value =
286      [NSString stringWithFormat:@"%d", static_cast<unsigned int>(getpid())];
287  SetCrashKeyValue(@"pid", pid_value);
288}
289
290}  // namespace breakpad
291