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.
5 #import "components/crash/app/breakpad_mac.h"
7 #include <CoreFoundation/CoreFoundation.h>
8 #import <Foundation/Foundation.h>
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/foundation_util.h"
21 #include "base/mac/mac_util.h"
22 #include "base/mac/scoped_cftyperef.h"
23 #import "base/mac/scoped_nsautorelease_pool.h"
24 #include "base/strings/sys_string_conversions.h"
25 #include "base/threading/platform_thread.h"
26 #include "base/threading/thread_restrictions.h"
27 #import "breakpad/src/client/mac/Framework/Breakpad.h"
28 #include "components/crash/app/crash_reporter_client.h"
30 using crash_reporter::GetCrashReporterClient;
36 BreakpadRef gBreakpadRef = NULL;
38 void SetCrashKeyValue(NSString* key, NSString* value) {
39 // Comment repeated from header to prevent confusion:
40 // IMPORTANT: On OS X, the key/value pairs are sent to the crash server
41 // out of bounds and not recorded on disk in the minidump, this means
42 // that if you look at the minidump file locally you won't see them!
43 if (gBreakpadRef == NULL) {
47 BreakpadAddUploadParameter(gBreakpadRef, key, value);
50 void ClearCrashKeyValue(NSString* key) {
51 if (gBreakpadRef == NULL) {
55 BreakpadRemoveUploadParameter(gBreakpadRef, key);
58 void SetCrashKeyValueImpl(const base::StringPiece& key,
59 const base::StringPiece& value) {
61 SetCrashKeyValue(base::SysUTF8ToNSString(key.as_string()),
62 base::SysUTF8ToNSString(value.as_string()));
66 void ClearCrashKeyValueImpl(const base::StringPiece& key) {
68 ClearCrashKeyValue(base::SysUTF8ToNSString(key.as_string()));
72 bool FatalMessageHandler(int severity, const char* file, int line,
73 size_t message_start, const std::string& str) {
74 // Do not handle non-FATAL.
75 if (severity != logging::LOG_FATAL)
78 // In case of OOM condition, this code could be reentered when
79 // constructing and storing the key. Using a static is not
80 // thread-safe, but if multiple threads are in the process of a
81 // fatal crash at the same time, this should work.
82 static bool guarded = false;
86 base::AutoReset<bool> guard(&guarded, true);
88 // Only log last path component. This matches logging.cc.
90 const char* slash = strrchr(file, '/');
95 NSString* fatal_key = @"LOG_FATAL";
96 NSString* fatal_value =
97 [NSString stringWithFormat:@"%s:%d: %s",
98 file, line, str.c_str() + message_start];
99 SetCrashKeyValue(fatal_key, fatal_value);
101 // Rather than including the code to force the crash here, allow the
106 // BreakpadGenerateAndSendReport() does not report the current
107 // thread. This class can be used to spin up a thread to run it.
108 class DumpHelper : public base::PlatformThread::Delegate {
110 static void DumpWithoutCrashing() {
112 base::PlatformThreadHandle handle;
113 if (base::PlatformThread::Create(0, &dumper, &handle)) {
114 // The entire point of this is to block so that the correct
116 base::ThreadRestrictions::ScopedAllowIO allow_io;
117 base::PlatformThread::Join(handle);
124 void ThreadMain() override {
125 base::PlatformThread::SetName("CrDumpHelper");
126 BreakpadGenerateAndSendReport(gBreakpadRef);
129 DISALLOW_COPY_AND_ASSIGN(DumpHelper);
132 void SIGABRTHandler(int signal) {
133 // The OSX abort() (link below) masks all signals for the process,
134 // and all except SIGABRT for the thread. SIGABRT will be masked
135 // when the SIGABRT is sent, which means at this point only SIGKILL
136 // and SIGSTOP can be delivered. Unmask others so that the code
137 // below crashes as desired.
139 // http://www.opensource.apple.com/source/Libc/Libc-825.26/stdlib/FreeBSD/abort.c
142 sigaddset(&mask, signal);
143 pthread_sigmask(SIG_SETMASK, &mask, NULL);
145 // Most interesting operations are not safe in a signal handler, just crash.
146 char* volatile death_ptr = NULL;
152 bool IsCrashReporterEnabled() {
153 return gBreakpadRef != NULL;
156 // Only called for a branded build of Chrome.app.
157 void InitCrashReporter(const std::string& process_type) {
158 DCHECK(!gBreakpadRef);
159 base::mac::ScopedNSAutoreleasePool autorelease_pool;
161 // Check whether crash reporting should be enabled. If enterprise
162 // configuration management controls crash reporting, it takes precedence.
163 // Otherwise, check whether the user has consented to stats and crash
164 // reporting. The browser process can make this determination directly.
165 // Helper processes may not have access to the disk or to the same data as
166 // the browser process, so the browser passes the decision to them on the
168 NSBundle* main_bundle = base::mac::FrameworkBundle();
169 bool is_browser = !base::mac::IsBackgroundOnlyProcess();
170 bool enable_breakpad = false;
171 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
174 // Since the configuration management infrastructure is possibly not
175 // initialized when this code runs, read the policy preference directly.
176 if (!GetCrashReporterClient()->ReportingIsEnforcedByPolicy(
178 // Controlled by the user. The crash reporter may be enabled by
179 // preference or through an environment variable, but the kDisableBreakpad
180 // switch overrides both.
181 enable_breakpad = GetCrashReporterClient()->GetCollectStatsConsent() ||
182 GetCrashReporterClient()->IsRunningUnattended();
183 enable_breakpad &= !command_line->HasSwitch(switches::kDisableBreakpad);
186 // This is a helper process, check the command line switch.
187 enable_breakpad = command_line->HasSwitch(switches::kEnableCrashReporter);
190 if (!enable_breakpad) {
191 VLOG_IF(1, is_browser) << "Breakpad disabled";
195 // Tell Breakpad where crash_inspector and crash_report_sender are.
196 NSString* resource_path = [main_bundle resourcePath];
197 NSString *inspector_location =
198 [resource_path stringByAppendingPathComponent:@"crash_inspector"];
199 NSString *reporter_bundle_location =
200 [resource_path stringByAppendingPathComponent:@"crash_report_sender.app"];
201 NSString *reporter_location =
202 [[NSBundle bundleWithPath:reporter_bundle_location] executablePath];
204 if (!inspector_location || !reporter_location) {
205 VLOG_IF(1, is_browser && base::mac::AmIBundled()) << "Breakpad disabled";
209 NSDictionary* info_dictionary = [main_bundle infoDictionary];
210 NSMutableDictionary *breakpad_config =
211 [[info_dictionary mutableCopy] autorelease];
212 [breakpad_config setObject:inspector_location
213 forKey:@BREAKPAD_INSPECTOR_LOCATION];
214 [breakpad_config setObject:reporter_location
215 forKey:@BREAKPAD_REPORTER_EXE_LOCATION];
217 // In the main application (the browser process), crashes can be passed to
218 // the system's Crash Reporter. This allows the system to notify the user
219 // when the application crashes, and provide the user with the option to
222 [breakpad_config setObject:@"NO" forKey:@BREAKPAD_SEND_AND_EXIT];
224 base::FilePath dir_crash_dumps;
225 GetCrashReporterClient()->GetCrashDumpLocation(&dir_crash_dumps);
226 [breakpad_config setObject:base::SysUTF8ToNSString(dir_crash_dumps.value())
227 forKey:@BREAKPAD_DUMP_DIRECTORY];
229 // Temporarily run Breakpad in-process on 10.10 and later because APIs that
230 // it depends on got broken (http://crbug.com/386208).
231 // This can catch crashes in the browser process only.
232 if (is_browser && base::mac::IsOSYosemiteOrLater()) {
233 [breakpad_config setObject:[NSNumber numberWithBool:YES]
234 forKey:@BREAKPAD_IN_PROCESS];
237 // Initialize Breakpad.
238 gBreakpadRef = BreakpadCreate(breakpad_config);
240 LOG_IF(ERROR, base::mac::AmIBundled()) << "Breakpad initialization failed";
244 // Initialize the scoped crash key system.
245 base::debug::SetCrashKeyReportingFunctions(&SetCrashKeyValueImpl,
246 &ClearCrashKeyValueImpl);
247 GetCrashReporterClient()->RegisterCrashKeys();
249 // Set Breakpad metadata values. These values are added to Info.plist during
250 // the branded Google Chrome.app build.
251 SetCrashKeyValue(@"ver", [info_dictionary objectForKey:@BREAKPAD_VERSION]);
252 SetCrashKeyValue(@"prod", [info_dictionary objectForKey:@BREAKPAD_PRODUCT]);
253 SetCrashKeyValue(@"plat", @"OS X");
256 // Get the guid from the command line switch.
257 std::string client_guid =
258 command_line->GetSwitchValueASCII(switches::kEnableCrashReporter);
259 GetCrashReporterClient()->SetCrashReporterClientIdFromGUID(client_guid);
262 logging::SetLogMessageHandler(&FatalMessageHandler);
263 base::debug::SetDumpWithoutCrashingFunction(&DumpHelper::DumpWithoutCrashing);
265 // abort() sends SIGABRT, which breakpad does not intercept.
266 // Register a signal handler to crash in a way breakpad will
268 struct sigaction sigact;
269 memset(&sigact, 0, sizeof(sigact));
270 sigact.sa_handler = SIGABRTHandler;
271 CHECK(0 == sigaction(SIGABRT, &sigact, NULL));
274 void InitCrashProcessInfo(const std::string& process_type_switch) {
275 if (gBreakpadRef == NULL) {
279 // Determine the process type.
280 NSString* process_type = @"browser";
281 if (!process_type_switch.empty()) {
282 process_type = base::SysUTF8ToNSString(process_type_switch);
285 GetCrashReporterClient()->InstallAdditionalFilters(gBreakpadRef);
287 // Store process type in crash dump.
288 SetCrashKeyValue(@"ptype", process_type);
290 NSString* pid_value =
291 [NSString stringWithFormat:@"%d", static_cast<unsigned int>(getpid())];
292 SetCrashKeyValue(@"pid", pid_value);
295 } // namespace breakpad