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