| // Copyright (c) 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/common/crash_keys.h" |
| |
| #include "base/command_line.h" |
| #include "base/format_macros.h" |
| #include "base/logging.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/installer/util/google_update_settings.h" |
| |
| #if defined(OS_MACOSX) |
| #include "breakpad/src/common/simple_string_dictionary.h" |
| #elif defined(OS_WIN) |
| #include "breakpad/src/client/windows/common/ipc_protocol.h" |
| #elif defined(OS_CHROMEOS) |
| #include "chrome/common/chrome_switches.h" |
| #include "gpu/command_buffer/service/gpu_switches.h" |
| #include "ui/gl/gl_switches.h" |
| #endif |
| |
| namespace crash_keys { |
| |
| // A small crash key, guaranteed to never be split into multiple pieces. |
| const size_t kSmallSize = 63; |
| |
| // A medium crash key, which will be chunked on certain platforms but not |
| // others. Guaranteed to never be more than four chunks. |
| const size_t kMediumSize = kSmallSize * 4; |
| |
| // A large crash key, which will be chunked on all platforms. This should be |
| // used sparingly. |
| const size_t kLargeSize = kSmallSize * 16; |
| |
| // The maximum lengths specified by breakpad include the trailing NULL, so |
| // the actual length of the string is one less. |
| #if defined(OS_MACOSX) |
| static const size_t kSingleChunkLength = |
| google_breakpad::SimpleStringDictionary::value_size - 1; |
| #elif defined(OS_WIN) |
| static const size_t kSingleChunkLength = |
| google_breakpad::CustomInfoEntry::kValueMaxLength - 1; |
| #else |
| static const size_t kSingleChunkLength = 63; |
| #endif |
| |
| // Guarantees for crash key sizes. |
| COMPILE_ASSERT(kSmallSize <= kSingleChunkLength, |
| crash_key_chunk_size_too_small); |
| #if defined(OS_MACOSX) |
| COMPILE_ASSERT(kMediumSize <= kSingleChunkLength, |
| mac_has_medium_size_crash_key_chunks); |
| #endif |
| |
| const char kClientID[] = "guid"; |
| |
| const char kChannel[] = "channel"; |
| |
| const char kActiveURL[] = "url-chunk"; |
| |
| const char kSwitch[] = "switch-%" PRIuS; |
| const char kNumSwitches[] = "num-switches"; |
| |
| const char kNumVariations[] = "num-experiments"; |
| const char kVariations[] = "variations"; |
| |
| const char kExtensionID[] = "extension-%" PRIuS; |
| const char kNumExtensionsCount[] = "num-extensions"; |
| |
| const char kNumberOfViews[] = "num-views"; |
| |
| const char kShutdownType[] = "shutdown-type"; |
| |
| #if !defined(OS_ANDROID) |
| const char kGPUVendorID[] = "gpu-venid"; |
| const char kGPUDeviceID[] = "gpu-devid"; |
| #endif |
| const char kGPUDriverVersion[] = "gpu-driver"; |
| const char kGPUPixelShaderVersion[] = "gpu-psver"; |
| const char kGPUVertexShaderVersion[] = "gpu-vsver"; |
| #if defined(OS_MACOSX) |
| const char kGPUGLVersion[] = "gpu-glver"; |
| #elif defined(OS_POSIX) |
| const char kGPUVendor[] = "gpu-gl-vendor"; |
| const char kGPURenderer[] = "gpu-gl-renderer"; |
| #endif |
| |
| const char kPrinterInfo[] = "prn-info-%" PRIuS; |
| |
| #if defined(OS_CHROMEOS) |
| const char kNumberOfUsers[] = "num-users"; |
| #endif |
| |
| #if defined(OS_MACOSX) |
| namespace mac { |
| |
| const char kFirstNSException[] = "firstexception"; |
| const char kFirstNSExceptionTrace[] = "firstexception_bt"; |
| |
| const char kLastNSException[] = "lastexception"; |
| const char kLastNSExceptionTrace[] = "lastexception_bt"; |
| |
| const char kNSException[] = "nsexception"; |
| const char kNSExceptionTrace[] = "nsexception_bt"; |
| |
| const char kSendAction[] = "sendaction"; |
| |
| const char kZombie[] = "zombie"; |
| const char kZombieTrace[] = "zombie_dealloc_bt"; |
| |
| } // namespace mac |
| #endif |
| |
| size_t RegisterChromeCrashKeys() { |
| // The following keys may be chunked by the underlying crash logging system, |
| // but ultimately constitute a single key-value pair. |
| base::debug::CrashKey fixed_keys[] = { |
| { kClientID, kSmallSize }, |
| { kChannel, kSmallSize }, |
| { kActiveURL, kLargeSize }, |
| { kNumSwitches, kSmallSize }, |
| { kNumVariations, kSmallSize }, |
| { kVariations, kLargeSize }, |
| { kNumExtensionsCount, kSmallSize }, |
| { kNumberOfViews, kSmallSize }, |
| { kShutdownType, kSmallSize }, |
| #if !defined(OS_ANDROID) |
| { kGPUVendorID, kSmallSize }, |
| { kGPUDeviceID, kSmallSize }, |
| #endif |
| { kGPUDriverVersion, kSmallSize }, |
| { kGPUPixelShaderVersion, kSmallSize }, |
| { kGPUVertexShaderVersion, kSmallSize }, |
| #if defined(OS_MACOSX) |
| { kGPUGLVersion, kSmallSize }, |
| #elif defined(OS_POSIX) |
| { kGPUVendor, kSmallSize }, |
| { kGPURenderer, kSmallSize }, |
| #endif |
| |
| // base/: |
| { "dm-usage", kSmallSize }, |
| // content/: |
| { "ppapi_path", kMediumSize }, |
| { "subresource_url", kLargeSize }, |
| #if defined(OS_CHROMEOS) |
| { kNumberOfUsers, kSmallSize }, |
| #endif |
| #if defined(OS_MACOSX) |
| { mac::kFirstNSException, kMediumSize }, |
| { mac::kFirstNSExceptionTrace, kMediumSize }, |
| { mac::kLastNSException, kMediumSize }, |
| { mac::kLastNSExceptionTrace, kMediumSize }, |
| { mac::kNSException, kMediumSize }, |
| { mac::kNSExceptionTrace, kMediumSize }, |
| { mac::kSendAction, kMediumSize }, |
| { mac::kZombie, kMediumSize }, |
| { mac::kZombieTrace, kMediumSize }, |
| // content/: |
| { "channel_error_bt", kMediumSize }, |
| { "remove_route_bt", kMediumSize }, |
| { "rwhvm_window", kMediumSize }, |
| // media/: |
| { "VideoCaptureDeviceQTKit", kSmallSize }, |
| #endif |
| }; |
| |
| // This dynamic set of keys is used for sets of key value pairs when gathering |
| // a collection of data, like command line switches or extension IDs. |
| std::vector<base::debug::CrashKey> keys( |
| fixed_keys, fixed_keys + arraysize(fixed_keys)); |
| |
| // Register the switches. |
| { |
| // The fixed_keys names are string constants. Use static storage for |
| // formatted key names as well, since they will persist for the duration of |
| // the program. |
| static char formatted_keys[kSwitchesMaxCount][sizeof(kSwitch) + 1] = |
| {{ 0 }}; |
| const size_t formatted_key_len = sizeof(formatted_keys[0]); |
| for (size_t i = 0; i < kSwitchesMaxCount; ++i) { |
| // Name the keys using 1-based indexing. |
| int n = base::snprintf( |
| formatted_keys[i], formatted_key_len, kSwitch, i + 1); |
| DCHECK_GT(n, 0); |
| base::debug::CrashKey crash_key = { formatted_keys[i], kSmallSize }; |
| keys.push_back(crash_key); |
| } |
| } |
| |
| // Register the extension IDs. |
| { |
| static char formatted_keys[kExtensionIDMaxCount][sizeof(kExtensionID) + 1] = |
| {{ 0 }}; |
| const size_t formatted_key_len = sizeof(formatted_keys[0]); |
| for (size_t i = 0; i < kExtensionIDMaxCount; ++i) { |
| int n = base::snprintf( |
| formatted_keys[i], formatted_key_len, kExtensionID, i + 1); |
| DCHECK_GT(n, 0); |
| base::debug::CrashKey crash_key = { formatted_keys[i], kSmallSize }; |
| keys.push_back(crash_key); |
| } |
| } |
| |
| // Register the printer info. |
| { |
| static char formatted_keys[kPrinterInfoCount][sizeof(kPrinterInfo) + 1] = |
| {{ 0 }}; |
| const size_t formatted_key_len = sizeof(formatted_keys[0]); |
| for (size_t i = 0; i < kPrinterInfoCount; ++i) { |
| // Key names are 1-indexed. |
| int n = base::snprintf( |
| formatted_keys[i], formatted_key_len, kPrinterInfo, i + 1); |
| DCHECK_GT(n, 0); |
| base::debug::CrashKey crash_key = { formatted_keys[i], kSmallSize }; |
| keys.push_back(crash_key); |
| } |
| } |
| |
| return base::debug::InitCrashKeys(&keys.at(0), keys.size(), |
| kSingleChunkLength); |
| } |
| |
| void SetClientID(const std::string& client_id) { |
| std::string guid(client_id); |
| // Remove all instance of '-' char from the GUID. So BCD-WXY becomes BCDWXY. |
| ReplaceSubstringsAfterOffset(&guid, 0, "-", ""); |
| if (guid.empty()) |
| return; |
| |
| base::debug::SetCrashKeyValue(kClientID, guid); |
| GoogleUpdateSettings::SetMetricsId(guid); |
| } |
| |
| static bool IsBoringSwitch(const std::string& flag) { |
| #if defined(OS_WIN) |
| return StartsWithASCII(flag, "--channel=", true) || |
| |
| // No point to including this since we already have a ptype field. |
| StartsWithASCII(flag, "--type=", true) || |
| |
| // Not particularly interesting |
| StartsWithASCII(flag, "--flash-broker=", true) || |
| |
| // Just about everything has this, don't bother. |
| StartsWithASCII(flag, "/prefetch:", true) || |
| |
| // We handle the plugin path separately since it is usually too big |
| // to fit in the switches (limited to 63 characters). |
| StartsWithASCII(flag, "--plugin-path=", true) || |
| |
| // This is too big so we end up truncating it anyway. |
| StartsWithASCII(flag, "--force-fieldtrials=", true) || |
| |
| // These surround the flags that were added by about:flags, it lets |
| // you distinguish which flags were added manually via the command |
| // line versus those added through about:flags. For the most part |
| // we don't care how an option was enabled, so we strip these. |
| // (If you need to know can always look at the PEB). |
| flag == "--flag-switches-begin" || |
| flag == "--flag-switches-end"; |
| #elif defined(OS_CHROMEOS) |
| static const char* kIgnoreSwitches[] = { |
| ::switches::kEnableCompositingForFixedPosition, |
| ::switches::kEnableImplSidePainting, |
| ::switches::kEnableLogging, |
| ::switches::kFlagSwitchesBegin, |
| ::switches::kFlagSwitchesEnd, |
| ::switches::kLoggingLevel, |
| ::switches::kPpapiFlashArgs, |
| ::switches::kPpapiFlashPath, |
| ::switches::kRegisterPepperPlugins, |
| ::switches::kUIPrioritizeInGpuProcess, |
| ::switches::kUseGL, |
| ::switches::kUserDataDir, |
| ::switches::kV, |
| ::switches::kVModule, |
| // Cros/CC flgas are specified as raw strings to avoid dependency. |
| "ash-default-wallpaper-large", |
| "ash-default-wallpaper-small", |
| "ash-guest-wallpaper-large", |
| "ash-guest-wallpaper-small", |
| "enterprise-enable-forced-re-enrollment", |
| "enterprise-enrollment-initial-modulus", |
| "enterprise-enrollment-modulus-limit", |
| "login-profile", |
| "login-user", |
| "max-tiles-for-interest-area", |
| "max-unused-resource-memory-usage-percentage", |
| "termination-message-file", |
| "use-cras", |
| }; |
| if (!StartsWithASCII(flag, "--", true)) |
| return false; |
| std::size_t end = flag.find("="); |
| int len = (end == std::string::npos) ? flag.length() - 2 : end - 2; |
| for (size_t i = 0; i < arraysize(kIgnoreSwitches); ++i) { |
| if (flag.compare(2, len, kIgnoreSwitches[i]) == 0) |
| return true; |
| } |
| return false; |
| #else |
| return false; |
| #endif |
| } |
| |
| void SetSwitchesFromCommandLine(const CommandLine* command_line) { |
| DCHECK(command_line); |
| if (!command_line) |
| return; |
| |
| const CommandLine::StringVector& argv = command_line->argv(); |
| |
| // Set the number of switches in case size > kNumSwitches. |
| base::debug::SetCrashKeyValue(kNumSwitches, |
| base::StringPrintf("%" PRIuS, argv.size() - 1)); |
| |
| size_t key_i = 1; // Key names are 1-indexed. |
| |
| // Go through the argv, skipping the exec path. |
| for (size_t i = 1; i < argv.size(); ++i) { |
| #if defined(OS_WIN) |
| std::string switch_str = base::WideToUTF8(argv[i]); |
| #else |
| std::string switch_str = argv[i]; |
| #endif |
| |
| // Skip uninteresting switches. |
| if (IsBoringSwitch(switch_str)) |
| continue; |
| |
| // Stop if there are too many switches. |
| if (i > crash_keys::kSwitchesMaxCount) |
| break; |
| |
| std::string key = base::StringPrintf(kSwitch, key_i++); |
| base::debug::SetCrashKeyValue(key, switch_str); |
| } |
| |
| // Clear any remaining switches. |
| for (; key_i <= kSwitchesMaxCount; ++key_i) { |
| base::debug::ClearCrashKey(base::StringPrintf(kSwitch, key_i)); |
| } |
| } |
| |
| void SetVariationsList(const std::vector<std::string>& variations) { |
| base::debug::SetCrashKeyValue(kNumVariations, |
| base::StringPrintf("%" PRIuS, variations.size())); |
| |
| std::string variations_string; |
| variations_string.reserve(kLargeSize); |
| |
| for (size_t i = 0; i < variations.size(); ++i) { |
| const std::string& variation = variations[i]; |
| // Do not truncate an individual experiment. |
| if (variations_string.size() + variation.size() >= kLargeSize) |
| break; |
| variations_string += variation; |
| variations_string += ","; |
| } |
| |
| base::debug::SetCrashKeyValue(kVariations, variations_string); |
| } |
| |
| void SetActiveExtensions(const std::set<std::string>& extensions) { |
| base::debug::SetCrashKeyValue(kNumExtensionsCount, |
| base::StringPrintf("%" PRIuS, extensions.size())); |
| |
| std::set<std::string>::const_iterator it = extensions.begin(); |
| for (size_t i = 0; i < kExtensionIDMaxCount; ++i) { |
| std::string key = base::StringPrintf(kExtensionID, i + 1); |
| if (it == extensions.end()) { |
| base::debug::ClearCrashKey(key); |
| } else { |
| base::debug::SetCrashKeyValue(key, *it); |
| ++it; |
| } |
| } |
| } |
| |
| ScopedPrinterInfo::ScopedPrinterInfo(const base::StringPiece& data) { |
| std::vector<std::string> info; |
| base::SplitString(data.as_string(), ';', &info); |
| for (size_t i = 0; i < kPrinterInfoCount; ++i) { |
| std::string key = base::StringPrintf(kPrinterInfo, i + 1); |
| std::string value; |
| if (i < info.size()) |
| value = info[i]; |
| base::debug::SetCrashKeyValue(key, value); |
| } |
| } |
| |
| ScopedPrinterInfo::~ScopedPrinterInfo() { |
| for (size_t i = 0; i < kPrinterInfoCount; ++i) { |
| std::string key = base::StringPrintf(kPrinterInfo, i + 1); |
| base::debug::ClearCrashKey(key); |
| } |
| } |
| |
| } // namespace crash_keys |