| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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/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/breakpad_client.h" | |
| 27 #include "content/public/common/content_switches.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/abor
t.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() { | |
| 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 GetBreakpadClient()->SetDumpWithoutCrashingFunction( | |
| 248 &DumpHelper::DumpWithoutCrashing); | |
| 249 | |
| 250 // abort() sends SIGABRT, which breakpad does not intercept. | |
| 251 // Register a signal handler to crash in a way breakpad will | |
| 252 // intercept. | |
| 253 struct sigaction sigact; | |
| 254 memset(&sigact, 0, sizeof(sigact)); | |
| 255 sigact.sa_handler = SIGABRTHandler; | |
| 256 CHECK(0 == sigaction(SIGABRT, &sigact, NULL)); | |
| 257 } | |
| 258 | |
| 259 void InitCrashProcessInfo() { | |
| 260 if (gBreakpadRef == NULL) { | |
| 261 return; | |
| 262 } | |
| 263 | |
| 264 // Determine the process type. | |
| 265 NSString* process_type = @"browser"; | |
| 266 std::string process_type_switch = | |
| 267 CommandLine::ForCurrentProcess()->GetSwitchValueASCII( | |
| 268 switches::kProcessType); | |
| 269 if (!process_type_switch.empty()) { | |
| 270 process_type = base::SysUTF8ToNSString(process_type_switch); | |
| 271 } | |
| 272 | |
| 273 GetBreakpadClient()->InstallAdditionalFilters(gBreakpadRef); | |
| 274 | |
| 275 // Store process type in crash dump. | |
| 276 SetCrashKeyValue(@"ptype", process_type); | |
| 277 } | |
| 278 | |
| 279 } // namespace breakpad | |
| OLD | NEW |