// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ios/chrome/browser/crash_report/breakpad_helper.h" #import #include #include "base/auto_reset.h" #include "base/bind.h" #include "base/debug/crash_logging.h" #include "base/files/file_enumerator.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/location.h" #include "base/logging.h" #include "base/path_service.h" #include "base/strings/sys_string_conversions.h" #include "ios/chrome/browser/chrome_paths.h" #import "ios/chrome/browser/crash_report/crash_report_user_application_state.h" #include "ios/web/public/web_thread.h" // TODO(stuartmorgan): Move this up where it belongs once // http://code.google.com/p/google-breakpad/issues/detail?id=487 // is fixed. For now, put it at the end to avoid compiler errors. #import "breakpad/src/client/ios/BreakpadController.h" namespace breakpad_helper { namespace { // Key in NSUserDefaults for a Boolean value that stores whether to upload // crash reports. NSString* const kCrashReportsUploadingEnabledKey = @"CrashReportsUploadingEnabled"; NSString* const kCrashedInBackground = @"crashed_in_background"; NSString* const kFreeDiskInKB = @"free_disk_in_kb"; NSString* const kFreeMemoryInKB = @"free_memory_in_kb"; NSString* const kMemoryWarningInProgress = @"memory_warning_in_progress"; NSString* const kMemoryWarningCount = @"memory_warning_count"; NSString* const kUptimeAtRestoreInMs = @"uptime_at_restore_in_ms"; NSString* const kUploadedInRecoveryMode = @"uploaded_in_recovery_mode"; // Multiple state information are combined into one CrachReportMultiParameter // to save limited and finite number of ReportParameters. // These are the values grouped in the user_application_state parameter. NSString* const kDataProxyIsEnabled = @"dataproxy"; NSString* const kOrientationState = @"orient"; NSString* const kHorizontalSizeClass = @"sizeclass"; NSString* const kSignedIn = @"signIn"; NSString* const kIsShowingPDF = @"pdf"; NSString* const kVideoPlaying = @"avplay"; // Whether the crash reporter is enabled. bool g_crash_reporter_enabled = false; void DeleteAllReportsInDirectory(base::FilePath directory) { base::FileEnumerator enumerator(directory, false, base::FileEnumerator::FILES); base::FilePath cur_file; while (!(cur_file = enumerator.Next()).value().empty()) { if (cur_file.BaseName().value() != kReporterLogFilename) base::DeleteFile(cur_file, false); } } // Callback for base::debug::SetCrashKeyReportingFunctions void SetCrashKeyValueImpl(const base::StringPiece& key, const base::StringPiece& value) { AddReportParameter(base::SysUTF8ToNSString(key.as_string()), base::SysUTF8ToNSString(value.as_string()), true); } // Callback for base::debug::SetCrashKeyReportingFunctions void ClearCrashKeyValueImpl(const base::StringPiece& key) { RemoveReportParameter(base::SysUTF8ToNSString(key.as_string())); } // Callback for logging::SetLogMessageHandler bool FatalMessageHandler(int severity, const char* file, int line, size_t message_start, const std::string& str) { // Do not handle non-FATAL. if (severity != logging::LOG_FATAL) return false; // In case of OOM condition, this code could be reentered when // constructing and storing the key. Using a static is not // thread-safe, but if multiple threads are in the process of a // fatal crash at the same time, this should work. static bool guarded = false; if (guarded) return false; base::AutoReset guard(&guarded, true); // Only log last path component. This matches logging.cc. if (file) { const char* slash = strrchr(file, '/'); if (slash) file = slash + 1; } NSString* fatal_key = @"LOG_FATAL"; NSString* fatal_value = [NSString stringWithFormat:@"%s:%d: %s", file, line, str.c_str() + message_start]; AddReportParameter(fatal_key, fatal_value, true); // Rather than including the code to force the crash here, allow the // caller to do it. return false; } // Caches the uploading flag in NSUserDefaults, so that we can access the value // in safe mode. void CacheUploadingEnabled(bool uploading_enabled) { NSUserDefaults* user_defaults = [NSUserDefaults standardUserDefaults]; [user_defaults setBool:uploading_enabled ? YES : NO forKey:kCrashReportsUploadingEnabledKey]; } } // namespace void Start(const std::string& channel_name) { DCHECK(!g_crash_reporter_enabled); [[BreakpadController sharedInstance] start:YES]; base::debug::SetCrashKeyReportingFunctions(&SetCrashKeyValueImpl, &ClearCrashKeyValueImpl); logging::SetLogMessageHandler(&FatalMessageHandler); g_crash_reporter_enabled = true; // Register channel information. if (channel_name.length()) { AddReportParameter(@"channel", base::SysUTF8ToNSString(channel_name), true); } // Notifying the PathService on the location of the crashes so that crashes // can be displayed to the user on the about:crashes page. NSArray* cachesDirectories = NSSearchPathForDirectoriesInDomains( NSCachesDirectory, NSUserDomainMask, YES); NSString* cachePath = [cachesDirectories objectAtIndex:0]; NSString* dumpDirectory = [cachePath stringByAppendingPathComponent:@kDefaultLibrarySubdirectory]; PathService::Override(ios::DIR_CRASH_DUMPS, base::FilePath(base::SysNSStringToUTF8(dumpDirectory))); } void SetEnabled(bool enabled) { if (g_crash_reporter_enabled == enabled) return; g_crash_reporter_enabled = enabled; if (g_crash_reporter_enabled) { [[BreakpadController sharedInstance] start:NO]; } else { [[BreakpadController sharedInstance] stop]; CacheUploadingEnabled(false); } } void SetUploadingEnabled(bool enabled) { CacheUploadingEnabled(g_crash_reporter_enabled && enabled); if (!g_crash_reporter_enabled) return; [[BreakpadController sharedInstance] setUploadingEnabled:enabled]; } bool IsUploadingEnabled() { // Return the value cached by CacheUploadingEnabled(). return [[NSUserDefaults standardUserDefaults] boolForKey:kCrashReportsUploadingEnabledKey]; } void CleanupCrashReports() { base::FilePath crash_directory; PathService::Get(ios::DIR_CRASH_DUMPS, &crash_directory); web::WebThread::PostBlockingPoolTask( FROM_HERE, base::Bind(&DeleteAllReportsInDirectory, crash_directory)); } void AddReportParameter(NSString* key, NSString* value, bool async) { if (!g_crash_reporter_enabled) return; if (async) { [[BreakpadController sharedInstance] addUploadParameter:value forKey:key]; return; } dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [[BreakpadController sharedInstance] withBreakpadRef:^(BreakpadRef ref) { if (ref) BreakpadAddUploadParameter(ref, key, value); dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); dispatch_release(semaphore); } int GetCrashReportCount() { dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); __block int outerCrashReportCount = 0; [[BreakpadController sharedInstance] getCrashReportCount:^(int count) { outerCrashReportCount = count; dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); dispatch_release(semaphore); return outerCrashReportCount; } void GetCrashReportCount(void (^callback)(int)) { [[BreakpadController sharedInstance] getCrashReportCount:callback]; } bool HasReportToUpload() { return GetCrashReportCount() > 0; } void RemoveReportParameter(NSString* key) { if (!g_crash_reporter_enabled) return; [[BreakpadController sharedInstance] removeUploadParameterForKey:key]; } void SetCurrentlyInBackground(bool background) { if (background) AddReportParameter(kCrashedInBackground, @"yes", true); else RemoveReportParameter(kCrashedInBackground); } void SetMemoryWarningCount(int count) { if (count) { AddReportParameter(kMemoryWarningCount, [NSString stringWithFormat:@"%d", count], true); } else { RemoveReportParameter(kMemoryWarningCount); } } void SetMemoryWarningInProgress(bool value) { if (value) AddReportParameter(kMemoryWarningInProgress, @"yes", true); else RemoveReportParameter(kMemoryWarningInProgress); } void SetCurrentFreeMemoryInKB(int value) { AddReportParameter(kFreeMemoryInKB, [NSString stringWithFormat:@"%d", value], true); } void SetCurrentFreeDiskInKB(int value) { AddReportParameter(kFreeDiskInKB, [NSString stringWithFormat:@"%d", value], true); } void SetCurrentTabIsPDF(bool value) { if (value) { [[CrashReportUserApplicationState sharedInstance] incrementValue:kIsShowingPDF]; } else { [[CrashReportUserApplicationState sharedInstance] decrementValue:kIsShowingPDF]; } } void SetCurrentOrientation(int statusBarOrientation, int deviceOrientation) { DCHECK((statusBarOrientation < 10) && (deviceOrientation < 10)); int deviceAndUIOrientation = 10 * statusBarOrientation + deviceOrientation; [[CrashReportUserApplicationState sharedInstance] setValue:kOrientationState withValue:deviceAndUIOrientation]; } void SetCurrentHorizontalSizeClass(int horizontalSizeClass) { [[CrashReportUserApplicationState sharedInstance] setValue:kHorizontalSizeClass withValue:horizontalSizeClass]; } void SetCurrentlySignedIn(bool signedIn) { if (signedIn) { [[CrashReportUserApplicationState sharedInstance] setValue:kSignedIn withValue:1]; } else { [[CrashReportUserApplicationState sharedInstance] removeValue:kSignedIn]; } } void SetDataReductionProxyIsEnabled(bool value) { if (value) { [[CrashReportUserApplicationState sharedInstance] setValue:kDataProxyIsEnabled withValue:value]; } else { [[CrashReportUserApplicationState sharedInstance] removeValue:kDataProxyIsEnabled]; } } void MediaStreamPlaybackDidStart() { [[CrashReportUserApplicationState sharedInstance] incrementValue:kVideoPlaying]; } void MediaStreamPlaybackDidStop() { [[CrashReportUserApplicationState sharedInstance] decrementValue:kVideoPlaying]; } // Records the current process uptime in the "uptime_at_restore_in_ms". This // will allow engineers to dremel crash logs to find crash whose delta between // process uptime at crash and process uptime at restore is smaller than X // seconds and find insta-crashers. void WillStartCrashRestoration() { if (!g_crash_reporter_enabled) return; // We use gettimeofday and BREAKPAD_PROCESS_START_TIME to compute the // uptime to stay as close as possible as how breakpad computes the // "ProcessUptime" in order to have meaningful comparison in dremel. struct timeval tv; gettimeofday(&tv, NULL); // The values stored in the breakpad log are only accessible through a // BreakpadRef. To record the process uptime at restore, the value of // BREAKPAD_PROCESS_START_TIME is required to compute the delta. [[BreakpadController sharedInstance] withBreakpadRef:^(BreakpadRef ref) { if (!ref) return; NSString* processStartTimeSecondsString = BreakpadKeyValue(ref, @BREAKPAD_PROCESS_START_TIME); if (!processStartTimeSecondsString) return; time_t processStartTimeSeconds = [processStartTimeSecondsString longLongValue]; time_t processUptimeSeconds = tv.tv_sec - processStartTimeSeconds; unsigned long long processUptimeMilliseconds = static_cast(processUptimeSeconds) * base::Time::kMillisecondsPerSecond; BreakpadAddUploadParameter( ref, kUptimeAtRestoreInMs, [NSString stringWithFormat:@"%llu", processUptimeMilliseconds]); }]; } void StartUploadingReportsInRecoveryMode() { if (!g_crash_reporter_enabled) return; [[BreakpadController sharedInstance] stop]; [[BreakpadController sharedInstance] setParametersToAddAtUploadTime:@{ kUploadedInRecoveryMode : @"yes" }]; [[BreakpadController sharedInstance] setUploadInterval:1]; [[BreakpadController sharedInstance] start:NO]; [[BreakpadController sharedInstance] setUploadingEnabled:YES]; } void RestoreDefaultConfiguration() { if (!g_crash_reporter_enabled) return; [[BreakpadController sharedInstance] stop]; [[BreakpadController sharedInstance] resetConfiguration]; [[BreakpadController sharedInstance] start:NO]; [[BreakpadController sharedInstance] setUploadingEnabled:NO]; } } // namespace breakpad_helper