Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2015 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 #include "ios/chrome/browser/crash_report/breakpad_helper.h" | |
| 6 | |
| 7 #import <Foundation/Foundation.h> | |
| 8 | |
| 9 #include "base/auto_reset.h" | |
| 10 #include "base/bind.h" | |
| 11 #include "base/debug/crash_logging.h" | |
| 12 #include "base/files/file_enumerator.h" | |
| 13 #include "base/files/file_path.h" | |
| 14 #include "base/files/file_util.h" | |
| 15 #include "base/location.h" | |
| 16 #include "base/logging.h" | |
| 17 #include "base/path_service.h" | |
| 18 #include "base/strings/sys_string_conversions.h" | |
| 19 #include "ios/chrome/browser/chrome_paths.h" | |
| 20 #import "ios/chrome/browser/crash_report/crash_report_user_application_state.h" | |
| 21 #include "ios/web/public/web_thread.h" | |
| 22 | |
| 23 // TODO(ios): Move this up where it belongs once | |
|
sdefresne
2015/07/08 14:18:34
nit: can you find a better attribution than "ios"?
| |
| 24 // http://code.google.com/p/google-breakpad/issues/detail?id=487 | |
| 25 // is fixed. For now, put it at the end to avoid compiler errors. | |
| 26 #import "breakpad/src/client/ios/BreakpadController.h" | |
| 27 | |
| 28 namespace breakpad_helper { | |
| 29 | |
| 30 namespace { | |
| 31 | |
| 32 // Key in NSUserDefaults for a Boolean value that stores whether to upload | |
| 33 // crash reports. | |
| 34 NSString* const kCrashReportsUploadingEnabledKey = | |
| 35 @"CrashReportsUploadingEnabled"; | |
| 36 | |
| 37 NSString* const kCrashedInBackground = @"crashed_in_background"; | |
| 38 NSString* const kFreeDiskInKB = @"free_disk_in_kb"; | |
| 39 NSString* const kFreeMemoryInKB = @"free_memory_in_kb"; | |
| 40 NSString* const kMemoryWarningInProgress = @"memory_warning_in_progress"; | |
| 41 NSString* const kMemoryWarningCount = @"memory_warning_count"; | |
| 42 NSString* const kUptimeAtRestoreInMs = @"uptime_at_restore_in_ms"; | |
| 43 NSString* const kUploadedInRecoveryMode = @"uploaded_in_recovery_mode"; | |
| 44 | |
| 45 // Multiple state information are combined into one CrachReportMultiParameter | |
| 46 // to save limited and finite number of ReportParameters. | |
| 47 // These are the values grouped in the user_application_state parameter. | |
| 48 NSString* const kDataProxyIsEnabled = @"dataproxy"; | |
| 49 NSString* const kOrientationState = @"orient"; | |
| 50 NSString* const kSignedIn = @"signIn"; | |
| 51 NSString* const kIsShowingPDF = @"pdf"; | |
| 52 NSString* const kVideoPlaying = @"avplay"; | |
| 53 | |
| 54 // Whether the crash reporter is enabled. | |
| 55 bool g_crash_reporter_enabled = false; | |
| 56 | |
| 57 void DeleteAllReportsInDirectory(base::FilePath directory) { | |
| 58 base::FileEnumerator enumerator(directory, false, | |
| 59 base::FileEnumerator::FILES); | |
| 60 base::FilePath cur_file; | |
| 61 while (!(cur_file = enumerator.Next()).value().empty()) { | |
| 62 if (cur_file.BaseName().value() != kReporterLogFilename) | |
| 63 base::DeleteFile(cur_file, false); | |
| 64 } | |
| 65 } | |
| 66 | |
| 67 // Callback for base::debug::SetCrashKeyReportingFunctions | |
| 68 void SetCrashKeyValueImpl(const base::StringPiece& key, | |
| 69 const base::StringPiece& value) { | |
| 70 AddReportParameter(base::SysUTF8ToNSString(key.as_string()), | |
| 71 base::SysUTF8ToNSString(value.as_string()), true); | |
| 72 } | |
| 73 | |
| 74 // Callback for base::debug::SetCrashKeyReportingFunctions | |
| 75 void ClearCrashKeyValueImpl(const base::StringPiece& key) { | |
| 76 RemoveReportParameter(base::SysUTF8ToNSString(key.as_string())); | |
| 77 } | |
| 78 | |
| 79 // Callback for logging::SetLogMessageHandler | |
| 80 bool FatalMessageHandler(int severity, | |
| 81 const char* file, | |
| 82 int line, | |
| 83 size_t message_start, | |
| 84 const std::string& str) { | |
| 85 // Do not handle non-FATAL. | |
| 86 if (severity != logging::LOG_FATAL) | |
| 87 return false; | |
| 88 | |
| 89 // In case of OOM condition, this code could be reentered when | |
| 90 // constructing and storing the key. Using a static is not | |
| 91 // thread-safe, but if multiple threads are in the process of a | |
| 92 // fatal crash at the same time, this should work. | |
| 93 static bool guarded = false; | |
| 94 if (guarded) | |
| 95 return false; | |
| 96 | |
| 97 base::AutoReset<bool> guard(&guarded, true); | |
| 98 | |
| 99 // Only log last path component. This matches logging.cc. | |
| 100 if (file) { | |
| 101 const char* slash = strrchr(file, '/'); | |
| 102 if (slash) | |
| 103 file = slash + 1; | |
| 104 } | |
| 105 | |
| 106 NSString* fatal_key = @"LOG_FATAL"; | |
| 107 NSString* fatal_value = [NSString | |
| 108 stringWithFormat:@"%s:%d: %s", file, line, str.c_str() + message_start]; | |
| 109 AddReportParameter(fatal_key, fatal_value, true); | |
| 110 | |
| 111 // Rather than including the code to force the crash here, allow the | |
| 112 // caller to do it. | |
| 113 return false; | |
| 114 } | |
| 115 | |
| 116 // Caches the uploading flag in NSUserDefaults, so that we can access the value | |
| 117 // in safe mode. | |
| 118 void CacheUploadingEnabled(bool uploading_enabled) { | |
| 119 NSUserDefaults* user_defaults = [NSUserDefaults standardUserDefaults]; | |
| 120 [user_defaults setBool:uploading_enabled ? YES : NO | |
| 121 forKey:kCrashReportsUploadingEnabledKey]; | |
| 122 } | |
| 123 | |
| 124 } // namespace | |
| 125 | |
| 126 void Start(const std::string& channel_name) { | |
| 127 DCHECK(!g_crash_reporter_enabled); | |
| 128 [[BreakpadController sharedInstance] start:YES]; | |
| 129 base::debug::SetCrashKeyReportingFunctions(&SetCrashKeyValueImpl, | |
| 130 &ClearCrashKeyValueImpl); | |
| 131 logging::SetLogMessageHandler(&FatalMessageHandler); | |
| 132 g_crash_reporter_enabled = true; | |
| 133 // Register channel information. | |
| 134 if (channel_name.length()) { | |
| 135 AddReportParameter(@"channel", base::SysUTF8ToNSString(channel_name), true); | |
| 136 } | |
| 137 // Notifying the PathService on the location of the crashes so that crashes | |
| 138 // can be displayed to the user on the about:crashes page. | |
| 139 NSArray* cachesDirectories = NSSearchPathForDirectoriesInDomains( | |
| 140 NSCachesDirectory, NSUserDomainMask, YES); | |
| 141 NSString* cachePath = [cachesDirectories objectAtIndex:0]; | |
| 142 NSString* dumpDirectory = | |
| 143 [cachePath stringByAppendingPathComponent:@kDefaultLibrarySubdirectory]; | |
| 144 PathService::Override(ios::DIR_CRASH_DUMPS, | |
| 145 base::FilePath(base::SysNSStringToUTF8(dumpDirectory))); | |
| 146 } | |
| 147 | |
| 148 void SetEnabled(bool enabled) { | |
| 149 if (g_crash_reporter_enabled == enabled) | |
| 150 return; | |
| 151 g_crash_reporter_enabled = enabled; | |
| 152 if (g_crash_reporter_enabled) { | |
| 153 [[BreakpadController sharedInstance] start:NO]; | |
| 154 } else { | |
| 155 [[BreakpadController sharedInstance] stop]; | |
| 156 CacheUploadingEnabled(false); | |
| 157 } | |
| 158 } | |
| 159 | |
| 160 void SetUploadingEnabled(bool enabled) { | |
| 161 CacheUploadingEnabled(g_crash_reporter_enabled && enabled); | |
| 162 | |
| 163 if (!g_crash_reporter_enabled) | |
| 164 return; | |
| 165 [[BreakpadController sharedInstance] setUploadingEnabled:enabled]; | |
| 166 } | |
| 167 | |
| 168 bool IsUploadingEnabled() { | |
| 169 // Return the value cached by CacheUploadingEnabled(). | |
| 170 return [[NSUserDefaults standardUserDefaults] | |
| 171 boolForKey:kCrashReportsUploadingEnabledKey]; | |
| 172 } | |
| 173 | |
| 174 void CleanupCrashReports() { | |
| 175 base::FilePath crash_directory; | |
| 176 PathService::Get(ios::DIR_CRASH_DUMPS, &crash_directory); | |
| 177 web::WebThread::PostBlockingPoolTask( | |
| 178 FROM_HERE, base::Bind(&DeleteAllReportsInDirectory, crash_directory)); | |
| 179 } | |
| 180 | |
| 181 void AddReportParameter(NSString* key, NSString* value, bool async) { | |
| 182 if (!g_crash_reporter_enabled) | |
| 183 return; | |
| 184 if (async) { | |
| 185 [[BreakpadController sharedInstance] addUploadParameter:value forKey:key]; | |
| 186 return; | |
| 187 } | |
| 188 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); | |
| 189 [[BreakpadController sharedInstance] withBreakpadRef:^(BreakpadRef ref) { | |
| 190 if (ref) | |
| 191 BreakpadAddUploadParameter(ref, key, value); | |
| 192 dispatch_semaphore_signal(semaphore); | |
| 193 }]; | |
| 194 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); | |
| 195 dispatch_release(semaphore); | |
| 196 } | |
| 197 | |
| 198 int GetCrashReportCount() { | |
| 199 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); | |
| 200 __block int outerCrashReportCount = 0; | |
| 201 [[BreakpadController sharedInstance] getCrashReportCount:^(int count) { | |
| 202 outerCrashReportCount = count; | |
| 203 dispatch_semaphore_signal(semaphore); | |
| 204 }]; | |
| 205 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); | |
| 206 dispatch_release(semaphore); | |
| 207 return outerCrashReportCount; | |
| 208 } | |
| 209 | |
| 210 void GetCrashReportCount(void (^callback)(int)) { | |
| 211 [[BreakpadController sharedInstance] getCrashReportCount:callback]; | |
| 212 } | |
| 213 | |
| 214 bool HasReportToUpload() { | |
| 215 return GetCrashReportCount() > 0; | |
| 216 } | |
| 217 | |
| 218 void RemoveReportParameter(NSString* key) { | |
| 219 if (!g_crash_reporter_enabled) | |
| 220 return; | |
| 221 [[BreakpadController sharedInstance] removeUploadParameterForKey:key]; | |
| 222 } | |
| 223 | |
| 224 void SetCurrentlyInBackground(bool background) { | |
| 225 if (background) | |
| 226 AddReportParameter(kCrashedInBackground, @"yes", true); | |
| 227 else | |
| 228 RemoveReportParameter(kCrashedInBackground); | |
| 229 } | |
| 230 | |
| 231 void SetMemoryWarningCount(int count) { | |
| 232 if (count) { | |
| 233 AddReportParameter(kMemoryWarningCount, | |
| 234 [NSString stringWithFormat:@"%d", count], true); | |
| 235 } else { | |
| 236 RemoveReportParameter(kMemoryWarningCount); | |
| 237 } | |
| 238 } | |
| 239 | |
| 240 void SetMemoryWarningInProgress(bool value) { | |
| 241 if (value) | |
| 242 AddReportParameter(kMemoryWarningInProgress, @"yes", true); | |
| 243 else | |
| 244 RemoveReportParameter(kMemoryWarningInProgress); | |
| 245 } | |
| 246 | |
| 247 void SetCurrentFreeMemoryInKB(int value) { | |
| 248 AddReportParameter(kFreeMemoryInKB, [NSString stringWithFormat:@"%d", value], | |
| 249 true); | |
| 250 } | |
| 251 | |
| 252 void SetCurrentFreeDiskInKB(int value) { | |
| 253 AddReportParameter(kFreeDiskInKB, [NSString stringWithFormat:@"%d", value], | |
| 254 true); | |
| 255 } | |
| 256 | |
| 257 void SetCurrentTabIsPDF(bool value) { | |
| 258 if (value) { | |
| 259 [[CrashReportUserApplicationState sharedInstance] | |
| 260 incrementValue:kIsShowingPDF]; | |
| 261 } else { | |
| 262 [[CrashReportUserApplicationState sharedInstance] | |
| 263 decrementValue:kIsShowingPDF]; | |
| 264 } | |
| 265 } | |
| 266 | |
| 267 void SetCurrentOrientation(int statusBarOrientation, int deviceOrientation) { | |
| 268 DCHECK((statusBarOrientation < 10) && (deviceOrientation < 10)); | |
| 269 int deviceAndUIOrientation = 10 * statusBarOrientation + deviceOrientation; | |
| 270 [[CrashReportUserApplicationState sharedInstance] | |
| 271 setValue:kOrientationState | |
| 272 withValue:deviceAndUIOrientation]; | |
| 273 } | |
| 274 | |
| 275 void SetCurrentlySignedIn(bool signedIn) { | |
| 276 if (signedIn) { | |
| 277 [[CrashReportUserApplicationState sharedInstance] setValue:kSignedIn | |
| 278 withValue:1]; | |
| 279 } else { | |
| 280 [[CrashReportUserApplicationState sharedInstance] removeValue:kSignedIn]; | |
| 281 } | |
| 282 } | |
| 283 | |
| 284 void SetDataReductionProxyIsEnabled(bool value) { | |
| 285 if (value) { | |
| 286 [[CrashReportUserApplicationState sharedInstance] | |
| 287 setValue:kDataProxyIsEnabled | |
| 288 withValue:value]; | |
| 289 } else { | |
| 290 [[CrashReportUserApplicationState sharedInstance] | |
| 291 removeValue:kDataProxyIsEnabled]; | |
| 292 } | |
| 293 } | |
| 294 | |
| 295 void MediaStreamPlaybackDidStart() { | |
| 296 [[CrashReportUserApplicationState sharedInstance] | |
| 297 incrementValue:kVideoPlaying]; | |
| 298 } | |
| 299 | |
| 300 void MediaStreamPlaybackDidStop() { | |
| 301 [[CrashReportUserApplicationState sharedInstance] | |
| 302 decrementValue:kVideoPlaying]; | |
| 303 } | |
| 304 | |
| 305 // Records the current process uptime in the "uptime_at_restore_in_ms". This | |
| 306 // will allow engineers to dremel crash logs to find crash whose delta between | |
| 307 // process uptime at crash and process uptime at restore is smaller than X | |
| 308 // seconds and find insta-crashers. | |
| 309 void WillStartCrashRestoration() { | |
| 310 if (!g_crash_reporter_enabled) | |
| 311 return; | |
| 312 // We use gettimeofday and BREAKPAD_PROCESS_START_TIME to compute the | |
| 313 // uptime to stay as close as possible as how breakpad computes the | |
| 314 // "ProcessUptime" in order to have meaningful comparison in dremel. | |
| 315 struct timeval tv; | |
| 316 gettimeofday(&tv, NULL); | |
| 317 // The values stored in the breakpad log are only accessible through a | |
| 318 // BreakpadRef. To record the process uptime at restore, the value of | |
| 319 // BREAKPAD_PROCESS_START_TIME is required to compute the delta. | |
| 320 [[BreakpadController sharedInstance] withBreakpadRef:^(BreakpadRef ref) { | |
| 321 if (!ref) | |
| 322 return; | |
| 323 NSString* processStartTimeSecondsString = | |
| 324 BreakpadKeyValue(ref, @BREAKPAD_PROCESS_START_TIME); | |
| 325 if (!processStartTimeSecondsString) | |
| 326 return; | |
| 327 | |
| 328 time_t processStartTimeSeconds = | |
| 329 [processStartTimeSecondsString longLongValue]; | |
| 330 time_t processUptimeSeconds = tv.tv_sec - processStartTimeSeconds; | |
| 331 unsigned long long processUptimeMilliseconds = | |
| 332 static_cast<unsigned long long>(processUptimeSeconds) * | |
| 333 base::Time::kMillisecondsPerSecond; | |
| 334 BreakpadAddUploadParameter( | |
| 335 ref, kUptimeAtRestoreInMs, | |
| 336 [NSString stringWithFormat:@"%llu", processUptimeMilliseconds]); | |
| 337 }]; | |
| 338 } | |
| 339 | |
| 340 void StartUploadingReportsInRecoveryMode() { | |
| 341 if (!g_crash_reporter_enabled) | |
| 342 return; | |
| 343 [[BreakpadController sharedInstance] stop]; | |
| 344 [[BreakpadController sharedInstance] setParametersToAddAtUploadTime:@{ | |
| 345 kUploadedInRecoveryMode : @"yes" | |
| 346 }]; | |
| 347 [[BreakpadController sharedInstance] setUploadInterval:1]; | |
| 348 [[BreakpadController sharedInstance] start:NO]; | |
| 349 [[BreakpadController sharedInstance] setUploadingEnabled:YES]; | |
| 350 } | |
| 351 | |
| 352 void RestoreDefaultConfiguration() { | |
| 353 if (!g_crash_reporter_enabled) | |
| 354 return; | |
| 355 [[BreakpadController sharedInstance] stop]; | |
| 356 [[BreakpadController sharedInstance] resetConfiguration]; | |
| 357 [[BreakpadController sharedInstance] start:NO]; | |
| 358 [[BreakpadController sharedInstance] setUploadingEnabled:NO]; | |
| 359 } | |
| 360 | |
| 361 } // namespace breakpad_helper | |
| OLD | NEW |