1// Copyright 2013 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 "chrome/app/chrome_breakpad_client.h"
6
7#include "base/atomicops.h"
8#include "base/command_line.h"
9#include "base/environment.h"
10#include "base/files/file_path.h"
11#include "base/logging.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/path_service.h"
14#include "base/strings/safe_sprintf.h"
15#include "base/strings/string_split.h"
16#include "base/strings/utf_string_conversions.h"
17#include "chrome/common/chrome_constants.h"
18#include "chrome/common/chrome_paths.h"
19#include "chrome/common/chrome_result_codes.h"
20#include "chrome/common/chrome_switches.h"
21#include "chrome/common/crash_keys.h"
22#include "chrome/common/env_vars.h"
23#include "chrome/installer/util/google_update_settings.h"
24
25#if defined(OS_WIN)
26#include <windows.h>
27
28#include "base/file_version_info.h"
29#include "base/win/registry.h"
30#include "chrome/installer/util/google_chrome_sxs_distribution.h"
31#include "chrome/installer/util/install_util.h"
32#include "chrome/installer/util/util_constants.h"
33#include "policy/policy_constants.h"
34#endif
35
36#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS)
37#include "chrome/browser/crash_upload_list.h"
38#include "chrome/common/chrome_version_info_posix.h"
39#endif
40
41#if defined(OS_POSIX)
42#include "base/debug/dump_without_crashing.h"
43#endif
44
45#if defined(OS_ANDROID)
46#include "chrome/common/descriptors_android.h"
47#endif
48
49#if defined(OS_CHROMEOS)
50#include "chrome/common/chrome_version_info.h"
51#include "chromeos/chromeos_switches.h"
52#endif
53
54namespace chrome {
55
56namespace {
57
58#if defined(OS_WIN)
59// This is the minimum version of google update that is required for deferred
60// crash uploads to work.
61const char kMinUpdateVersion[] = "1.3.21.115";
62
63// The value name prefix will be of the form {chrome-version}-{pid}-{timestamp}
64// (i.e., "#####.#####.#####.#####-########-########") which easily fits into a
65// 63 character buffer.
66const char kBrowserCrashDumpPrefixTemplate[] = "%s-%08x-%08x";
67const size_t kBrowserCrashDumpPrefixLength = 63;
68char g_browser_crash_dump_prefix[kBrowserCrashDumpPrefixLength + 1] = {};
69
70// These registry key to which we'll write a value for each crash dump attempt.
71HKEY g_browser_crash_dump_regkey = NULL;
72
73// A atomic counter to make each crash dump value name unique.
74base::subtle::Atomic32 g_browser_crash_dump_count = 0;
75#endif
76
77}  // namespace
78
79ChromeBreakpadClient::ChromeBreakpadClient() {}
80
81ChromeBreakpadClient::~ChromeBreakpadClient() {}
82
83void ChromeBreakpadClient::SetClientID(const std::string& client_id) {
84  crash_keys::SetClientID(client_id);
85}
86
87#if defined(OS_WIN)
88bool ChromeBreakpadClient::GetAlternativeCrashDumpLocation(
89    base::FilePath* crash_dir) {
90  // By setting the BREAKPAD_DUMP_LOCATION environment variable, an alternate
91  // location to write breakpad crash dumps can be set.
92  scoped_ptr<base::Environment> env(base::Environment::Create());
93  std::string alternate_crash_dump_location;
94  if (env->GetVar("BREAKPAD_DUMP_LOCATION", &alternate_crash_dump_location)) {
95    *crash_dir = base::FilePath::FromUTF8Unsafe(alternate_crash_dump_location);
96    return true;
97  }
98
99  return false;
100}
101
102void ChromeBreakpadClient::GetProductNameAndVersion(
103    const base::FilePath& exe_path,
104    base::string16* product_name,
105    base::string16* version,
106    base::string16* special_build,
107    base::string16* channel_name) {
108  DCHECK(product_name);
109  DCHECK(version);
110  DCHECK(special_build);
111  DCHECK(channel_name);
112
113  scoped_ptr<FileVersionInfo> version_info(
114      FileVersionInfo::CreateFileVersionInfo(exe_path));
115
116  if (version_info.get()) {
117    // Get the information from the file.
118    *version = version_info->product_version();
119    if (!version_info->is_official_build())
120      version->append(base::ASCIIToUTF16("-devel"));
121
122    *product_name = version_info->product_short_name();
123    *special_build = version_info->special_build();
124  } else {
125    // No version info found. Make up the values.
126    *product_name = base::ASCIIToUTF16("Chrome");
127    *version = base::ASCIIToUTF16("0.0.0.0-devel");
128  }
129
130  GoogleUpdateSettings::GetChromeChannelAndModifiers(
131      !GetIsPerUserInstall(exe_path), channel_name);
132}
133
134bool ChromeBreakpadClient::ShouldShowRestartDialog(base::string16* title,
135                                                   base::string16* message,
136                                                   bool* is_rtl_locale) {
137  scoped_ptr<base::Environment> env(base::Environment::Create());
138  if (!env->HasVar(env_vars::kShowRestart) ||
139      !env->HasVar(env_vars::kRestartInfo) ||
140      env->HasVar(env_vars::kMetroConnected)) {
141    return false;
142  }
143
144  std::string restart_info;
145  env->GetVar(env_vars::kRestartInfo, &restart_info);
146
147  // The CHROME_RESTART var contains the dialog strings separated by '|'.
148  // See ChromeBrowserMainPartsWin::PrepareRestartOnCrashEnviroment()
149  // for details.
150  std::vector<std::string> dlg_strings;
151  base::SplitString(restart_info, '|', &dlg_strings);
152
153  if (dlg_strings.size() < 3)
154    return false;
155
156  *title = base::UTF8ToUTF16(dlg_strings[0]);
157  *message = base::UTF8ToUTF16(dlg_strings[1]);
158  *is_rtl_locale = dlg_strings[2] == env_vars::kRtlLocale;
159  return true;
160}
161
162bool ChromeBreakpadClient::AboutToRestart() {
163  scoped_ptr<base::Environment> env(base::Environment::Create());
164  if (!env->HasVar(env_vars::kRestartInfo))
165    return false;
166
167  env->SetVar(env_vars::kShowRestart, "1");
168  return true;
169}
170
171bool ChromeBreakpadClient::GetDeferredUploadsSupported(
172    bool is_per_user_install) {
173  Version update_version = GoogleUpdateSettings::GetGoogleUpdateVersion(
174      !is_per_user_install);
175  if (!update_version.IsValid() ||
176      update_version.IsOlderThan(std::string(kMinUpdateVersion)))
177    return false;
178
179  return true;
180}
181
182bool ChromeBreakpadClient::GetIsPerUserInstall(const base::FilePath& exe_path) {
183  return InstallUtil::IsPerUserInstall(exe_path.value().c_str());
184}
185
186bool ChromeBreakpadClient::GetShouldDumpLargerDumps(bool is_per_user_install) {
187  base::string16 channel_name =
188      GoogleUpdateSettings::GetChromeChannel(!is_per_user_install);
189
190  // Capture more detail in crash dumps for beta and dev channel builds.
191  return (channel_name == installer::kChromeChannelDev ||
192          channel_name == installer::kChromeChannelBeta ||
193          channel_name == GoogleChromeSxSDistribution::ChannelName());
194}
195
196int ChromeBreakpadClient::GetResultCodeRespawnFailed() {
197  return chrome::RESULT_CODE_RESPAWN_FAILED;
198}
199
200void ChromeBreakpadClient::InitBrowserCrashDumpsRegKey() {
201  DCHECK(g_browser_crash_dump_regkey == NULL);
202
203  base::win::RegKey regkey;
204  if (regkey.Create(HKEY_CURRENT_USER,
205                    chrome::kBrowserCrashDumpAttemptsRegistryPath,
206                    KEY_ALL_ACCESS) != ERROR_SUCCESS) {
207    return;
208  }
209
210  // We use the current process id and the current tick count as a (hopefully)
211  // unique combination for the crash dump value. There's a small chance that
212  // across a reboot we might have a crash dump signal written, and the next
213  // browser process might have the same process id and tick count, but crash
214  // before consuming the signal (overwriting the signal with an identical one).
215  // For now, we're willing to live with that risk.
216  int length = base::strings::SafeSPrintf(g_browser_crash_dump_prefix,
217                                          kBrowserCrashDumpPrefixTemplate,
218                                          chrome::kChromeVersion,
219                                          ::GetCurrentProcessId(),
220                                          ::GetTickCount());
221  if (length <= 0) {
222    NOTREACHED();
223    g_browser_crash_dump_prefix[0] = '\0';
224    return;
225  }
226
227  // Hold the registry key in a global for update on crash dump.
228  g_browser_crash_dump_regkey = regkey.Take();
229}
230
231void ChromeBreakpadClient::RecordCrashDumpAttempt(bool is_real_crash) {
232  // If we're not a browser (or the registry is unavailable to us for some
233  // reason) then there's nothing to do.
234  if (g_browser_crash_dump_regkey == NULL)
235    return;
236
237  // Generate the final value name we'll use (appends the crash number to the
238  // base value name).
239  const size_t kMaxValueSize = 2 * kBrowserCrashDumpPrefixLength;
240  char value_name[kMaxValueSize + 1] = {};
241  int length = base::strings::SafeSPrintf(
242      value_name,
243      "%s-%x",
244      g_browser_crash_dump_prefix,
245      base::subtle::NoBarrier_AtomicIncrement(&g_browser_crash_dump_count, 1));
246
247  if (length > 0) {
248    DWORD value_dword = is_real_crash ? 1 : 0;
249    ::RegSetValueExA(g_browser_crash_dump_regkey, value_name, 0, REG_DWORD,
250                     reinterpret_cast<BYTE*>(&value_dword),
251                     sizeof(value_dword));
252  }
253}
254
255bool ChromeBreakpadClient::ReportingIsEnforcedByPolicy(bool* breakpad_enabled) {
256// Determine whether configuration management allows loading the crash reporter.
257// Since the configuration management infrastructure is not initialized at this
258// point, we read the corresponding registry key directly. The return status
259// indicates whether policy data was successfully read. If it is true,
260// |breakpad_enabled| contains the value set by policy.
261  base::string16 key_name =
262      base::UTF8ToUTF16(policy::key::kMetricsReportingEnabled);
263  DWORD value = 0;
264  base::win::RegKey hklm_policy_key(HKEY_LOCAL_MACHINE,
265                                    policy::kRegistryChromePolicyKey, KEY_READ);
266  if (hklm_policy_key.ReadValueDW(key_name.c_str(), &value) == ERROR_SUCCESS) {
267    *breakpad_enabled = value != 0;
268    return true;
269  }
270
271  base::win::RegKey hkcu_policy_key(HKEY_CURRENT_USER,
272                                    policy::kRegistryChromePolicyKey, KEY_READ);
273  if (hkcu_policy_key.ReadValueDW(key_name.c_str(), &value) == ERROR_SUCCESS) {
274    *breakpad_enabled = value != 0;
275    return true;
276  }
277
278  return false;
279}
280#endif  // defined(OS_WIN)
281
282#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS)
283void ChromeBreakpadClient::GetProductNameAndVersion(std::string* product_name,
284                                                    std::string* version) {
285  DCHECK(product_name);
286  DCHECK(version);
287#if defined(OS_ANDROID)
288  *product_name = "Chrome_Android";
289#elif defined(OS_CHROMEOS)
290  *product_name = "Chrome_ChromeOS";
291#else  // OS_LINUX
292#if !defined(ADDRESS_SANITIZER)
293  *product_name = "Chrome_Linux";
294#else
295  *product_name = "Chrome_Linux_ASan";
296#endif
297#endif
298
299  *version = PRODUCT_VERSION;
300}
301
302base::FilePath ChromeBreakpadClient::GetReporterLogFilename() {
303  return base::FilePath(CrashUploadList::kReporterLogFilename);
304}
305#endif
306
307bool ChromeBreakpadClient::GetCrashDumpLocation(base::FilePath* crash_dir) {
308  // By setting the BREAKPAD_DUMP_LOCATION environment variable, an alternate
309  // location to write breakpad crash dumps can be set.
310  scoped_ptr<base::Environment> env(base::Environment::Create());
311  std::string alternate_crash_dump_location;
312  if (env->GetVar("BREAKPAD_DUMP_LOCATION", &alternate_crash_dump_location)) {
313    base::FilePath crash_dumps_dir_path =
314        base::FilePath::FromUTF8Unsafe(alternate_crash_dump_location);
315    PathService::Override(chrome::DIR_CRASH_DUMPS, crash_dumps_dir_path);
316  }
317
318  return PathService::Get(chrome::DIR_CRASH_DUMPS, crash_dir);
319}
320
321size_t ChromeBreakpadClient::RegisterCrashKeys() {
322  // Note: This is not called on Windows because Breakpad is initialized in the
323  // EXE module, but code that uses crash keys is in the DLL module.
324  // RegisterChromeCrashKeys() will be called after the DLL is loaded.
325  return crash_keys::RegisterChromeCrashKeys();
326}
327
328bool ChromeBreakpadClient::IsRunningUnattended() {
329  scoped_ptr<base::Environment> env(base::Environment::Create());
330  return env->HasVar(env_vars::kHeadless);
331}
332
333bool ChromeBreakpadClient::GetCollectStatsConsent() {
334#if defined(GOOGLE_CHROME_BUILD)
335  bool is_official_chrome_build = true;
336#else
337  bool is_official_chrome_build = false;
338#endif
339
340#if defined(OS_CHROMEOS)
341  bool is_guest_session = CommandLine::ForCurrentProcess()->HasSwitch(
342      chromeos::switches::kGuestSession);
343  bool is_stable_channel =
344      chrome::VersionInfo::GetChannel() == chrome::VersionInfo::CHANNEL_STABLE;
345
346  if (is_guest_session && is_stable_channel)
347    return false;
348#endif  // defined(OS_CHROMEOS)
349
350#if defined(OS_ANDROID)
351  // TODO(jcivelli): we should not initialize the crash-reporter when it was not
352  // enabled. Right now if it is disabled we still generate the minidumps but we
353  // do not upload them.
354  return is_official_chrome_build;
355#else  // !defined(OS_ANDROID)
356  return is_official_chrome_build &&
357      GoogleUpdateSettings::GetCollectStatsConsent();
358#endif  // defined(OS_ANDROID)
359}
360
361#if defined(OS_ANDROID)
362int ChromeBreakpadClient::GetAndroidMinidumpDescriptor() {
363  return kAndroidMinidumpDescriptor;
364}
365#endif
366
367bool ChromeBreakpadClient::EnableBreakpadForProcess(
368    const std::string& process_type) {
369  return process_type == switches::kRendererProcess ||
370         process_type == switches::kPluginProcess ||
371         process_type == switches::kPpapiPluginProcess ||
372         process_type == switches::kZygoteProcess ||
373         process_type == switches::kGpuProcess;
374}
375
376}  // namespace chrome
377