OLD | NEW |
(Empty) | |
| 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/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" |
| 29 |
| 30 using crash_reporter::GetCrashReporterClient; |
| 31 |
| 32 namespace breakpad { |
| 33 |
| 34 namespace { |
| 35 |
| 36 BreakpadRef gBreakpadRef = NULL; |
| 37 |
| 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) { |
| 44 return; |
| 45 } |
| 46 |
| 47 BreakpadAddUploadParameter(gBreakpadRef, key, value); |
| 48 } |
| 49 |
| 50 void ClearCrashKeyValue(NSString* key) { |
| 51 if (gBreakpadRef == NULL) { |
| 52 return; |
| 53 } |
| 54 |
| 55 BreakpadRemoveUploadParameter(gBreakpadRef, key); |
| 56 } |
| 57 |
| 58 void SetCrashKeyValueImpl(const base::StringPiece& key, |
| 59 const base::StringPiece& value) { |
| 60 @autoreleasepool { |
| 61 SetCrashKeyValue(base::SysUTF8ToNSString(key.as_string()), |
| 62 base::SysUTF8ToNSString(value.as_string())); |
| 63 } |
| 64 } |
| 65 |
| 66 void ClearCrashKeyValueImpl(const base::StringPiece& key) { |
| 67 @autoreleasepool { |
| 68 ClearCrashKeyValue(base::SysUTF8ToNSString(key.as_string())); |
| 69 } |
| 70 } |
| 71 |
| 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) |
| 76 return false; |
| 77 |
| 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; |
| 83 if (guarded) |
| 84 return false; |
| 85 |
| 86 base::AutoReset<bool> guard(&guarded, true); |
| 87 |
| 88 // Only log last path component. This matches logging.cc. |
| 89 if (file) { |
| 90 const char* slash = strrchr(file, '/'); |
| 91 if (slash) |
| 92 file = slash + 1; |
| 93 } |
| 94 |
| 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); |
| 100 |
| 101 // Rather than including the code to force the crash here, allow the |
| 102 // caller to do it. |
| 103 return false; |
| 104 } |
| 105 |
| 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 { |
| 109 public: |
| 110 static void DumpWithoutCrashing() { |
| 111 DumpHelper dumper; |
| 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 |
| 115 // stack is logged. |
| 116 base::ThreadRestrictions::ScopedAllowIO allow_io; |
| 117 base::PlatformThread::Join(handle); |
| 118 } |
| 119 } |
| 120 |
| 121 private: |
| 122 DumpHelper() {} |
| 123 |
| 124 void ThreadMain() override { |
| 125 base::PlatformThread::SetName("CrDumpHelper"); |
| 126 BreakpadGenerateAndSendReport(gBreakpadRef); |
| 127 } |
| 128 |
| 129 DISALLOW_COPY_AND_ASSIGN(DumpHelper); |
| 130 }; |
| 131 |
| 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. |
| 138 // |
| 139 // http://www.opensource.apple.com/source/Libc/Libc-825.26/stdlib/FreeBSD/abor
t.c |
| 140 sigset_t mask; |
| 141 sigemptyset(&mask); |
| 142 sigaddset(&mask, signal); |
| 143 pthread_sigmask(SIG_SETMASK, &mask, NULL); |
| 144 |
| 145 // Most interesting operations are not safe in a signal handler, just crash. |
| 146 char* volatile death_ptr = NULL; |
| 147 *death_ptr = '!'; |
| 148 } |
| 149 |
| 150 } // namespace |
| 151 |
| 152 bool IsCrashReporterEnabled() { |
| 153 return gBreakpadRef != NULL; |
| 154 } |
| 155 |
| 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; |
| 160 |
| 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 |
| 167 // command line. |
| 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(); |
| 172 |
| 173 if (is_browser) { |
| 174 // Since the configuration management infrastructure is possibly not |
| 175 // initialized when this code runs, read the policy preference directly. |
| 176 if (!GetCrashReporterClient()->ReportingIsEnforcedByPolicy( |
| 177 &enable_breakpad)) { |
| 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); |
| 184 } |
| 185 } else { |
| 186 // This is a helper process, check the command line switch. |
| 187 enable_breakpad = command_line->HasSwitch(switches::kEnableCrashReporter); |
| 188 } |
| 189 |
| 190 if (!enable_breakpad) { |
| 191 VLOG_IF(1, is_browser) << "Breakpad disabled"; |
| 192 return; |
| 193 } |
| 194 |
| 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]; |
| 203 |
| 204 if (!inspector_location || !reporter_location) { |
| 205 VLOG_IF(1, is_browser && base::mac::AmIBundled()) << "Breakpad disabled"; |
| 206 return; |
| 207 } |
| 208 |
| 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]; |
| 216 |
| 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 |
| 220 // restart it. |
| 221 if (is_browser) |
| 222 [breakpad_config setObject:@"NO" forKey:@BREAKPAD_SEND_AND_EXIT]; |
| 223 |
| 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]; |
| 228 |
| 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]; |
| 235 } |
| 236 |
| 237 // Initialize Breakpad. |
| 238 gBreakpadRef = BreakpadCreate(breakpad_config); |
| 239 if (!gBreakpadRef) { |
| 240 LOG_IF(ERROR, base::mac::AmIBundled()) << "Breakpad initialization failed"; |
| 241 return; |
| 242 } |
| 243 |
| 244 // Initialize the scoped crash key system. |
| 245 base::debug::SetCrashKeyReportingFunctions(&SetCrashKeyValueImpl, |
| 246 &ClearCrashKeyValueImpl); |
| 247 GetCrashReporterClient()->RegisterCrashKeys(); |
| 248 |
| 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"); |
| 254 |
| 255 logging::SetLogMessageHandler(&FatalMessageHandler); |
| 256 base::debug::SetDumpWithoutCrashingFunction(&DumpHelper::DumpWithoutCrashing); |
| 257 |
| 258 // abort() sends SIGABRT, which breakpad does not intercept. |
| 259 // Register a signal handler to crash in a way breakpad will |
| 260 // intercept. |
| 261 struct sigaction sigact; |
| 262 memset(&sigact, 0, sizeof(sigact)); |
| 263 sigact.sa_handler = SIGABRTHandler; |
| 264 CHECK(0 == sigaction(SIGABRT, &sigact, NULL)); |
| 265 } |
| 266 |
| 267 void InitCrashProcessInfo(const std::string& process_type_switch) { |
| 268 if (gBreakpadRef == NULL) { |
| 269 return; |
| 270 } |
| 271 |
| 272 // Determine the process type. |
| 273 NSString* process_type = @"browser"; |
| 274 if (!process_type_switch.empty()) { |
| 275 process_type = base::SysUTF8ToNSString(process_type_switch); |
| 276 } |
| 277 |
| 278 // Store process type in crash dump. |
| 279 SetCrashKeyValue(@"ptype", process_type); |
| 280 |
| 281 NSString* pid_value = |
| 282 [NSString stringWithFormat:@"%d", static_cast<unsigned int>(getpid())]; |
| 283 SetCrashKeyValue(@"pid", pid_value); |
| 284 } |
| 285 |
| 286 } // namespace breakpad |
OLD | NEW |