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