Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ios / chrome / browser / crash_report / breakpad_helper.mm
blob526ab12b18a2d5b340f1f20a90ba270771dfcd7b
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.
5 #include "ios/chrome/browser/crash_report/breakpad_helper.h"
7 #import <Foundation/Foundation.h>
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"
23 // TODO(stuartmorgan): Move this up where it belongs once
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"
28 namespace breakpad_helper {
30 namespace {
32 // Key in NSUserDefaults for a Boolean value that stores whether to upload
33 // crash reports.
34 NSString* const kCrashReportsUploadingEnabledKey =
35     @"CrashReportsUploadingEnabled";
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";
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";
54 // Whether the crash reporter is enabled.
55 bool g_crash_reporter_enabled = false;
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   }
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);
74 // Callback for base::debug::SetCrashKeyReportingFunctions
75 void ClearCrashKeyValueImpl(const base::StringPiece& key) {
76   RemoveReportParameter(base::SysUTF8ToNSString(key.as_string()));
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;
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;
97   base::AutoReset<bool> guard(&guarded, true);
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   }
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);
111   // Rather than including the code to force the crash here, allow the
112   // caller to do it.
113   return false;
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];
124 }  // namespace
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)));
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   }
160 void SetUploadingEnabled(bool enabled) {
161   CacheUploadingEnabled(g_crash_reporter_enabled && enabled);
163   if (!g_crash_reporter_enabled)
164     return;
165   [[BreakpadController sharedInstance] setUploadingEnabled:enabled];
168 bool IsUploadingEnabled() {
169   // Return the value cached by CacheUploadingEnabled().
170   return [[NSUserDefaults standardUserDefaults]
171       boolForKey:kCrashReportsUploadingEnabledKey];
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));
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);
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;
210 void GetCrashReportCount(void (^callback)(int)) {
211   [[BreakpadController sharedInstance] getCrashReportCount:callback];
214 bool HasReportToUpload() {
215   return GetCrashReportCount() > 0;
218 void RemoveReportParameter(NSString* key) {
219   if (!g_crash_reporter_enabled)
220     return;
221   [[BreakpadController sharedInstance] removeUploadParameterForKey:key];
224 void SetCurrentlyInBackground(bool background) {
225   if (background)
226     AddReportParameter(kCrashedInBackground, @"yes", true);
227   else
228     RemoveReportParameter(kCrashedInBackground);
231 void SetMemoryWarningCount(int count) {
232   if (count) {
233     AddReportParameter(kMemoryWarningCount,
234                        [NSString stringWithFormat:@"%d", count], true);
235   } else {
236     RemoveReportParameter(kMemoryWarningCount);
237   }
240 void SetMemoryWarningInProgress(bool value) {
241   if (value)
242     AddReportParameter(kMemoryWarningInProgress, @"yes", true);
243   else
244     RemoveReportParameter(kMemoryWarningInProgress);
247 void SetCurrentFreeMemoryInKB(int value) {
248   AddReportParameter(kFreeMemoryInKB, [NSString stringWithFormat:@"%d", value],
249                      true);
252 void SetCurrentFreeDiskInKB(int value) {
253   AddReportParameter(kFreeDiskInKB, [NSString stringWithFormat:@"%d", value],
254                      true);
257 void SetCurrentTabIsPDF(bool value) {
258   if (value) {
259     [[CrashReportUserApplicationState sharedInstance]
260         incrementValue:kIsShowingPDF];
261   } else {
262     [[CrashReportUserApplicationState sharedInstance]
263         decrementValue:kIsShowingPDF];
264   }
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];
275 void SetCurrentlySignedIn(bool signedIn) {
276   if (signedIn) {
277     [[CrashReportUserApplicationState sharedInstance] setValue:kSignedIn
278                                                      withValue:1];
279   } else {
280     [[CrashReportUserApplicationState sharedInstance] removeValue:kSignedIn];
281   }
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   }
295 void MediaStreamPlaybackDidStart() {
296   [[CrashReportUserApplicationState sharedInstance]
297       incrementValue:kVideoPlaying];
300 void MediaStreamPlaybackDidStop() {
301   [[CrashReportUserApplicationState sharedInstance]
302       decrementValue:kVideoPlaying];
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;
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   }];
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];
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];
361 }  // namespace breakpad_helper