// Copyright 2013 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 "chrome/app/chrome_breakpad_client.h" #include "base/atomicops.h" #include "base/command_line.h" #include "base/environment.h" #include "base/files/file_path.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/path_service.h" #include "base/strings/safe_sprintf.h" #include "base/strings/string_split.h" #include "base/strings/utf_string_conversions.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_result_codes.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/crash_keys.h" #include "chrome/common/env_vars.h" #include "chrome/installer/util/google_update_settings.h" #if defined(OS_WIN) #include <windows.h> #include "base/file_version_info.h" #include "base/win/registry.h" #include "chrome/installer/util/google_chrome_sxs_distribution.h" #include "chrome/installer/util/install_util.h" #include "chrome/installer/util/util_constants.h" #include "policy/policy_constants.h" #endif #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) #include "chrome/browser/crash_upload_list.h" #include "chrome/common/chrome_version_info_posix.h" #endif #if defined(OS_POSIX) #include "base/debug/dump_without_crashing.h" #endif #if defined(OS_ANDROID) #include "chrome/common/descriptors_android.h" #endif #if defined(OS_CHROMEOS) #include "chrome/common/chrome_version_info.h" #include "chromeos/chromeos_switches.h" #endif namespace chrome { namespace { #if defined(OS_WIN) // This is the minimum version of google update that is required for deferred // crash uploads to work. const char kMinUpdateVersion[] = "1.3.21.115"; // The value name prefix will be of the form {chrome-version}-{pid}-{timestamp} // (i.e., "#####.#####.#####.#####-########-########") which easily fits into a // 63 character buffer. const char kBrowserCrashDumpPrefixTemplate[] = "%s-%08x-%08x"; const size_t kBrowserCrashDumpPrefixLength = 63; char g_browser_crash_dump_prefix[kBrowserCrashDumpPrefixLength + 1] = {}; // These registry key to which we'll write a value for each crash dump attempt. HKEY g_browser_crash_dump_regkey = NULL; // A atomic counter to make each crash dump value name unique. base::subtle::Atomic32 g_browser_crash_dump_count = 0; #endif } // namespace ChromeBreakpadClient::ChromeBreakpadClient() {} ChromeBreakpadClient::~ChromeBreakpadClient() {} void ChromeBreakpadClient::SetClientID(const std::string& client_id) { crash_keys::SetClientID(client_id); } #if defined(OS_WIN) bool ChromeBreakpadClient::GetAlternativeCrashDumpLocation( base::FilePath* crash_dir) { // By setting the BREAKPAD_DUMP_LOCATION environment variable, an alternate // location to write breakpad crash dumps can be set. scoped_ptr<base::Environment> env(base::Environment::Create()); std::string alternate_crash_dump_location; if (env->GetVar("BREAKPAD_DUMP_LOCATION", &alternate_crash_dump_location)) { *crash_dir = base::FilePath::FromUTF8Unsafe(alternate_crash_dump_location); return true; } return false; } void ChromeBreakpadClient::GetProductNameAndVersion( const base::FilePath& exe_path, base::string16* product_name, base::string16* version, base::string16* special_build, base::string16* channel_name) { DCHECK(product_name); DCHECK(version); DCHECK(special_build); DCHECK(channel_name); scoped_ptr<FileVersionInfo> version_info( FileVersionInfo::CreateFileVersionInfo(exe_path)); if (version_info.get()) { // Get the information from the file. *version = version_info->product_version(); if (!version_info->is_official_build()) version->append(base::ASCIIToUTF16("-devel")); *product_name = version_info->product_short_name(); *special_build = version_info->special_build(); } else { // No version info found. Make up the values. *product_name = base::ASCIIToUTF16("Chrome"); *version = base::ASCIIToUTF16("0.0.0.0-devel"); } GoogleUpdateSettings::GetChromeChannelAndModifiers( !GetIsPerUserInstall(exe_path), channel_name); } bool ChromeBreakpadClient::ShouldShowRestartDialog(base::string16* title, base::string16* message, bool* is_rtl_locale) { scoped_ptr<base::Environment> env(base::Environment::Create()); if (!env->HasVar(env_vars::kShowRestart) || !env->HasVar(env_vars::kRestartInfo) || env->HasVar(env_vars::kMetroConnected)) { return false; } std::string restart_info; env->GetVar(env_vars::kRestartInfo, &restart_info); // The CHROME_RESTART var contains the dialog strings separated by '|'. // See ChromeBrowserMainPartsWin::PrepareRestartOnCrashEnviroment() // for details. std::vector<std::string> dlg_strings; base::SplitString(restart_info, '|', &dlg_strings); if (dlg_strings.size() < 3) return false; *title = base::UTF8ToUTF16(dlg_strings[0]); *message = base::UTF8ToUTF16(dlg_strings[1]); *is_rtl_locale = dlg_strings[2] == env_vars::kRtlLocale; return true; } bool ChromeBreakpadClient::AboutToRestart() { scoped_ptr<base::Environment> env(base::Environment::Create()); if (!env->HasVar(env_vars::kRestartInfo)) return false; env->SetVar(env_vars::kShowRestart, "1"); return true; } bool ChromeBreakpadClient::GetDeferredUploadsSupported( bool is_per_user_install) { Version update_version = GoogleUpdateSettings::GetGoogleUpdateVersion( !is_per_user_install); if (!update_version.IsValid() || update_version.IsOlderThan(std::string(kMinUpdateVersion))) return false; return true; } bool ChromeBreakpadClient::GetIsPerUserInstall(const base::FilePath& exe_path) { return InstallUtil::IsPerUserInstall(exe_path.value().c_str()); } bool ChromeBreakpadClient::GetShouldDumpLargerDumps(bool is_per_user_install) { base::string16 channel_name = GoogleUpdateSettings::GetChromeChannel(!is_per_user_install); // Capture more detail in crash dumps for beta and dev channel builds. return (channel_name == installer::kChromeChannelDev || channel_name == installer::kChromeChannelBeta || channel_name == GoogleChromeSxSDistribution::ChannelName()); } int ChromeBreakpadClient::GetResultCodeRespawnFailed() { return chrome::RESULT_CODE_RESPAWN_FAILED; } void ChromeBreakpadClient::InitBrowserCrashDumpsRegKey() { DCHECK(g_browser_crash_dump_regkey == NULL); base::win::RegKey regkey; if (regkey.Create(HKEY_CURRENT_USER, chrome::kBrowserCrashDumpAttemptsRegistryPath, KEY_ALL_ACCESS) != ERROR_SUCCESS) { return; } // We use the current process id and the current tick count as a (hopefully) // unique combination for the crash dump value. There's a small chance that // across a reboot we might have a crash dump signal written, and the next // browser process might have the same process id and tick count, but crash // before consuming the signal (overwriting the signal with an identical one). // For now, we're willing to live with that risk. int length = base::strings::SafeSPrintf(g_browser_crash_dump_prefix, kBrowserCrashDumpPrefixTemplate, chrome::kChromeVersion, ::GetCurrentProcessId(), ::GetTickCount()); if (length <= 0) { NOTREACHED(); g_browser_crash_dump_prefix[0] = '\0'; return; } // Hold the registry key in a global for update on crash dump. g_browser_crash_dump_regkey = regkey.Take(); } void ChromeBreakpadClient::RecordCrashDumpAttempt(bool is_real_crash) { // If we're not a browser (or the registry is unavailable to us for some // reason) then there's nothing to do. if (g_browser_crash_dump_regkey == NULL) return; // Generate the final value name we'll use (appends the crash number to the // base value name). const size_t kMaxValueSize = 2 * kBrowserCrashDumpPrefixLength; char value_name[kMaxValueSize + 1] = {}; int length = base::strings::SafeSPrintf( value_name, "%s-%x", g_browser_crash_dump_prefix, base::subtle::NoBarrier_AtomicIncrement(&g_browser_crash_dump_count, 1)); if (length > 0) { DWORD value_dword = is_real_crash ? 1 : 0; ::RegSetValueExA(g_browser_crash_dump_regkey, value_name, 0, REG_DWORD, reinterpret_cast<BYTE*>(&value_dword), sizeof(value_dword)); } } bool ChromeBreakpadClient::ReportingIsEnforcedByPolicy(bool* breakpad_enabled) { // Determine whether configuration management allows loading the crash reporter. // Since the configuration management infrastructure is not initialized at this // point, we read the corresponding registry key directly. The return status // indicates whether policy data was successfully read. If it is true, // |breakpad_enabled| contains the value set by policy. base::string16 key_name = base::UTF8ToUTF16(policy::key::kMetricsReportingEnabled); DWORD value = 0; base::win::RegKey hklm_policy_key(HKEY_LOCAL_MACHINE, policy::kRegistryChromePolicyKey, KEY_READ); if (hklm_policy_key.ReadValueDW(key_name.c_str(), &value) == ERROR_SUCCESS) { *breakpad_enabled = value != 0; return true; } base::win::RegKey hkcu_policy_key(HKEY_CURRENT_USER, policy::kRegistryChromePolicyKey, KEY_READ); if (hkcu_policy_key.ReadValueDW(key_name.c_str(), &value) == ERROR_SUCCESS) { *breakpad_enabled = value != 0; return true; } return false; } #endif // defined(OS_WIN) #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) void ChromeBreakpadClient::GetProductNameAndVersion(std::string* product_name, std::string* version) { DCHECK(product_name); DCHECK(version); #if defined(OS_ANDROID) *product_name = "Chrome_Android"; #elif defined(OS_CHROMEOS) *product_name = "Chrome_ChromeOS"; #else // OS_LINUX #if !defined(ADDRESS_SANITIZER) *product_name = "Chrome_Linux"; #else *product_name = "Chrome_Linux_ASan"; #endif #endif *version = PRODUCT_VERSION; } base::FilePath ChromeBreakpadClient::GetReporterLogFilename() { return base::FilePath(CrashUploadList::kReporterLogFilename); } #endif bool ChromeBreakpadClient::GetCrashDumpLocation(base::FilePath* crash_dir) { // By setting the BREAKPAD_DUMP_LOCATION environment variable, an alternate // location to write breakpad crash dumps can be set. scoped_ptr<base::Environment> env(base::Environment::Create()); std::string alternate_crash_dump_location; if (env->GetVar("BREAKPAD_DUMP_LOCATION", &alternate_crash_dump_location)) { base::FilePath crash_dumps_dir_path = base::FilePath::FromUTF8Unsafe(alternate_crash_dump_location); PathService::Override(chrome::DIR_CRASH_DUMPS, crash_dumps_dir_path); } return PathService::Get(chrome::DIR_CRASH_DUMPS, crash_dir); } size_t ChromeBreakpadClient::RegisterCrashKeys() { // Note: This is not called on Windows because Breakpad is initialized in the // EXE module, but code that uses crash keys is in the DLL module. // RegisterChromeCrashKeys() will be called after the DLL is loaded. return crash_keys::RegisterChromeCrashKeys(); } bool ChromeBreakpadClient::IsRunningUnattended() { scoped_ptr<base::Environment> env(base::Environment::Create()); return env->HasVar(env_vars::kHeadless); } bool ChromeBreakpadClient::GetCollectStatsConsent() { #if defined(GOOGLE_CHROME_BUILD) bool is_official_chrome_build = true; #else bool is_official_chrome_build = false; #endif #if defined(OS_CHROMEOS) bool is_guest_session = CommandLine::ForCurrentProcess()->HasSwitch( chromeos::switches::kGuestSession); bool is_stable_channel = chrome::VersionInfo::GetChannel() == chrome::VersionInfo::CHANNEL_STABLE; if (is_guest_session && is_stable_channel) return false; #endif // defined(OS_CHROMEOS) #if defined(OS_ANDROID) // TODO(jcivelli): we should not initialize the crash-reporter when it was not // enabled. Right now if it is disabled we still generate the minidumps but we // do not upload them. return is_official_chrome_build; #else // !defined(OS_ANDROID) return is_official_chrome_build && GoogleUpdateSettings::GetCollectStatsConsent(); #endif // defined(OS_ANDROID) } #if defined(OS_ANDROID) int ChromeBreakpadClient::GetAndroidMinidumpDescriptor() { return kAndroidMinidumpDescriptor; } #endif bool ChromeBreakpadClient::EnableBreakpadForProcess( const std::string& process_type) { return process_type == switches::kRendererProcess || process_type == switches::kPluginProcess || process_type == switches::kPpapiPluginProcess || process_type == switches::kZygoteProcess || process_type == switches::kGpuProcess; } } // namespace chrome