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 29 using crash_reporter::GetCrashReporterClient; 30 31 namespace breakpad { 32 33 namespace { 34 35 BreakpadRef gBreakpadRef = NULL; 36 37 void 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 49 void ClearCrashKeyValue(NSString* key) { 50 if (gBreakpadRef == NULL) { 51 return; 52 } 53 54 BreakpadRemoveUploadParameter(gBreakpadRef, key); 55 } 56 57 void 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 63 void ClearCrashKeyValueImpl(const base::StringPiece& key) { 64 ClearCrashKeyValue(base::SysUTF8ToNSString(key.as_string())); 65 } 66 67 bool 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. 103 class 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 127 void 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 147 bool IsCrashReporterEnabled() { 148 return gBreakpadRef != NULL; 149 } 150 151 // Only called for a branded build of Chrome.app. 152 void 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 269 void 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