| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/browser/crash_report/model/crash_helper.h" |
| |
| #import <UIKit/UIKit.h> |
| #import <stddef.h> |
| #import <stdint.h> |
| #import <sys/stat.h> |
| #import <sys/sysctl.h> |
| |
| #import "base/auto_reset.h" |
| #import "base/debug/crash_logging.h" |
| #import "base/feature_list.h" |
| #import "base/files/file_enumerator.h" |
| #import "base/files/file_path.h" |
| #import "base/files/file_util.h" |
| #import "base/functional/bind.h" |
| #import "base/ios/ios_util.h" |
| #import "base/location.h" |
| #import "base/logging.h" |
| #import "base/metrics/histogram_functions.h" |
| #import "base/metrics/histogram_macros.h" |
| #import "base/path_service.h" |
| #import "base/strings/string_number_conversions.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "base/task/thread_pool.h" |
| #import "base/time/time.h" |
| #import "components/crash/core/app/crashpad.h" |
| #import "components/crash/core/common/crash_key.h" |
| #import "components/crash/core/common/reporter_running_ios.h" |
| #import "components/gwp_asan/crash_handler/crash_handler.h" |
| #import "components/previous_session_info/previous_session_info.h" |
| #import "ios/chrome/browser/crash_report/model/crash_report_user_application_state.h" |
| #import "ios/chrome/browser/crash_report/model/crash_upload_list.h" |
| #import "ios/chrome/browser/crash_report/model/features.h" |
| #import "ios/chrome/browser/shared/model/application_context/application_context.h" |
| #import "ios/chrome/browser/shared/model/paths/paths.h" |
| #import "ios/chrome/common/app_group/app_group_constants.h" |
| #import "ios/chrome/common/channel_info.h" |
| #import "ios/chrome/common/crash_report/crash_helper.h" |
| |
| namespace crash_helper { |
| |
| namespace { |
| |
| // Disable all crash uploading (including during safe mode) if the |
| // kIOSCrashUploadKillSwitch is enabled. By revoking upload consent Crashpad |
| // will mark any pending reports as skipped. By disabling UserEnabledUploading |
| // safe mode crashes will be ignored. This also disables the main thread freeze |
| // detector. |
| BASE_FEATURE(kIOSCrashUploadKillSwitch, base::FEATURE_DISABLED_BY_DEFAULT); |
| |
| const char kUptimeAtRestoreInMs[] = "uptime_at_restore_in_ms"; |
| const char kUploadedInRecoveryMode[] = "uploaded_in_recovery_mode"; |
| |
| // This mirrors the logic in MobileSessionShutdownMetricsProvider to avoid a |
| // dependency loop. |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum MobileSessionShutdownType { |
| SHUTDOWN_IN_BACKGROUND = 0, |
| SHUTDOWN_IN_FOREGROUND_NO_CRASH_LOG_NO_MEMORY_WARNING, |
| SHUTDOWN_IN_FOREGROUND_WITH_CRASH_LOG_NO_MEMORY_WARNING, |
| SHUTDOWN_IN_FOREGROUND_NO_CRASH_LOG_WITH_MEMORY_WARNING, |
| SHUTDOWN_IN_FOREGROUND_WITH_CRASH_LOG_WITH_MEMORY_WARNING, |
| FIRST_LAUNCH_AFTER_UPGRADE, |
| SHUTDOWN_IN_FOREGROUND_WITH_MAIN_THREAD_FROZEN, |
| MOBILE_SESSION_SHUTDOWN_TYPE_COUNT, |
| }; |
| |
| // This mirrors the logic in MobileSessionShutdownMetricsProvider, which |
| // currently calls crash_helper::HasReportToUpload() before Crashpad calls |
| // ProcessIntermediateDumps. Experiment with instead calling this later during |
| // startup, but after Crashpad can process intermediate dumps. |
| MobileSessionShutdownType GetLastShutdownType(bool has_reports_to_upload) { |
| if ([[PreviousSessionInfo sharedInstance] isFirstSessionAfterUpgrade]) { |
| return FIRST_LAUNCH_AFTER_UPGRADE; |
| } |
| |
| // If the last app lifetime did not end with a crash, then log it as a normal |
| // shutdown while in the background. |
| if (GetApplicationContext()->WasLastShutdownClean()) { |
| return SHUTDOWN_IN_BACKGROUND; |
| } |
| |
| if (has_reports_to_upload) { |
| // The cause of the crash is known. |
| if ([[PreviousSessionInfo sharedInstance] |
| didSeeMemoryWarningShortlyBeforeTerminating]) { |
| return SHUTDOWN_IN_FOREGROUND_WITH_CRASH_LOG_WITH_MEMORY_WARNING; |
| } |
| return SHUTDOWN_IN_FOREGROUND_WITH_CRASH_LOG_NO_MEMORY_WARNING; |
| } |
| |
| // The cause of the crash is not known. Check the common causes in order of |
| // severity and likeliness to have caused the crash. |
| if ([[PreviousSessionInfo sharedInstance] |
| didSeeMemoryWarningShortlyBeforeTerminating]) { |
| return SHUTDOWN_IN_FOREGROUND_NO_CRASH_LOG_WITH_MEMORY_WARNING; |
| } |
| // There is no known cause. |
| return SHUTDOWN_IN_FOREGROUND_NO_CRASH_LOG_NO_MEMORY_WARNING; |
| } |
| |
| // Cleaning up the cache is best effort. Ignore removal results and errors. |
| // Remove this after a few milestones. |
| void ClearMainThreadFreezeDetectorCache() { |
| NSString* cacheDirectory = NSSearchPathForDirectoriesInDomains( |
| NSCachesDirectory, NSUserDomainMask, YES)[0]; |
| // The directory containing old UTE crash reports. |
| NSString* UTEDirectory = |
| [cacheDirectory stringByAppendingPathComponent:@"UTE"]; |
| BOOL isDirectory = NO; |
| NSError* error = nil; |
| NSFileManager* fileManager = [NSFileManager defaultManager]; |
| if ([fileManager fileExistsAtPath:UTEDirectory isDirectory:&isDirectory] && |
| isDirectory) { |
| [fileManager removeItemAtPath:UTEDirectory error:&error]; |
| } |
| |
| // The directory containing old UTE crash reports eligible for crashpad |
| // processing. |
| NSString* UTEPendingCrashpadDirectory = |
| [cacheDirectory stringByAppendingPathComponent:@"UTE_CrashpadPending"]; |
| isDirectory = NO; |
| if ([fileManager fileExistsAtPath:UTEPendingCrashpadDirectory |
| isDirectory:&isDirectory] && |
| isDirectory) { |
| [fileManager removeItemAtPath:UTEPendingCrashpadDirectory error:&error]; |
| } |
| } |
| |
| // Tells crashpad to start processing previously created intermediate dumps and |
| // begin uploading when possible. |
| void ProcessIntermediateDumps() { |
| crashpad::UserStreamDataSources user_stream_data_sources; |
| user_stream_data_sources.push_back( |
| std::make_unique<gwp_asan::UserStreamDataSource>()); |
| crash_reporter::ProcessIntermediateDumps({}, &user_stream_data_sources); |
| crash_reporter::StartProcessingPendingReports(); |
| |
| // Remove this after a few milestones. |
| ClearMainThreadFreezeDetectorCache(); |
| |
| bool has_reports_to_upload = HasReportToUpload(); |
| |
| // Wait until after processing intermediate dumps to record last shutdown |
| // type. |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| // This histogram is similar to MobileSessionShutdownType, but will not |
| // appear in the initial stability log. Because of this, the stability flag |
| // on this histogram doesn't matter. It will be reported like any other |
| // metric. |
| UMA_STABILITY_HISTOGRAM_ENUMERATION( |
| "Stability.MobileSessionShutdownType2", |
| GetLastShutdownType(has_reports_to_upload), |
| MOBILE_SESSION_SHUTDOWN_TYPE_COUNT); |
| }); |
| } |
| |
| // Returns the uptime, the difference between now and start time. |
| int64_t GetUptimeMilliseconds() { |
| struct timeval tv; |
| gettimeofday(&tv, NULL); |
| |
| kinfo_proc kern_proc_info; |
| int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()}; |
| size_t len = sizeof(kern_proc_info); |
| if (sysctl(mib, std::size(mib), &kern_proc_info, &len, nullptr, 0) != 0) { |
| return 0; |
| } |
| time_t process_uptime_seconds = |
| tv.tv_sec - kern_proc_info.kp_proc.p_starttime.tv_sec; |
| return static_cast<const int64_t>(process_uptime_seconds) * |
| base::Time::kMillisecondsPerSecond; |
| } |
| |
| } // namespace |
| |
| void Start() { |
| DCHECK(!crash_reporter::IsCrashpadRunning()); |
| |
| // Notifying the PathService on the location of the crashes so that crashes |
| // can be displayed to the user on the about:crashes page. Use the app group |
| // so crashes can be shared by plugins. |
| base::PathService::Override(ios::DIR_CRASH_DUMPS, |
| common::CrashpadDumpLocation()); |
| bool initialized = common::StartCrashpad(); |
| if (initialized) { |
| crash_reporter::SetCrashpadRunning(true); |
| } |
| UMA_HISTOGRAM_BOOLEAN("Stability.IOS.Crashpad.Initialized", initialized); |
| |
| #if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| static crash_reporter::CrashKeyString<4> key("partition_alloc"); |
| key.Set("yes"); |
| #endif |
| |
| if (base::ios::IsApplicationPreWarmed()) { |
| static crash_reporter::CrashKeyString<4> prewarmed_key("is_prewarmed"); |
| prewarmed_key.Set("yes"); |
| } |
| } |
| |
| void SetEnabled(bool enabled) { |
| if (base::FeatureList::IsEnabled(kIOSCrashUploadKillSwitch)) { |
| enabled = false; |
| } |
| // Caches the uploading flag in NSUserDefaults, so that we can access the |
| // value immediately on startup, such as in safe mode or extensions. |
| crash_helper::common::SetUserEnabledUploading(enabled); |
| |
| // Don't sync upload consent when the app is backgrounded. Crashpad |
| // flocks the settings file, and because Chrome puts this in a shared |
| // container, slow reads and writes can lead to watchdog kills. |
| if (UIApplication.sharedApplication.applicationState == |
| UIApplicationStateActive) { |
| // Posts SetUploadConsent on blocking pool thread because it needs access |
| // to IO and cannot work from UI thread. |
| base::ThreadPool::PostTask( |
| FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, |
| base::BindOnce(^{ |
| crash_reporter::SetUploadConsent(enabled); |
| })); |
| } |
| } |
| |
| void UploadCrashReports() { |
| if (crash_reporter::IsCrashpadRunning()) { |
| static dispatch_once_t once_token; |
| dispatch_once(&once_token, ^{ |
| base::ThreadPool::PostTask( |
| FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, |
| base::BindOnce(&ProcessIntermediateDumps)); |
| return; |
| }); |
| } |
| } |
| |
| void ProcessIntermediateReportsForSafeMode() { |
| if (crash_reporter::IsCrashpadRunning()) { |
| crash_reporter::ProcessIntermediateDumps( |
| {{kUploadedInRecoveryMode, "yes"}}); |
| } |
| } |
| |
| int GetPendingCrashReportCount() { |
| int count = 0; |
| if (crash_reporter::IsCrashpadRunning()) { |
| std::vector<crash_reporter::Report> reports; |
| crash_reporter::GetReports(&reports); |
| for (auto& report : reports) { |
| if (report.state == crash_reporter::ReportUploadState::Pending || |
| report.state == |
| crash_reporter::ReportUploadState::Pending_UserRequested) { |
| count++; |
| } |
| } |
| } |
| return count; |
| } |
| |
| bool HasReportToUpload() { |
| int pending_reports = GetPendingCrashReportCount(); |
| |
| // This can get called before crash_reporter::StartProcessingPendingReports() |
| // is called, which means we need to look for non-zero length files in |
| // common::CrashpadDumpLocation()/ dir. See crbug.com/1365765 for details, |
| // but this should be removed once MobileSessionShutdownType2 is validated. |
| if (crash_reporter::IsCrashpadRunning()) { |
| const base::FilePath path = |
| common::CrashpadDumpLocation().Append("pending-serialized-ios-dump"); |
| NSString* path_ns = base::SysUTF8ToNSString(path.value()); |
| NSArray<NSString*>* pending_files = |
| [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path_ns |
| error:nil]; |
| for (NSString* pending_filename : pending_files) { |
| NSString* pending_file = |
| [path_ns stringByAppendingPathComponent:pending_filename]; |
| NSDictionary* fileAttributes = |
| [[NSFileManager defaultManager] attributesOfItemAtPath:pending_file |
| error:nil]; |
| if ([[fileAttributes objectForKey:NSFileSize] longLongValue] > 0) { |
| pending_reports++; |
| } |
| } |
| } |
| return pending_reports > 0; |
| } |
| |
| // Records the current process uptime in the kUptimeAtRestoreInMs. 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 (crash_reporter::IsCrashpadRunning()) { |
| const int64_t uptime_milliseconds = GetUptimeMilliseconds(); |
| if (uptime_milliseconds > 0) { |
| static crash_reporter::CrashKeyString<16> key(kUptimeAtRestoreInMs); |
| key.Set(base::NumberToString((uptime_milliseconds))); |
| } |
| return; |
| } |
| } |
| |
| void StartUploadingReportsInRecoveryMode() { |
| if (crash_reporter::IsCrashpadRunning()) { |
| crash_reporter::StartProcessingPendingReports(); |
| return; |
| } |
| } |
| |
| void ClearReportsBetween(base::Time delete_begin, base::Time delete_end) { |
| ios::CreateCrashUploadList()->Clear(delete_begin, delete_end); |
| } |
| |
| } // namespace crash_helper |