| Index: components/crash/app/breakpad_mac.mm
|
| diff --git a/components/crash/app/breakpad_mac.mm b/components/crash/app/breakpad_mac.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..f435483fee4d643705940f18e37ee9cb21f8314c
|
| --- /dev/null
|
| +++ b/components/crash/app/breakpad_mac.mm
|
| @@ -0,0 +1,286 @@
|
| +// Copyright 2013 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#import "components/crash/app/breakpad_mac.h"
|
| +
|
| +#include <CoreFoundation/CoreFoundation.h>
|
| +#import <Foundation/Foundation.h>
|
| +
|
| +#include "base/auto_reset.h"
|
| +#include "base/base_switches.h"
|
| +#import "base/basictypes.h"
|
| +#include "base/command_line.h"
|
| +#include "base/debug/crash_logging.h"
|
| +#include "base/debug/dump_without_crashing.h"
|
| +#include "base/files/file_path.h"
|
| +#include "base/files/file_util.h"
|
| +#import "base/logging.h"
|
| +#include "base/mac/bundle_locations.h"
|
| +#include "base/mac/foundation_util.h"
|
| +#include "base/mac/mac_util.h"
|
| +#include "base/mac/scoped_cftyperef.h"
|
| +#import "base/mac/scoped_nsautorelease_pool.h"
|
| +#include "base/strings/sys_string_conversions.h"
|
| +#include "base/threading/platform_thread.h"
|
| +#include "base/threading/thread_restrictions.h"
|
| +#import "breakpad/src/client/mac/Framework/Breakpad.h"
|
| +#include "components/crash/app/crash_reporter_client.h"
|
| +
|
| +using crash_reporter::GetCrashReporterClient;
|
| +
|
| +namespace breakpad {
|
| +
|
| +namespace {
|
| +
|
| +BreakpadRef gBreakpadRef = NULL;
|
| +
|
| +void SetCrashKeyValue(NSString* key, NSString* value) {
|
| + // Comment repeated from header to prevent confusion:
|
| + // IMPORTANT: On OS X, the key/value pairs are sent to the crash server
|
| + // out of bounds and not recorded on disk in the minidump, this means
|
| + // that if you look at the minidump file locally you won't see them!
|
| + if (gBreakpadRef == NULL) {
|
| + return;
|
| + }
|
| +
|
| + BreakpadAddUploadParameter(gBreakpadRef, key, value);
|
| +}
|
| +
|
| +void ClearCrashKeyValue(NSString* key) {
|
| + if (gBreakpadRef == NULL) {
|
| + return;
|
| + }
|
| +
|
| + BreakpadRemoveUploadParameter(gBreakpadRef, key);
|
| +}
|
| +
|
| +void SetCrashKeyValueImpl(const base::StringPiece& key,
|
| + const base::StringPiece& value) {
|
| + @autoreleasepool {
|
| + SetCrashKeyValue(base::SysUTF8ToNSString(key.as_string()),
|
| + base::SysUTF8ToNSString(value.as_string()));
|
| + }
|
| +}
|
| +
|
| +void ClearCrashKeyValueImpl(const base::StringPiece& key) {
|
| + @autoreleasepool {
|
| + ClearCrashKeyValue(base::SysUTF8ToNSString(key.as_string()));
|
| + }
|
| +}
|
| +
|
| +bool FatalMessageHandler(int severity, const char* file, int line,
|
| + size_t message_start, const std::string& str) {
|
| + // Do not handle non-FATAL.
|
| + if (severity != logging::LOG_FATAL)
|
| + return false;
|
| +
|
| + // In case of OOM condition, this code could be reentered when
|
| + // constructing and storing the key. Using a static is not
|
| + // thread-safe, but if multiple threads are in the process of a
|
| + // fatal crash at the same time, this should work.
|
| + static bool guarded = false;
|
| + if (guarded)
|
| + return false;
|
| +
|
| + base::AutoReset<bool> guard(&guarded, true);
|
| +
|
| + // Only log last path component. This matches logging.cc.
|
| + if (file) {
|
| + const char* slash = strrchr(file, '/');
|
| + if (slash)
|
| + file = slash + 1;
|
| + }
|
| +
|
| + NSString* fatal_key = @"LOG_FATAL";
|
| + NSString* fatal_value =
|
| + [NSString stringWithFormat:@"%s:%d: %s",
|
| + file, line, str.c_str() + message_start];
|
| + SetCrashKeyValue(fatal_key, fatal_value);
|
| +
|
| + // Rather than including the code to force the crash here, allow the
|
| + // caller to do it.
|
| + return false;
|
| +}
|
| +
|
| +// BreakpadGenerateAndSendReport() does not report the current
|
| +// thread. This class can be used to spin up a thread to run it.
|
| +class DumpHelper : public base::PlatformThread::Delegate {
|
| + public:
|
| + static void DumpWithoutCrashing() {
|
| + DumpHelper dumper;
|
| + base::PlatformThreadHandle handle;
|
| + if (base::PlatformThread::Create(0, &dumper, &handle)) {
|
| + // The entire point of this is to block so that the correct
|
| + // stack is logged.
|
| + base::ThreadRestrictions::ScopedAllowIO allow_io;
|
| + base::PlatformThread::Join(handle);
|
| + }
|
| + }
|
| +
|
| + private:
|
| + DumpHelper() {}
|
| +
|
| + void ThreadMain() override {
|
| + base::PlatformThread::SetName("CrDumpHelper");
|
| + BreakpadGenerateAndSendReport(gBreakpadRef);
|
| + }
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(DumpHelper);
|
| +};
|
| +
|
| +void SIGABRTHandler(int signal) {
|
| + // The OSX abort() (link below) masks all signals for the process,
|
| + // and all except SIGABRT for the thread. SIGABRT will be masked
|
| + // when the SIGABRT is sent, which means at this point only SIGKILL
|
| + // and SIGSTOP can be delivered. Unmask others so that the code
|
| + // below crashes as desired.
|
| + //
|
| + // http://www.opensource.apple.com/source/Libc/Libc-825.26/stdlib/FreeBSD/abort.c
|
| + sigset_t mask;
|
| + sigemptyset(&mask);
|
| + sigaddset(&mask, signal);
|
| + pthread_sigmask(SIG_SETMASK, &mask, NULL);
|
| +
|
| + // Most interesting operations are not safe in a signal handler, just crash.
|
| + char* volatile death_ptr = NULL;
|
| + *death_ptr = '!';
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +bool IsCrashReporterEnabled() {
|
| + return gBreakpadRef != NULL;
|
| +}
|
| +
|
| +// Only called for a branded build of Chrome.app.
|
| +void InitCrashReporter(const std::string& process_type) {
|
| + DCHECK(!gBreakpadRef);
|
| + base::mac::ScopedNSAutoreleasePool autorelease_pool;
|
| +
|
| + // Check whether crash reporting should be enabled. If enterprise
|
| + // configuration management controls crash reporting, it takes precedence.
|
| + // Otherwise, check whether the user has consented to stats and crash
|
| + // reporting. The browser process can make this determination directly.
|
| + // Helper processes may not have access to the disk or to the same data as
|
| + // the browser process, so the browser passes the decision to them on the
|
| + // command line.
|
| + NSBundle* main_bundle = base::mac::FrameworkBundle();
|
| + bool is_browser = !base::mac::IsBackgroundOnlyProcess();
|
| + bool enable_breakpad = false;
|
| + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
|
| +
|
| + if (is_browser) {
|
| + // Since the configuration management infrastructure is possibly not
|
| + // initialized when this code runs, read the policy preference directly.
|
| + if (!GetCrashReporterClient()->ReportingIsEnforcedByPolicy(
|
| + &enable_breakpad)) {
|
| + // Controlled by the user. The crash reporter may be enabled by
|
| + // preference or through an environment variable, but the kDisableBreakpad
|
| + // switch overrides both.
|
| + enable_breakpad = GetCrashReporterClient()->GetCollectStatsConsent() ||
|
| + GetCrashReporterClient()->IsRunningUnattended();
|
| + enable_breakpad &= !command_line->HasSwitch(switches::kDisableBreakpad);
|
| + }
|
| + } else {
|
| + // This is a helper process, check the command line switch.
|
| + enable_breakpad = command_line->HasSwitch(switches::kEnableCrashReporter);
|
| + }
|
| +
|
| + if (!enable_breakpad) {
|
| + VLOG_IF(1, is_browser) << "Breakpad disabled";
|
| + return;
|
| + }
|
| +
|
| + // Tell Breakpad where crash_inspector and crash_report_sender are.
|
| + NSString* resource_path = [main_bundle resourcePath];
|
| + NSString *inspector_location =
|
| + [resource_path stringByAppendingPathComponent:@"crash_inspector"];
|
| + NSString *reporter_bundle_location =
|
| + [resource_path stringByAppendingPathComponent:@"crash_report_sender.app"];
|
| + NSString *reporter_location =
|
| + [[NSBundle bundleWithPath:reporter_bundle_location] executablePath];
|
| +
|
| + if (!inspector_location || !reporter_location) {
|
| + VLOG_IF(1, is_browser && base::mac::AmIBundled()) << "Breakpad disabled";
|
| + return;
|
| + }
|
| +
|
| + NSDictionary* info_dictionary = [main_bundle infoDictionary];
|
| + NSMutableDictionary *breakpad_config =
|
| + [[info_dictionary mutableCopy] autorelease];
|
| + [breakpad_config setObject:inspector_location
|
| + forKey:@BREAKPAD_INSPECTOR_LOCATION];
|
| + [breakpad_config setObject:reporter_location
|
| + forKey:@BREAKPAD_REPORTER_EXE_LOCATION];
|
| +
|
| + // In the main application (the browser process), crashes can be passed to
|
| + // the system's Crash Reporter. This allows the system to notify the user
|
| + // when the application crashes, and provide the user with the option to
|
| + // restart it.
|
| + if (is_browser)
|
| + [breakpad_config setObject:@"NO" forKey:@BREAKPAD_SEND_AND_EXIT];
|
| +
|
| + base::FilePath dir_crash_dumps;
|
| + GetCrashReporterClient()->GetCrashDumpLocation(&dir_crash_dumps);
|
| + [breakpad_config setObject:base::SysUTF8ToNSString(dir_crash_dumps.value())
|
| + forKey:@BREAKPAD_DUMP_DIRECTORY];
|
| +
|
| + // Temporarily run Breakpad in-process on 10.10 and later because APIs that
|
| + // it depends on got broken (http://crbug.com/386208).
|
| + // This can catch crashes in the browser process only.
|
| + if (is_browser && base::mac::IsOSYosemiteOrLater()) {
|
| + [breakpad_config setObject:[NSNumber numberWithBool:YES]
|
| + forKey:@BREAKPAD_IN_PROCESS];
|
| + }
|
| +
|
| + // Initialize Breakpad.
|
| + gBreakpadRef = BreakpadCreate(breakpad_config);
|
| + if (!gBreakpadRef) {
|
| + LOG_IF(ERROR, base::mac::AmIBundled()) << "Breakpad initialization failed";
|
| + return;
|
| + }
|
| +
|
| + // Initialize the scoped crash key system.
|
| + base::debug::SetCrashKeyReportingFunctions(&SetCrashKeyValueImpl,
|
| + &ClearCrashKeyValueImpl);
|
| + GetCrashReporterClient()->RegisterCrashKeys();
|
| +
|
| + // Set Breakpad metadata values. These values are added to Info.plist during
|
| + // the branded Google Chrome.app build.
|
| + SetCrashKeyValue(@"ver", [info_dictionary objectForKey:@BREAKPAD_VERSION]);
|
| + SetCrashKeyValue(@"prod", [info_dictionary objectForKey:@BREAKPAD_PRODUCT]);
|
| + SetCrashKeyValue(@"plat", @"OS X");
|
| +
|
| + logging::SetLogMessageHandler(&FatalMessageHandler);
|
| + base::debug::SetDumpWithoutCrashingFunction(&DumpHelper::DumpWithoutCrashing);
|
| +
|
| + // abort() sends SIGABRT, which breakpad does not intercept.
|
| + // Register a signal handler to crash in a way breakpad will
|
| + // intercept.
|
| + struct sigaction sigact;
|
| + memset(&sigact, 0, sizeof(sigact));
|
| + sigact.sa_handler = SIGABRTHandler;
|
| + CHECK(0 == sigaction(SIGABRT, &sigact, NULL));
|
| +}
|
| +
|
| +void InitCrashProcessInfo(const std::string& process_type_switch) {
|
| + if (gBreakpadRef == NULL) {
|
| + return;
|
| + }
|
| +
|
| + // Determine the process type.
|
| + NSString* process_type = @"browser";
|
| + if (!process_type_switch.empty()) {
|
| + process_type = base::SysUTF8ToNSString(process_type_switch);
|
| + }
|
| +
|
| + // Store process type in crash dump.
|
| + SetCrashKeyValue(@"ptype", process_type);
|
| +
|
| + NSString* pid_value =
|
| + [NSString stringWithFormat:@"%d", static_cast<unsigned int>(getpid())];
|
| + SetCrashKeyValue(@"pid", pid_value);
|
| +}
|
| +
|
| +} // namespace breakpad
|
|
|