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