Merge branch 'rewrite-crash-reporter' into merge-crash-reporter
BUG:22773266
This commit is contained in:
commit
5cab52906c
42 changed files with 7478 additions and 0 deletions
1
crash_reporter/.project_alias
Normal file
1
crash_reporter/.project_alias
Normal file
|
@ -0,0 +1 @@
|
|||
crash
|
6
crash_reporter/99-crash-reporter.rules
Normal file
6
crash_reporter/99-crash-reporter.rules
Normal file
|
@ -0,0 +1,6 @@
|
|||
ACTION=="change", SUBSYSTEM=="drm", KERNEL=="card0", ENV{ERROR}=="1", RUN+="/sbin/crash_reporter --udev=KERNEL=card0:SUBSYSTEM=drm:ACTION=change"
|
||||
# For detecting cypress trackpad issue. Passing into crash_reporter SUBSYSTEM=i2c-cyapa since crash_reporter does not handle DRIVER string.
|
||||
ACTION=="change", SUBSYSTEM=="i2c", DRIVER=="cyapa", ENV{ERROR}=="1", RUN+="/sbin/crash_reporter --udev=SUBSYSTEM=i2c-cyapa:ACTION=change"
|
||||
# For detecting Atmel trackpad/touchscreen issue. Passing into crash_reporter SUBSYSTEM=i2c-atmel_mxt_ts since crash_reporter does not handle DRIVER string.
|
||||
ACTION=="change", SUBSYSTEM=="i2c", DRIVER=="atmel_mxt_ts", ENV{ERROR}=="1", RUN+="/sbin/crash_reporter --udev=SUBSYSTEM=i2c-atmel_mxt_ts:ACTION=change"
|
||||
ACTION=="add", SUBSYSTEM=="devcoredump", RUN+="/sbin/crash_reporter --udev=SUBSYSTEM=devcoredump:ACTION=add:KERNEL_NUMBER=%n"
|
2
crash_reporter/OWNERS
Normal file
2
crash_reporter/OWNERS
Normal file
|
@ -0,0 +1,2 @@
|
|||
set noparent
|
||||
vapier@chromium.org
|
31
crash_reporter/TEST_WARNING
Normal file
31
crash_reporter/TEST_WARNING
Normal file
|
@ -0,0 +1,31 @@
|
|||
Apr 31 25:25:25 localhost kernel: [117959.226729] [<ffffffff810e16bf>] do_vfs_ioctl+0x469/0x4b3
|
||||
Apr 31 25:25:25 localhost kernel: [117959.226738] [<ffffffff810d3117>] ? fsnotify_access+0x58/0x60
|
||||
Apr 31 25:25:25 localhost kernel: [117959.226747] [<ffffffff810d3791>] ? vfs_read+0xad/0xd7
|
||||
Apr 31 25:25:25 localhost kernel: [117959.226756] [<ffffffff810e175f>] sys_ioctl+0x56/0x7b
|
||||
Apr 31 25:25:25 localhost kernel: [117959.226765] [<ffffffff810d37fe>] ? sys_read+0x43/0x73
|
||||
Apr 31 25:25:25 localhost kernel: [117959.226774] [<ffffffff8146b7d2>] system_call_fastpath+0x16/0x1b
|
||||
Apr 31 25:25:25 localhost kernel: [117959.226782] ---[ end trace f16822cad7406cec ]---
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231085] ------------[ cut here ]------------
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231100] WARNING: at /mnt/host/source/src/third_party/kernel/files/drivers/gpu/drm/i915/intel_dp.c:351 intel_dp_check_edp+0x6b/0xb9()
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231113] Hardware name: Link
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231117] eDP powered off while attempting aux channel communication.
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231240] Pid: 10508, comm: X Tainted: G WC 3.4.0 #1
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231247] Call Trace:
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231393] [<ffffffff810d3117>] ? fsnotify_access+0x58/0x60
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231402] [<ffffffff810d3791>] ? vfs_read+0xad/0xd7
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231411] [<ffffffff810e175f>] sys_ioctl+0x56/0x7b
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231420] [<ffffffff810d37fe>] ? sys_read+0x43/0x73
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231431] [<ffffffff8146b7d2>] system_call_fastpath+0x16/0x1b
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231439] ---[ end trace f16822cad7406ced ]---
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231450] ------------[ cut here ]------------
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231458] BARNING: at /mnt/host/source/src/third_party/kernel/files/drivers/gpu/drm/i915/intel_dp.c:351 intel_dp_check_edp+0x6b/0xb9()
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231458] ("BARNING" above is intentional)
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231471] Hardware name: Link
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231475] eDP powered off while attempting aux channel communication.
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231482] Modules linked in: nls_iso8859_1 nls_cp437 vfat fat rfcomm i2c_dev ath9k_btcoex snd_hda_codec_hdmi snd_hda_codec_ca0132 mac80211 snd_hda_intel ath9k_common_btcoex snd_hda_codec ath9k_hw_btcoex aesni_intel cryptd snd_hwdep ath snd_pcm aes_x86_64 isl29018(C) memconsole snd_timer snd_page_alloc industrialio(C) cfg80211 rtc_cmos nm10_gpio zram(C) zsmalloc(C) lzo_decompress lzo_compress fuse nf_conntrack_ipv6 nf_defrag_ipv6 ip6table_filter ip6_tables xt_mark option usb_wwan cdc_ether usbnet ath3k btusb bluetooth uvcvideo videobuf2_core videodev videobuf2_vmalloc videobuf2_memops joydev
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231588] Pid: 10508, comm: X Tainted: G WC 3.4.0 #1
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231595] Call Trace:
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231601] [<ffffffff8102a931>] warn_slowpath_common+0x83/0x9c
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231610] [<ffffffff8102a9ed>] warn_slowpath_fmt+0x46/0x48
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231620] [<ffffffff812af495>] intel_dp_check_edp+0x6b/0xb9
|
||||
Apr 31 25:25:25 localhost kernel: [117959.231629] [<ffffffff8102a9ed>] ? warn_slowpath_fmt+
|
335
crash_reporter/chrome_collector.cc
Normal file
335
crash_reporter/chrome_collector.cc
Normal file
|
@ -0,0 +1,335 @@
|
|||
// Copyright (c) 2013 The Chromium OS 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 "crash-reporter/chrome_collector.h"
|
||||
|
||||
#include <pcrecpp.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <base/files/file_util.h>
|
||||
#include <base/logging.h>
|
||||
#include <base/strings/string_number_conversions.h>
|
||||
#include <base/strings/string_util.h>
|
||||
#include <chromeos/data_encoding.h>
|
||||
#include <chromeos/dbus/service_constants.h>
|
||||
#include <chromeos/process.h>
|
||||
#include <chromeos/syslog_logging.h>
|
||||
|
||||
using base::FilePath;
|
||||
using base::StringPrintf;
|
||||
|
||||
namespace {
|
||||
|
||||
const char kDefaultMinidumpName[] = "upload_file_minidump";
|
||||
|
||||
// Path to the gzip binary.
|
||||
const char kGzipPath[] = "/bin/gzip";
|
||||
|
||||
// Filenames for logs attached to crash reports. Also used as metadata keys.
|
||||
const char kChromeLogFilename[] = "chrome.txt";
|
||||
const char kGpuStateFilename[] = "i915_error_state.log.xz";
|
||||
|
||||
// From //net/crash/collector/collector.h
|
||||
const int kDefaultMaxUploadBytes = 1024 * 1024;
|
||||
|
||||
// Extract a string delimited by the given character, from the given offset
|
||||
// into a source string. Returns false if the string is zero-sized or no
|
||||
// delimiter was found.
|
||||
bool GetDelimitedString(const std::string &str, char ch, size_t offset,
|
||||
std::string *substr) {
|
||||
size_t at = str.find_first_of(ch, offset);
|
||||
if (at == std::string::npos || at == offset)
|
||||
return false;
|
||||
*substr = str.substr(offset, at - offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Gets the GPU's error state from debugd and writes it to |error_state_path|.
|
||||
// Returns true on success.
|
||||
bool GetDriErrorState(const FilePath &error_state_path,
|
||||
org::chromium::debugdProxy *proxy) {
|
||||
chromeos::ErrorPtr error;
|
||||
std::string error_state_str;
|
||||
|
||||
proxy->GetLog("i915_error_state", &error_state_str, &error);
|
||||
|
||||
if (error) {
|
||||
LOG(ERROR) << "Error calling D-Bus proxy call to interface "
|
||||
<< "'" << proxy->GetObjectPath().value() << "':"
|
||||
<< error->GetMessage();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (error_state_str == "<empty>")
|
||||
return false;
|
||||
|
||||
const char kBase64Header[] = "<base64>: ";
|
||||
const size_t kBase64HeaderLength = sizeof(kBase64Header) - 1;
|
||||
if (error_state_str.compare(0, kBase64HeaderLength, kBase64Header)) {
|
||||
LOG(ERROR) << "i915_error_state is missing base64 header";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string decoded_error_state;
|
||||
|
||||
if (!chromeos::data_encoding::Base64Decode(
|
||||
error_state_str.c_str() + kBase64HeaderLength,
|
||||
&decoded_error_state)) {
|
||||
LOG(ERROR) << "Could not decode i915_error_state";
|
||||
return false;
|
||||
}
|
||||
|
||||
int written = base::WriteFile(error_state_path,
|
||||
decoded_error_state.c_str(),
|
||||
decoded_error_state.length());
|
||||
if (written < 0 ||
|
||||
static_cast<size_t>(written) != decoded_error_state.length()) {
|
||||
LOG(ERROR) << "Could not write file " << error_state_path.value()
|
||||
<< " Written: " << written << " Len: "
|
||||
<< decoded_error_state.length();
|
||||
base::DeleteFile(error_state_path, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Gzip-compresses |path|, removes the original file, and returns the path of
|
||||
// the new file. On failure, the original file is left alone and an empty path
|
||||
// is returned.
|
||||
FilePath GzipFile(const FilePath& path) {
|
||||
chromeos::ProcessImpl proc;
|
||||
proc.AddArg(kGzipPath);
|
||||
proc.AddArg(path.value());
|
||||
const int res = proc.Run();
|
||||
if (res != 0) {
|
||||
LOG(ERROR) << "Failed to gzip " << path.value();
|
||||
return FilePath();
|
||||
}
|
||||
return path.AddExtension(".gz");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
ChromeCollector::ChromeCollector() : output_file_ptr_(stdout) {}
|
||||
|
||||
ChromeCollector::~ChromeCollector() {}
|
||||
|
||||
bool ChromeCollector::HandleCrash(const FilePath &file_path,
|
||||
const std::string &pid_string,
|
||||
const std::string &uid_string,
|
||||
const std::string &exe_name) {
|
||||
if (!is_feedback_allowed_function_())
|
||||
return true;
|
||||
|
||||
LOG(WARNING) << "Received crash notification for " << exe_name << "["
|
||||
<< pid_string << "] user " << uid_string << " (called directly)";
|
||||
|
||||
if (exe_name.find('/') != std::string::npos) {
|
||||
LOG(ERROR) << "exe_name contains illegal characters: " << exe_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
FilePath dir;
|
||||
uid_t uid = atoi(uid_string.c_str());
|
||||
pid_t pid = atoi(pid_string.c_str());
|
||||
if (!GetCreatedCrashDirectoryByEuid(uid, &dir, nullptr)) {
|
||||
LOG(ERROR) << "Can't create crash directory for uid " << uid;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string dump_basename = FormatDumpBasename(exe_name, time(nullptr), pid);
|
||||
FilePath meta_path = GetCrashPath(dir, dump_basename, "meta");
|
||||
FilePath minidump_path = GetCrashPath(dir, dump_basename, "dmp");
|
||||
|
||||
std::string data;
|
||||
if (!base::ReadFileToString(file_path, &data)) {
|
||||
LOG(ERROR) << "Can't read crash log: " << file_path.value();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ParseCrashLog(data, dir, minidump_path, dump_basename)) {
|
||||
LOG(ERROR) << "Failed to parse Chrome's crash log";
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int64_t report_size = 0;
|
||||
base::GetFileSize(minidump_path, &report_size);
|
||||
|
||||
// Keyed by crash metadata key name.
|
||||
const std::map<std::string, base::FilePath> additional_logs =
|
||||
GetAdditionalLogs(dir, dump_basename, exe_name);
|
||||
for (auto it : additional_logs) {
|
||||
int64_t file_size = 0;
|
||||
if (!base::GetFileSize(it.second, &file_size)) {
|
||||
PLOG(WARNING) << "Unable to get size of " << it.second.value();
|
||||
continue;
|
||||
}
|
||||
if (report_size + file_size > kDefaultMaxUploadBytes) {
|
||||
LOG(INFO) << "Skipping upload of " << it.second.value() << "("
|
||||
<< file_size << "B) because report size would exceed limit ("
|
||||
<< kDefaultMaxUploadBytes << "B)";
|
||||
continue;
|
||||
}
|
||||
VLOG(1) << "Adding metadata: " << it.first << " -> " << it.second.value();
|
||||
// Call AddCrashMetaUploadFile() rather than AddCrashMetaData() here. The
|
||||
// former adds a prefix to the key name; without the prefix, only the key
|
||||
// "logs" appears to be displayed on the crash server.
|
||||
AddCrashMetaUploadFile(it.first, it.second.value());
|
||||
report_size += file_size;
|
||||
}
|
||||
|
||||
// We're done.
|
||||
WriteCrashMetaData(meta_path, exe_name, minidump_path.value());
|
||||
|
||||
fprintf(output_file_ptr_, "%s", kSuccessMagic);
|
||||
fflush(output_file_ptr_);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ChromeCollector::SetUpDBus() {
|
||||
CrashCollector::SetUpDBus();
|
||||
|
||||
debugd_proxy_.reset(
|
||||
new org::chromium::debugdProxy(bus_, debugd::kDebugdServiceName));
|
||||
}
|
||||
|
||||
bool ChromeCollector::ParseCrashLog(const std::string &data,
|
||||
const FilePath &dir,
|
||||
const FilePath &minidump,
|
||||
const std::string &basename) {
|
||||
size_t at = 0;
|
||||
while (at < data.size()) {
|
||||
// Look for a : followed by a decimal number, followed by another :
|
||||
// followed by N bytes of data.
|
||||
std::string name, size_string;
|
||||
if (!GetDelimitedString(data, ':', at, &name)) {
|
||||
LOG(ERROR) << "Can't find : after name @ offset " << at;
|
||||
break;
|
||||
}
|
||||
at += name.size() + 1; // Skip the name & : delimiter.
|
||||
|
||||
if (!GetDelimitedString(data, ':', at, &size_string)) {
|
||||
LOG(ERROR) << "Can't find : after size @ offset " << at;
|
||||
break;
|
||||
}
|
||||
at += size_string.size() + 1; // Skip the size & : delimiter.
|
||||
|
||||
size_t size;
|
||||
if (!base::StringToSizeT(size_string, &size)) {
|
||||
LOG(ERROR) << "String not convertible to integer: " << size_string;
|
||||
break;
|
||||
}
|
||||
|
||||
// Data would run past the end, did we get a truncated file?
|
||||
if (at + size > data.size()) {
|
||||
LOG(ERROR) << "Overrun, expected " << size << " bytes of data, got "
|
||||
<< (data.size() - at);
|
||||
break;
|
||||
}
|
||||
|
||||
if (name.find("filename") != std::string::npos) {
|
||||
// File.
|
||||
// Name will be in a semi-MIME format of
|
||||
// <descriptive name>"; filename="<name>"
|
||||
// Descriptive name will be upload_file_minidump for the dump.
|
||||
std::string desc, filename;
|
||||
pcrecpp::RE re("(.*)\" *; *filename=\"(.*)\"");
|
||||
if (!re.FullMatch(name.c_str(), &desc, &filename)) {
|
||||
LOG(ERROR) << "Filename was not in expected format: " << name;
|
||||
break;
|
||||
}
|
||||
|
||||
if (desc.compare(kDefaultMinidumpName) == 0) {
|
||||
// The minidump.
|
||||
WriteNewFile(minidump, data.c_str() + at, size);
|
||||
} else {
|
||||
// Some other file.
|
||||
FilePath path = GetCrashPath(dir, basename + "-" + filename, "other");
|
||||
if (WriteNewFile(path, data.c_str() + at, size) >= 0) {
|
||||
AddCrashMetaUploadFile(desc, path.value());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Other attribute.
|
||||
std::string value_str;
|
||||
value_str.reserve(size);
|
||||
|
||||
// Since metadata is one line/value the values must be escaped properly.
|
||||
for (size_t i = at; i < at + size; i++) {
|
||||
switch (data[i]) {
|
||||
case '"':
|
||||
case '\\':
|
||||
value_str.push_back('\\');
|
||||
value_str.push_back(data[i]);
|
||||
break;
|
||||
|
||||
case '\r':
|
||||
value_str += "\\r";
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
value_str += "\\n";
|
||||
break;
|
||||
|
||||
case '\t':
|
||||
value_str += "\\t";
|
||||
break;
|
||||
|
||||
case '\0':
|
||||
value_str += "\\0";
|
||||
break;
|
||||
|
||||
default:
|
||||
value_str.push_back(data[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
AddCrashMetaUploadData(name, value_str);
|
||||
}
|
||||
|
||||
at += size;
|
||||
}
|
||||
|
||||
return at == data.size();
|
||||
}
|
||||
|
||||
std::map<std::string, base::FilePath> ChromeCollector::GetAdditionalLogs(
|
||||
const FilePath &dir,
|
||||
const std::string &basename,
|
||||
const std::string &exe_name) {
|
||||
std::map<std::string, base::FilePath> logs;
|
||||
|
||||
// Run the command specified by the config file to gather logs.
|
||||
const FilePath chrome_log_path =
|
||||
GetCrashPath(dir, basename, kChromeLogFilename);
|
||||
if (GetLogContents(log_config_path_, exe_name, chrome_log_path)) {
|
||||
const FilePath compressed_path = GzipFile(chrome_log_path);
|
||||
if (!compressed_path.empty())
|
||||
logs[kChromeLogFilename] = compressed_path;
|
||||
else
|
||||
base::DeleteFile(chrome_log_path, false /* recursive */);
|
||||
}
|
||||
|
||||
// For unit testing, debugd_proxy_ isn't initialized, so skip attempting to
|
||||
// get the GPU error state from debugd.
|
||||
if (debugd_proxy_) {
|
||||
const FilePath dri_error_state_path =
|
||||
GetCrashPath(dir, basename, kGpuStateFilename);
|
||||
if (GetDriErrorState(dri_error_state_path, debugd_proxy_.get()))
|
||||
logs[kGpuStateFilename] = dri_error_state_path;
|
||||
}
|
||||
|
||||
return logs;
|
||||
}
|
||||
|
||||
// static
|
||||
const char ChromeCollector::kSuccessMagic[] = "_sys_cr_finished";
|
72
crash_reporter/chrome_collector.h
Normal file
72
crash_reporter/chrome_collector.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CRASH_REPORTER_CHROME_COLLECTOR_H_
|
||||
#define CRASH_REPORTER_CHROME_COLLECTOR_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <base/files/file_path.h>
|
||||
#include <base/macros.h>
|
||||
#include <gtest/gtest_prod.h> // for FRIEND_TEST
|
||||
|
||||
#include "crash-reporter/crash_collector.h"
|
||||
#include "debugd/dbus-proxies.h"
|
||||
|
||||
class SystemLogging;
|
||||
|
||||
// Chrome crash collector.
|
||||
class ChromeCollector : public CrashCollector {
|
||||
public:
|
||||
ChromeCollector();
|
||||
~ChromeCollector() override;
|
||||
|
||||
// Magic string to let Chrome know the crash report succeeded.
|
||||
static const char kSuccessMagic[];
|
||||
|
||||
// Handle a specific chrome crash. Returns true on success.
|
||||
bool HandleCrash(const base::FilePath &file_path,
|
||||
const std::string &pid_string,
|
||||
const std::string &uid_string,
|
||||
const std::string &exe_name);
|
||||
|
||||
protected:
|
||||
void SetUpDBus() override;
|
||||
|
||||
private:
|
||||
friend class ChromeCollectorTest;
|
||||
FRIEND_TEST(ChromeCollectorTest, GoodValues);
|
||||
FRIEND_TEST(ChromeCollectorTest, BadValues);
|
||||
FRIEND_TEST(ChromeCollectorTest, Newlines);
|
||||
FRIEND_TEST(ChromeCollectorTest, File);
|
||||
FRIEND_TEST(ChromeCollectorTest, HandleCrash);
|
||||
|
||||
// Crashes are expected to be in a TLV-style format of:
|
||||
// <name>:<length>:<value>
|
||||
// Length is encoded as a decimal number. It can be zero, but must consist of
|
||||
// at least one character
|
||||
// For file values, name actually contains both a description and a filename,
|
||||
// in a fixed format of: <description>"; filename="<filename>"
|
||||
bool ParseCrashLog(const std::string &data, const base::FilePath &dir,
|
||||
const base::FilePath &minidump,
|
||||
const std::string &basename);
|
||||
|
||||
// Writes additional logs for |exe_name| to files based on |basename| within
|
||||
// |dir|. Crash report metadata key names and the corresponding file paths are
|
||||
// returned.
|
||||
std::map<std::string, base::FilePath> GetAdditionalLogs(
|
||||
const base::FilePath &dir,
|
||||
const std::string &basename,
|
||||
const std::string &exe_name);
|
||||
|
||||
FILE *output_file_ptr_;
|
||||
|
||||
// D-Bus proxy for debugd interface. Unset in unit tests.
|
||||
std::unique_ptr<org::chromium::debugdProxy> debugd_proxy_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ChromeCollector);
|
||||
};
|
||||
|
||||
#endif // CRASH_REPORTER_CHROME_COLLECTOR_H_
|
150
crash_reporter/chrome_collector_test.cc
Normal file
150
crash_reporter/chrome_collector_test.cc
Normal file
|
@ -0,0 +1,150 @@
|
|||
// Copyright (c) 2013 The Chromium OS 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 "crash-reporter/chrome_collector.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <base/auto_reset.h>
|
||||
#include <base/files/file_util.h>
|
||||
#include <base/files/scoped_temp_dir.h>
|
||||
#include <chromeos/syslog_logging.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using base::FilePath;
|
||||
|
||||
namespace {
|
||||
|
||||
const char kCrashFormatGood[] = "value1:10:abcdefghijvalue2:5:12345";
|
||||
const char kCrashFormatEmbeddedNewline[] =
|
||||
"value1:10:abcd\r\nghijvalue2:5:12\n34";
|
||||
const char kCrashFormatBad1[] = "value1:10:abcdefghijvalue2:6=12345";
|
||||
const char kCrashFormatBad2[] = "value1:10:abcdefghijvalue2:512345";
|
||||
const char kCrashFormatBad3[] = "value1:10::abcdefghijvalue2:5=12345";
|
||||
const char kCrashFormatBad4[] = "value1:10:abcdefghijvalue2:4=12345";
|
||||
|
||||
const char kCrashFormatWithFile[] =
|
||||
"value1:10:abcdefghijvalue2:5:12345"
|
||||
"some_file\"; filename=\"foo.txt\":15:12345\n789\n12345"
|
||||
"value3:2:ok";
|
||||
|
||||
void CountCrash() {
|
||||
}
|
||||
|
||||
bool s_allow_crash = false;
|
||||
|
||||
bool IsMetrics() {
|
||||
return s_allow_crash;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class ChromeCollectorMock : public ChromeCollector {
|
||||
public:
|
||||
MOCK_METHOD0(SetUpDBus, void());
|
||||
};
|
||||
|
||||
class ChromeCollectorTest : public ::testing::Test {
|
||||
protected:
|
||||
void ExpectFileEquals(const char *golden,
|
||||
const FilePath &file_path) {
|
||||
std::string contents;
|
||||
EXPECT_TRUE(base::ReadFileToString(file_path, &contents));
|
||||
EXPECT_EQ(golden, contents);
|
||||
}
|
||||
|
||||
ChromeCollectorMock collector_;
|
||||
|
||||
private:
|
||||
void SetUp() override {
|
||||
EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
|
||||
|
||||
collector_.Initialize(CountCrash, IsMetrics);
|
||||
chromeos::ClearLog();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ChromeCollectorTest, GoodValues) {
|
||||
FilePath dir(".");
|
||||
EXPECT_TRUE(collector_.ParseCrashLog(kCrashFormatGood,
|
||||
dir, dir.Append("minidump.dmp"),
|
||||
"base"));
|
||||
|
||||
// Check to see if the values made it in properly.
|
||||
std::string meta = collector_.extra_metadata_;
|
||||
EXPECT_TRUE(meta.find("value1=abcdefghij") != std::string::npos);
|
||||
EXPECT_TRUE(meta.find("value2=12345") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(ChromeCollectorTest, Newlines) {
|
||||
FilePath dir(".");
|
||||
EXPECT_TRUE(collector_.ParseCrashLog(kCrashFormatEmbeddedNewline,
|
||||
dir, dir.Append("minidump.dmp"),
|
||||
"base"));
|
||||
|
||||
// Check to see if the values were escaped.
|
||||
std::string meta = collector_.extra_metadata_;
|
||||
EXPECT_TRUE(meta.find("value1=abcd\\r\\nghij") != std::string::npos);
|
||||
EXPECT_TRUE(meta.find("value2=12\\n34") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(ChromeCollectorTest, BadValues) {
|
||||
FilePath dir(".");
|
||||
const struct {
|
||||
const char *data;
|
||||
} list[] = {
|
||||
{kCrashFormatBad1, },
|
||||
{kCrashFormatBad2, },
|
||||
{kCrashFormatBad3, },
|
||||
{kCrashFormatBad4, },
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(list) / sizeof(list[0]); i++) {
|
||||
chromeos::ClearLog();
|
||||
EXPECT_FALSE(collector_.ParseCrashLog(list[i].data,
|
||||
dir, dir.Append("minidump.dmp"),
|
||||
"base"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChromeCollectorTest, File) {
|
||||
base::ScopedTempDir scoped_temp_dir;
|
||||
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
|
||||
const FilePath& dir = scoped_temp_dir.path();
|
||||
EXPECT_TRUE(collector_.ParseCrashLog(kCrashFormatWithFile,
|
||||
dir, dir.Append("minidump.dmp"),
|
||||
"base"));
|
||||
|
||||
// Check to see if the values are still correct and that the file was
|
||||
// written with the right data.
|
||||
std::string meta = collector_.extra_metadata_;
|
||||
EXPECT_TRUE(meta.find("value1=abcdefghij") != std::string::npos);
|
||||
EXPECT_TRUE(meta.find("value2=12345") != std::string::npos);
|
||||
EXPECT_TRUE(meta.find("value3=ok") != std::string::npos);
|
||||
ExpectFileEquals("12345\n789\n12345", dir.Append("base-foo.txt.other"));
|
||||
}
|
||||
|
||||
TEST_F(ChromeCollectorTest, HandleCrash) {
|
||||
base::AutoReset<bool> auto_reset(&s_allow_crash, true);
|
||||
base::ScopedTempDir scoped_temp_dir;
|
||||
ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
|
||||
const FilePath& dir = scoped_temp_dir.path();
|
||||
FilePath dump_file = dir.Append("test.dmp");
|
||||
ASSERT_EQ(strlen(kCrashFormatWithFile),
|
||||
base::WriteFile(dump_file, kCrashFormatWithFile,
|
||||
strlen(kCrashFormatWithFile)));
|
||||
collector_.ForceCrashDirectory(dir);
|
||||
|
||||
FilePath log_file;
|
||||
{
|
||||
base::ScopedFILE output(
|
||||
base::CreateAndOpenTemporaryFileInDir(dir, &log_file));
|
||||
ASSERT_TRUE(output.get());
|
||||
base::AutoReset<FILE*> auto_reset_file_ptr(&collector_.output_file_ptr_,
|
||||
output.get());
|
||||
EXPECT_TRUE(collector_.HandleCrash(dump_file, "123", "456", "chrome_test"));
|
||||
}
|
||||
ExpectFileEquals(ChromeCollector::kSuccessMagic, log_file);
|
||||
}
|
147
crash_reporter/crash-reporter.gyp
Normal file
147
crash_reporter/crash-reporter.gyp
Normal file
|
@ -0,0 +1,147 @@
|
|||
{
|
||||
# Shouldn't need this, but doesn't work otherwise.
|
||||
# http://crbug.com/340086 and http://crbug.com/385186
|
||||
# Note: the unused dependencies are optimized out by the compiler.
|
||||
'target_defaults': {
|
||||
'variables': {
|
||||
'deps': [
|
||||
'libchromeos-<(libbase_ver)',
|
||||
],
|
||||
},
|
||||
},
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'libcrash',
|
||||
'type': 'static_library',
|
||||
'variables': {
|
||||
'exported_deps': [
|
||||
'libchrome-<(libbase_ver)',
|
||||
'libpcrecpp',
|
||||
],
|
||||
'deps': ['<@(exported_deps)'],
|
||||
},
|
||||
'all_dependent_settings': {
|
||||
'variables': {
|
||||
'deps': [
|
||||
'<@(exported_deps)',
|
||||
],
|
||||
},
|
||||
},
|
||||
'sources': [
|
||||
'chrome_collector.cc',
|
||||
'crash_collector.cc',
|
||||
'kernel_collector.cc',
|
||||
'kernel_warning_collector.cc',
|
||||
'udev_collector.cc',
|
||||
'unclean_shutdown_collector.cc',
|
||||
'user_collector.cc',
|
||||
],
|
||||
'actions': [
|
||||
{
|
||||
'action_name': 'generate-session-manager-proxies',
|
||||
'variables': {
|
||||
'proxy_output_file': 'include/session_manager/dbus-proxies.h'
|
||||
},
|
||||
'sources': [
|
||||
'../login_manager/org.chromium.SessionManagerInterface.xml',
|
||||
],
|
||||
'includes': ['../common-mk/generate-dbus-proxies.gypi'],
|
||||
},
|
||||
{
|
||||
'action_name': 'generate-debugd-proxies',
|
||||
'variables': {
|
||||
'proxy_output_file': 'include/debugd/dbus-proxies.h'
|
||||
},
|
||||
'sources': [
|
||||
'../debugd/share/org.chromium.debugd.xml',
|
||||
],
|
||||
'includes': ['../common-mk/generate-dbus-proxies.gypi'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'crash_reporter',
|
||||
'type': 'executable',
|
||||
'variables': {
|
||||
'deps': [
|
||||
'dbus-1',
|
||||
'libmetrics-<(libbase_ver)',
|
||||
],
|
||||
},
|
||||
'dependencies': [
|
||||
'libcrash',
|
||||
],
|
||||
'sources': [
|
||||
'crash_reporter.cc',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'list_proxies',
|
||||
'type': 'executable',
|
||||
'variables': {
|
||||
'deps': [
|
||||
'dbus-1',
|
||||
'libchrome-<(libbase_ver)',
|
||||
],
|
||||
},
|
||||
'sources': [
|
||||
'list_proxies.cc',
|
||||
],
|
||||
'actions': [
|
||||
{
|
||||
'action_name': 'generate-lib-cros-service-proxies',
|
||||
'variables': {
|
||||
'proxy_output_file': 'include/libcrosservice/dbus-proxies.h'
|
||||
},
|
||||
'sources': [
|
||||
'./dbus_bindings/org.chromium.LibCrosService.xml',
|
||||
],
|
||||
'includes': ['../common-mk/generate-dbus-proxies.gypi'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'warn_collector',
|
||||
'type': 'executable',
|
||||
'variables': {
|
||||
'lexer_out_dir': 'crash-reporter',
|
||||
'deps': [
|
||||
'libmetrics-<(libbase_ver)',
|
||||
],
|
||||
},
|
||||
'link_settings': {
|
||||
'libraries': [
|
||||
'-lfl',
|
||||
],
|
||||
},
|
||||
'sources': [
|
||||
'warn_collector.l',
|
||||
],
|
||||
'includes': ['../common-mk/lex.gypi'],
|
||||
},
|
||||
],
|
||||
'conditions': [
|
||||
['USE_test == 1', {
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'crash_reporter_test',
|
||||
'type': 'executable',
|
||||
'includes': ['../common-mk/common_test.gypi'],
|
||||
'dependencies': ['libcrash'],
|
||||
'sources': [
|
||||
'chrome_collector_test.cc',
|
||||
'crash_collector_test.cc',
|
||||
'crash_collector_test.h',
|
||||
'crash_reporter_logs_test.cc',
|
||||
'kernel_collector_test.cc',
|
||||
'kernel_collector_test.h',
|
||||
'testrunner.cc',
|
||||
'udev_collector_test.cc',
|
||||
'unclean_shutdown_collector_test.cc',
|
||||
'user_collector_test.cc',
|
||||
],
|
||||
},
|
||||
],
|
||||
}],
|
||||
],
|
||||
}
|
512
crash_reporter/crash_collector.cc
Normal file
512
crash_reporter/crash_collector.cc
Normal file
|
@ -0,0 +1,512 @@
|
|||
// Copyright (c) 2012 The Chromium OS 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 "crash-reporter/crash_collector.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h> // For file creation modes.
|
||||
#include <inttypes.h>
|
||||
#include <linux/limits.h> // PATH_MAX
|
||||
#include <pwd.h> // For struct passwd.
|
||||
#include <sys/types.h> // for mode_t.
|
||||
#include <sys/wait.h> // For waitpid.
|
||||
#include <unistd.h> // For execv and fork.
|
||||
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <base/files/file_util.h>
|
||||
#include <base/logging.h>
|
||||
#include <base/posix/eintr_wrapper.h>
|
||||
#include <base/strings/string_split.h>
|
||||
#include <base/strings/string_util.h>
|
||||
#include <base/strings/stringprintf.h>
|
||||
#include <chromeos/cryptohome.h>
|
||||
#include <chromeos/dbus/service_constants.h>
|
||||
#include <chromeos/key_value_store.h>
|
||||
#include <chromeos/process.h>
|
||||
|
||||
namespace {
|
||||
|
||||
const char kCollectChromeFile[] =
|
||||
"/mnt/stateful_partition/etc/collect_chrome_crashes";
|
||||
const char kCrashTestInProgressPath[] = "/tmp/crash-test-in-progress";
|
||||
const char kDefaultLogConfig[] = "/etc/crash_reporter_logs.conf";
|
||||
const char kDefaultUserName[] = "chronos";
|
||||
const char kLeaveCoreFile[] = "/root/.leave_core";
|
||||
const char kLsbRelease[] = "/etc/lsb-release";
|
||||
const char kShellPath[] = "/bin/sh";
|
||||
const char kSystemCrashPath[] = "/var/spool/crash";
|
||||
const char kUploadVarPrefix[] = "upload_var_";
|
||||
const char kUploadFilePrefix[] = "upload_file_";
|
||||
|
||||
// Key of the lsb-release entry containing the OS version.
|
||||
const char kLsbVersionKey[] = "CHROMEOS_RELEASE_VERSION";
|
||||
|
||||
// Normally this path is not used. Unfortunately, there are a few edge cases
|
||||
// where we need this. Any process that runs as kDefaultUserName that crashes
|
||||
// is consider a "user crash". That includes the initial Chrome browser that
|
||||
// runs the login screen. If that blows up, there is no logged in user yet,
|
||||
// so there is no per-user dir for us to stash things in. Instead we fallback
|
||||
// to this path as it is at least encrypted on a per-system basis.
|
||||
//
|
||||
// This also comes up when running autotests. The GUI is sitting at the login
|
||||
// screen while tests are sshing in, changing users, and triggering crashes as
|
||||
// the user (purposefully).
|
||||
const char kFallbackUserCrashPath[] = "/home/chronos/crash";
|
||||
|
||||
// Directory mode of the user crash spool directory.
|
||||
const mode_t kUserCrashPathMode = 0755;
|
||||
|
||||
// Directory mode of the system crash spool directory.
|
||||
const mode_t kSystemCrashPathMode = 01755;
|
||||
|
||||
const uid_t kRootOwner = 0;
|
||||
const uid_t kRootGroup = 0;
|
||||
|
||||
} // namespace
|
||||
|
||||
// Maximum crash reports per crash spool directory. Note that this is
|
||||
// a separate maximum from the maximum rate at which we upload these
|
||||
// diagnostics. The higher this rate is, the more space we allow for
|
||||
// core files, minidumps, and kcrash logs, and equivalently the more
|
||||
// processor and I/O bandwidth we dedicate to handling these crashes when
|
||||
// many occur at once. Also note that if core files are configured to
|
||||
// be left on the file system, we stop adding crashes when either the
|
||||
// number of core files or minidumps reaches this number.
|
||||
const int CrashCollector::kMaxCrashDirectorySize = 32;
|
||||
|
||||
using base::FilePath;
|
||||
using base::StringPrintf;
|
||||
|
||||
CrashCollector::CrashCollector()
|
||||
: lsb_release_(kLsbRelease),
|
||||
log_config_path_(kDefaultLogConfig) {
|
||||
}
|
||||
|
||||
CrashCollector::~CrashCollector() {
|
||||
if (bus_)
|
||||
bus_->ShutdownAndBlock();
|
||||
}
|
||||
|
||||
void CrashCollector::Initialize(
|
||||
CrashCollector::CountCrashFunction count_crash_function,
|
||||
CrashCollector::IsFeedbackAllowedFunction is_feedback_allowed_function) {
|
||||
CHECK(count_crash_function);
|
||||
CHECK(is_feedback_allowed_function);
|
||||
|
||||
count_crash_function_ = count_crash_function;
|
||||
is_feedback_allowed_function_ = is_feedback_allowed_function;
|
||||
|
||||
SetUpDBus();
|
||||
}
|
||||
|
||||
void CrashCollector::SetUpDBus() {
|
||||
dbus::Bus::Options options;
|
||||
options.bus_type = dbus::Bus::SYSTEM;
|
||||
|
||||
bus_ = new dbus::Bus(options);
|
||||
CHECK(bus_->Connect());
|
||||
|
||||
session_manager_proxy_.reset(
|
||||
new org::chromium::SessionManagerInterfaceProxy(
|
||||
bus_,
|
||||
login_manager::kSessionManagerServiceName));
|
||||
}
|
||||
|
||||
int CrashCollector::WriteNewFile(const FilePath &filename,
|
||||
const char *data,
|
||||
int size) {
|
||||
int fd = HANDLE_EINTR(open(filename.value().c_str(),
|
||||
O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0666));
|
||||
if (fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rv = base::WriteFileDescriptor(fd, data, size) ? size : -1;
|
||||
IGNORE_EINTR(close(fd));
|
||||
return rv;
|
||||
}
|
||||
|
||||
std::string CrashCollector::Sanitize(const std::string &name) {
|
||||
// Make sure the sanitized name does not include any periods.
|
||||
// The logic in crash_sender relies on this.
|
||||
std::string result = name;
|
||||
for (size_t i = 0; i < name.size(); ++i) {
|
||||
if (!isalnum(result[i]) && result[i] != '_')
|
||||
result[i] = '_';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string CrashCollector::FormatDumpBasename(const std::string &exec_name,
|
||||
time_t timestamp,
|
||||
pid_t pid) {
|
||||
struct tm tm;
|
||||
localtime_r(×tamp, &tm);
|
||||
std::string sanitized_exec_name = Sanitize(exec_name);
|
||||
return StringPrintf("%s.%04d%02d%02d.%02d%02d%02d.%d",
|
||||
sanitized_exec_name.c_str(),
|
||||
tm.tm_year + 1900,
|
||||
tm.tm_mon + 1,
|
||||
tm.tm_mday,
|
||||
tm.tm_hour,
|
||||
tm.tm_min,
|
||||
tm.tm_sec,
|
||||
pid);
|
||||
}
|
||||
|
||||
FilePath CrashCollector::GetCrashPath(const FilePath &crash_directory,
|
||||
const std::string &basename,
|
||||
const std::string &extension) {
|
||||
return crash_directory.Append(StringPrintf("%s.%s",
|
||||
basename.c_str(),
|
||||
extension.c_str()));
|
||||
}
|
||||
|
||||
bool CrashCollector::GetActiveUserSessions(
|
||||
std::map<std::string, std::string> *sessions) {
|
||||
chromeos::ErrorPtr error;
|
||||
session_manager_proxy_->RetrieveActiveSessions(sessions, &error);
|
||||
|
||||
if (error) {
|
||||
LOG(ERROR) << "Error calling D-Bus proxy call to interface "
|
||||
<< "'" << session_manager_proxy_->GetObjectPath().value() << "':"
|
||||
<< error->GetMessage();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FilePath CrashCollector::GetUserCrashPath() {
|
||||
// In this multiprofile world, there is no one-specific user dir anymore.
|
||||
// Ask the session manager for the active ones, then just run with the
|
||||
// first result we get back.
|
||||
FilePath user_path = FilePath(kFallbackUserCrashPath);
|
||||
std::map<std::string, std::string> active_sessions;
|
||||
if (!GetActiveUserSessions(&active_sessions) || active_sessions.empty()) {
|
||||
LOG(ERROR) << "Could not get active user sessions, using default.";
|
||||
return user_path;
|
||||
}
|
||||
|
||||
user_path = chromeos::cryptohome::home::GetHashedUserPath(
|
||||
active_sessions.begin()->second).Append("crash");
|
||||
|
||||
return user_path;
|
||||
}
|
||||
|
||||
FilePath CrashCollector::GetCrashDirectoryInfo(
|
||||
uid_t process_euid,
|
||||
uid_t default_user_id,
|
||||
gid_t default_user_group,
|
||||
mode_t *mode,
|
||||
uid_t *directory_owner,
|
||||
gid_t *directory_group) {
|
||||
// TODO(mkrebs): This can go away once Chrome crashes are handled
|
||||
// normally (see crosbug.com/5872).
|
||||
// Check if the user crash directory should be used. If we are
|
||||
// collecting chrome crashes during autotesting, we want to put them in
|
||||
// the system crash directory so they are outside the cryptohome -- in
|
||||
// case we are being run during logout (see crosbug.com/18637).
|
||||
if (process_euid == default_user_id && IsUserSpecificDirectoryEnabled()) {
|
||||
*mode = kUserCrashPathMode;
|
||||
*directory_owner = default_user_id;
|
||||
*directory_group = default_user_group;
|
||||
return GetUserCrashPath();
|
||||
} else {
|
||||
*mode = kSystemCrashPathMode;
|
||||
*directory_owner = kRootOwner;
|
||||
*directory_group = kRootGroup;
|
||||
return FilePath(kSystemCrashPath);
|
||||
}
|
||||
}
|
||||
|
||||
bool CrashCollector::GetUserInfoFromName(const std::string &name,
|
||||
uid_t *uid,
|
||||
gid_t *gid) {
|
||||
char storage[256];
|
||||
struct passwd passwd_storage;
|
||||
struct passwd *passwd_result = nullptr;
|
||||
|
||||
if (getpwnam_r(name.c_str(), &passwd_storage, storage, sizeof(storage),
|
||||
&passwd_result) != 0 || passwd_result == nullptr) {
|
||||
LOG(ERROR) << "Cannot find user named " << name;
|
||||
return false;
|
||||
}
|
||||
|
||||
*uid = passwd_result->pw_uid;
|
||||
*gid = passwd_result->pw_gid;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CrashCollector::GetCreatedCrashDirectoryByEuid(uid_t euid,
|
||||
FilePath *crash_directory,
|
||||
bool *out_of_capacity) {
|
||||
uid_t default_user_id;
|
||||
gid_t default_user_group;
|
||||
|
||||
if (out_of_capacity) *out_of_capacity = false;
|
||||
|
||||
// For testing.
|
||||
if (!forced_crash_directory_.empty()) {
|
||||
*crash_directory = forced_crash_directory_;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!GetUserInfoFromName(kDefaultUserName,
|
||||
&default_user_id,
|
||||
&default_user_group)) {
|
||||
LOG(ERROR) << "Could not find default user info";
|
||||
return false;
|
||||
}
|
||||
mode_t directory_mode;
|
||||
uid_t directory_owner;
|
||||
gid_t directory_group;
|
||||
*crash_directory =
|
||||
GetCrashDirectoryInfo(euid,
|
||||
default_user_id,
|
||||
default_user_group,
|
||||
&directory_mode,
|
||||
&directory_owner,
|
||||
&directory_group);
|
||||
|
||||
if (!base::PathExists(*crash_directory)) {
|
||||
// Create the spool directory with the appropriate mode (regardless of
|
||||
// umask) and ownership.
|
||||
mode_t old_mask = umask(0);
|
||||
if (mkdir(crash_directory->value().c_str(), directory_mode) < 0 ||
|
||||
chown(crash_directory->value().c_str(),
|
||||
directory_owner,
|
||||
directory_group) < 0) {
|
||||
LOG(ERROR) << "Unable to create appropriate crash directory";
|
||||
return false;
|
||||
}
|
||||
umask(old_mask);
|
||||
}
|
||||
|
||||
if (!base::PathExists(*crash_directory)) {
|
||||
LOG(ERROR) << "Unable to create crash directory "
|
||||
<< crash_directory->value().c_str();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckHasCapacity(*crash_directory)) {
|
||||
if (out_of_capacity) *out_of_capacity = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FilePath CrashCollector::GetProcessPath(pid_t pid) {
|
||||
return FilePath(StringPrintf("/proc/%d", pid));
|
||||
}
|
||||
|
||||
bool CrashCollector::GetSymlinkTarget(const FilePath &symlink,
|
||||
FilePath *target) {
|
||||
ssize_t max_size = 64;
|
||||
std::vector<char> buffer;
|
||||
|
||||
while (true) {
|
||||
buffer.resize(max_size + 1);
|
||||
ssize_t size = readlink(symlink.value().c_str(), buffer.data(), max_size);
|
||||
if (size < 0) {
|
||||
int saved_errno = errno;
|
||||
LOG(ERROR) << "Readlink failed on " << symlink.value() << " with "
|
||||
<< saved_errno;
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer[size] = 0;
|
||||
if (size == max_size) {
|
||||
max_size *= 2;
|
||||
if (max_size > PATH_MAX) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
*target = FilePath(buffer.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CrashCollector::GetExecutableBaseNameFromPid(pid_t pid,
|
||||
std::string *base_name) {
|
||||
FilePath target;
|
||||
FilePath process_path = GetProcessPath(pid);
|
||||
FilePath exe_path = process_path.Append("exe");
|
||||
if (!GetSymlinkTarget(exe_path, &target)) {
|
||||
LOG(INFO) << "GetSymlinkTarget failed - Path " << process_path.value()
|
||||
<< " DirectoryExists: "
|
||||
<< base::DirectoryExists(process_path);
|
||||
// Try to further diagnose exe readlink failure cause.
|
||||
struct stat buf;
|
||||
int stat_result = stat(exe_path.value().c_str(), &buf);
|
||||
int saved_errno = errno;
|
||||
if (stat_result < 0) {
|
||||
LOG(INFO) << "stat " << exe_path.value() << " failed: " << stat_result
|
||||
<< " " << saved_errno;
|
||||
} else {
|
||||
LOG(INFO) << "stat " << exe_path.value() << " succeeded: st_mode="
|
||||
<< buf.st_mode;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
*base_name = target.BaseName().value();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return true if the given crash directory has not already reached
|
||||
// maximum capacity.
|
||||
bool CrashCollector::CheckHasCapacity(const FilePath &crash_directory) {
|
||||
DIR* dir = opendir(crash_directory.value().c_str());
|
||||
if (!dir) {
|
||||
return false;
|
||||
}
|
||||
struct dirent ent_buf;
|
||||
struct dirent* ent;
|
||||
bool full = false;
|
||||
std::set<std::string> basenames;
|
||||
while (readdir_r(dir, &ent_buf, &ent) == 0 && ent) {
|
||||
if ((strcmp(ent->d_name, ".") == 0) ||
|
||||
(strcmp(ent->d_name, "..") == 0))
|
||||
continue;
|
||||
|
||||
std::string filename(ent->d_name);
|
||||
size_t last_dot = filename.rfind(".");
|
||||
std::string basename;
|
||||
// If there is a valid looking extension, use the base part of the
|
||||
// name. If the only dot is the first byte (aka a dot file), treat
|
||||
// it as unique to avoid allowing a directory full of dot files
|
||||
// from accumulating.
|
||||
if (last_dot != std::string::npos && last_dot != 0)
|
||||
basename = filename.substr(0, last_dot);
|
||||
else
|
||||
basename = filename;
|
||||
basenames.insert(basename);
|
||||
|
||||
if (basenames.size() >= static_cast<size_t>(kMaxCrashDirectorySize)) {
|
||||
LOG(WARNING) << "Crash directory " << crash_directory.value()
|
||||
<< " already full with " << kMaxCrashDirectorySize
|
||||
<< " pending reports";
|
||||
full = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
return !full;
|
||||
}
|
||||
|
||||
bool CrashCollector::GetLogContents(const FilePath &config_path,
|
||||
const std::string &exec_name,
|
||||
const FilePath &output_file) {
|
||||
chromeos::KeyValueStore store;
|
||||
if (!store.Load(config_path)) {
|
||||
LOG(INFO) << "Unable to read log configuration file "
|
||||
<< config_path.value();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string command;
|
||||
if (!store.GetString(exec_name, &command))
|
||||
return false;
|
||||
|
||||
chromeos::ProcessImpl diag_process;
|
||||
diag_process.AddArg(kShellPath);
|
||||
diag_process.AddStringOption("-c", command);
|
||||
diag_process.RedirectOutput(output_file.value());
|
||||
|
||||
const int result = diag_process.Run();
|
||||
if (result != 0) {
|
||||
LOG(INFO) << "Log command \"" << command << "\" exited with " << result;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CrashCollector::AddCrashMetaData(const std::string &key,
|
||||
const std::string &value) {
|
||||
extra_metadata_.append(StringPrintf("%s=%s\n", key.c_str(), value.c_str()));
|
||||
}
|
||||
|
||||
void CrashCollector::AddCrashMetaUploadFile(const std::string &key,
|
||||
const std::string &path) {
|
||||
if (!path.empty())
|
||||
AddCrashMetaData(kUploadFilePrefix + key, path);
|
||||
}
|
||||
|
||||
void CrashCollector::AddCrashMetaUploadData(const std::string &key,
|
||||
const std::string &value) {
|
||||
if (!value.empty())
|
||||
AddCrashMetaData(kUploadVarPrefix + key, value);
|
||||
}
|
||||
|
||||
void CrashCollector::WriteCrashMetaData(const FilePath &meta_path,
|
||||
const std::string &exec_name,
|
||||
const std::string &payload_path) {
|
||||
chromeos::KeyValueStore store;
|
||||
if (!store.Load(FilePath(lsb_release_))) {
|
||||
LOG(ERROR) << "Problem parsing " << lsb_release_;
|
||||
// Even though there was some failure, take as much as we could read.
|
||||
}
|
||||
|
||||
std::string version("unknown");
|
||||
if (!store.GetString(kLsbVersionKey, &version)) {
|
||||
LOG(ERROR) << "Unable to read " << kLsbVersionKey << " from "
|
||||
<< lsb_release_;
|
||||
}
|
||||
int64_t payload_size = -1;
|
||||
base::GetFileSize(FilePath(payload_path), &payload_size);
|
||||
std::string meta_data = StringPrintf("%sexec_name=%s\n"
|
||||
"ver=%s\n"
|
||||
"payload=%s\n"
|
||||
"payload_size=%" PRId64 "\n"
|
||||
"done=1\n",
|
||||
extra_metadata_.c_str(),
|
||||
exec_name.c_str(),
|
||||
version.c_str(),
|
||||
payload_path.c_str(),
|
||||
payload_size);
|
||||
// We must use WriteNewFile instead of base::WriteFile as we
|
||||
// do not want to write with root access to a symlink that an attacker
|
||||
// might have created.
|
||||
if (WriteNewFile(meta_path, meta_data.c_str(), meta_data.size()) < 0) {
|
||||
LOG(ERROR) << "Unable to write " << meta_path.value();
|
||||
}
|
||||
}
|
||||
|
||||
bool CrashCollector::IsCrashTestInProgress() {
|
||||
return base::PathExists(FilePath(kCrashTestInProgressPath));
|
||||
}
|
||||
|
||||
bool CrashCollector::IsDeveloperImage() {
|
||||
// If we're testing crash reporter itself, we don't want to special-case
|
||||
// for developer images.
|
||||
if (IsCrashTestInProgress())
|
||||
return false;
|
||||
return base::PathExists(FilePath(kLeaveCoreFile));
|
||||
}
|
||||
|
||||
bool CrashCollector::ShouldHandleChromeCrashes() {
|
||||
// If we're testing crash reporter itself, we don't want to allow an
|
||||
// override for chrome crashes. And, let's be conservative and only
|
||||
// allow an override for developer images.
|
||||
if (!IsCrashTestInProgress() && IsDeveloperImage()) {
|
||||
// Check if there's an override to indicate we should indeed collect
|
||||
// chrome crashes. This allows the crashes to still be tracked when
|
||||
// they occur in autotests. See "crosbug.com/17987".
|
||||
if (base::PathExists(FilePath(kCollectChromeFile)))
|
||||
return true;
|
||||
}
|
||||
// We default to ignoring chrome crashes.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CrashCollector::IsUserSpecificDirectoryEnabled() {
|
||||
return !ShouldHandleChromeCrashes();
|
||||
}
|
179
crash_reporter/crash_collector.h
Normal file
179
crash_reporter/crash_collector.h
Normal file
|
@ -0,0 +1,179 @@
|
|||
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CRASH_REPORTER_CRASH_COLLECTOR_H_
|
||||
#define CRASH_REPORTER_CRASH_COLLECTOR_H_
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <base/files/file_path.h>
|
||||
#include <base/macros.h>
|
||||
#include <base/memory/scoped_ptr.h>
|
||||
#include <gtest/gtest_prod.h> // for FRIEND_TEST
|
||||
|
||||
#include "session_manager/dbus-proxies.h"
|
||||
|
||||
// User crash collector.
|
||||
class CrashCollector {
|
||||
public:
|
||||
typedef void (*CountCrashFunction)();
|
||||
typedef bool (*IsFeedbackAllowedFunction)();
|
||||
|
||||
CrashCollector();
|
||||
|
||||
virtual ~CrashCollector();
|
||||
|
||||
// Initialize the crash collector for detection of crashes, given a
|
||||
// crash counting function, and metrics collection enabled oracle.
|
||||
void Initialize(CountCrashFunction count_crash,
|
||||
IsFeedbackAllowedFunction is_metrics_allowed);
|
||||
|
||||
protected:
|
||||
friend class CrashCollectorTest;
|
||||
FRIEND_TEST(ChromeCollectorTest, HandleCrash);
|
||||
FRIEND_TEST(CrashCollectorTest, CheckHasCapacityCorrectBasename);
|
||||
FRIEND_TEST(CrashCollectorTest, CheckHasCapacityStrangeNames);
|
||||
FRIEND_TEST(CrashCollectorTest, CheckHasCapacityUsual);
|
||||
FRIEND_TEST(CrashCollectorTest, GetCrashDirectoryInfo);
|
||||
FRIEND_TEST(CrashCollectorTest, GetCrashPath);
|
||||
FRIEND_TEST(CrashCollectorTest, GetLogContents);
|
||||
FRIEND_TEST(CrashCollectorTest, ForkExecAndPipe);
|
||||
FRIEND_TEST(CrashCollectorTest, FormatDumpBasename);
|
||||
FRIEND_TEST(CrashCollectorTest, Initialize);
|
||||
FRIEND_TEST(CrashCollectorTest, IsUserSpecificDirectoryEnabled);
|
||||
FRIEND_TEST(CrashCollectorTest, MetaData);
|
||||
FRIEND_TEST(CrashCollectorTest, Sanitize);
|
||||
FRIEND_TEST(CrashCollectorTest, WriteNewFile);
|
||||
FRIEND_TEST(ForkExecAndPipeTest, Basic);
|
||||
FRIEND_TEST(ForkExecAndPipeTest, NonZeroReturnValue);
|
||||
FRIEND_TEST(ForkExecAndPipeTest, BadOutputFile);
|
||||
FRIEND_TEST(ForkExecAndPipeTest, ExistingOutputFile);
|
||||
FRIEND_TEST(ForkExecAndPipeTest, BadExecutable);
|
||||
FRIEND_TEST(ForkExecAndPipeTest, StderrCaptured);
|
||||
FRIEND_TEST(ForkExecAndPipeTest, NULLParam);
|
||||
FRIEND_TEST(ForkExecAndPipeTest, NoParams);
|
||||
FRIEND_TEST(ForkExecAndPipeTest, SegFaultHandling);
|
||||
|
||||
// Set maximum enqueued crashes in a crash directory.
|
||||
static const int kMaxCrashDirectorySize;
|
||||
|
||||
// Set up D-Bus.
|
||||
virtual void SetUpDBus();
|
||||
|
||||
// Writes |data| of |size| to |filename|, which must be a new file.
|
||||
// If the file already exists or writing fails, return a negative value.
|
||||
// Otherwise returns the number of bytes written.
|
||||
int WriteNewFile(const base::FilePath &filename, const char *data, int size);
|
||||
|
||||
// Return a filename that has only [a-z0-1_] characters by mapping
|
||||
// all others into '_'.
|
||||
std::string Sanitize(const std::string &name);
|
||||
|
||||
// For testing, set the directory always returned by
|
||||
// GetCreatedCrashDirectoryByEuid.
|
||||
void ForceCrashDirectory(const base::FilePath &forced_directory) {
|
||||
forced_crash_directory_ = forced_directory;
|
||||
}
|
||||
|
||||
virtual bool GetActiveUserSessions(
|
||||
std::map<std::string, std::string> *sessions);
|
||||
base::FilePath GetUserCrashPath();
|
||||
base::FilePath GetCrashDirectoryInfo(uid_t process_euid,
|
||||
uid_t default_user_id,
|
||||
gid_t default_user_group,
|
||||
mode_t *mode,
|
||||
uid_t *directory_owner,
|
||||
gid_t *directory_group);
|
||||
bool GetUserInfoFromName(const std::string &name,
|
||||
uid_t *uid,
|
||||
gid_t *gid);
|
||||
|
||||
// Determines the crash directory for given euid, and creates the
|
||||
// directory if necessary with appropriate permissions. If
|
||||
// |out_of_capacity| is not nullptr, it is set to indicate if the call
|
||||
// failed due to not having capacity in the crash directory. Returns
|
||||
// true whether or not directory needed to be created, false on any
|
||||
// failure. If the crash directory is at capacity, returns false.
|
||||
bool GetCreatedCrashDirectoryByEuid(uid_t euid,
|
||||
base::FilePath *crash_file_path,
|
||||
bool *out_of_capacity);
|
||||
|
||||
// Format crash name based on components.
|
||||
std::string FormatDumpBasename(const std::string &exec_name,
|
||||
time_t timestamp,
|
||||
pid_t pid);
|
||||
|
||||
// Create a file path to a file in |crash_directory| with the given
|
||||
// |basename| and |extension|.
|
||||
base::FilePath GetCrashPath(const base::FilePath &crash_directory,
|
||||
const std::string &basename,
|
||||
const std::string &extension);
|
||||
|
||||
base::FilePath GetProcessPath(pid_t pid);
|
||||
bool GetSymlinkTarget(const base::FilePath &symlink,
|
||||
base::FilePath *target);
|
||||
bool GetExecutableBaseNameFromPid(pid_t pid,
|
||||
std::string *base_name);
|
||||
|
||||
// Check given crash directory still has remaining capacity for another
|
||||
// crash.
|
||||
bool CheckHasCapacity(const base::FilePath &crash_directory);
|
||||
|
||||
// Write a log applicable to |exec_name| to |output_file| based on the
|
||||
// log configuration file at |config_path|.
|
||||
bool GetLogContents(const base::FilePath &config_path,
|
||||
const std::string &exec_name,
|
||||
const base::FilePath &output_file);
|
||||
|
||||
// Add non-standard meta data to the crash metadata file. Call
|
||||
// before calling WriteCrashMetaData. Key must not contain "=" or
|
||||
// "\n" characters. Value must not contain "\n" characters.
|
||||
void AddCrashMetaData(const std::string &key, const std::string &value);
|
||||
|
||||
// Add a file to be uploaded to the crash reporter server. The file must
|
||||
// persist until the crash report is sent; ideally it should live in the same
|
||||
// place as the .meta file, so it can be cleaned up automatically.
|
||||
void AddCrashMetaUploadFile(const std::string &key, const std::string &path);
|
||||
|
||||
// Add non-standard meta data to the crash metadata file.
|
||||
// Data added though this call will be uploaded to the crash reporter server,
|
||||
// appearing as a form field.
|
||||
void AddCrashMetaUploadData(const std::string &key, const std::string &value);
|
||||
|
||||
// Write a file of metadata about crash.
|
||||
void WriteCrashMetaData(const base::FilePath &meta_path,
|
||||
const std::string &exec_name,
|
||||
const std::string &payload_path);
|
||||
|
||||
// Returns true if the a crash test is currently running.
|
||||
bool IsCrashTestInProgress();
|
||||
// Returns true if we should consider ourselves to be running on a
|
||||
// developer image.
|
||||
bool IsDeveloperImage();
|
||||
// Returns true if chrome crashes should be handled.
|
||||
bool ShouldHandleChromeCrashes();
|
||||
// Returns true if user crash directory may be used.
|
||||
bool IsUserSpecificDirectoryEnabled();
|
||||
|
||||
CountCrashFunction count_crash_function_;
|
||||
IsFeedbackAllowedFunction is_feedback_allowed_function_;
|
||||
std::string extra_metadata_;
|
||||
base::FilePath forced_crash_directory_;
|
||||
std::string lsb_release_;
|
||||
base::FilePath log_config_path_;
|
||||
|
||||
scoped_refptr<dbus::Bus> bus_;
|
||||
|
||||
private:
|
||||
// D-Bus proxy for session manager interface.
|
||||
std::unique_ptr<org::chromium::SessionManagerInterfaceProxy>
|
||||
session_manager_proxy_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CrashCollector);
|
||||
};
|
||||
|
||||
#endif // CRASH_REPORTER_CRASH_COLLECTOR_H_
|
299
crash_reporter/crash_collector_test.cc
Normal file
299
crash_reporter/crash_collector_test.cc
Normal file
|
@ -0,0 +1,299 @@
|
|||
// Copyright (c) 2012 The Chromium OS 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 "crash-reporter/crash_collector_test.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
|
||||
#include <base/files/file_util.h>
|
||||
#include <base/strings/string_util.h>
|
||||
#include <base/strings/stringprintf.h>
|
||||
#include <chromeos/syslog_logging.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "crash-reporter/crash_collector.h"
|
||||
|
||||
using base::FilePath;
|
||||
using base::StringPrintf;
|
||||
using chromeos::FindLog;
|
||||
using ::testing::Invoke;
|
||||
using ::testing::Return;
|
||||
|
||||
namespace {
|
||||
|
||||
void CountCrash() {
|
||||
ADD_FAILURE();
|
||||
}
|
||||
|
||||
bool IsMetrics() {
|
||||
ADD_FAILURE();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetActiveUserSessionsImpl(std::map<std::string, std::string> *sessions) {
|
||||
char kUser[] = "chicken@butt.com";
|
||||
char kHash[] = "hashcakes";
|
||||
sessions->insert(std::pair<std::string, std::string>(kUser, kHash));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class CrashCollectorTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() {
|
||||
EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(Return());
|
||||
|
||||
collector_.Initialize(CountCrash, IsMetrics);
|
||||
test_dir_ = FilePath("test");
|
||||
base::CreateDirectory(test_dir_);
|
||||
chromeos::ClearLog();
|
||||
}
|
||||
|
||||
void TearDown() {
|
||||
base::DeleteFile(test_dir_, true);
|
||||
}
|
||||
|
||||
bool CheckHasCapacity();
|
||||
|
||||
protected:
|
||||
CrashCollectorMock collector_;
|
||||
FilePath test_dir_;
|
||||
};
|
||||
|
||||
TEST_F(CrashCollectorTest, Initialize) {
|
||||
ASSERT_TRUE(CountCrash == collector_.count_crash_function_);
|
||||
ASSERT_TRUE(IsMetrics == collector_.is_feedback_allowed_function_);
|
||||
}
|
||||
|
||||
TEST_F(CrashCollectorTest, WriteNewFile) {
|
||||
FilePath test_file = test_dir_.Append("test_new");
|
||||
const char kBuffer[] = "buffer";
|
||||
EXPECT_EQ(strlen(kBuffer),
|
||||
collector_.WriteNewFile(test_file,
|
||||
kBuffer,
|
||||
strlen(kBuffer)));
|
||||
EXPECT_LT(collector_.WriteNewFile(test_file,
|
||||
kBuffer,
|
||||
strlen(kBuffer)), 0);
|
||||
}
|
||||
|
||||
TEST_F(CrashCollectorTest, Sanitize) {
|
||||
EXPECT_EQ("chrome", collector_.Sanitize("chrome"));
|
||||
EXPECT_EQ("CHROME", collector_.Sanitize("CHROME"));
|
||||
EXPECT_EQ("1chrome2", collector_.Sanitize("1chrome2"));
|
||||
EXPECT_EQ("chrome__deleted_", collector_.Sanitize("chrome (deleted)"));
|
||||
EXPECT_EQ("foo_bar", collector_.Sanitize("foo.bar"));
|
||||
EXPECT_EQ("", collector_.Sanitize(""));
|
||||
EXPECT_EQ("_", collector_.Sanitize(" "));
|
||||
}
|
||||
|
||||
TEST_F(CrashCollectorTest, GetCrashDirectoryInfo) {
|
||||
FilePath path;
|
||||
const int kRootUid = 0;
|
||||
const int kRootGid = 0;
|
||||
const int kNtpUid = 5;
|
||||
const int kChronosUid = 1000;
|
||||
const int kChronosGid = 1001;
|
||||
const mode_t kExpectedSystemMode = 01755;
|
||||
const mode_t kExpectedUserMode = 0755;
|
||||
|
||||
mode_t directory_mode;
|
||||
uid_t directory_owner;
|
||||
gid_t directory_group;
|
||||
|
||||
path = collector_.GetCrashDirectoryInfo(kRootUid,
|
||||
kChronosUid,
|
||||
kChronosGid,
|
||||
&directory_mode,
|
||||
&directory_owner,
|
||||
&directory_group);
|
||||
EXPECT_EQ("/var/spool/crash", path.value());
|
||||
EXPECT_EQ(kExpectedSystemMode, directory_mode);
|
||||
EXPECT_EQ(kRootUid, directory_owner);
|
||||
EXPECT_EQ(kRootGid, directory_group);
|
||||
|
||||
path = collector_.GetCrashDirectoryInfo(kNtpUid,
|
||||
kChronosUid,
|
||||
kChronosGid,
|
||||
&directory_mode,
|
||||
&directory_owner,
|
||||
&directory_group);
|
||||
EXPECT_EQ("/var/spool/crash", path.value());
|
||||
EXPECT_EQ(kExpectedSystemMode, directory_mode);
|
||||
EXPECT_EQ(kRootUid, directory_owner);
|
||||
EXPECT_EQ(kRootGid, directory_group);
|
||||
|
||||
EXPECT_CALL(collector_, GetActiveUserSessions(testing::_))
|
||||
.WillOnce(Invoke(&GetActiveUserSessionsImpl));
|
||||
|
||||
EXPECT_EQ(collector_.IsUserSpecificDirectoryEnabled(), true);
|
||||
|
||||
path = collector_.GetCrashDirectoryInfo(kChronosUid,
|
||||
kChronosUid,
|
||||
kChronosGid,
|
||||
&directory_mode,
|
||||
&directory_owner,
|
||||
&directory_group);
|
||||
EXPECT_EQ("/home/user/hashcakes/crash", path.value());
|
||||
EXPECT_EQ(kExpectedUserMode, directory_mode);
|
||||
EXPECT_EQ(kChronosUid, directory_owner);
|
||||
EXPECT_EQ(kChronosGid, directory_group);
|
||||
}
|
||||
|
||||
TEST_F(CrashCollectorTest, FormatDumpBasename) {
|
||||
struct tm tm = {0};
|
||||
tm.tm_sec = 15;
|
||||
tm.tm_min = 50;
|
||||
tm.tm_hour = 13;
|
||||
tm.tm_mday = 23;
|
||||
tm.tm_mon = 4;
|
||||
tm.tm_year = 110;
|
||||
tm.tm_isdst = -1;
|
||||
std::string basename =
|
||||
collector_.FormatDumpBasename("foo", mktime(&tm), 100);
|
||||
ASSERT_EQ("foo.20100523.135015.100", basename);
|
||||
}
|
||||
|
||||
TEST_F(CrashCollectorTest, GetCrashPath) {
|
||||
EXPECT_EQ("/var/spool/crash/myprog.20100101.1200.1234.core",
|
||||
collector_.GetCrashPath(FilePath("/var/spool/crash"),
|
||||
"myprog.20100101.1200.1234",
|
||||
"core").value());
|
||||
EXPECT_EQ("/home/chronos/user/crash/chrome.20100101.1200.1234.dmp",
|
||||
collector_.GetCrashPath(FilePath("/home/chronos/user/crash"),
|
||||
"chrome.20100101.1200.1234",
|
||||
"dmp").value());
|
||||
}
|
||||
|
||||
|
||||
bool CrashCollectorTest::CheckHasCapacity() {
|
||||
static const char kFullMessage[] = "Crash directory test already full";
|
||||
bool has_capacity = collector_.CheckHasCapacity(test_dir_);
|
||||
bool has_message = FindLog(kFullMessage);
|
||||
EXPECT_EQ(has_message, !has_capacity);
|
||||
return has_capacity;
|
||||
}
|
||||
|
||||
TEST_F(CrashCollectorTest, CheckHasCapacityUsual) {
|
||||
// Test kMaxCrashDirectorySize - 1 non-meta files can be added.
|
||||
for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 1; ++i) {
|
||||
base::WriteFile(test_dir_.Append(StringPrintf("file%d.core", i)), "", 0);
|
||||
EXPECT_TRUE(CheckHasCapacity());
|
||||
}
|
||||
|
||||
// Test an additional kMaxCrashDirectorySize - 1 meta files fit.
|
||||
for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 1; ++i) {
|
||||
base::WriteFile(test_dir_.Append(StringPrintf("file%d.meta", i)), "", 0);
|
||||
EXPECT_TRUE(CheckHasCapacity());
|
||||
}
|
||||
|
||||
// Test an additional kMaxCrashDirectorySize meta files don't fit.
|
||||
for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize; ++i) {
|
||||
base::WriteFile(test_dir_.Append(StringPrintf("overage%d.meta", i)), "", 0);
|
||||
EXPECT_FALSE(CheckHasCapacity());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CrashCollectorTest, CheckHasCapacityCorrectBasename) {
|
||||
// Test kMaxCrashDirectorySize - 1 files can be added.
|
||||
for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 1; ++i) {
|
||||
base::WriteFile(test_dir_.Append(StringPrintf("file.%d.core", i)), "", 0);
|
||||
EXPECT_TRUE(CheckHasCapacity());
|
||||
}
|
||||
base::WriteFile(test_dir_.Append("file.last.core"), "", 0);
|
||||
EXPECT_FALSE(CheckHasCapacity());
|
||||
}
|
||||
|
||||
TEST_F(CrashCollectorTest, CheckHasCapacityStrangeNames) {
|
||||
// Test many files with different extensions and same base fit.
|
||||
for (int i = 0; i < 5 * CrashCollector::kMaxCrashDirectorySize; ++i) {
|
||||
base::WriteFile(test_dir_.Append(StringPrintf("a.%d", i)), "", 0);
|
||||
EXPECT_TRUE(CheckHasCapacity());
|
||||
}
|
||||
// Test dot files are treated as individual files.
|
||||
for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 2; ++i) {
|
||||
base::WriteFile(test_dir_.Append(StringPrintf(".file%d", i)), "", 0);
|
||||
EXPECT_TRUE(CheckHasCapacity());
|
||||
}
|
||||
base::WriteFile(test_dir_.Append("normal.meta"), "", 0);
|
||||
EXPECT_FALSE(CheckHasCapacity());
|
||||
}
|
||||
|
||||
TEST_F(CrashCollectorTest, MetaData) {
|
||||
const char kMetaFileBasename[] = "generated.meta";
|
||||
FilePath meta_file = test_dir_.Append(kMetaFileBasename);
|
||||
FilePath lsb_release = test_dir_.Append("lsb-release");
|
||||
FilePath payload_file = test_dir_.Append("payload-file");
|
||||
std::string contents;
|
||||
collector_.lsb_release_ = lsb_release.value();
|
||||
const char kLsbContents[] =
|
||||
"CHROMEOS_RELEASE_BOARD=lumpy\n"
|
||||
"CHROMEOS_RELEASE_VERSION=6727.0.2015_01_26_0853\n"
|
||||
"CHROMEOS_RELEASE_NAME=Chromium OS\n";
|
||||
ASSERT_TRUE(base::WriteFile(lsb_release, kLsbContents, strlen(kLsbContents)));
|
||||
const char kPayload[] = "foo";
|
||||
ASSERT_TRUE(base::WriteFile(payload_file, kPayload, strlen(kPayload)));
|
||||
collector_.AddCrashMetaData("foo", "bar");
|
||||
collector_.WriteCrashMetaData(meta_file, "kernel", payload_file.value());
|
||||
EXPECT_TRUE(base::ReadFileToString(meta_file, &contents));
|
||||
const char kExpectedMeta[] =
|
||||
"foo=bar\n"
|
||||
"exec_name=kernel\n"
|
||||
"ver=6727.0.2015_01_26_0853\n"
|
||||
"payload=test/payload-file\n"
|
||||
"payload_size=3\n"
|
||||
"done=1\n";
|
||||
EXPECT_EQ(kExpectedMeta, contents);
|
||||
|
||||
// Test target of symlink is not overwritten.
|
||||
payload_file = test_dir_.Append("payload2-file");
|
||||
ASSERT_TRUE(base::WriteFile(payload_file, kPayload, strlen(kPayload)));
|
||||
FilePath meta_symlink_path = test_dir_.Append("symlink.meta");
|
||||
ASSERT_EQ(0,
|
||||
symlink(kMetaFileBasename,
|
||||
meta_symlink_path.value().c_str()));
|
||||
ASSERT_TRUE(base::PathExists(meta_symlink_path));
|
||||
chromeos::ClearLog();
|
||||
collector_.WriteCrashMetaData(meta_symlink_path,
|
||||
"kernel",
|
||||
payload_file.value());
|
||||
// Target metadata contents should have stayed the same.
|
||||
contents.clear();
|
||||
EXPECT_TRUE(base::ReadFileToString(meta_file, &contents));
|
||||
EXPECT_EQ(kExpectedMeta, contents);
|
||||
EXPECT_TRUE(FindLog("Unable to write"));
|
||||
|
||||
// Test target of dangling symlink is not created.
|
||||
base::DeleteFile(meta_file, false);
|
||||
ASSERT_FALSE(base::PathExists(meta_file));
|
||||
chromeos::ClearLog();
|
||||
collector_.WriteCrashMetaData(meta_symlink_path, "kernel",
|
||||
payload_file.value());
|
||||
EXPECT_FALSE(base::PathExists(meta_file));
|
||||
EXPECT_TRUE(FindLog("Unable to write"));
|
||||
}
|
||||
|
||||
TEST_F(CrashCollectorTest, GetLogContents) {
|
||||
FilePath config_file = test_dir_.Append("crash_config");
|
||||
FilePath output_file = test_dir_.Append("crash_log");
|
||||
const char kConfigContents[] =
|
||||
"foobar=echo hello there | \\\n sed -e \"s/there/world/\"";
|
||||
ASSERT_TRUE(
|
||||
base::WriteFile(config_file, kConfigContents, strlen(kConfigContents)));
|
||||
base::DeleteFile(FilePath(output_file), false);
|
||||
EXPECT_FALSE(collector_.GetLogContents(config_file,
|
||||
"barfoo",
|
||||
output_file));
|
||||
EXPECT_FALSE(base::PathExists(output_file));
|
||||
base::DeleteFile(FilePath(output_file), false);
|
||||
EXPECT_TRUE(collector_.GetLogContents(config_file,
|
||||
"foobar",
|
||||
output_file));
|
||||
ASSERT_TRUE(base::PathExists(output_file));
|
||||
std::string contents;
|
||||
EXPECT_TRUE(base::ReadFileToString(output_file, &contents));
|
||||
EXPECT_EQ("hello world\n", contents);
|
||||
}
|
23
crash_reporter/crash_collector_test.h
Normal file
23
crash_reporter/crash_collector_test.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CRASH_REPORTER_CRASH_COLLECTOR_TEST_H_
|
||||
#define CRASH_REPORTER_CRASH_COLLECTOR_TEST_H_
|
||||
|
||||
#include "crash-reporter/crash_collector.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
class CrashCollectorMock : public CrashCollector {
|
||||
public:
|
||||
MOCK_METHOD0(SetUpDBus, void());
|
||||
MOCK_METHOD1(GetActiveUserSessions,
|
||||
bool(std::map<std::string, std::string> *sessions));
|
||||
};
|
||||
|
||||
#endif // CRASH_REPORTER_CRASH_COLLECTOR_TEST_H_
|
334
crash_reporter/crash_reporter.cc
Normal file
334
crash_reporter/crash_reporter.cc
Normal file
|
@ -0,0 +1,334 @@
|
|||
// Copyright (c) 2012 The Chromium OS 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 <fcntl.h> // for open
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <base/files/file_util.h>
|
||||
#include <base/logging.h>
|
||||
#include <base/strings/string_split.h>
|
||||
#include <base/strings/string_util.h>
|
||||
#include <base/strings/stringprintf.h>
|
||||
#include <chromeos/flag_helper.h>
|
||||
#include <chromeos/syslog_logging.h>
|
||||
#include <metrics/metrics_library.h>
|
||||
|
||||
#include "crash-reporter/chrome_collector.h"
|
||||
#include "crash-reporter/kernel_collector.h"
|
||||
#include "crash-reporter/kernel_warning_collector.h"
|
||||
#include "crash-reporter/udev_collector.h"
|
||||
#include "crash-reporter/unclean_shutdown_collector.h"
|
||||
#include "crash-reporter/user_collector.h"
|
||||
|
||||
static const char kCrashCounterHistogram[] = "Logging.CrashCounter";
|
||||
static const char kUserCrashSignal[] =
|
||||
"org.chromium.CrashReporter.UserCrash";
|
||||
static const char kKernelCrashDetected[] = "/var/run/kernel-crash-detected";
|
||||
static const char kUncleanShutdownDetected[] =
|
||||
"/var/run/unclean-shutdown-detected";
|
||||
|
||||
// Enumeration of kinds of crashes to be used in the CrashCounter histogram.
|
||||
enum CrashKinds {
|
||||
kCrashKindUncleanShutdown = 1,
|
||||
kCrashKindUser = 2,
|
||||
kCrashKindKernel = 3,
|
||||
kCrashKindUdev = 4,
|
||||
kCrashKindKernelWarning = 5,
|
||||
kCrashKindMax
|
||||
};
|
||||
|
||||
static MetricsLibrary s_metrics_lib;
|
||||
|
||||
using base::FilePath;
|
||||
using base::StringPrintf;
|
||||
|
||||
static bool IsFeedbackAllowed() {
|
||||
return s_metrics_lib.AreMetricsEnabled();
|
||||
}
|
||||
|
||||
static bool TouchFile(const FilePath &file_path) {
|
||||
return base::WriteFile(file_path, "", 0) == 0;
|
||||
}
|
||||
|
||||
static void SendCrashMetrics(CrashKinds type, const char* name) {
|
||||
// TODO(kmixter): We can remove this histogram as part of
|
||||
// crosbug.com/11163.
|
||||
s_metrics_lib.SendEnumToUMA(kCrashCounterHistogram, type, kCrashKindMax);
|
||||
s_metrics_lib.SendCrashToUMA(name);
|
||||
}
|
||||
|
||||
static void CountKernelCrash() {
|
||||
SendCrashMetrics(kCrashKindKernel, "kernel");
|
||||
}
|
||||
|
||||
static void CountUdevCrash() {
|
||||
SendCrashMetrics(kCrashKindUdev, "udevcrash");
|
||||
}
|
||||
|
||||
static void CountUncleanShutdown() {
|
||||
SendCrashMetrics(kCrashKindUncleanShutdown, "uncleanshutdown");
|
||||
}
|
||||
|
||||
static void CountUserCrash() {
|
||||
SendCrashMetrics(kCrashKindUser, "user");
|
||||
std::string command = StringPrintf(
|
||||
"/usr/bin/dbus-send --type=signal --system / \"%s\" &",
|
||||
kUserCrashSignal);
|
||||
// Announce through D-Bus whenever a user crash happens. This is
|
||||
// used by the metrics daemon to log active use time between
|
||||
// crashes.
|
||||
//
|
||||
// This could be done more efficiently by explicit fork/exec or
|
||||
// using a dbus library directly. However, this should run
|
||||
// relatively rarely and longer term we may need to implement a
|
||||
// better way to do this that doesn't rely on D-Bus.
|
||||
//
|
||||
// We run in the background in case dbus daemon itself is crashed
|
||||
// and not responding. This allows us to not block and potentially
|
||||
// deadlock on a dbus-daemon crash. If dbus-daemon crashes without
|
||||
// restarting, each crash will fork off a lot of dbus-send
|
||||
// processes. Such a system is in a unusable state and will need
|
||||
// to be restarted anyway.
|
||||
|
||||
int status = system(command.c_str());
|
||||
LOG_IF(WARNING, status != 0) << "dbus-send running failed";
|
||||
}
|
||||
|
||||
static void CountChromeCrash() {
|
||||
// For now, consider chrome crashes the same as user crashes for reporting
|
||||
// purposes.
|
||||
CountUserCrash();
|
||||
}
|
||||
|
||||
|
||||
static int Initialize(KernelCollector *kernel_collector,
|
||||
UserCollector *user_collector,
|
||||
UncleanShutdownCollector *unclean_shutdown_collector,
|
||||
const bool unclean_check,
|
||||
const bool clean_shutdown) {
|
||||
CHECK(!clean_shutdown) << "Incompatible options";
|
||||
|
||||
bool was_kernel_crash = false;
|
||||
bool was_unclean_shutdown = false;
|
||||
kernel_collector->Enable();
|
||||
if (kernel_collector->is_enabled()) {
|
||||
was_kernel_crash = kernel_collector->Collect();
|
||||
}
|
||||
|
||||
if (unclean_check) {
|
||||
was_unclean_shutdown = unclean_shutdown_collector->Collect();
|
||||
}
|
||||
|
||||
// Touch a file to notify the metrics daemon that a kernel
|
||||
// crash has been detected so that it can log the time since
|
||||
// the last kernel crash.
|
||||
if (IsFeedbackAllowed()) {
|
||||
if (was_kernel_crash) {
|
||||
TouchFile(FilePath(kKernelCrashDetected));
|
||||
} else if (was_unclean_shutdown) {
|
||||
// We only count an unclean shutdown if it did not come with
|
||||
// an associated kernel crash.
|
||||
TouchFile(FilePath(kUncleanShutdownDetected));
|
||||
}
|
||||
}
|
||||
|
||||
// Must enable the unclean shutdown collector *after* collecting.
|
||||
unclean_shutdown_collector->Enable();
|
||||
user_collector->Enable();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int HandleUserCrash(UserCollector *user_collector,
|
||||
const std::string& user, const bool crash_test) {
|
||||
// Handle a specific user space crash.
|
||||
CHECK(!user.empty()) << "--user= must be set";
|
||||
|
||||
// Make it possible to test what happens when we crash while
|
||||
// handling a crash.
|
||||
if (crash_test) {
|
||||
*(volatile char *)0 = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Accumulate logs to help in diagnosing failures during user collection.
|
||||
chromeos::LogToString(true);
|
||||
// Handle the crash, get the name of the process from procfs.
|
||||
bool handled = user_collector->HandleCrash(user, nullptr);
|
||||
chromeos::LogToString(false);
|
||||
if (!handled)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int HandleChromeCrash(ChromeCollector *chrome_collector,
|
||||
const std::string& chrome_dump_file,
|
||||
const std::string& pid,
|
||||
const std::string& uid,
|
||||
const std::string& exe) {
|
||||
CHECK(!chrome_dump_file.empty()) << "--chrome= must be set";
|
||||
CHECK(!pid.empty()) << "--pid= must be set";
|
||||
CHECK(!uid.empty()) << "--uid= must be set";
|
||||
CHECK(!exe.empty()) << "--exe= must be set";
|
||||
|
||||
chromeos::LogToString(true);
|
||||
bool handled = chrome_collector->HandleCrash(FilePath(chrome_dump_file),
|
||||
pid, uid, exe);
|
||||
chromeos::LogToString(false);
|
||||
if (!handled)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int HandleUdevCrash(UdevCollector *udev_collector,
|
||||
const std::string& udev_event) {
|
||||
// Handle a crash indicated by a udev event.
|
||||
CHECK(!udev_event.empty()) << "--udev= must be set";
|
||||
|
||||
// Accumulate logs to help in diagnosing failures during user collection.
|
||||
chromeos::LogToString(true);
|
||||
bool handled = udev_collector->HandleCrash(udev_event);
|
||||
chromeos::LogToString(false);
|
||||
if (!handled)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int HandleKernelWarning(KernelWarningCollector
|
||||
*kernel_warning_collector) {
|
||||
// Accumulate logs to help in diagnosing failures during collection.
|
||||
chromeos::LogToString(true);
|
||||
bool handled = kernel_warning_collector->Collect();
|
||||
chromeos::LogToString(false);
|
||||
if (!handled)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Interactive/diagnostics mode for generating kernel crash signatures.
|
||||
static int GenerateKernelSignature(KernelCollector *kernel_collector,
|
||||
const std::string& kernel_signature_file) {
|
||||
std::string kcrash_contents;
|
||||
std::string signature;
|
||||
if (!base::ReadFileToString(FilePath(kernel_signature_file),
|
||||
&kcrash_contents)) {
|
||||
fprintf(stderr, "Could not read file.\n");
|
||||
return 1;
|
||||
}
|
||||
if (!kernel_collector->ComputeKernelStackSignature(
|
||||
kcrash_contents,
|
||||
&signature,
|
||||
true)) {
|
||||
fprintf(stderr, "Signature could not be generated.\n");
|
||||
return 1;
|
||||
}
|
||||
printf("Kernel crash signature is \"%s\".\n", signature.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Ensure stdout, stdin, and stderr are open file descriptors. If
|
||||
// they are not, any code which writes to stderr/stdout may write out
|
||||
// to files opened during execution. In particular, when
|
||||
// crash_reporter is run by the kernel coredump pipe handler (via
|
||||
// kthread_create/kernel_execve), it will not have file table entries
|
||||
// 1 and 2 (stdout and stderr) populated. We populate them here.
|
||||
static void OpenStandardFileDescriptors() {
|
||||
int new_fd = -1;
|
||||
// We open /dev/null to fill in any of the standard [0, 2] file
|
||||
// descriptors. We leave these open for the duration of the
|
||||
// process. This works because open returns the lowest numbered
|
||||
// invalid fd.
|
||||
do {
|
||||
new_fd = open("/dev/null", 0);
|
||||
CHECK_GE(new_fd, 0) << "Unable to open /dev/null";
|
||||
} while (new_fd >= 0 && new_fd <= 2);
|
||||
close(new_fd);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
DEFINE_bool(init, false, "Initialize crash logging");
|
||||
DEFINE_bool(clean_shutdown, false, "Signal clean shutdown");
|
||||
DEFINE_string(generate_kernel_signature, "",
|
||||
"Generate signature from given kcrash file");
|
||||
DEFINE_bool(crash_test, false, "Crash test");
|
||||
DEFINE_string(user, "", "User crash info (pid:signal:exec_name)");
|
||||
DEFINE_bool(unclean_check, true, "Check for unclean shutdown");
|
||||
DEFINE_string(udev, "", "Udev event description (type:device:subsystem)");
|
||||
DEFINE_bool(kernel_warning, false, "Report collected kernel warning");
|
||||
DEFINE_string(chrome, "", "Chrome crash dump file");
|
||||
DEFINE_string(pid, "", "PID of crashing process");
|
||||
DEFINE_string(uid, "", "UID of crashing process");
|
||||
DEFINE_string(exe, "", "Executable name of crashing process");
|
||||
DEFINE_bool(core2md_failure, false, "Core2md failure test");
|
||||
DEFINE_bool(directory_failure, false, "Spool directory failure test");
|
||||
DEFINE_string(filter_in, "",
|
||||
"Ignore all crashes but this for testing");
|
||||
|
||||
OpenStandardFileDescriptors();
|
||||
FilePath my_path = base::MakeAbsoluteFilePath(FilePath(argv[0]));
|
||||
s_metrics_lib.Init();
|
||||
chromeos::FlagHelper::Init(argc, argv, "Chromium OS Crash Reporter");
|
||||
chromeos::OpenLog(my_path.BaseName().value().c_str(), true);
|
||||
chromeos::InitLog(chromeos::kLogToSyslog);
|
||||
|
||||
KernelCollector kernel_collector;
|
||||
kernel_collector.Initialize(CountKernelCrash, IsFeedbackAllowed);
|
||||
UserCollector user_collector;
|
||||
user_collector.Initialize(CountUserCrash,
|
||||
my_path.value(),
|
||||
IsFeedbackAllowed,
|
||||
true, // generate_diagnostics
|
||||
FLAGS_core2md_failure,
|
||||
FLAGS_directory_failure,
|
||||
FLAGS_filter_in);
|
||||
UncleanShutdownCollector unclean_shutdown_collector;
|
||||
unclean_shutdown_collector.Initialize(CountUncleanShutdown,
|
||||
IsFeedbackAllowed);
|
||||
UdevCollector udev_collector;
|
||||
udev_collector.Initialize(CountUdevCrash, IsFeedbackAllowed);
|
||||
ChromeCollector chrome_collector;
|
||||
chrome_collector.Initialize(CountChromeCrash, IsFeedbackAllowed);
|
||||
|
||||
KernelWarningCollector kernel_warning_collector;
|
||||
kernel_warning_collector.Initialize(CountUdevCrash, IsFeedbackAllowed);
|
||||
|
||||
if (FLAGS_init) {
|
||||
return Initialize(&kernel_collector,
|
||||
&user_collector,
|
||||
&unclean_shutdown_collector,
|
||||
FLAGS_unclean_check,
|
||||
FLAGS_clean_shutdown);
|
||||
}
|
||||
|
||||
if (FLAGS_clean_shutdown) {
|
||||
unclean_shutdown_collector.Disable();
|
||||
user_collector.Disable();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!FLAGS_generate_kernel_signature.empty()) {
|
||||
return GenerateKernelSignature(&kernel_collector,
|
||||
FLAGS_generate_kernel_signature);
|
||||
}
|
||||
|
||||
if (!FLAGS_udev.empty()) {
|
||||
return HandleUdevCrash(&udev_collector, FLAGS_udev);
|
||||
}
|
||||
|
||||
if (FLAGS_kernel_warning) {
|
||||
return HandleKernelWarning(&kernel_warning_collector);
|
||||
}
|
||||
|
||||
if (!FLAGS_chrome.empty()) {
|
||||
return HandleChromeCrash(&chrome_collector,
|
||||
FLAGS_chrome,
|
||||
FLAGS_pid,
|
||||
FLAGS_uid,
|
||||
FLAGS_exe);
|
||||
}
|
||||
|
||||
return HandleUserCrash(&user_collector, FLAGS_user, FLAGS_crash_test);
|
||||
}
|
112
crash_reporter/crash_reporter_logs.conf
Normal file
112
crash_reporter/crash_reporter_logs.conf
Normal file
|
@ -0,0 +1,112 @@
|
|||
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can
|
||||
# be found in the LICENSE file.
|
||||
|
||||
# This file is parsed by chromeos::KeyValueStore. It has the format:
|
||||
#
|
||||
# <basename>=<shell command>\n
|
||||
#
|
||||
# Commands may be split across multiple lines using trailing backslashes.
|
||||
#
|
||||
# When an executable named <basename> crashes, the corresponding command is
|
||||
# executed and its standard output and standard error are attached to the crash
|
||||
# report.
|
||||
#
|
||||
# Use caution in modifying this file. Only run common Unix commands here, as
|
||||
# these commands will be run when a crash has recently occurred and we should
|
||||
# avoid running anything that might cause another crash. Similarly, these
|
||||
# commands block notification of the crash to parent processes, so commands
|
||||
# should execute quickly.
|
||||
|
||||
update_engine=cat $(ls -1tr /var/log/update_engine | tail -5 | \
|
||||
sed s.^./var/log/update_engine/.) | tail -c 50000
|
||||
|
||||
# The cros_installer output is logged into the update engine log file,
|
||||
# so it is handled in the same way as update_engine.
|
||||
cros_installer=cat $(ls -1tr /var/log/update_engine | tail -5 | \
|
||||
sed s.^./var/log/update_engine/.) | tail -c 50000
|
||||
|
||||
# Dump the last 20 lines of the last two files in Chrome's system and user log
|
||||
# directories, along with the last 20 messages from the session manager.
|
||||
chrome=\
|
||||
for f in $(ls -1rt /var/log/chrome/chrome_[0-9]* | tail -2) \
|
||||
$(ls -1rt /home/chronos/u-*/log/chrome_[0-9]* 2>/dev/null | tail -2); do \
|
||||
echo "===$f (tail)==="; \
|
||||
tail -20 $f; \
|
||||
echo EOF; \
|
||||
echo; \
|
||||
done; \
|
||||
echo "===session_manager (tail)==="; \
|
||||
awk '$3 ~ "^session_manager\[" { print }' /var/log/messages | tail -20; \
|
||||
echo EOF
|
||||
|
||||
# The following rule is used for generating additional diagnostics when
|
||||
# collection of user crashes fails. This output should not be too large
|
||||
# as it is stored in memory. The output format specified for 'ps' is the
|
||||
# same as with the "u" ("user-oriented") option, except it doesn't show
|
||||
# the commands' arguments (i.e. "comm" instead of "command").
|
||||
crash_reporter-user-collection=\
|
||||
echo "===ps output==="; \
|
||||
ps axw -o user,pid,%cpu,%mem,vsz,rss,tname,stat,start_time,bsdtime,comm | \
|
||||
tail -c 25000; \
|
||||
echo "===meminfo==="; \
|
||||
cat /proc/meminfo
|
||||
|
||||
# This rule is similar to the crash_reporter-user-collection rule, except it is
|
||||
# run for kernel errors reported through udev events.
|
||||
crash_reporter-udev-collection-change-card0-drm=\
|
||||
for dri in /sys/kernel/debug/dri/*; do \
|
||||
echo "===$dri/i915_error_state==="; \
|
||||
cat $dri/i915_error_state; \
|
||||
done
|
||||
|
||||
# When trackpad driver cyapa detects some abnormal behavior, we collect
|
||||
# additional logs from kernel messages.
|
||||
crash_reporter-udev-collection-change--i2c-cyapa=\
|
||||
/usr/sbin/kernel_log_collector.sh cyapa 30
|
||||
# When trackpad/touchscreen driver atmel_mxt_ts detects some abnormal behavior,
|
||||
# we collect additional logs from kernel messages.
|
||||
crash_reporter-udev-collection-change--i2c-atmel_mxt_ts=\
|
||||
/usr/sbin/kernel_log_collector.sh atmel 30
|
||||
# When touch device noise are detected, we collect relevant logs.
|
||||
# (crosbug.com/p/16788)
|
||||
crash_reporter-udev-collection---TouchNoise=cat /var/log/touch_noise.log
|
||||
# Periodically collect touch event log for debugging (crosbug.com/p/17244)
|
||||
crash_reporter-udev-collection---TouchEvent=cat /var/log/touch_event.log
|
||||
|
||||
# Collect the last 50 lines of /var/log/messages and /var/log/net.log for
|
||||
# intel wifi driver (iwlwifi) for debugging purpose.
|
||||
crash_reporter-udev-collection-devcoredump-iwlwifi=\
|
||||
echo "===/var/log/messages==="; \
|
||||
tail -n 50 /var/log/messages; \
|
||||
echo "===/var/log/net.log==="; \
|
||||
tail -n 50 /var/log/net.log; \
|
||||
echo EOF
|
||||
|
||||
# Dump the last 50 lines of the last two powerd log files -- if the job has
|
||||
# already restarted, we want to see the end of the previous instance's logs.
|
||||
powerd=\
|
||||
for f in $(ls -1tr /var/log/power_manager/powerd.[0-9]* | tail -2); do \
|
||||
echo "===$(basename $f) (tail)==="; \
|
||||
tail -50 $f; \
|
||||
echo EOF; \
|
||||
done
|
||||
# If power_supply_info aborts (due to e.g. a bad battery), its failure message
|
||||
# could end up in various places depending on which process was running it.
|
||||
# Attach the end of powerd's log since it might've also logged the underlying
|
||||
# problem.
|
||||
power_supply_info=\
|
||||
echo "===powerd.LATEST (tail)==="; \
|
||||
tail -50 /var/log/power_manager/powerd.LATEST; \
|
||||
echo EOF
|
||||
# powerd_setuid_helper gets run by powerd, so its stdout/stderr will be mixed in
|
||||
# with powerd's stdout/stderr.
|
||||
powerd_setuid_helper=\
|
||||
echo "===powerd.OUT (tail)==="; \
|
||||
tail -50 /var/log/powerd.out; \
|
||||
echo EOF
|
||||
|
||||
# The following rules are only for testing purposes.
|
||||
crash_log_test=echo hello world
|
||||
crash_log_recursion_test=sleep 1 && \
|
||||
/usr/local/autotest/tests/crash_log_recursion_test
|
28
crash_reporter/crash_reporter_logs_test.cc
Normal file
28
crash_reporter/crash_reporter_logs_test.cc
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2015 The Chromium OS 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 <string>
|
||||
|
||||
#include <base/files/file_path.h>
|
||||
#include <chromeos/key_value_store.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace {
|
||||
|
||||
// Name of the checked-in configuration file containing log-collection commands.
|
||||
const char kConfigFile[] = "crash_reporter_logs.conf";
|
||||
|
||||
// Executable name for Chrome. kConfigFile is expected to contain this entry.
|
||||
const char kChromeExecName[] = "chrome";
|
||||
|
||||
} // namespace
|
||||
|
||||
// Tests that the config file is parsable and that Chrome is listed.
|
||||
TEST(CrashReporterLogsTest, ReadConfig) {
|
||||
chromeos::KeyValueStore store;
|
||||
ASSERT_TRUE(store.Load(base::FilePath(kConfigFile)));
|
||||
std::string command;
|
||||
EXPECT_TRUE(store.GetString(kChromeExecName, &command));
|
||||
EXPECT_FALSE(command.empty());
|
||||
}
|
713
crash_reporter/crash_sender
Executable file
713
crash_reporter/crash_sender
Executable file
|
@ -0,0 +1,713 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
set -e
|
||||
|
||||
# Default product ID in crash report (used if GOOGLE_CRASH_* is undefined).
|
||||
CHROMEOS_PRODUCT=ChromeOS
|
||||
|
||||
# File whose existence implies crash reports may be sent, and whose
|
||||
# contents includes our machine's anonymized guid.
|
||||
CONSENT_ID="/home/chronos/Consent To Send Stats"
|
||||
|
||||
# Crash sender lock in case the sender is already running.
|
||||
CRASH_SENDER_LOCK="/var/lock/crash_sender"
|
||||
|
||||
# Path to file that indicates a crash test is currently running.
|
||||
CRASH_TEST_IN_PROGRESS_FILE="/tmp/crash-test-in-progress"
|
||||
|
||||
# Path to find which is required for computing the crash rate.
|
||||
FIND="/usr/bin/find"
|
||||
|
||||
# Set this to 1 in the environment to allow uploading crash reports
|
||||
# for unofficial versions.
|
||||
FORCE_OFFICIAL=${FORCE_OFFICIAL:-0}
|
||||
|
||||
# Path to hardware class description.
|
||||
HWCLASS_PATH="/sys/devices/platform/chromeos_acpi/HWID"
|
||||
|
||||
# Path to file that indicates this is a developer image.
|
||||
LEAVE_CORE_FILE="/root/.leave_core"
|
||||
|
||||
# Path to list_proxies.
|
||||
LIST_PROXIES="/usr/bin/list_proxies"
|
||||
|
||||
# Maximum crashes to send per day.
|
||||
MAX_CRASH_RATE=${MAX_CRASH_RATE:-32}
|
||||
|
||||
# Path to metrics_client.
|
||||
METRICS_CLIENT="/usr/bin/metrics_client"
|
||||
|
||||
# File whose existence mocks crash sending. If empty we pretend the
|
||||
# crash sending was successful, otherwise unsuccessful.
|
||||
MOCK_CRASH_SENDING="/tmp/mock-crash-sending"
|
||||
|
||||
# Set this to 1 in the environment to pretend to have booted in developer
|
||||
# mode. This is used by autotests.
|
||||
MOCK_DEVELOPER_MODE=${MOCK_DEVELOPER_MODE:-0}
|
||||
|
||||
# Ignore PAUSE_CRASH_SENDING file if set.
|
||||
OVERRIDE_PAUSE_SENDING=${OVERRIDE_PAUSE_SENDING:-0}
|
||||
|
||||
# File whose existence causes crash sending to be delayed (for testing).
|
||||
# Must be stateful to enable testing kernel crashes.
|
||||
PAUSE_CRASH_SENDING="/var/lib/crash_sender_paused"
|
||||
|
||||
# URL to send official build crash reports to.
|
||||
REPORT_UPLOAD_PROD_URL="https://clients2.google.com/cr/report"
|
||||
|
||||
# Path to a directory of restricted certificates which includes
|
||||
# a certificate for ${REPORT_UPLOAD_PROD_URL}.
|
||||
RESTRICTED_CERTIFICATES_PATH="/usr/share/chromeos-ca-certificates"
|
||||
|
||||
# File whose existence implies we're running and not to start again.
|
||||
RUN_FILE="/var/run/crash_sender.pid"
|
||||
|
||||
# Maximum time to sleep between sends.
|
||||
SECONDS_SEND_SPREAD=${SECONDS_SEND_SPREAD:-600}
|
||||
|
||||
# Set this to 1 to allow uploading of device coredumps.
|
||||
DEVCOREDUMP_UPLOAD_FLAG_FILE=\
|
||||
"/var/lib/crash_reporter/device_coredump_upload_allowed"
|
||||
|
||||
# The syslog tag for all logging we emit.
|
||||
TAG="$(basename $0)[$$]"
|
||||
|
||||
# Directory to store timestamp files indicating the uploads in the past 24
|
||||
# hours.
|
||||
TIMESTAMPS_DIR="/var/lib/crash_sender"
|
||||
|
||||
# Temp directory for this process.
|
||||
TMP_DIR=""
|
||||
|
||||
# Chrome's crash report log file.
|
||||
CHROME_CRASH_LOG="/var/log/chrome/Crash Reports/uploads.log"
|
||||
|
||||
lecho() {
|
||||
logger -t "${TAG}" "$@"
|
||||
}
|
||||
|
||||
# Returns true if mock is enabled.
|
||||
is_mock() {
|
||||
[ -f "${MOCK_CRASH_SENDING}" ] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
is_mock_successful() {
|
||||
local mock_in=$(cat "${MOCK_CRASH_SENDING}")
|
||||
[ "${mock_in}" = "" ] && return 0 # empty file means success
|
||||
return 1
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [ -n "${TMP_DIR}" ]; then
|
||||
rm -rf "${TMP_DIR}"
|
||||
fi
|
||||
rm -f "${RUN_FILE}"
|
||||
crash_done
|
||||
}
|
||||
|
||||
crash_done() {
|
||||
if is_mock; then
|
||||
# For testing purposes, emit a message to log so that we
|
||||
# know when the test has received all the messages from this run.
|
||||
lecho "crash_sender done."
|
||||
fi
|
||||
}
|
||||
|
||||
is_official_image() {
|
||||
[ ${FORCE_OFFICIAL} -ne 0 ] && return 0
|
||||
grep ^CHROMEOS_RELEASE_DESCRIPTION /etc/lsb-release | grep -q Official
|
||||
}
|
||||
|
||||
# Returns 0 if the a crash test is currently running. NOTE: Mirrors
|
||||
# crash_collector.cc:CrashCollector::IsCrashTestInProgress().
|
||||
is_crash_test_in_progress() {
|
||||
[ -f "${CRASH_TEST_IN_PROGRESS_FILE}" ] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
# Returns 0 if we should consider ourselves to be running on a developer
|
||||
# image. NOTE: Mirrors crash_collector.cc:CrashCollector::IsDeveloperImage().
|
||||
is_developer_image() {
|
||||
# If we're testing crash reporter itself, we don't want to special-case
|
||||
# for developer images.
|
||||
is_crash_test_in_progress && return 1
|
||||
[ -f "${LEAVE_CORE_FILE}" ] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
# Returns 0 if we should consider ourselves to be running on a test image.
|
||||
is_test_image() {
|
||||
# If we're testing crash reporter itself, we don't want to special-case
|
||||
# for test images.
|
||||
is_crash_test_in_progress && return 1
|
||||
case $(get_channel) in
|
||||
test*) return 0;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
# Returns 0 if the machine booted up in developer mode.
|
||||
is_developer_mode() {
|
||||
[ ${MOCK_DEVELOPER_MODE} -ne 0 ] && return 0
|
||||
# If we're testing crash reporter itself, we don't want to special-case
|
||||
# for developer mode.
|
||||
is_crash_test_in_progress && return 1
|
||||
crossystem "devsw_boot?1" # exit status will be accurate
|
||||
}
|
||||
|
||||
# Return 0 if the uploading of device coredumps is allowed.
|
||||
is_device_coredump_upload_allowed() {
|
||||
[ -f "${DEVCOREDUMP_UPLOAD_FLAG_FILE}" ] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
# Generate a uniform random number in 0..max-1.
|
||||
generate_uniform_random() {
|
||||
local max=$1
|
||||
local random="$(od -An -N4 -tu /dev/urandom)"
|
||||
echo $((random % max))
|
||||
}
|
||||
|
||||
# Check if sending a crash now does not exceed the maximum 24hr rate and
|
||||
# commit to doing so, if not.
|
||||
check_rate() {
|
||||
mkdir -p ${TIMESTAMPS_DIR}
|
||||
# Only consider minidumps written in the past 24 hours by removing all older.
|
||||
${FIND} "${TIMESTAMPS_DIR}" -mindepth 1 -mmin +$((24 * 60)) \
|
||||
-exec rm -- '{}' ';'
|
||||
local sends_in_24hrs=$(echo "${TIMESTAMPS_DIR}"/* | wc -w)
|
||||
lecho "Current send rate: ${sends_in_24hrs}sends/24hrs"
|
||||
if [ ${sends_in_24hrs} -ge ${MAX_CRASH_RATE} ]; then
|
||||
lecho "Cannot send more crashes:"
|
||||
lecho " current ${sends_in_24hrs}send/24hrs >= " \
|
||||
"max ${MAX_CRASH_RATE}send/24hrs"
|
||||
return 1
|
||||
fi
|
||||
mktemp "${TIMESTAMPS_DIR}"/XXXX > /dev/null
|
||||
return 0
|
||||
}
|
||||
|
||||
# Gets the base part of a crash report file, such as name.01234.5678.9012 from
|
||||
# name.01234.5678.9012.meta or name.01234.5678.9012.log.tar.xz. We make sure
|
||||
# "name" is sanitized in CrashCollector::Sanitize to not include any periods.
|
||||
get_base() {
|
||||
echo "$1" | cut -d. -f-4
|
||||
}
|
||||
|
||||
get_extension() {
|
||||
local extension="${1##*.}"
|
||||
local filename="${1%.*}"
|
||||
# For gzipped file, we ignore .gz and get the real extension
|
||||
if [ "${extension}" = "gz" ]; then
|
||||
echo "${filename##*.}"
|
||||
else
|
||||
echo "${extension}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Return which kind of report the given metadata file relates to
|
||||
get_kind() {
|
||||
local payload="$(get_key_value "$1" "payload")"
|
||||
if [ ! -r "${payload}" ]; then
|
||||
lecho "Missing payload: ${payload}"
|
||||
echo "undefined"
|
||||
return
|
||||
fi
|
||||
local kind="$(get_extension "${payload}")"
|
||||
if [ "${kind}" = "dmp" ]; then
|
||||
echo "minidump"
|
||||
return
|
||||
fi
|
||||
echo "${kind}"
|
||||
}
|
||||
|
||||
get_key_value() {
|
||||
local file="$1" key="$2" value
|
||||
|
||||
if [ -f "${file}" ]; then
|
||||
# Return the first entry. There shouldn't be more than one anyways.
|
||||
# Substr at length($1) + 2 skips past the key and following = sign (awk
|
||||
# uses 1-based indexes), but preserves embedded = characters.
|
||||
value=$(sed -n "/^${key}[[:space:]]*=/{s:^[^=]*=::p;q}" "${file}")
|
||||
fi
|
||||
|
||||
echo "${value:-undefined}"
|
||||
}
|
||||
|
||||
get_keys() {
|
||||
local file="$1" regex="$2"
|
||||
|
||||
awk -F'[[:space:]=]' -vregex="${regex}" \
|
||||
'match($1, regex) { print $1 }' "${file}"
|
||||
}
|
||||
|
||||
# Return the board name.
|
||||
get_board() {
|
||||
get_key_value "/etc/lsb-release" "CHROMEOS_RELEASE_BOARD"
|
||||
}
|
||||
|
||||
# Return the channel name (sans "-channel" suffix).
|
||||
get_channel() {
|
||||
get_key_value "/etc/lsb-release" "CHROMEOS_RELEASE_TRACK" |
|
||||
sed 's:-channel$::'
|
||||
}
|
||||
|
||||
# Return the hardware class or "undefined".
|
||||
get_hardware_class() {
|
||||
if [ -r "${HWCLASS_PATH}" ]; then
|
||||
cat "${HWCLASS_PATH}"
|
||||
elif crossystem hwid > /dev/null 2>&1; then
|
||||
echo "$(crossystem hwid)"
|
||||
else
|
||||
echo "undefined"
|
||||
fi
|
||||
}
|
||||
|
||||
send_crash() {
|
||||
local meta_path="$1"
|
||||
local report_payload="$(get_key_value "${meta_path}" "payload")"
|
||||
local kind="$(get_kind "${meta_path}")"
|
||||
local exec_name="$(get_key_value "${meta_path}" "exec_name")"
|
||||
local url="${REPORT_UPLOAD_PROD_URL}"
|
||||
local chromeos_version="$(get_key_value "${meta_path}" "ver")"
|
||||
local board="$(get_board)"
|
||||
local hwclass="$(get_hardware_class)"
|
||||
local write_payload_size="$(get_key_value "${meta_path}" "payload_size")"
|
||||
local log="$(get_key_value "${meta_path}" "log")"
|
||||
local sig="$(get_key_value "${meta_path}" "sig")"
|
||||
local send_payload_size="$(stat --printf=%s "${report_payload}" 2>/dev/null)"
|
||||
local product="$(get_key_value "${meta_path}" "upload_var_prod")"
|
||||
local version="$(get_key_value "${meta_path}" "upload_var_ver")"
|
||||
local upload_prefix="$(get_key_value "${meta_path}" "upload_prefix")"
|
||||
local guid
|
||||
|
||||
set -- \
|
||||
-F "write_payload_size=${write_payload_size}" \
|
||||
-F "send_payload_size=${send_payload_size}"
|
||||
if [ "${sig}" != "undefined" ]; then
|
||||
set -- "$@" \
|
||||
-F "sig=${sig}" \
|
||||
-F "sig2=${sig}"
|
||||
fi
|
||||
if [ -r "${report_payload}" ]; then
|
||||
set -- "$@" \
|
||||
-F "upload_file_${kind}=@${report_payload}"
|
||||
fi
|
||||
if [ "${log}" != "undefined" -a -r "${log}" ]; then
|
||||
set -- "$@" \
|
||||
-F "log=@${log}"
|
||||
fi
|
||||
|
||||
if [ "${upload_prefix}" = "undefined" ]; then
|
||||
upload_prefix=""
|
||||
fi
|
||||
|
||||
# Grab any variable that begins with upload_.
|
||||
local v
|
||||
for k in $(get_keys "${meta_path}" "^upload_"); do
|
||||
v="$(get_key_value "${meta_path}" "${k}")"
|
||||
case ${k} in
|
||||
# Product & version are handled separately.
|
||||
upload_var_prod) ;;
|
||||
upload_var_ver) ;;
|
||||
upload_var_*)
|
||||
set -- "$@" -F "${upload_prefix}${k#upload_var_}=${v}"
|
||||
;;
|
||||
upload_file_*)
|
||||
if [ -r "${v}" ]; then
|
||||
set -- "$@" -F "${upload_prefix}${k#upload_file_}=@${v}"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# When uploading Chrome reports we need to report the right product and
|
||||
# version. If the meta file does not specify it, use GOOGLE_CRASH_ID
|
||||
# as the product and GOOGLE_CRASH_VERSION_ID as the version.
|
||||
if [ "${product}" = "undefined" ]; then
|
||||
product="$(get_key_value /etc/os-release 'GOOGLE_CRASH_ID')"
|
||||
fi
|
||||
if [ "${version}" = "undefined" ]; then
|
||||
version="$(get_key_value /etc/os-release 'GOOGLE_CRASH_VERSION_ID')"
|
||||
fi
|
||||
|
||||
# If GOOGLE_CRASH_* is undefined, we look for ID and VERSION_ID in
|
||||
# /etc/os-release.
|
||||
if [ "${product}" = "undefined" ]; then
|
||||
product="$(get_key_value /etc/os-release 'ID')"
|
||||
fi
|
||||
if [ "${version}" = "undefined" ]; then
|
||||
version="$(get_key_value /etc/os-release 'VERSION_ID')"
|
||||
fi
|
||||
|
||||
# If ID or VERSION_ID is undefined, we use the default product name
|
||||
# and CHROMEOS_RELEASE_VERSION from /etc/lsb-release.
|
||||
if [ "${product}" = "undefined" ]; then
|
||||
product="${CHROMEOS_PRODUCT}"
|
||||
fi
|
||||
if [ "${version}" = "undefined" ]; then
|
||||
version="${chromeos_version}"
|
||||
fi
|
||||
|
||||
local image_type
|
||||
if is_test_image; then
|
||||
image_type="test"
|
||||
elif is_developer_image; then
|
||||
image_type="dev"
|
||||
elif [ ${FORCE_OFFICIAL} -ne 0 ]; then
|
||||
image_type="force-official"
|
||||
elif is_mock && ! is_mock_successful; then
|
||||
image_type="mock-fail"
|
||||
fi
|
||||
|
||||
local boot_mode
|
||||
if ! crossystem "cros_debug" > /dev/null 2>&1; then
|
||||
# Sanity-check failed that makes sure crossystem exists.
|
||||
lecho "Cannot determine boot mode due to error running crossystem command"
|
||||
boot_mode="missing-crossystem"
|
||||
elif is_developer_mode; then
|
||||
boot_mode="dev"
|
||||
fi
|
||||
|
||||
# Need to strip dashes ourselves as Chrome preserves it in the file
|
||||
# nowadays. This is also what the Chrome breakpad client does.
|
||||
guid=$(tr -d '-' < "${CONSENT_ID}")
|
||||
|
||||
local error_type="$(get_key_value "${meta_path}" "error_type")"
|
||||
[ "${error_type}" = "undefined" ] && error_type=
|
||||
|
||||
lecho "Sending crash:"
|
||||
if [ "${product}" != "${CHROMEOS_PRODUCT}" ]; then
|
||||
lecho " Sending crash report on behalf of ${product}"
|
||||
fi
|
||||
lecho " Metadata: ${meta_path} (${kind})"
|
||||
lecho " Payload: ${report_payload}"
|
||||
lecho " Version: ${version}"
|
||||
[ -n "${image_type}" ] && lecho " Image type: ${image_type}"
|
||||
[ -n "${boot_mode}" ] && lecho " Boot mode: ${boot_mode}"
|
||||
if is_mock; then
|
||||
lecho " Product: ${product}"
|
||||
lecho " URL: ${url}"
|
||||
lecho " Board: ${board}"
|
||||
lecho " HWClass: ${hwclass}"
|
||||
lecho " write_payload_size: ${write_payload_size}"
|
||||
lecho " send_payload_size: ${send_payload_size}"
|
||||
if [ "${log}" != "undefined" ]; then
|
||||
lecho " log: @${log}"
|
||||
fi
|
||||
if [ "${sig}" != "undefined" ]; then
|
||||
lecho " sig: ${sig}"
|
||||
fi
|
||||
fi
|
||||
lecho " Exec name: ${exec_name}"
|
||||
[ -n "${error_type}" ] && lecho " Error type: ${error_type}"
|
||||
if is_mock; then
|
||||
if ! is_mock_successful; then
|
||||
lecho "Mocking unsuccessful send"
|
||||
return 1
|
||||
fi
|
||||
lecho "Mocking successful send"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Read in the first proxy, if any, for a given URL. NOTE: The
|
||||
# double-quotes are necessary due to a bug in dash with the "local"
|
||||
# builtin command and values that have spaces in them (see
|
||||
# "https://bugs.launchpad.net/ubuntu/+source/dash/+bug/139097").
|
||||
if [ -f "${LIST_PROXIES}" ]; then
|
||||
local proxy ret
|
||||
proxy=$("${LIST_PROXIES}" --quiet "${url}")
|
||||
ret=$?
|
||||
if [ ${ret} -ne 0 ]; then
|
||||
proxy=''
|
||||
lecho -psyslog.warn \
|
||||
"Listing proxies failed with exit code ${ret}"
|
||||
else
|
||||
proxy=$(echo "${proxy}" | head -1)
|
||||
fi
|
||||
fi
|
||||
# if a direct connection should be used, unset the proxy variable.
|
||||
[ "${proxy}" = "direct://" ] && proxy=
|
||||
local report_id="${TMP_DIR}/report_id"
|
||||
local curl_stderr="${TMP_DIR}/curl_stderr"
|
||||
|
||||
set +e
|
||||
curl "${url}" -v ${proxy:+--proxy "$proxy"} \
|
||||
--capath "${RESTRICTED_CERTIFICATES_PATH}" --ciphers HIGH \
|
||||
-F "prod=${product}" \
|
||||
-F "ver=${version}" \
|
||||
-F "board=${board}" \
|
||||
-F "hwclass=${hwclass}" \
|
||||
-F "exec_name=${exec_name}" \
|
||||
${image_type:+-F "image_type=${image_type}"} \
|
||||
${boot_mode:+-F "boot_mode=${boot_mode}"} \
|
||||
${error_type:+-F "error_type=${error_type}"} \
|
||||
-F "guid=${guid}" \
|
||||
-o "${report_id}" \
|
||||
"$@" \
|
||||
2>"${curl_stderr}"
|
||||
curl_result=$?
|
||||
set -e
|
||||
|
||||
if [ ${curl_result} -eq 0 ]; then
|
||||
local id="$(cat "${report_id}")"
|
||||
local product_name
|
||||
local timestamp="$(date +%s)"
|
||||
case ${product} in
|
||||
Chrome_ChromeOS)
|
||||
if is_official_image; then
|
||||
product_name="Chrome"
|
||||
else
|
||||
product_name="Chromium"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if is_official_image; then
|
||||
product_name="ChromeOS"
|
||||
else
|
||||
product_name="ChromiumOS"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
printf '%s,%s,%s\n' \
|
||||
"${timestamp}" "${id}" "${product_name}" >> "${CHROME_CRASH_LOG}"
|
||||
lecho "Crash report receipt ID ${id}"
|
||||
else
|
||||
lecho "Crash sending failed with exit code ${curl_result}: " \
|
||||
"$(cat "${curl_stderr}")"
|
||||
fi
|
||||
|
||||
rm -f "${report_id}"
|
||||
|
||||
return ${curl_result}
|
||||
}
|
||||
|
||||
# *.meta files always end with done=1 so we can tell if they are complete.
|
||||
is_complete_metadata() {
|
||||
grep -q "done=1" "$1"
|
||||
}
|
||||
|
||||
# Remove the given report path.
|
||||
remove_report() {
|
||||
local base="${1%.*}"
|
||||
rm -f -- "${base}".*
|
||||
}
|
||||
|
||||
# Send all crashes from the given directory. This applies even when we're on a
|
||||
# 3G connection (see crosbug.com/3304 for discussion).
|
||||
send_crashes() {
|
||||
local dir="$1"
|
||||
|
||||
if [ ! -d "${dir}" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
# Consider any old files which still have no corresponding meta file
|
||||
# as orphaned, and remove them.
|
||||
for old_file in $(${FIND} "${dir}" -mindepth 1 \
|
||||
-mmin +$((24 * 60)) -type f); do
|
||||
if [ ! -e "$(get_base "${old_file}").meta" ]; then
|
||||
lecho "Removing old orphaned file: ${old_file}."
|
||||
rm -f -- "${old_file}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Look through all metadata (*.meta) files, oldest first. That way, the rate
|
||||
# limit does not stall old crashes if there's a high amount of new crashes
|
||||
# coming in.
|
||||
# For each crash report, first evaluate conditions that might lead to its
|
||||
# removal to honor user choice and to free disk space as soon as possible,
|
||||
# then decide whether it should be sent right now or kept for later sending.
|
||||
for meta_path in $(ls -1tr "${dir}"/*.meta 2>/dev/null); do
|
||||
lecho "Considering metadata ${meta_path}."
|
||||
|
||||
local kind=$(get_kind "${meta_path}")
|
||||
if [ "${kind}" != "minidump" ] && \
|
||||
[ "${kind}" != "kcrash" ] && \
|
||||
[ "${kind}" != "log" ] &&
|
||||
[ "${kind}" != "devcore" ]; then
|
||||
lecho "Unknown report kind ${kind}. Removing report."
|
||||
remove_report "${meta_path}"
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! is_complete_metadata "${meta_path}"; then
|
||||
# This report is incomplete, so if it's old, just remove it.
|
||||
local old_meta=$(${FIND} "${dir}" -mindepth 1 -name \
|
||||
$(basename "${meta_path}") -mmin +$((24 * 60)) -type f)
|
||||
if [ -n "${old_meta}" ]; then
|
||||
lecho "Removing old incomplete metadata."
|
||||
remove_report "${meta_path}"
|
||||
else
|
||||
lecho "Ignoring recent incomplete metadata."
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
# Ignore device coredump if device coredump uploading is not allowed.
|
||||
if [ "${kind}" = "devcore" ] && ! is_device_coredump_upload_allowed; then
|
||||
lecho "Ignoring device coredump. Device coredump upload not allowed."
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! is_mock && ! is_official_image; then
|
||||
lecho "Not an official OS version. Removing crash."
|
||||
remove_report "${meta_path}"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Don't send crash reports from previous sessions while we're in guest mode
|
||||
# to avoid the impression that crash reporting was enabled, which it isn't.
|
||||
# (Don't exit right now because subsequent reports may be candidates for
|
||||
# deletion.)
|
||||
if ${METRICS_CLIENT} -g; then
|
||||
lecho "Guest mode has been entered. Delaying crash sending until exited."
|
||||
continue
|
||||
fi
|
||||
|
||||
# Remove existing crashes in case user consent has not (yet) been given or
|
||||
# has been revoked. This must come after the guest mode check because
|
||||
# ${METRICS_CLIENT} always returns "not consented" in guest mode.
|
||||
if ! ${METRICS_CLIENT} -c; then
|
||||
lecho "Crash reporting is disabled. Removing crash."
|
||||
remove_report "${meta_path}"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Skip report if the upload rate is exceeded. (Don't exit right now because
|
||||
# subsequent reports may be candidates for deletion.)
|
||||
if ! check_rate; then
|
||||
lecho "Sending ${meta_path} would exceed rate. Leaving for later."
|
||||
continue
|
||||
fi
|
||||
|
||||
# The .meta file should be written *after* all to-be-uploaded files that it
|
||||
# references. Nevertheless, as a safeguard, a hold-off time of thirty
|
||||
# seconds after writing the .meta file is ensured. Also, sending of crash
|
||||
# reports is spread out randomly by up to SECONDS_SEND_SPREAD. Thus, for
|
||||
# the sleep call the greater of the two delays is used.
|
||||
local now=$(date +%s)
|
||||
local holdoff_time=$(($(stat --format=%Y "${meta_path}") + 30 - ${now}))
|
||||
local spread_time=$(generate_uniform_random "${SECONDS_SEND_SPREAD}")
|
||||
local sleep_time
|
||||
if [ ${spread_time} -gt ${holdoff_time} ]; then
|
||||
sleep_time="${spread_time}"
|
||||
else
|
||||
sleep_time="${holdoff_time}"
|
||||
fi
|
||||
lecho "Scheduled to send in ${sleep_time}s."
|
||||
if ! is_mock; then
|
||||
if ! sleep "${sleep_time}"; then
|
||||
lecho "Sleep failed"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Try to upload.
|
||||
if ! send_crash "${meta_path}"; then
|
||||
lecho "Problem sending ${meta_path}, not removing."
|
||||
continue
|
||||
fi
|
||||
|
||||
# Send was successful, now remove.
|
||||
lecho "Successfully sent crash ${meta_path} and removing."
|
||||
remove_report "${meta_path}"
|
||||
done
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: crash_sender [options]
|
||||
|
||||
Options:
|
||||
-e <var>=<val> Set env |var| to |val| (only some vars)
|
||||
EOF
|
||||
exit ${1:-1}
|
||||
}
|
||||
|
||||
parseargs() {
|
||||
# Parse the command line arguments.
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
-e)
|
||||
shift
|
||||
case $1 in
|
||||
FORCE_OFFICIAL=*|\
|
||||
MAX_CRASH_RATE=*|\
|
||||
MOCK_DEVELOPER_MODE=*|\
|
||||
OVERRIDE_PAUSE_SENDING=*|\
|
||||
SECONDS_SEND_SPREAD=*)
|
||||
export "$1"
|
||||
;;
|
||||
*)
|
||||
lecho "Unknown var passed to -e: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
-h)
|
||||
usage 0
|
||||
;;
|
||||
*)
|
||||
lecho "Unknown options: $*"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
}
|
||||
|
||||
main() {
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
parseargs "$@"
|
||||
|
||||
if [ -e "${PAUSE_CRASH_SENDING}" ] && \
|
||||
[ ${OVERRIDE_PAUSE_SENDING} -eq 0 ]; then
|
||||
lecho "Exiting early due to ${PAUSE_CRASH_SENDING}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if is_test_image; then
|
||||
lecho "Exiting early due to test image."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# We don't perform checks on this because we have a master lock with the
|
||||
# CRASH_SENDER_LOCK file. This pid file is for the system to keep track
|
||||
# (like with autotests) that we're still running.
|
||||
echo $$ > "${RUN_FILE}"
|
||||
|
||||
for dependency in "${FIND}" "${METRICS_CLIENT}" \
|
||||
"${RESTRICTED_CERTIFICATES_PATH}"; do
|
||||
if [ ! -x "${dependency}" ]; then
|
||||
lecho "Fatal: Crash sending disabled: ${dependency} not found."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
TMP_DIR="$(mktemp -d /tmp/crash_sender.XXXXXX)"
|
||||
|
||||
# Send system-wide crashes
|
||||
send_crashes "/var/spool/crash"
|
||||
|
||||
# Send user-specific crashes
|
||||
local d
|
||||
for d in /home/chronos/crash /home/chronos/u-*/crash; do
|
||||
send_crashes "${d}"
|
||||
done
|
||||
}
|
||||
|
||||
(
|
||||
if ! flock -n 9; then
|
||||
lecho "Already running; quitting."
|
||||
crash_done
|
||||
exit 1
|
||||
fi
|
||||
main "$@"
|
||||
) 9>"${CRASH_SENDER_LOCK}"
|
20
crash_reporter/dbus_bindings/org.chromium.LibCrosService.xml
Normal file
20
crash_reporter/dbus_bindings/org.chromium.LibCrosService.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<node name="/org/chromium/LibCrosService"
|
||||
xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
|
||||
<interface name="org.chromium.LibCrosServiceInterface">
|
||||
<method name="ResolveNetworkProxy">
|
||||
<arg name="source_url" type="s" direction="in"/>
|
||||
<arg name="signal_interface" type="s" direction="in"/>
|
||||
<arg name="signal_name" type="s" direction="in"/>
|
||||
<annotation name="org.chromium.DBus.Method.Kind" value="simple"/>
|
||||
</method>
|
||||
</interface>
|
||||
<interface name="org.chromium.CrashReporterLibcrosProxyResolvedInterface">
|
||||
<signal name="ProxyResolved">
|
||||
<arg name="source_url" type="s" direction="out"/>
|
||||
<arg name="proxy_info" type="s" direction="out"/>
|
||||
<arg name="error_message" type="s" direction="out"/>
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
27
crash_reporter/init/crash-reporter.conf
Normal file
27
crash_reporter/init/crash-reporter.conf
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
description "Initialize crash reporting services"
|
||||
author "chromium-os-dev@chromium.org"
|
||||
|
||||
# This job merely initializes its service and then terminates; the
|
||||
# actual checking and reporting of crash dumps is triggered by an
|
||||
# hourly cron job.
|
||||
start on starting system-services
|
||||
|
||||
pre-start script
|
||||
mkdir -p /var/spool
|
||||
|
||||
# Only allow device coredumps on a "developer system".
|
||||
if ! is_developer_end_user; then
|
||||
# consumer end-user - disable device coredumps, if driver exists.
|
||||
echo 1 > /sys/class/devcoredump/disabled || true
|
||||
fi
|
||||
end script
|
||||
|
||||
# crash_reporter uses argv[0] as part of the command line for
|
||||
# /proc/sys/kernel/core_pattern. That command line is invoked by
|
||||
# the kernel, and can't rely on PATH, so argv[0] must be a full
|
||||
# path; we invoke it as such here.
|
||||
exec /sbin/crash_reporter --init
|
11
crash_reporter/init/crash-sender.conf
Normal file
11
crash_reporter/init/crash-sender.conf
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
description "Run the crash sender periodically"
|
||||
author "chromium-os-dev@chromium.org"
|
||||
|
||||
start on starting system-services
|
||||
stop on stopping system-services
|
||||
|
||||
exec periodic_scheduler 3600 14400 crash_sender /sbin/crash_sender
|
12
crash_reporter/init/warn-collector.conf
Normal file
12
crash_reporter/init/warn-collector.conf
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
description "Runs a daemon which collects and reports kernel warnings"
|
||||
author "chromium-os-dev@chromium.org"
|
||||
|
||||
start on started system-services
|
||||
stop on stopping system-services
|
||||
respawn
|
||||
|
||||
exec warn_collector
|
591
crash_reporter/kernel_collector.cc
Normal file
591
crash_reporter/kernel_collector.cc
Normal file
|
@ -0,0 +1,591 @@
|
|||
// Copyright (c) 2012 The Chromium OS 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 "crash-reporter/kernel_collector.h"
|
||||
|
||||
#include <map>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <base/files/file_util.h>
|
||||
#include <base/logging.h>
|
||||
#include <base/strings/string_util.h>
|
||||
#include <base/strings/stringprintf.h>
|
||||
|
||||
using base::FilePath;
|
||||
using base::StringPrintf;
|
||||
|
||||
namespace {
|
||||
|
||||
const char kDefaultKernelStackSignature[] = "kernel-UnspecifiedStackSignature";
|
||||
const char kDumpParentPath[] = "/dev";
|
||||
const char kDumpPath[] = "/dev/pstore";
|
||||
const char kDumpFormat[] = "dmesg-ramoops-%zu";
|
||||
const char kKernelExecName[] = "kernel";
|
||||
// Maximum number of records to examine in the kDumpPath.
|
||||
const size_t kMaxDumpRecords = 100;
|
||||
const pid_t kKernelPid = 0;
|
||||
const char kKernelSignatureKey[] = "sig";
|
||||
// Byte length of maximum human readable portion of a kernel crash signature.
|
||||
const int kMaxHumanStringLength = 40;
|
||||
const uid_t kRootUid = 0;
|
||||
// Time in seconds from the final kernel log message for a call stack
|
||||
// to count towards the signature of the kcrash.
|
||||
const int kSignatureTimestampWindow = 2;
|
||||
// Kernel log timestamp regular expression.
|
||||
const char kTimestampRegex[] = "^<.*>\\[\\s*(\\d+\\.\\d+)\\]";
|
||||
|
||||
//
|
||||
// These regular expressions enable to us capture the PC in a backtrace.
|
||||
// The backtrace is obtained through dmesg or the kernel's preserved/kcrashmem
|
||||
// feature.
|
||||
//
|
||||
// For ARM we see:
|
||||
// "<5>[ 39.458982] PC is at write_breakme+0xd0/0x1b4"
|
||||
// For MIPS we see:
|
||||
// "<5>[ 3378.552000] epc : 804010f0 lkdtm_do_action+0x68/0x3f8"
|
||||
// For x86:
|
||||
// "<0>[ 37.474699] EIP: [<790ed488>] write_breakme+0x80/0x108
|
||||
// SS:ESP 0068:e9dd3efc"
|
||||
//
|
||||
const char* const kPCRegex[] = {
|
||||
0,
|
||||
" PC is at ([^\\+ ]+).*",
|
||||
" epc\\s+:\\s+\\S+\\s+([^\\+ ]+).*", // MIPS has an exception program counter
|
||||
" EIP: \\[<.*>\\] ([^\\+ ]+).*", // X86 uses EIP for the program counter
|
||||
" RIP \\[<.*>\\] ([^\\+ ]+).*", // X86_64 uses RIP for the program counter
|
||||
};
|
||||
|
||||
COMPILE_ASSERT(arraysize(kPCRegex) == KernelCollector::kArchCount,
|
||||
missing_arch_pc_regexp);
|
||||
|
||||
} // namespace
|
||||
|
||||
KernelCollector::KernelCollector()
|
||||
: is_enabled_(false),
|
||||
ramoops_dump_path_(kDumpPath),
|
||||
records_(0),
|
||||
// We expect crash dumps in the format of architecture we are built for.
|
||||
arch_(GetCompilerArch()) {
|
||||
}
|
||||
|
||||
KernelCollector::~KernelCollector() {
|
||||
}
|
||||
|
||||
void KernelCollector::OverridePreservedDumpPath(const FilePath &file_path) {
|
||||
ramoops_dump_path_ = file_path;
|
||||
}
|
||||
|
||||
bool KernelCollector::ReadRecordToString(std::string *contents,
|
||||
size_t current_record,
|
||||
bool *record_found) {
|
||||
// A record is a ramoops dump. It has an associated size of "record_size".
|
||||
std::string record;
|
||||
std::string captured;
|
||||
|
||||
// Ramoops appends a header to a crash which contains ==== followed by a
|
||||
// timestamp. Ignore the header.
|
||||
pcrecpp::RE record_re(
|
||||
"====\\d+\\.\\d+\n(.*)",
|
||||
pcrecpp::RE_Options().set_multiline(true).set_dotall(true));
|
||||
|
||||
pcrecpp::RE sanity_check_re("\n<\\d+>\\[\\s*(\\d+\\.\\d+)\\]");
|
||||
|
||||
FilePath ramoops_record;
|
||||
GetRamoopsRecordPath(&ramoops_record, current_record);
|
||||
if (!base::ReadFileToString(ramoops_record, &record)) {
|
||||
LOG(ERROR) << "Unable to open " << ramoops_record.value();
|
||||
return false;
|
||||
}
|
||||
|
||||
*record_found = false;
|
||||
if (record_re.FullMatch(record, &captured)) {
|
||||
// Found a ramoops header, so strip the header and append the rest.
|
||||
contents->append(captured);
|
||||
*record_found = true;
|
||||
} else if (sanity_check_re.PartialMatch(record.substr(0, 1024))) {
|
||||
// pstore compression has been added since kernel 3.12. In order to
|
||||
// decompress dmesg correctly, ramoops driver has to strip the header
|
||||
// before handing over the record to the pstore driver, so we don't
|
||||
// need to do it here anymore. However, the sanity check is needed because
|
||||
// sometimes a pstore record is just a chunk of uninitialized memory which
|
||||
// is not the result of a kernel crash. See crbug.com/443764
|
||||
contents->append(record);
|
||||
*record_found = true;
|
||||
} else {
|
||||
LOG(WARNING) << "Found invalid record at " << ramoops_record.value();
|
||||
}
|
||||
|
||||
// Remove the record from pstore after it's found.
|
||||
if (*record_found)
|
||||
base::DeleteFile(ramoops_record, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void KernelCollector::GetRamoopsRecordPath(FilePath *path,
|
||||
size_t record) {
|
||||
// Disable error "format not a string literal, argument types not checked"
|
||||
// because this is valid, but GNU apparently doesn't bother checking a const
|
||||
// format string.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
||||
*path = ramoops_dump_path_.Append(StringPrintf(kDumpFormat, record));
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
bool KernelCollector::LoadParameters() {
|
||||
// Discover how many ramoops records are being exported by the driver.
|
||||
size_t count;
|
||||
|
||||
for (count = 0; count < kMaxDumpRecords; ++count) {
|
||||
FilePath ramoops_record;
|
||||
GetRamoopsRecordPath(&ramoops_record, count);
|
||||
|
||||
if (!base::PathExists(ramoops_record))
|
||||
break;
|
||||
}
|
||||
|
||||
records_ = count;
|
||||
return (records_ > 0);
|
||||
}
|
||||
|
||||
bool KernelCollector::LoadPreservedDump(std::string *contents) {
|
||||
// Load dumps from the preserved memory and save them in contents.
|
||||
// Since the system is set to restart on oops we won't actually ever have
|
||||
// multiple records (only 0 or 1), but check in case we don't restart on
|
||||
// oops in the future.
|
||||
bool any_records_found = false;
|
||||
bool record_found = false;
|
||||
// clear contents since ReadFileToString actually appends to the string.
|
||||
contents->clear();
|
||||
|
||||
for (size_t i = 0; i < records_; ++i) {
|
||||
if (!ReadRecordToString(contents, i, &record_found)) {
|
||||
break;
|
||||
}
|
||||
if (record_found) {
|
||||
any_records_found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!any_records_found) {
|
||||
LOG(ERROR) << "No valid records found in " << ramoops_dump_path_.value();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void KernelCollector::StripSensitiveData(std::string *kernel_dump) {
|
||||
// Strip any data that the user might not want sent up to the crash servers.
|
||||
// We'll read in from kernel_dump and also place our output there.
|
||||
//
|
||||
// At the moment, the only sensitive data we strip is MAC addresses.
|
||||
|
||||
// Get rid of things that look like MAC addresses, since they could possibly
|
||||
// give information about where someone has been. This is strings that look
|
||||
// like this: 11:22:33:44:55:66
|
||||
// Complications:
|
||||
// - Within a given kernel_dump, want to be able to tell when the same MAC
|
||||
// was used more than once. Thus, we'll consistently replace the first
|
||||
// MAC found with 00:00:00:00:00:01, the second with ...:02, etc.
|
||||
// - ACPI commands look like MAC addresses. We'll specifically avoid getting
|
||||
// rid of those.
|
||||
std::ostringstream result;
|
||||
std::string pre_mac_str;
|
||||
std::string mac_str;
|
||||
std::map<std::string, std::string> mac_map;
|
||||
pcrecpp::StringPiece input(*kernel_dump);
|
||||
|
||||
// This RE will find the next MAC address and can return us the data preceding
|
||||
// the MAC and the MAC itself.
|
||||
pcrecpp::RE mac_re("(.*?)("
|
||||
"[0-9a-fA-F][0-9a-fA-F]:"
|
||||
"[0-9a-fA-F][0-9a-fA-F]:"
|
||||
"[0-9a-fA-F][0-9a-fA-F]:"
|
||||
"[0-9a-fA-F][0-9a-fA-F]:"
|
||||
"[0-9a-fA-F][0-9a-fA-F]:"
|
||||
"[0-9a-fA-F][0-9a-fA-F])",
|
||||
pcrecpp::RE_Options()
|
||||
.set_multiline(true)
|
||||
.set_dotall(true));
|
||||
|
||||
// This RE will identify when the 'pre_mac_str' shows that the MAC address
|
||||
// was really an ACPI cmd. The full string looks like this:
|
||||
// ata1.00: ACPI cmd ef/10:03:00:00:00:a0 (SET FEATURES) filtered out
|
||||
pcrecpp::RE acpi_re("ACPI cmd ef/$",
|
||||
pcrecpp::RE_Options()
|
||||
.set_multiline(true)
|
||||
.set_dotall(true));
|
||||
|
||||
// Keep consuming, building up a result string as we go.
|
||||
while (mac_re.Consume(&input, &pre_mac_str, &mac_str)) {
|
||||
if (acpi_re.PartialMatch(pre_mac_str)) {
|
||||
// We really saw an ACPI command; add to result w/ no stripping.
|
||||
result << pre_mac_str << mac_str;
|
||||
} else {
|
||||
// Found a MAC address; look up in our hash for the mapping.
|
||||
std::string replacement_mac = mac_map[mac_str];
|
||||
if (replacement_mac == "") {
|
||||
// It wasn't present, so build up a replacement string.
|
||||
int mac_id = mac_map.size();
|
||||
|
||||
// Handle up to 2^32 unique MAC address; overkill, but doesn't hurt.
|
||||
replacement_mac = StringPrintf("00:00:%02x:%02x:%02x:%02x",
|
||||
(mac_id & 0xff000000) >> 24,
|
||||
(mac_id & 0x00ff0000) >> 16,
|
||||
(mac_id & 0x0000ff00) >> 8,
|
||||
(mac_id & 0x000000ff));
|
||||
mac_map[mac_str] = replacement_mac;
|
||||
}
|
||||
|
||||
// Dump the string before the MAC and the fake MAC address into result.
|
||||
result << pre_mac_str << replacement_mac;
|
||||
}
|
||||
}
|
||||
|
||||
// One last bit of data might still be in the input.
|
||||
result << input;
|
||||
|
||||
// We'll just assign right back to kernel_dump.
|
||||
*kernel_dump = result.str();
|
||||
}
|
||||
|
||||
bool KernelCollector::DumpDirMounted() {
|
||||
struct stat st_parent;
|
||||
if (stat(kDumpParentPath, &st_parent)) {
|
||||
PLOG(WARNING) << "Could not stat " << kDumpParentPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
struct stat st_dump;
|
||||
if (stat(kDumpPath, &st_dump)) {
|
||||
PLOG(WARNING) << "Could not stat " << kDumpPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (st_parent.st_dev == st_dump.st_dev) {
|
||||
LOG(WARNING) << "Dump dir " << kDumpPath << " not mounted";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KernelCollector::Enable() {
|
||||
if (arch_ == kArchUnknown || arch_ >= kArchCount ||
|
||||
kPCRegex[arch_] == nullptr) {
|
||||
LOG(WARNING) << "KernelCollector does not understand this architecture";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DumpDirMounted()) {
|
||||
LOG(WARNING) << "Kernel does not support crash dumping";
|
||||
return false;
|
||||
}
|
||||
|
||||
// To enable crashes, we will eventually need to set
|
||||
// the chnv bit in BIOS, but it does not yet work.
|
||||
LOG(INFO) << "Enabling kernel crash handling";
|
||||
is_enabled_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hash a string to a number. We define our own hash function to not
|
||||
// be dependent on a C++ library that might change. This function
|
||||
// uses basically the same approach as tr1/functional_hash.h but with
|
||||
// a larger prime number (16127 vs 131).
|
||||
static unsigned HashString(const std::string &input) {
|
||||
unsigned hash = 0;
|
||||
for (size_t i = 0; i < input.length(); ++i)
|
||||
hash = hash * 16127 + input[i];
|
||||
return hash;
|
||||
}
|
||||
|
||||
void KernelCollector::ProcessStackTrace(
|
||||
pcrecpp::StringPiece kernel_dump,
|
||||
bool print_diagnostics,
|
||||
unsigned *hash,
|
||||
float *last_stack_timestamp,
|
||||
bool *is_watchdog_crash) {
|
||||
pcrecpp::RE line_re("(.+)", pcrecpp::MULTILINE());
|
||||
pcrecpp::RE stack_trace_start_re(std::string(kTimestampRegex) +
|
||||
" (Call Trace|Backtrace):$");
|
||||
|
||||
// Match lines such as the following and grab out "function_name".
|
||||
// The ? may or may not be present.
|
||||
//
|
||||
// For ARM:
|
||||
// <4>[ 3498.731164] [<c0057220>] ? (function_name+0x20/0x2c) from
|
||||
// [<c018062c>] (foo_bar+0xdc/0x1bc)
|
||||
//
|
||||
// For MIPS:
|
||||
// <5>[ 3378.656000] [<804010f0>] lkdtm_do_action+0x68/0x3f8
|
||||
//
|
||||
// For X86:
|
||||
// <4>[ 6066.849504] [<7937bcee>] ? function_name+0x66/0x6c
|
||||
//
|
||||
pcrecpp::RE stack_entry_re(std::string(kTimestampRegex) +
|
||||
"\\s+\\[<[[:xdigit:]]+>\\]" // Matches " [<7937bcee>]"
|
||||
"([\\s\\?(]+)" // Matches " ? (" (ARM) or " ? " (X86)
|
||||
"([^\\+ )]+)"); // Matches until delimiter reached
|
||||
std::string line;
|
||||
std::string hashable;
|
||||
std::string previous_hashable;
|
||||
bool is_watchdog = false;
|
||||
|
||||
*hash = 0;
|
||||
*last_stack_timestamp = 0;
|
||||
|
||||
// Find the last and second-to-last stack traces. The latter is used when
|
||||
// the panic is from a watchdog timeout.
|
||||
while (line_re.FindAndConsume(&kernel_dump, &line)) {
|
||||
std::string certainty;
|
||||
std::string function_name;
|
||||
if (stack_trace_start_re.PartialMatch(line, last_stack_timestamp)) {
|
||||
if (print_diagnostics) {
|
||||
printf("Stack trace starting.%s\n",
|
||||
hashable.empty() ? "" : " Saving prior trace.");
|
||||
}
|
||||
previous_hashable = hashable;
|
||||
hashable.clear();
|
||||
is_watchdog = false;
|
||||
} else if (stack_entry_re.PartialMatch(line,
|
||||
last_stack_timestamp,
|
||||
&certainty,
|
||||
&function_name)) {
|
||||
bool is_certain = certainty.find('?') == std::string::npos;
|
||||
if (print_diagnostics) {
|
||||
printf("@%f: stack entry for %s (%s)\n",
|
||||
*last_stack_timestamp,
|
||||
function_name.c_str(),
|
||||
is_certain ? "certain" : "uncertain");
|
||||
}
|
||||
// Do not include any uncertain (prefixed by '?') frames in our hash.
|
||||
if (!is_certain)
|
||||
continue;
|
||||
if (!hashable.empty())
|
||||
hashable.append("|");
|
||||
if (function_name == "watchdog_timer_fn" ||
|
||||
function_name == "watchdog") {
|
||||
is_watchdog = true;
|
||||
}
|
||||
hashable.append(function_name);
|
||||
}
|
||||
}
|
||||
|
||||
// If the last stack trace contains a watchdog function we assume the panic
|
||||
// is from the watchdog timer, and we hash the previous stack trace rather
|
||||
// than the last one, assuming that the previous stack is that of the hung
|
||||
// thread.
|
||||
//
|
||||
// In addition, if the hashable is empty (meaning all frames are uncertain,
|
||||
// for whatever reason) also use the previous frame, as it cannot be any
|
||||
// worse.
|
||||
if (is_watchdog || hashable.empty()) {
|
||||
hashable = previous_hashable;
|
||||
}
|
||||
|
||||
*hash = HashString(hashable);
|
||||
*is_watchdog_crash = is_watchdog;
|
||||
|
||||
if (print_diagnostics) {
|
||||
printf("Hash based on stack trace: \"%s\" at %f.\n",
|
||||
hashable.c_str(), *last_stack_timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
KernelCollector::ArchKind KernelCollector::GetCompilerArch() {
|
||||
#if defined(COMPILER_GCC) && defined(ARCH_CPU_ARM_FAMILY)
|
||||
return kArchArm;
|
||||
#elif defined(COMPILER_GCC) && defined(ARCH_CPU_MIPS_FAMILY)
|
||||
return kArchMips;
|
||||
#elif defined(COMPILER_GCC) && defined(ARCH_CPU_X86_64)
|
||||
return kArchX86_64;
|
||||
#elif defined(COMPILER_GCC) && defined(ARCH_CPU_X86_FAMILY)
|
||||
return kArchX86;
|
||||
#else
|
||||
return kArchUnknown;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool KernelCollector::FindCrashingFunction(
|
||||
pcrecpp::StringPiece kernel_dump,
|
||||
bool print_diagnostics,
|
||||
float stack_trace_timestamp,
|
||||
std::string *crashing_function) {
|
||||
float timestamp = 0;
|
||||
|
||||
// Use the correct regex for this architecture.
|
||||
pcrecpp::RE eip_re(std::string(kTimestampRegex) + kPCRegex[arch_],
|
||||
pcrecpp::MULTILINE());
|
||||
|
||||
while (eip_re.FindAndConsume(&kernel_dump, ×tamp, crashing_function)) {
|
||||
if (print_diagnostics) {
|
||||
printf("@%f: found crashing function %s\n",
|
||||
timestamp,
|
||||
crashing_function->c_str());
|
||||
}
|
||||
}
|
||||
if (timestamp == 0) {
|
||||
if (print_diagnostics) {
|
||||
printf("Found no crashing function.\n");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (stack_trace_timestamp != 0 &&
|
||||
abs(static_cast<int>(stack_trace_timestamp - timestamp))
|
||||
> kSignatureTimestampWindow) {
|
||||
if (print_diagnostics) {
|
||||
printf("Found crashing function but not within window.\n");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (print_diagnostics) {
|
||||
printf("Found crashing function %s\n", crashing_function->c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KernelCollector::FindPanicMessage(pcrecpp::StringPiece kernel_dump,
|
||||
bool print_diagnostics,
|
||||
std::string *panic_message) {
|
||||
// Match lines such as the following and grab out "Fatal exception"
|
||||
// <0>[ 342.841135] Kernel panic - not syncing: Fatal exception
|
||||
pcrecpp::RE kernel_panic_re(std::string(kTimestampRegex) +
|
||||
" Kernel panic[^\\:]*\\:\\s*(.*)",
|
||||
pcrecpp::MULTILINE());
|
||||
float timestamp = 0;
|
||||
while (kernel_panic_re.FindAndConsume(&kernel_dump,
|
||||
×tamp,
|
||||
panic_message)) {
|
||||
if (print_diagnostics) {
|
||||
printf("@%f: panic message %s\n",
|
||||
timestamp,
|
||||
panic_message->c_str());
|
||||
}
|
||||
}
|
||||
if (timestamp == 0) {
|
||||
if (print_diagnostics) {
|
||||
printf("Found no panic message.\n");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KernelCollector::ComputeKernelStackSignature(
|
||||
const std::string &kernel_dump,
|
||||
std::string *kernel_signature,
|
||||
bool print_diagnostics) {
|
||||
unsigned stack_hash = 0;
|
||||
float last_stack_timestamp = 0;
|
||||
std::string human_string;
|
||||
bool is_watchdog_crash;
|
||||
|
||||
ProcessStackTrace(kernel_dump,
|
||||
print_diagnostics,
|
||||
&stack_hash,
|
||||
&last_stack_timestamp,
|
||||
&is_watchdog_crash);
|
||||
|
||||
if (!FindCrashingFunction(kernel_dump,
|
||||
print_diagnostics,
|
||||
last_stack_timestamp,
|
||||
&human_string)) {
|
||||
if (!FindPanicMessage(kernel_dump, print_diagnostics, &human_string)) {
|
||||
if (print_diagnostics) {
|
||||
printf("Found no human readable string, using empty string.\n");
|
||||
}
|
||||
human_string.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (human_string.empty() && stack_hash == 0) {
|
||||
if (print_diagnostics) {
|
||||
printf("Found neither a stack nor a human readable string, failing.\n");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
human_string = human_string.substr(0, kMaxHumanStringLength);
|
||||
*kernel_signature = StringPrintf("%s-%s%s-%08X",
|
||||
kKernelExecName,
|
||||
(is_watchdog_crash ? "(HANG)-" : ""),
|
||||
human_string.c_str(),
|
||||
stack_hash);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KernelCollector::Collect() {
|
||||
std::string kernel_dump;
|
||||
FilePath root_crash_directory;
|
||||
|
||||
if (!LoadParameters()) {
|
||||
return false;
|
||||
}
|
||||
if (!LoadPreservedDump(&kernel_dump)) {
|
||||
return false;
|
||||
}
|
||||
StripSensitiveData(&kernel_dump);
|
||||
if (kernel_dump.empty()) {
|
||||
return false;
|
||||
}
|
||||
std::string signature;
|
||||
if (!ComputeKernelStackSignature(kernel_dump, &signature, false)) {
|
||||
signature = kDefaultKernelStackSignature;
|
||||
}
|
||||
|
||||
std::string reason = "handling";
|
||||
bool feedback = true;
|
||||
if (IsDeveloperImage()) {
|
||||
reason = "developer build - always dumping";
|
||||
feedback = true;
|
||||
} else if (!is_feedback_allowed_function_()) {
|
||||
reason = "ignoring - no consent";
|
||||
feedback = false;
|
||||
}
|
||||
|
||||
LOG(INFO) << "Received prior crash notification from "
|
||||
<< "kernel (signature " << signature << ") (" << reason << ")";
|
||||
|
||||
if (feedback) {
|
||||
count_crash_function_();
|
||||
|
||||
if (!GetCreatedCrashDirectoryByEuid(kRootUid,
|
||||
&root_crash_directory,
|
||||
nullptr)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string dump_basename =
|
||||
FormatDumpBasename(kKernelExecName, time(nullptr), kKernelPid);
|
||||
FilePath kernel_crash_path = root_crash_directory.Append(
|
||||
StringPrintf("%s.kcrash", dump_basename.c_str()));
|
||||
|
||||
// We must use WriteNewFile instead of base::WriteFile as we
|
||||
// do not want to write with root access to a symlink that an attacker
|
||||
// might have created.
|
||||
if (WriteNewFile(kernel_crash_path,
|
||||
kernel_dump.data(),
|
||||
kernel_dump.length()) !=
|
||||
static_cast<int>(kernel_dump.length())) {
|
||||
LOG(INFO) << "Failed to write kernel dump to "
|
||||
<< kernel_crash_path.value().c_str();
|
||||
return true;
|
||||
}
|
||||
|
||||
AddCrashMetaData(kKernelSignatureKey, signature);
|
||||
WriteCrashMetaData(
|
||||
root_crash_directory.Append(
|
||||
StringPrintf("%s.meta", dump_basename.c_str())),
|
||||
kKernelExecName,
|
||||
kernel_crash_path.value());
|
||||
|
||||
LOG(INFO) << "Stored kcrash to " << kernel_crash_path.value();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
111
crash_reporter/kernel_collector.h
Normal file
111
crash_reporter/kernel_collector.h
Normal file
|
@ -0,0 +1,111 @@
|
|||
// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CRASH_REPORTER_KERNEL_COLLECTOR_H_
|
||||
#define CRASH_REPORTER_KERNEL_COLLECTOR_H_
|
||||
|
||||
#include <pcrecpp.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <base/files/file_path.h>
|
||||
#include <base/macros.h>
|
||||
#include <gtest/gtest_prod.h> // for FRIEND_TEST
|
||||
|
||||
#include "crash-reporter/crash_collector.h"
|
||||
|
||||
// Kernel crash collector.
|
||||
class KernelCollector : public CrashCollector {
|
||||
public:
|
||||
// Enumeration to specify architecture type.
|
||||
enum ArchKind {
|
||||
kArchUnknown,
|
||||
kArchArm,
|
||||
kArchMips,
|
||||
kArchX86,
|
||||
kArchX86_64,
|
||||
|
||||
kArchCount // Number of architectures.
|
||||
};
|
||||
|
||||
KernelCollector();
|
||||
|
||||
~KernelCollector() override;
|
||||
|
||||
void OverridePreservedDumpPath(const base::FilePath &file_path);
|
||||
|
||||
// Enable collection.
|
||||
bool Enable();
|
||||
|
||||
// Returns true if the kernel collection currently enabled.
|
||||
bool is_enabled() const { return is_enabled_; }
|
||||
|
||||
// Collect any preserved kernel crash dump. Returns true if there was
|
||||
// a dump (even if there were problems storing the dump), false otherwise.
|
||||
bool Collect();
|
||||
|
||||
// Compute a stack signature string from a kernel dump.
|
||||
bool ComputeKernelStackSignature(const std::string &kernel_dump,
|
||||
std::string *kernel_signature,
|
||||
bool print_diagnostics);
|
||||
|
||||
// Set the architecture of the crash dumps we are looking at.
|
||||
void set_arch(ArchKind arch) { arch_ = arch; }
|
||||
ArchKind arch() const { return arch_; }
|
||||
|
||||
private:
|
||||
friend class KernelCollectorTest;
|
||||
FRIEND_TEST(KernelCollectorTest, LoadPreservedDump);
|
||||
FRIEND_TEST(KernelCollectorTest, StripSensitiveDataBasic);
|
||||
FRIEND_TEST(KernelCollectorTest, StripSensitiveDataBulk);
|
||||
FRIEND_TEST(KernelCollectorTest, StripSensitiveDataSample);
|
||||
FRIEND_TEST(KernelCollectorTest, CollectOK);
|
||||
|
||||
virtual bool DumpDirMounted();
|
||||
|
||||
bool LoadPreservedDump(std::string *contents);
|
||||
void StripSensitiveData(std::string *kernel_dump);
|
||||
|
||||
void GetRamoopsRecordPath(base::FilePath *path, size_t record);
|
||||
bool LoadParameters();
|
||||
bool HasMoreRecords();
|
||||
|
||||
// Read a record to string, modified from file_utils since that didn't
|
||||
// provide a way to restrict the read length.
|
||||
// Return value indicates (only) error state:
|
||||
// * false when we get an error (can't read from dump location).
|
||||
// * true if no error occured.
|
||||
// Not finding a valid record is not an error state and is signaled by the
|
||||
// record_found output parameter.
|
||||
bool ReadRecordToString(std::string *contents,
|
||||
size_t current_record,
|
||||
bool *record_found);
|
||||
|
||||
void ProcessStackTrace(pcrecpp::StringPiece kernel_dump,
|
||||
bool print_diagnostics,
|
||||
unsigned *hash,
|
||||
float *last_stack_timestamp,
|
||||
bool *is_watchdog_crash);
|
||||
bool FindCrashingFunction(pcrecpp::StringPiece kernel_dump,
|
||||
bool print_diagnostics,
|
||||
float stack_trace_timestamp,
|
||||
std::string *crashing_function);
|
||||
bool FindPanicMessage(pcrecpp::StringPiece kernel_dump,
|
||||
bool print_diagnostics,
|
||||
std::string *panic_message);
|
||||
|
||||
// Returns the architecture kind for which we are built.
|
||||
static ArchKind GetCompilerArch();
|
||||
|
||||
bool is_enabled_;
|
||||
base::FilePath ramoops_dump_path_;
|
||||
size_t records_;
|
||||
|
||||
// The architecture of kernel dump strings we are working with.
|
||||
ArchKind arch_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(KernelCollector);
|
||||
};
|
||||
|
||||
#endif // CRASH_REPORTER_KERNEL_COLLECTOR_H_
|
674
crash_reporter/kernel_collector_test.cc
Normal file
674
crash_reporter/kernel_collector_test.cc
Normal file
|
@ -0,0 +1,674 @@
|
|||
// Copyright (c) 2012 The Chromium OS 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 "crash-reporter/kernel_collector_test.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <base/files/file_util.h>
|
||||
#include <base/files/scoped_temp_dir.h>
|
||||
#include <base/strings/string_util.h>
|
||||
#include <base/strings/stringprintf.h>
|
||||
#include <chromeos/syslog_logging.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using base::FilePath;
|
||||
using base::StringPrintf;
|
||||
using chromeos::FindLog;
|
||||
using chromeos::GetLog;
|
||||
|
||||
namespace {
|
||||
|
||||
int s_crashes = 0;
|
||||
bool s_metrics = false;
|
||||
|
||||
void CountCrash() {
|
||||
++s_crashes;
|
||||
}
|
||||
|
||||
bool IsMetrics() {
|
||||
return s_metrics;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class KernelCollectorTest : public ::testing::Test {
|
||||
protected:
|
||||
void WriteStringToFile(const FilePath &file_path,
|
||||
const char *data) {
|
||||
ASSERT_EQ(strlen(data), base::WriteFile(file_path, data, strlen(data)));
|
||||
}
|
||||
|
||||
void SetUpSuccessfulCollect();
|
||||
void ComputeKernelStackSignatureCommon();
|
||||
|
||||
const FilePath &kcrash_file() const { return test_kcrash_; }
|
||||
const FilePath &test_crash_directory() const { return test_crash_directory_; }
|
||||
|
||||
KernelCollectorMock collector_;
|
||||
|
||||
private:
|
||||
void SetUp() override {
|
||||
s_crashes = 0;
|
||||
s_metrics = true;
|
||||
|
||||
EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
|
||||
|
||||
collector_.Initialize(CountCrash, IsMetrics);
|
||||
ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
|
||||
test_kcrash_ = scoped_temp_dir_.path().Append("kcrash");
|
||||
ASSERT_TRUE(base::CreateDirectory(test_kcrash_));
|
||||
collector_.OverridePreservedDumpPath(test_kcrash_);
|
||||
|
||||
test_kcrash_ = test_kcrash_.Append("dmesg-ramoops-0");
|
||||
ASSERT_FALSE(base::PathExists(test_kcrash_));
|
||||
|
||||
test_crash_directory_ = scoped_temp_dir_.path().Append("crash_directory");
|
||||
ASSERT_TRUE(base::CreateDirectory(test_crash_directory_));
|
||||
chromeos::ClearLog();
|
||||
}
|
||||
|
||||
FilePath test_kcrash_;
|
||||
FilePath test_crash_directory_;
|
||||
base::ScopedTempDir scoped_temp_dir_;
|
||||
};
|
||||
|
||||
TEST_F(KernelCollectorTest, ComputeKernelStackSignatureBase) {
|
||||
// Make sure the normal build architecture is detected
|
||||
EXPECT_NE(KernelCollector::kArchUnknown, collector_.arch());
|
||||
}
|
||||
|
||||
TEST_F(KernelCollectorTest, LoadPreservedDump) {
|
||||
ASSERT_FALSE(base::PathExists(kcrash_file()));
|
||||
std::string dump;
|
||||
dump.clear();
|
||||
|
||||
WriteStringToFile(kcrash_file(),
|
||||
"CrashRecordWithoutRamoopsHeader\n<6>[ 0.078852]");
|
||||
ASSERT_TRUE(collector_.LoadParameters());
|
||||
ASSERT_TRUE(collector_.LoadPreservedDump(&dump));
|
||||
ASSERT_EQ("CrashRecordWithoutRamoopsHeader\n<6>[ 0.078852]", dump);
|
||||
|
||||
WriteStringToFile(kcrash_file(), "====1.1\nsomething");
|
||||
ASSERT_TRUE(collector_.LoadParameters());
|
||||
ASSERT_TRUE(collector_.LoadPreservedDump(&dump));
|
||||
ASSERT_EQ("something", dump);
|
||||
|
||||
WriteStringToFile(kcrash_file(), "\x01\x02\xfe\xff random blob");
|
||||
ASSERT_TRUE(collector_.LoadParameters());
|
||||
ASSERT_FALSE(collector_.LoadPreservedDump(&dump));
|
||||
ASSERT_EQ("", dump);
|
||||
}
|
||||
|
||||
TEST_F(KernelCollectorTest, EnableMissingKernel) {
|
||||
ASSERT_FALSE(collector_.Enable());
|
||||
ASSERT_FALSE(collector_.is_enabled());
|
||||
ASSERT_TRUE(FindLog(
|
||||
"Kernel does not support crash dumping"));
|
||||
ASSERT_EQ(s_crashes, 0);
|
||||
}
|
||||
|
||||
TEST_F(KernelCollectorTest, EnableOK) {
|
||||
WriteStringToFile(kcrash_file(), "");
|
||||
EXPECT_CALL(collector_, DumpDirMounted()).WillOnce(::testing::Return(true));
|
||||
ASSERT_TRUE(collector_.Enable());
|
||||
ASSERT_TRUE(collector_.is_enabled());
|
||||
ASSERT_TRUE(FindLog("Enabling kernel crash handling"));
|
||||
ASSERT_EQ(s_crashes, 0);
|
||||
}
|
||||
|
||||
TEST_F(KernelCollectorTest, StripSensitiveDataBasic) {
|
||||
// Basic tests of StripSensitiveData...
|
||||
|
||||
// Make sure we work OK with a string w/ no MAC addresses.
|
||||
const std::string kCrashWithNoMacsOrig =
|
||||
"<7>[111566.131728] PM: Entering mem sleep\n";
|
||||
std::string crash_with_no_macs(kCrashWithNoMacsOrig);
|
||||
collector_.StripSensitiveData(&crash_with_no_macs);
|
||||
EXPECT_EQ(kCrashWithNoMacsOrig, crash_with_no_macs);
|
||||
|
||||
// Make sure that we handle the case where there's nothing before/after the
|
||||
// MAC address.
|
||||
const std::string kJustAMacOrig =
|
||||
"11:22:33:44:55:66";
|
||||
const std::string kJustAMacStripped =
|
||||
"00:00:00:00:00:01";
|
||||
std::string just_a_mac(kJustAMacOrig);
|
||||
collector_.StripSensitiveData(&just_a_mac);
|
||||
EXPECT_EQ(kJustAMacStripped, just_a_mac);
|
||||
|
||||
// Test MAC addresses crammed together to make sure it gets both of them.
|
||||
//
|
||||
// I'm not sure that the code does ideal on these two test cases (they don't
|
||||
// look like two MAC addresses to me), but since we don't see them I think
|
||||
// it's OK to behave as shown here.
|
||||
const std::string kCrammedMacs1Orig =
|
||||
"11:22:33:44:55:66:11:22:33:44:55:66";
|
||||
const std::string kCrammedMacs1Stripped =
|
||||
"00:00:00:00:00:01:00:00:00:00:00:01";
|
||||
std::string crammed_macs_1(kCrammedMacs1Orig);
|
||||
collector_.StripSensitiveData(&crammed_macs_1);
|
||||
EXPECT_EQ(kCrammedMacs1Stripped, crammed_macs_1);
|
||||
|
||||
const std::string kCrammedMacs2Orig =
|
||||
"11:22:33:44:55:6611:22:33:44:55:66";
|
||||
const std::string kCrammedMacs2Stripped =
|
||||
"00:00:00:00:00:0100:00:00:00:00:01";
|
||||
std::string crammed_macs_2(kCrammedMacs2Orig);
|
||||
collector_.StripSensitiveData(&crammed_macs_2);
|
||||
EXPECT_EQ(kCrammedMacs2Stripped, crammed_macs_2);
|
||||
|
||||
// Test case-sensitiveness (we shouldn't be case-senstive).
|
||||
const std::string kCapsMacOrig =
|
||||
"AA:BB:CC:DD:EE:FF";
|
||||
const std::string kCapsMacStripped =
|
||||
"00:00:00:00:00:01";
|
||||
std::string caps_mac(kCapsMacOrig);
|
||||
collector_.StripSensitiveData(&caps_mac);
|
||||
EXPECT_EQ(kCapsMacStripped, caps_mac);
|
||||
|
||||
const std::string kLowerMacOrig =
|
||||
"aa:bb:cc:dd:ee:ff";
|
||||
const std::string kLowerMacStripped =
|
||||
"00:00:00:00:00:01";
|
||||
std::string lower_mac(kLowerMacOrig);
|
||||
collector_.StripSensitiveData(&lower_mac);
|
||||
EXPECT_EQ(kLowerMacStripped, lower_mac);
|
||||
}
|
||||
|
||||
TEST_F(KernelCollectorTest, StripSensitiveDataBulk) {
|
||||
// Test calling StripSensitiveData w/ lots of MAC addresses in the "log".
|
||||
|
||||
// Test that stripping code handles more than 256 unique MAC addresses, since
|
||||
// that overflows past the last byte...
|
||||
// We'll write up some code that generates 258 unique MAC addresses. Sorta
|
||||
// cheating since the code is very similar to the current code in
|
||||
// StripSensitiveData(), but would catch if someone changed that later.
|
||||
std::string lotsa_macs_orig;
|
||||
std::string lotsa_macs_stripped;
|
||||
int i;
|
||||
for (i = 0; i < 258; i++) {
|
||||
lotsa_macs_orig += StringPrintf(" 11:11:11:11:%02X:%02x",
|
||||
(i & 0xff00) >> 8, i & 0x00ff);
|
||||
lotsa_macs_stripped += StringPrintf(" 00:00:00:00:%02X:%02x",
|
||||
((i+1) & 0xff00) >> 8, (i+1) & 0x00ff);
|
||||
}
|
||||
std::string lotsa_macs(lotsa_macs_orig);
|
||||
collector_.StripSensitiveData(&lotsa_macs);
|
||||
EXPECT_EQ(lotsa_macs_stripped, lotsa_macs);
|
||||
}
|
||||
|
||||
TEST_F(KernelCollectorTest, StripSensitiveDataSample) {
|
||||
// Test calling StripSensitiveData w/ some actual lines from a real crash;
|
||||
// included two MAC addresses (though replaced them with some bogusness).
|
||||
const std::string kCrashWithMacsOrig =
|
||||
"<6>[111567.195339] ata1.00: ACPI cmd ef/10:03:00:00:00:a0 (SET FEATURES)"
|
||||
" filtered out\n"
|
||||
"<7>[108539.540144] wlan0: authenticate with 11:22:33:44:55:66 (try 1)\n"
|
||||
"<7>[108539.554973] wlan0: associate with 11:22:33:44:55:66 (try 1)\n"
|
||||
"<6>[110136.587583] usb0: register 'QCUSBNet2k' at usb-0000:00:1d.7-2,"
|
||||
" QCUSBNet Ethernet Device, 99:88:77:66:55:44\n"
|
||||
"<7>[110964.314648] wlan0: deauthenticated from 11:22:33:44:55:66"
|
||||
" (Reason: 6)\n"
|
||||
"<7>[110964.325057] phy0: Removed STA 11:22:33:44:55:66\n"
|
||||
"<7>[110964.325115] phy0: Destroyed STA 11:22:33:44:55:66\n"
|
||||
"<6>[110969.219172] usb0: register 'QCUSBNet2k' at usb-0000:00:1d.7-2,"
|
||||
" QCUSBNet Ethernet Device, 99:88:77:66:55:44\n"
|
||||
"<7>[111566.131728] PM: Entering mem sleep\n";
|
||||
const std::string kCrashWithMacsStripped =
|
||||
"<6>[111567.195339] ata1.00: ACPI cmd ef/10:03:00:00:00:a0 (SET FEATURES)"
|
||||
" filtered out\n"
|
||||
"<7>[108539.540144] wlan0: authenticate with 00:00:00:00:00:01 (try 1)\n"
|
||||
"<7>[108539.554973] wlan0: associate with 00:00:00:00:00:01 (try 1)\n"
|
||||
"<6>[110136.587583] usb0: register 'QCUSBNet2k' at usb-0000:00:1d.7-2,"
|
||||
" QCUSBNet Ethernet Device, 00:00:00:00:00:02\n"
|
||||
"<7>[110964.314648] wlan0: deauthenticated from 00:00:00:00:00:01"
|
||||
" (Reason: 6)\n"
|
||||
"<7>[110964.325057] phy0: Removed STA 00:00:00:00:00:01\n"
|
||||
"<7>[110964.325115] phy0: Destroyed STA 00:00:00:00:00:01\n"
|
||||
"<6>[110969.219172] usb0: register 'QCUSBNet2k' at usb-0000:00:1d.7-2,"
|
||||
" QCUSBNet Ethernet Device, 00:00:00:00:00:02\n"
|
||||
"<7>[111566.131728] PM: Entering mem sleep\n";
|
||||
std::string crash_with_macs(kCrashWithMacsOrig);
|
||||
collector_.StripSensitiveData(&crash_with_macs);
|
||||
EXPECT_EQ(kCrashWithMacsStripped, crash_with_macs);
|
||||
}
|
||||
|
||||
TEST_F(KernelCollectorTest, CollectPreservedFileMissing) {
|
||||
ASSERT_FALSE(collector_.Collect());
|
||||
ASSERT_FALSE(FindLog("Stored kcrash to "));
|
||||
ASSERT_EQ(0, s_crashes);
|
||||
}
|
||||
|
||||
TEST_F(KernelCollectorTest, CollectBadDirectory) {
|
||||
WriteStringToFile(kcrash_file(), "====1.1\nsomething");
|
||||
ASSERT_TRUE(collector_.Collect());
|
||||
ASSERT_TRUE(FindLog("Unable to create appropriate crash directory"))
|
||||
<< "Did not find expected error string in log: {\n"
|
||||
<< GetLog() << "}";
|
||||
ASSERT_EQ(1, s_crashes);
|
||||
}
|
||||
|
||||
void KernelCollectorTest::SetUpSuccessfulCollect() {
|
||||
collector_.ForceCrashDirectory(test_crash_directory());
|
||||
WriteStringToFile(kcrash_file(), "====1.1\nsomething");
|
||||
ASSERT_EQ(0, s_crashes);
|
||||
}
|
||||
|
||||
TEST_F(KernelCollectorTest, CollectOptedOut) {
|
||||
SetUpSuccessfulCollect();
|
||||
s_metrics = false;
|
||||
ASSERT_TRUE(collector_.Collect());
|
||||
ASSERT_TRUE(FindLog("(ignoring - no consent)"));
|
||||
ASSERT_EQ(0, s_crashes);
|
||||
}
|
||||
|
||||
TEST_F(KernelCollectorTest, CollectOK) {
|
||||
SetUpSuccessfulCollect();
|
||||
ASSERT_TRUE(collector_.Collect());
|
||||
ASSERT_EQ(1, s_crashes);
|
||||
ASSERT_TRUE(FindLog("(handling)"));
|
||||
static const char kNamePrefix[] = "Stored kcrash to ";
|
||||
std::string log = chromeos::GetLog();
|
||||
size_t pos = log.find(kNamePrefix);
|
||||
ASSERT_NE(std::string::npos, pos)
|
||||
<< "Did not find string \"" << kNamePrefix << "\" in log: {\n"
|
||||
<< log << "}";
|
||||
pos += strlen(kNamePrefix);
|
||||
std::string filename = log.substr(pos, std::string::npos);
|
||||
// Take the name up until \n
|
||||
size_t end_pos = filename.find_first_of("\n");
|
||||
ASSERT_NE(std::string::npos, end_pos);
|
||||
filename = filename.substr(0, end_pos);
|
||||
ASSERT_EQ(0, filename.find(test_crash_directory().value()));
|
||||
ASSERT_TRUE(base::PathExists(FilePath(filename)));
|
||||
std::string contents;
|
||||
ASSERT_TRUE(base::ReadFileToString(FilePath(filename), &contents));
|
||||
ASSERT_EQ("something", contents);
|
||||
}
|
||||
|
||||
// Perform tests which are common across architectures
|
||||
void KernelCollectorTest::ComputeKernelStackSignatureCommon() {
|
||||
std::string signature;
|
||||
|
||||
const char kStackButNoPC[] =
|
||||
"<4>[ 6066.829029] [<790340af>] __do_softirq+0xa6/0x143\n";
|
||||
EXPECT_TRUE(
|
||||
collector_.ComputeKernelStackSignature(kStackButNoPC, &signature, false));
|
||||
EXPECT_EQ("kernel--83615F0A", signature);
|
||||
|
||||
const char kMissingEverything[] =
|
||||
"<4>[ 6066.829029] [<790340af>] ? __do_softirq+0xa6/0x143\n";
|
||||
EXPECT_FALSE(
|
||||
collector_.ComputeKernelStackSignature(kMissingEverything,
|
||||
&signature,
|
||||
false));
|
||||
|
||||
// Long message.
|
||||
const char kTruncatedMessage[] =
|
||||
"<0>[ 87.485611] Kernel panic - not syncing: 01234567890123456789"
|
||||
"01234567890123456789X\n";
|
||||
EXPECT_TRUE(
|
||||
collector_.ComputeKernelStackSignature(kTruncatedMessage,
|
||||
&signature,
|
||||
false));
|
||||
EXPECT_EQ("kernel-0123456789012345678901234567890123456789-00000000",
|
||||
signature);
|
||||
}
|
||||
|
||||
TEST_F(KernelCollectorTest, ComputeKernelStackSignatureARM) {
|
||||
const char kBugToPanic[] =
|
||||
"<5>[ 123.412524] Modules linked in:\n"
|
||||
"<5>[ 123.412534] CPU: 0 Tainted: G W "
|
||||
"(2.6.37-01030-g51cee64 #153)\n"
|
||||
"<5>[ 123.412552] PC is at write_breakme+0xd0/0x1b4\n"
|
||||
"<5>[ 123.412560] LR is at write_breakme+0xc8/0x1b4\n"
|
||||
"<5>[ 123.412569] pc : [<c0058220>] lr : [<c005821c>] "
|
||||
"psr: 60000013\n"
|
||||
"<5>[ 123.412574] sp : f4e0ded8 ip : c04d104c fp : 000e45e0\n"
|
||||
"<5>[ 123.412581] r10: 400ff000 r9 : f4e0c000 r8 : 00000004\n"
|
||||
"<5>[ 123.412589] r7 : f4e0df80 r6 : f4820c80 r5 : 00000004 "
|
||||
"r4 : f4e0dee8\n"
|
||||
"<5>[ 123.412598] r3 : 00000000 r2 : f4e0decc r1 : c05f88a9 "
|
||||
"r0 : 00000039\n"
|
||||
"<5>[ 123.412608] Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA "
|
||||
"ARM Segment user\n"
|
||||
"<5>[ 123.412617] Control: 10c53c7d Table: 34dcc04a DAC: 00000015\n"
|
||||
"<0>[ 123.412626] Process bash (pid: 1014, stack limit = 0xf4e0c2f8)\n"
|
||||
"<0>[ 123.412634] Stack: (0xf4e0ded8 to 0xf4e0e000)\n"
|
||||
"<0>[ 123.412641] dec0: "
|
||||
" f4e0dee8 c0183678\n"
|
||||
"<0>[ 123.412654] dee0: 00000000 00000000 00677562 0000081f c06a6a78 "
|
||||
"400ff000 f4e0dfb0 00000000\n"
|
||||
"<0>[ 123.412666] df00: bec7ab44 000b1719 bec7ab0c c004f498 bec7a314 "
|
||||
"c024acc8 00000001 c018359c\n"
|
||||
"<0>[ 123.412679] df20: f4e0df34 c04d10fc f5803c80 271beb39 000e45e0 "
|
||||
"f5803c80 c018359c c017bfe0\n"
|
||||
"<0>[ 123.412691] df40: 00000004 f4820c80 400ff000 f4e0df80 00000004 "
|
||||
"f4e0c000 00000000 c01383e4\n"
|
||||
"<0>[ 123.412703] df60: f4820c80 400ff000 f4820c80 400ff000 00000000 "
|
||||
"00000000 00000004 c0138578\n"
|
||||
"<0>[ 123.412715] df80: 00000000 00000000 00000004 00000000 00000004 "
|
||||
"402f95d0 00000004 00000004\n"
|
||||
"<0>[ 123.412727] dfa0: c0054984 c00547c0 00000004 402f95d0 00000001 "
|
||||
"400ff000 00000004 00000000\n"
|
||||
"<0>[ 123.412739] dfc0: 00000004 402f95d0 00000004 00000004 400ff000 "
|
||||
"000c194c bec7ab58 000e45e0\n"
|
||||
"<0>[ 123.412751] dfe0: 00000000 bec7aad8 40232520 40284e9c 60000010 "
|
||||
"00000001 00000000 00000000\n"
|
||||
"<5>[ 39.496577] Backtrace:\n"
|
||||
"<5>[ 123.412782] [<c0058220>] (__bug+0x20/0x2c) from [<c0183678>] "
|
||||
"(write_breakme+0xdc/0x1bc)\n"
|
||||
"<5>[ 123.412798] [<c0183678>] (write_breakme+0xdc/0x1bc) from "
|
||||
"[<c017bfe0>] (proc_reg_write+0x88/0x9c)\n";
|
||||
std::string signature;
|
||||
|
||||
collector_.set_arch(KernelCollector::kArchArm);
|
||||
EXPECT_TRUE(
|
||||
collector_.ComputeKernelStackSignature(kBugToPanic, &signature, false));
|
||||
EXPECT_EQ("kernel-write_breakme-97D3E92F", signature);
|
||||
|
||||
ComputeKernelStackSignatureCommon();
|
||||
}
|
||||
|
||||
TEST_F(KernelCollectorTest, ComputeKernelStackSignatureMIPS) {
|
||||
const char kBugToPanic[] =
|
||||
"<5>[ 3378.472000] lkdtm: Performing direct entry BUG\n"
|
||||
"<5>[ 3378.476000] Kernel bug detected[#1]:\n"
|
||||
"<5>[ 3378.484000] CPU: 0 PID: 185 Comm: dash Not tainted 3.14.0 #1\n"
|
||||
"<5>[ 3378.488000] task: 8fed5220 ti: 8ec4a000 task.ti: 8ec4a000\n"
|
||||
"<5>[ 3378.496000] $ 0 : 00000000 804018b8 804010f0 7785b507\n"
|
||||
"<5>[ 3378.500000] $ 4 : 8061ab64 81204478 81205b20 00000000\n"
|
||||
"<5>[ 3378.508000] $ 8 : 80830000 20746365 72746e65 55422079\n"
|
||||
"<5>[ 3378.512000] $12 : 8ec4be94 000000fc 00000000 00000048\n"
|
||||
"<5>[ 3378.520000] $16 : 00000004 8ef54000 80710000 00000002\n"
|
||||
"<5>[ 3378.528000] $20 : 7765b6d4 00000004 7fffffff 00000002\n"
|
||||
"<5>[ 3378.532000] $24 : 00000001 803dc0dc \n"
|
||||
"<5>[ 3378.540000] $28 : 8ec4a000 8ec4be20 7775438d 804018b8\n"
|
||||
"<5>[ 3378.544000] Hi : 00000000\n"
|
||||
"<5>[ 3378.548000] Lo : 49bf8080\n"
|
||||
"<5>[ 3378.552000] epc : 804010f0 lkdtm_do_action+0x68/0x3f8\n"
|
||||
"<5>[ 3378.560000] Not tainted\n"
|
||||
"<5>[ 3378.564000] ra : 804018b8 direct_entry+0x110/0x154\n"
|
||||
"<5>[ 3378.568000] Status: 3100dc03 KERNEL EXL IE \n"
|
||||
"<5>[ 3378.572000] Cause : 10800024\n"
|
||||
"<5>[ 3378.576000] PrId : 0001a120 (MIPS interAptiv (multi))\n"
|
||||
"<5>[ 3378.580000] Modules linked in: uinput cfg80211 nf_conntrack_ipv6 "
|
||||
"nf_defrag_ipv6 ip6table_filter ip6_tables pcnet32 mii fuse "
|
||||
"ppp_async ppp_generic slhc tun\n"
|
||||
"<5>[ 3378.600000] Process dash (pid: 185, threadinfo=8ec4a000, "
|
||||
"task=8fed5220, tls=77632490)\n"
|
||||
"<5>[ 3378.608000] Stack : 00000006 ffffff9c 00000000 00000000 00000000 "
|
||||
"00000000 8083454a 00000022\n"
|
||||
"<5> 7765baa1 00001fee 80710000 8ef54000 8ec4bf08 00000002 "
|
||||
"7765b6d4 00000004\n"
|
||||
"<5> 7fffffff 00000002 7775438d 805e5158 7fffffff 00000002 "
|
||||
"00000000 7785b507\n"
|
||||
"<5> 806a96bc 00000004 8ef54000 8ec4bf08 00000002 804018b8 "
|
||||
"80710000 806a98bc\n"
|
||||
"<5> 00000002 00000020 00000004 8d515600 77756450 00000004 "
|
||||
"8ec4bf08 802377e4\n"
|
||||
"<5> ...\n"
|
||||
"<5>[ 3378.652000] Call Trace:\n"
|
||||
"<5>[ 3378.656000] [<804010f0>] lkdtm_do_action+0x68/0x3f8\n"
|
||||
"<5>[ 3378.660000] [<804018b8>] direct_entry+0x110/0x154\n"
|
||||
"<5>[ 3378.664000] [<802377e4>] vfs_write+0xe0/0x1bc\n"
|
||||
"<5>[ 3378.672000] [<80237f90>] SyS_write+0x78/0xf8\n"
|
||||
"<5>[ 3378.676000] [<80111888>] handle_sys+0x128/0x14c\n"
|
||||
"<5>[ 3378.680000] \n"
|
||||
"<5>[ 3378.684000] \n"
|
||||
"<5>Code: 3c04806b 0c1793aa 248494f0 <000c000d> 3c04806b 248494fc "
|
||||
"0c04cc7f 2405017a 08100514 \n"
|
||||
"<5>[ 3378.696000] ---[ end trace 75067432f24bbc93 ]---\n";
|
||||
std::string signature;
|
||||
|
||||
collector_.set_arch(KernelCollector::kArchMips);
|
||||
EXPECT_TRUE(
|
||||
collector_.ComputeKernelStackSignature(kBugToPanic, &signature, false));
|
||||
EXPECT_EQ("kernel-lkdtm_do_action-5E600A6B", signature);
|
||||
|
||||
ComputeKernelStackSignatureCommon();
|
||||
}
|
||||
|
||||
TEST_F(KernelCollectorTest, ComputeKernelStackSignatureX86) {
|
||||
const char kBugToPanic[] =
|
||||
"<4>[ 6066.829029] [<79039d16>] ? run_timer_softirq+0x165/0x1e6\n"
|
||||
"<4>[ 6066.829029] [<790340af>] ignore_old_stack+0xa6/0x143\n"
|
||||
"<0>[ 6066.829029] EIP: [<b82d7c15>] ieee80211_stop_tx_ba_session+"
|
||||
"0xa3/0xb5 [mac80211] SS:ESP 0068:7951febc\n"
|
||||
"<0>[ 6066.829029] CR2: 00000000323038a7\n"
|
||||
"<4>[ 6066.845422] ---[ end trace 12b058bb46c43500 ]---\n"
|
||||
"<0>[ 6066.845747] Kernel panic - not syncing: Fatal exception "
|
||||
"in interrupt\n"
|
||||
"<0>[ 6066.846902] Call Trace:\n"
|
||||
"<4>[ 6066.846902] [<7937a07b>] ? printk+0x14/0x19\n"
|
||||
"<4>[ 6066.949779] [<79379fc1>] panic+0x3e/0xe4\n"
|
||||
"<4>[ 6066.949971] [<7937c5c5>] oops_end+0x73/0x81\n"
|
||||
"<4>[ 6066.950208] [<7901b260>] no_context+0x10d/0x117\n";
|
||||
std::string signature;
|
||||
|
||||
collector_.set_arch(KernelCollector::kArchX86);
|
||||
EXPECT_TRUE(
|
||||
collector_.ComputeKernelStackSignature(kBugToPanic, &signature, false));
|
||||
EXPECT_EQ("kernel-ieee80211_stop_tx_ba_session-DE253569", signature);
|
||||
|
||||
const char kPCButNoStack[] =
|
||||
"<0>[ 6066.829029] EIP: [<b82d7c15>] ieee80211_stop_tx_ba_session+";
|
||||
EXPECT_TRUE(
|
||||
collector_.ComputeKernelStackSignature(kPCButNoStack, &signature, false));
|
||||
EXPECT_EQ("kernel-ieee80211_stop_tx_ba_session-00000000", signature);
|
||||
|
||||
const char kBreakmeBug[] =
|
||||
"<4>[ 180.492137] [<790970c6>] ? handle_mm_fault+0x67f/0x96d\n"
|
||||
"<4>[ 180.492137] [<790dcdfe>] ? proc_reg_write+0x5f/0x73\n"
|
||||
"<4>[ 180.492137] [<790e2224>] ? write_breakme+0x0/0x108\n"
|
||||
"<4>[ 180.492137] [<790dcd9f>] ? proc_reg_write+0x0/0x73\n"
|
||||
"<4>[ 180.492137] [<790ac0aa>] vfs_write+0x85/0xe4\n"
|
||||
"<0>[ 180.492137] Code: c6 44 05 b2 00 89 d8 e8 0c ef 09 00 85 c0 75 "
|
||||
"0b c7 00 00 00 00 00 e9 8e 00 00 00 ba e6 75 4b 79 89 d8 e8 f1 ee 09 "
|
||||
"00 85 c0 75 04 <0f> 0b eb fe ba 58 47 49 79 89 d8 e8 dd ee 09 00 85 "
|
||||
"c0 75 0a 68\n"
|
||||
"<0>[ 180.492137] EIP: [<790e22a4>] write_breakme+0x80/0x108 SS:ESP "
|
||||
"0068:aa3e9efc\n"
|
||||
"<4>[ 180.501800] ---[ end trace 2a6b72965e1b1523 ]---\n"
|
||||
"<0>[ 180.502026] Kernel panic - not syncing: Fatal exception\n"
|
||||
"<4>[ 180.502026] Call Trace:\n"
|
||||
"<4>[ 180.502806] [<79379aba>] ? printk+0x14/0x1a\n"
|
||||
"<4>[ 180.503033] [<79379a00>] panic+0x3e/0xe4\n"
|
||||
"<4>[ 180.503287] [<7937c005>] oops_end+0x73/0x81\n"
|
||||
"<4>[ 180.503520] [<790055dd>] die+0x58/0x5e\n"
|
||||
"<4>[ 180.503538] [<7937b96c>] do_trap+0x8e/0xa7\n"
|
||||
"<4>[ 180.503555] [<79003d70>] ? do_invalid_op+0x0/0x80\n";
|
||||
|
||||
EXPECT_TRUE(
|
||||
collector_.ComputeKernelStackSignature(kBreakmeBug, &signature, false));
|
||||
EXPECT_EQ("kernel-write_breakme-122AB3CD", signature);
|
||||
|
||||
const char kPCLineTooOld[] =
|
||||
"<4>[ 174.492137] [<790970c6>] ignored_function+0x67f/0x96d\n"
|
||||
"<4>[ 175.492137] [<790970c6>] ignored_function2+0x67f/0x96d\n"
|
||||
"<0>[ 174.492137] EIP: [<790e22a4>] write_breakme+0x80/0x108 SS:ESP "
|
||||
"0068:aa3e9efc\n"
|
||||
"<4>[ 180.501800] ---[ end trace 2a6b72965e1b1523 ]---\n"
|
||||
"<4>[ 180.502026] Call Trace:\n"
|
||||
"<0>[ 180.502026] Kernel panic - not syncing: Fatal exception\n"
|
||||
"<4>[ 180.502806] [<79379aba>] printk+0x14/0x1a\n";
|
||||
|
||||
EXPECT_TRUE(
|
||||
collector_.ComputeKernelStackSignature(kPCLineTooOld, &signature, false));
|
||||
EXPECT_EQ("kernel-Fatal exception-ED4C84FE", signature);
|
||||
|
||||
// Panic without EIP line.
|
||||
const char kExamplePanicOnly[] =
|
||||
"<0>[ 87.485611] Kernel panic - not syncing: Testing panic\n"
|
||||
"<4>[ 87.485630] Pid: 2825, comm: bash Tainted: G "
|
||||
"C 2.6.32.23+drm33.10 #1\n"
|
||||
"<4>[ 87.485639] Call Trace:\n"
|
||||
"<4>[ 87.485660] [<8133f71d>] ? printk+0x14/0x17\n"
|
||||
"<4>[ 87.485674] [<8133f663>] panic+0x3e/0xe4\n"
|
||||
"<4>[ 87.485689] [<810d062e>] write_breakme+0xaa/0x124\n";
|
||||
EXPECT_TRUE(
|
||||
collector_.ComputeKernelStackSignature(kExamplePanicOnly,
|
||||
&signature,
|
||||
false));
|
||||
EXPECT_EQ("kernel-Testing panic-E0FC3552", signature);
|
||||
|
||||
// Panic from hung task.
|
||||
const char kHungTaskBreakMe[] =
|
||||
"<3>[ 720.459157] INFO: task bash:2287 blocked blah blah\n"
|
||||
"<5>[ 720.459282] Call Trace:\n"
|
||||
"<5>[ 720.459307] [<810a457b>] ? __dentry_open+0x186/0x23e\n"
|
||||
"<5>[ 720.459323] [<810b9c71>] ? mntput_no_expire+0x29/0xe2\n"
|
||||
"<5>[ 720.459336] [<810b9d48>] ? mntput+0x1e/0x20\n"
|
||||
"<5>[ 720.459350] [<810ad135>] ? path_put+0x1a/0x1d\n"
|
||||
"<5>[ 720.459366] [<8137cacc>] schedule+0x4d/0x4f\n"
|
||||
"<5>[ 720.459379] [<8137ccfb>] schedule_timeout+0x26/0xaf\n"
|
||||
"<5>[ 720.459394] [<8102127e>] ? should_resched+0xd/0x27\n"
|
||||
"<5>[ 720.459409] [<81174d1f>] ? _copy_from_user+0x3c/0x50\n"
|
||||
"<5>[ 720.459423] [<8137cd9e>] "
|
||||
"schedule_timeout_uninterruptible+0x1a/0x1c\n"
|
||||
"<5>[ 720.459438] [<810dee63>] write_breakme+0xb3/0x178\n"
|
||||
"<5>[ 720.459453] [<810dedb0>] ? meminfo_proc_show+0x2f2/0x2f2\n"
|
||||
"<5>[ 720.459467] [<810d94ae>] proc_reg_write+0x6d/0x87\n"
|
||||
"<5>[ 720.459481] [<810d9441>] ? proc_reg_poll+0x76/0x76\n"
|
||||
"<5>[ 720.459493] [<810a5e9e>] vfs_write+0x79/0xa5\n"
|
||||
"<5>[ 720.459505] [<810a6011>] sys_write+0x40/0x65\n"
|
||||
"<5>[ 720.459519] [<8137e677>] sysenter_do_call+0x12/0x26\n"
|
||||
"<0>[ 720.459530] Kernel panic - not syncing: hung_task: blocked tasks\n"
|
||||
"<5>[ 720.459768] Pid: 31, comm: khungtaskd Tainted: "
|
||||
"G C 3.0.8 #1\n"
|
||||
"<5>[ 720.459998] Call Trace:\n"
|
||||
"<5>[ 720.460140] [<81378a35>] panic+0x53/0x14a\n"
|
||||
"<5>[ 720.460312] [<8105f875>] watchdog+0x15b/0x1a0\n"
|
||||
"<5>[ 720.460495] [<8105f71a>] ? hung_task_panic+0x16/0x16\n"
|
||||
"<5>[ 720.460693] [<81043af3>] kthread+0x67/0x6c\n"
|
||||
"<5>[ 720.460862] [<81043a8c>] ? __init_kthread_worker+0x2d/0x2d\n"
|
||||
"<5>[ 720.461106] [<8137eb9e>] kernel_thread_helper+0x6/0x10\n";
|
||||
|
||||
EXPECT_TRUE(
|
||||
collector_.ComputeKernelStackSignature(kHungTaskBreakMe,
|
||||
&signature,
|
||||
false));
|
||||
|
||||
EXPECT_EQ("kernel-(HANG)-hung_task: blocked tasks-600B37EA", signature);
|
||||
|
||||
// Panic with all question marks in the last stack trace.
|
||||
const char kUncertainStackTrace[] =
|
||||
"<0>[56279.689669] ------------[ cut here ]------------\n"
|
||||
"<2>[56279.689677] kernel BUG at /build/x86-alex/tmp/portage/"
|
||||
"sys-kernel/chromeos-kernel-0.0.1-r516/work/chromeos-kernel-0.0.1/"
|
||||
"kernel/timer.c:844!\n"
|
||||
"<0>[56279.689683] invalid opcode: 0000 [#1] SMP \n"
|
||||
"<0>[56279.689688] last sysfs file: /sys/power/state\n"
|
||||
"<5>[56279.689692] Modules linked in: nls_iso8859_1 nls_cp437 vfat fat "
|
||||
"gobi usbnet tsl2583(C) industrialio(C) snd_hda_codec_realtek "
|
||||
"snd_hda_intel i2c_dev snd_hda_codec snd_hwdep qcserial snd_pcm usb_wwan "
|
||||
"i2c_i801 snd_timer nm10_gpio snd_page_alloc rtc_cmos fuse "
|
||||
"nf_conntrack_ipv6 nf_defrag_ipv6 uvcvideo videodev ip6table_filter "
|
||||
"ath9k ip6_tables ipv6 mac80211 ath9k_common ath9k_hw ath cfg80211 "
|
||||
"xt_mark\n"
|
||||
"<5>[56279.689731] \n"
|
||||
"<5>[56279.689738] Pid: 24607, comm: powerd_suspend Tainted: G "
|
||||
"WC 2.6.38.3+ #1 SAMSUNG ELECTRONICS CO., LTD. Alex/G100 \n"
|
||||
"<5>[56279.689748] EIP: 0060:[<8103e3ea>] EFLAGS: 00210286 CPU: 3\n"
|
||||
"<5>[56279.689758] EIP is at add_timer+0xd/0x1b\n"
|
||||
"<5>[56279.689762] EAX: f5e00684 EBX: f5e003c0 ECX: 00000002 EDX: "
|
||||
"00200246\n"
|
||||
"<5>[56279.689767] ESI: f5e003c0 EDI: d28bc03c EBP: d2be5e40 ESP: "
|
||||
"d2be5e40\n"
|
||||
"<5>[56279.689772] DS: 007b ES: 007b FS: 00d8 GS: 00e0 SS: 0068\n"
|
||||
"<0>[56279.689778] Process powerd_suspend (pid: 24607, ti=d2be4000 "
|
||||
"task=f5dc9b60 task.ti=d2be4000)\n"
|
||||
"<0>[56279.689782] Stack:\n"
|
||||
"<5>[56279.689785] d2be5e4c f8dccced f4ac02c0 d2be5e70 f8ddc752 "
|
||||
"f5e003c0 f4ac0458 f4ac092c\n"
|
||||
"<5>[56279.689797] f4ac043c f4ac02c0 f4ac0000 f4ac007c d2be5e7c "
|
||||
"f8dd4a33 f4ac0164 d2be5e94\n"
|
||||
"<5>[56279.689809] f87e0304 f69ff0cc f4ac0164 f87e02a4 f4ac0164 "
|
||||
"d2be5eb0 81248968 00000000\n"
|
||||
"<0>[56279.689821] Call Trace:\n"
|
||||
"<5>[56279.689840] [<f8dccced>] ieee80211_sta_restart+0x25/0x8c "
|
||||
"[mac80211]\n"
|
||||
"<5>[56279.689854] [<f8ddc752>] ieee80211_reconfig+0x2e9/0x339 "
|
||||
"[mac80211]\n"
|
||||
"<5>[56279.689869] [<f8dd4a33>] ieee80211_aes_cmac+0x182d/0x184e "
|
||||
"[mac80211]\n"
|
||||
"<5>[56279.689883] [<f87e0304>] cfg80211_get_dev_from_info+0x29b/0x2c0 "
|
||||
"[cfg80211]\n"
|
||||
"<5>[56279.689895] [<f87e02a4>] ? "
|
||||
"cfg80211_get_dev_from_info+0x23b/0x2c0 [cfg80211]\n"
|
||||
"<5>[56279.689904] [<81248968>] legacy_resume+0x25/0x5d\n"
|
||||
"<5>[56279.689910] [<812490ae>] device_resume+0xdd/0x110\n"
|
||||
"<5>[56279.689917] [<812491c2>] dpm_resume_end+0xe1/0x271\n"
|
||||
"<5>[56279.689925] [<81060481>] suspend_devices_and_enter+0x18b/0x1de\n"
|
||||
"<5>[56279.689932] [<810605ba>] enter_state+0xe6/0x132\n"
|
||||
"<5>[56279.689939] [<8105fd4b>] state_store+0x91/0x9d\n"
|
||||
"<5>[56279.689945] [<8105fcba>] ? state_store+0x0/0x9d\n"
|
||||
"<5>[56279.689953] [<81178fb1>] kobj_attr_store+0x16/0x22\n"
|
||||
"<5>[56279.689961] [<810eea5e>] sysfs_write_file+0xc1/0xec\n"
|
||||
"<5>[56279.689969] [<810af443>] vfs_write+0x8f/0x101\n"
|
||||
"<5>[56279.689975] [<810ee99d>] ? sysfs_write_file+0x0/0xec\n"
|
||||
"<5>[56279.689982] [<810af556>] sys_write+0x40/0x65\n"
|
||||
"<5>[56279.689989] [<81002d57>] sysenter_do_call+0x12/0x26\n"
|
||||
"<0>[56279.689993] Code: c1 d3 e2 4a 89 55 f4 f7 d2 21 f2 6a 00 31 c9 89 "
|
||||
"d8 e8 6e fd ff ff 5a 8d 65 f8 5b 5e 5d c3 55 89 e5 3e 8d 74 26 00 83 38 "
|
||||
"00 74 04 <0f> 0b eb fe 8b 50 08 e8 6f ff ff ff 5d c3 55 89 e5 3e 8d 74 "
|
||||
"26 \n"
|
||||
"<0>[56279.690009] EIP: [<8103e3ea>] add_timer+0xd/0x1b SS:ESP "
|
||||
"0068:d2be5e40\n"
|
||||
"<4>[56279.690113] ---[ end trace b71141bb67c6032a ]---\n"
|
||||
"<7>[56279.694069] wlan0: deauthenticated from 00:00:00:00:00:01 "
|
||||
"(Reason: 6)\n"
|
||||
"<0>[56279.703465] Kernel panic - not syncing: Fatal exception\n"
|
||||
"<5>[56279.703471] Pid: 24607, comm: powerd_suspend Tainted: G D "
|
||||
"WC 2.6.38.3+ #1\n"
|
||||
"<5>[56279.703475] Call Trace:\n"
|
||||
"<5>[56279.703483] [<8136648c>] ? panic+0x55/0x152\n"
|
||||
"<5>[56279.703491] [<810057fa>] ? oops_end+0x73/0x81\n"
|
||||
"<5>[56279.703497] [<81005a44>] ? die+0xed/0xf5\n"
|
||||
"<5>[56279.703503] [<810033cb>] ? do_trap+0x7a/0x80\n"
|
||||
"<5>[56279.703509] [<8100369b>] ? do_invalid_op+0x0/0x80\n"
|
||||
"<5>[56279.703515] [<81003711>] ? do_invalid_op+0x76/0x80\n"
|
||||
"<5>[56279.703522] [<8103e3ea>] ? add_timer+0xd/0x1b\n"
|
||||
"<5>[56279.703529] [<81025e23>] ? check_preempt_curr+0x2e/0x69\n"
|
||||
"<5>[56279.703536] [<8102ef28>] ? ttwu_post_activation+0x5a/0x11b\n"
|
||||
"<5>[56279.703543] [<8102fa8d>] ? try_to_wake_up+0x213/0x21d\n"
|
||||
"<5>[56279.703550] [<81368b7f>] ? error_code+0x67/0x6c\n"
|
||||
"<5>[56279.703557] [<8103e3ea>] ? add_timer+0xd/0x1b\n"
|
||||
"<5>[56279.703577] [<f8dccced>] ? ieee80211_sta_restart+0x25/0x8c "
|
||||
"[mac80211]\n"
|
||||
"<5>[56279.703591] [<f8ddc752>] ? ieee80211_reconfig+0x2e9/0x339 "
|
||||
"[mac80211]\n"
|
||||
"<5>[56279.703605] [<f8dd4a33>] ? ieee80211_aes_cmac+0x182d/0x184e "
|
||||
"[mac80211]\n"
|
||||
"<5>[56279.703618] [<f87e0304>] ? "
|
||||
"cfg80211_get_dev_from_info+0x29b/0x2c0 [cfg80211]\n"
|
||||
"<5>[56279.703630] [<f87e02a4>] ? "
|
||||
"cfg80211_get_dev_from_info+0x23b/0x2c0 [cfg80211]\n"
|
||||
"<5>[56279.703637] [<81248968>] ? legacy_resume+0x25/0x5d\n"
|
||||
"<5>[56279.703643] [<812490ae>] ? device_resume+0xdd/0x110\n"
|
||||
"<5>[56279.703649] [<812491c2>] ? dpm_resume_end+0xe1/0x271\n"
|
||||
"<5>[56279.703657] [<81060481>] ? "
|
||||
"suspend_devices_and_enter+0x18b/0x1de\n"
|
||||
"<5>[56279.703663] [<810605ba>] ? enter_state+0xe6/0x132\n"
|
||||
"<5>[56279.703670] [<8105fd4b>] ? state_store+0x91/0x9d\n"
|
||||
"<5>[56279.703676] [<8105fcba>] ? state_store+0x0/0x9d\n"
|
||||
"<5>[56279.703683] [<81178fb1>] ? kobj_attr_store+0x16/0x22\n"
|
||||
"<5>[56279.703690] [<810eea5e>] ? sysfs_write_file+0xc1/0xec\n"
|
||||
"<5>[56279.703697] [<810af443>] ? vfs_write+0x8f/0x101\n"
|
||||
"<5>[56279.703703] [<810ee99d>] ? sysfs_write_file+0x0/0xec\n"
|
||||
"<5>[56279.703709] [<810af556>] ? sys_write+0x40/0x65\n"
|
||||
"<5>[56279.703716] [<81002d57>] ? sysenter_do_call+0x12/0x26\n";
|
||||
|
||||
EXPECT_TRUE(
|
||||
collector_.ComputeKernelStackSignature(kUncertainStackTrace,
|
||||
&signature,
|
||||
false));
|
||||
// The first trace contains only uncertain entries and its hash is 00000000,
|
||||
// so, if we used that, the signature would be kernel-add_timer-00000000.
|
||||
// Instead we use the second-to-last trace for the hash.
|
||||
EXPECT_EQ("kernel-add_timer-B5178878", signature);
|
||||
|
||||
ComputeKernelStackSignatureCommon();
|
||||
}
|
19
crash_reporter/kernel_collector_test.h
Normal file
19
crash_reporter/kernel_collector_test.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2014 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CRASH_REPORTER_KERNEL_COLLECTOR_TEST_H_
|
||||
#define CRASH_REPORTER_KERNEL_COLLECTOR_TEST_H_
|
||||
|
||||
#include "crash-reporter/kernel_collector.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
class KernelCollectorMock : public KernelCollector {
|
||||
public:
|
||||
MOCK_METHOD0(DumpDirMounted, bool());
|
||||
MOCK_METHOD0(SetUpDBus, void());
|
||||
};
|
||||
|
||||
#endif // CRASH_REPORTER_KERNEL_COLLECTOR_TEST_H_
|
49
crash_reporter/kernel_log_collector.sh
Normal file
49
crash_reporter/kernel_log_collector.sh
Normal file
|
@ -0,0 +1,49 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
# Usage example: "kernel_log_collector.sh XXX YYY"
|
||||
# This script searches logs in the /var/log/messages which have the keyword XXX.
|
||||
# And only those logs which are within the last YYY seconds of the latest log
|
||||
# that has the keyword XXX are printed.
|
||||
|
||||
# Kernel log has the possible formats:
|
||||
# 2013-06-14T16:31:40.514513-07:00 localhost kernel: [ 2.682472] MSG MSG ...
|
||||
# 2013-06-19T20:38:58.661826+00:00 localhost kernel: [ 1.668092] MSG MSG ...
|
||||
|
||||
search_key=$1
|
||||
time_duration=$2
|
||||
msg_pattern="^[0-9-]*T[0-9:.+-]* localhost kernel"
|
||||
|
||||
die() {
|
||||
echo "kernel_log_collector: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
get_timestamp() {
|
||||
timestamp="$(echo $1 | cut -d " " -f 1)"
|
||||
timestamp="$(date -d "${timestamp}" +%s)" || exit $?
|
||||
echo "${timestamp}"
|
||||
}
|
||||
|
||||
last_line=$(grep "${msg_pattern}" /var/log/messages | grep -- "${search_key}" | tail -n 1)
|
||||
|
||||
if [ -n "${last_line}" ]; then
|
||||
if ! allowed_timestamp=$(get_timestamp "${last_line}"); then
|
||||
die "coule not get timestamp from: ${last_line}"
|
||||
fi
|
||||
: $(( allowed_timestamp -= ${time_duration} ))
|
||||
grep "${msg_pattern}" /var/log/messages | grep -- "${search_key}" | while read line; do
|
||||
if ! timestamp=$(get_timestamp "${line}"); then
|
||||
die "could not get timestamp from: ${line}"
|
||||
fi
|
||||
if [ ${timestamp} -gt ${allowed_timestamp} ]; then
|
||||
echo "${line}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "END-OF-LOG"
|
||||
|
101
crash_reporter/kernel_warning_collector.cc
Normal file
101
crash_reporter/kernel_warning_collector.cc
Normal file
|
@ -0,0 +1,101 @@
|
|||
// Copyright (c) 2012 The Chromium OS 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 "crash-reporter/kernel_warning_collector.h"
|
||||
|
||||
#include <base/files/file_util.h>
|
||||
#include <base/logging.h>
|
||||
#include <base/strings/string_number_conversions.h>
|
||||
#include <base/strings/string_util.h>
|
||||
#include <base/strings/stringprintf.h>
|
||||
|
||||
namespace {
|
||||
const char kExecName[] = "kernel-warning";
|
||||
const char kKernelWarningSignatureKey[] = "sig";
|
||||
const char kKernelWarningPath[] = "/var/run/kwarn/warning";
|
||||
const pid_t kKernelPid = 0;
|
||||
const uid_t kRootUid = 0;
|
||||
} // namespace
|
||||
|
||||
using base::FilePath;
|
||||
using base::StringPrintf;
|
||||
|
||||
KernelWarningCollector::KernelWarningCollector() {
|
||||
}
|
||||
|
||||
KernelWarningCollector::~KernelWarningCollector() {
|
||||
}
|
||||
|
||||
bool KernelWarningCollector::LoadKernelWarning(std::string *content,
|
||||
std::string *signature) {
|
||||
FilePath kernel_warning_path(kKernelWarningPath);
|
||||
if (!base::ReadFileToString(kernel_warning_path, content)) {
|
||||
LOG(ERROR) << "Could not open " << kKernelWarningPath;
|
||||
return false;
|
||||
}
|
||||
// The signature is in the first line.
|
||||
std::string::size_type end_position = content->find('\n');
|
||||
if (end_position == std::string::npos) {
|
||||
LOG(ERROR) << "unexpected kernel warning format";
|
||||
return false;
|
||||
}
|
||||
*signature = content->substr(0, end_position);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KernelWarningCollector::Collect() {
|
||||
std::string reason = "normal collection";
|
||||
bool feedback = true;
|
||||
if (IsDeveloperImage()) {
|
||||
reason = "always collect from developer builds";
|
||||
feedback = true;
|
||||
} else if (!is_feedback_allowed_function_()) {
|
||||
reason = "no user consent";
|
||||
feedback = false;
|
||||
}
|
||||
|
||||
LOG(INFO) << "Processing kernel warning: " << reason;
|
||||
|
||||
if (!feedback) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string kernel_warning;
|
||||
std::string warning_signature;
|
||||
if (!LoadKernelWarning(&kernel_warning, &warning_signature)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
FilePath root_crash_directory;
|
||||
if (!GetCreatedCrashDirectoryByEuid(kRootUid, &root_crash_directory,
|
||||
nullptr)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string dump_basename =
|
||||
FormatDumpBasename(kExecName, time(nullptr), kKernelPid);
|
||||
FilePath kernel_crash_path = root_crash_directory.Append(
|
||||
StringPrintf("%s.kcrash", dump_basename.c_str()));
|
||||
|
||||
// We must use WriteNewFile instead of base::WriteFile as we
|
||||
// do not want to write with root access to a symlink that an attacker
|
||||
// might have created.
|
||||
if (WriteNewFile(kernel_crash_path,
|
||||
kernel_warning.data(),
|
||||
kernel_warning.length()) !=
|
||||
static_cast<int>(kernel_warning.length())) {
|
||||
LOG(INFO) << "Failed to write kernel warning to "
|
||||
<< kernel_crash_path.value().c_str();
|
||||
return true;
|
||||
}
|
||||
|
||||
AddCrashMetaData(kKernelWarningSignatureKey, warning_signature);
|
||||
WriteCrashMetaData(
|
||||
root_crash_directory.Append(
|
||||
StringPrintf("%s.meta", dump_basename.c_str())),
|
||||
kExecName, kernel_crash_path.value());
|
||||
|
||||
LOG(INFO) << "Stored kernel warning into " << kernel_crash_path.value();
|
||||
return true;
|
||||
}
|
35
crash_reporter/kernel_warning_collector.h
Normal file
35
crash_reporter/kernel_warning_collector.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
|
||||
#define CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <base/macros.h>
|
||||
#include <gtest/gtest_prod.h> // for FRIEND_TEST
|
||||
|
||||
#include "crash-reporter/crash_collector.h"
|
||||
|
||||
// Kernel warning collector.
|
||||
class KernelWarningCollector : public CrashCollector {
|
||||
public:
|
||||
KernelWarningCollector();
|
||||
|
||||
~KernelWarningCollector() override;
|
||||
|
||||
// Collects warning.
|
||||
bool Collect();
|
||||
|
||||
private:
|
||||
friend class KernelWarningCollectorTest;
|
||||
FRIEND_TEST(KernelWarningCollectorTest, CollectOK);
|
||||
|
||||
// Reads the full content of the kernel warn dump and its signature.
|
||||
bool LoadKernelWarning(std::string *content, std::string *signature);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(KernelWarningCollector);
|
||||
};
|
||||
|
||||
#endif // CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
|
291
crash_reporter/list_proxies.cc
Normal file
291
crash_reporter/list_proxies.cc
Normal file
|
@ -0,0 +1,291 @@
|
|||
// Copyright (c) 2011 The Chromium OS 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 <sysexits.h>
|
||||
#include <unistd.h> // for isatty()
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <base/cancelable_callback.h>
|
||||
#include <base/command_line.h>
|
||||
#include <base/files/file_util.h>
|
||||
#include <base/memory/weak_ptr.h>
|
||||
#include <base/strings/string_number_conversions.h>
|
||||
#include <base/strings/string_tokenizer.h>
|
||||
#include <base/strings/string_util.h>
|
||||
#include <base/values.h>
|
||||
#include <chromeos/daemons/dbus_daemon.h>
|
||||
#include <chromeos/syslog_logging.h>
|
||||
|
||||
#include "libcrosservice/dbus-proxies.h"
|
||||
|
||||
using std::unique_ptr;
|
||||
|
||||
namespace {
|
||||
|
||||
const char kLibCrosProxyResolvedSignalInterface[] =
|
||||
"org.chromium.CrashReporterLibcrosProxyResolvedInterface";
|
||||
const char kLibCrosProxyResolvedName[] = "ProxyResolved";
|
||||
const char kLibCrosServiceName[] = "org.chromium.LibCrosService";
|
||||
const char kNoProxy[] = "direct://";
|
||||
|
||||
const int kTimeoutDefaultSeconds = 5;
|
||||
|
||||
const char kHelp[] = "help";
|
||||
const char kQuiet[] = "quiet";
|
||||
const char kTimeout[] = "timeout";
|
||||
const char kVerbose[] = "verbose";
|
||||
// Help message to show when the --help command line switch is specified.
|
||||
const char kHelpMessage[] =
|
||||
"Chromium OS Crash helper: proxy lister\n"
|
||||
"\n"
|
||||
"Available Switches:\n"
|
||||
" --quiet Only print the proxies\n"
|
||||
" --verbose Print additional messages even when not run from a TTY\n"
|
||||
" --timeout=N Set timeout for browser resolving proxies (default is 5)\n"
|
||||
" --help Show this help.\n";
|
||||
|
||||
// Copied from src/update_engine/chrome_browser_proxy_resolver.cc
|
||||
// Parses the browser's answer for resolved proxies. It returns a
|
||||
// list of strings, each of which is a resolved proxy.
|
||||
std::vector<std::string> ParseProxyString(const std::string& input) {
|
||||
std::vector<std::string> ret;
|
||||
// Some of this code taken from
|
||||
// http://src.chromium.org/svn/trunk/src/net/proxy/proxy_server.cc and
|
||||
// http://src.chromium.org/svn/trunk/src/net/proxy/proxy_list.cc
|
||||
base::StringTokenizer entry_tok(input, ";");
|
||||
while (entry_tok.GetNext()) {
|
||||
std::string token = entry_tok.token();
|
||||
base::TrimWhitespaceASCII(token, base::TRIM_ALL, &token);
|
||||
|
||||
// Start by finding the first space (if any).
|
||||
std::string::iterator space;
|
||||
for (space = token.begin(); space != token.end(); ++space) {
|
||||
if (IsAsciiWhitespace(*space)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string scheme = std::string(token.begin(), space);
|
||||
base::StringToLowerASCII(&scheme);
|
||||
// Chrome uses "socks" to mean socks4 and "proxy" to mean http.
|
||||
if (scheme == "socks")
|
||||
scheme += "4";
|
||||
else if (scheme == "proxy")
|
||||
scheme = "http";
|
||||
else if (scheme != "https" &&
|
||||
scheme != "socks4" &&
|
||||
scheme != "socks5" &&
|
||||
scheme != "direct")
|
||||
continue; // Invalid proxy scheme
|
||||
|
||||
std::string host_and_port = std::string(space, token.end());
|
||||
base::TrimWhitespaceASCII(host_and_port, base::TRIM_ALL, &host_and_port);
|
||||
if (scheme != "direct" && host_and_port.empty())
|
||||
continue; // Must supply host/port when non-direct proxy used.
|
||||
ret.push_back(scheme + "://" + host_and_port);
|
||||
}
|
||||
if (ret.empty() || *ret.rbegin() != kNoProxy)
|
||||
ret.push_back(kNoProxy);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// A class for interfacing with Chrome to resolve proxies for a given source
|
||||
// url. The class is initialized with the given source url to check, the
|
||||
// signal interface and name that Chrome will reply to, and how long to wait
|
||||
// for the resolve request to timeout. Once initialized, the Run() function
|
||||
// must be called, which blocks on the D-Bus call to Chrome. The call returns
|
||||
// after either the timeout or the proxy has been resolved. The resolved
|
||||
// proxies can then be accessed through the proxies() function.
|
||||
class ProxyResolver : public chromeos::DBusDaemon {
|
||||
public:
|
||||
ProxyResolver(const std::string& source_url,
|
||||
const std::string& signal_interface,
|
||||
const std::string& signal_name,
|
||||
base::TimeDelta timeout)
|
||||
: source_url_(source_url),
|
||||
signal_interface_(signal_interface),
|
||||
signal_name_(signal_name),
|
||||
timeout_(timeout),
|
||||
weak_ptr_factory_(this),
|
||||
timeout_callback_(base::Bind(&ProxyResolver::HandleBrowserTimeout,
|
||||
weak_ptr_factory_.GetWeakPtr())) {}
|
||||
|
||||
~ProxyResolver() override {}
|
||||
|
||||
const std::vector<std::string>& proxies() {
|
||||
return proxies_;
|
||||
}
|
||||
|
||||
int Run() override {
|
||||
// Add task for if the browser proxy call times out.
|
||||
base::MessageLoop::current()->PostDelayedTask(
|
||||
FROM_HERE,
|
||||
timeout_callback_.callback(),
|
||||
timeout_);
|
||||
|
||||
return chromeos::DBusDaemon::Run();
|
||||
}
|
||||
|
||||
protected:
|
||||
// If the browser times out, quit the run loop.
|
||||
void HandleBrowserTimeout() {
|
||||
LOG(ERROR) << "Timeout while waiting for browser to resolve proxy";
|
||||
Quit();
|
||||
}
|
||||
|
||||
// If the signal handler connects successfully, call the browser's
|
||||
// ResolveNetworkProxy D-Bus method. Otherwise, don't do anything and let
|
||||
// the timeout task quit the run loop.
|
||||
void HandleDBusSignalConnected(const std::string& interface,
|
||||
const std::string& signal,
|
||||
bool success) {
|
||||
if (!success) {
|
||||
LOG(ERROR) << "Could not connect to signal " << interface << "."
|
||||
<< signal;
|
||||
timeout_callback_.Cancel();
|
||||
Quit();
|
||||
return;
|
||||
}
|
||||
|
||||
chromeos::ErrorPtr error;
|
||||
call_proxy_->ResolveNetworkProxy(source_url_,
|
||||
signal_interface_,
|
||||
signal_name_,
|
||||
&error);
|
||||
|
||||
if (error) {
|
||||
LOG(ERROR) << "Call to ResolveNetworkProxy failed: "
|
||||
<< error->GetMessage();
|
||||
timeout_callback_.Cancel();
|
||||
Quit();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle incoming ProxyResolved signal.
|
||||
void HandleProxyResolvedSignal(const std::string& source_url,
|
||||
const std::string& proxy_info,
|
||||
const std::string& error_message) {
|
||||
timeout_callback_.Cancel();
|
||||
proxies_ = ParseProxyString(proxy_info);
|
||||
LOG(INFO) << "Found proxies via browser signal: "
|
||||
<< JoinString(proxies_, 'x');
|
||||
|
||||
Quit();
|
||||
}
|
||||
|
||||
int OnInit() override {
|
||||
int return_code = chromeos::DBusDaemon::OnInit();
|
||||
if (return_code != EX_OK)
|
||||
return return_code;
|
||||
|
||||
// Initialize D-Bus proxies.
|
||||
call_proxy_.reset(
|
||||
new org::chromium::LibCrosServiceInterfaceProxy(bus_,
|
||||
kLibCrosServiceName));
|
||||
signal_proxy_.reset(
|
||||
new org::chromium::CrashReporterLibcrosProxyResolvedInterfaceProxy(
|
||||
bus_,
|
||||
kLibCrosServiceName));
|
||||
|
||||
// Set up the D-Bus signal handler.
|
||||
// TODO(crbug.com/446115): Update ResolveNetworkProxy call to use an
|
||||
// asynchronous return value rather than a return signal.
|
||||
signal_proxy_->RegisterProxyResolvedSignalHandler(
|
||||
base::Bind(&ProxyResolver::HandleProxyResolvedSignal,
|
||||
weak_ptr_factory_.GetWeakPtr()),
|
||||
base::Bind(&ProxyResolver::HandleDBusSignalConnected,
|
||||
weak_ptr_factory_.GetWeakPtr()));
|
||||
|
||||
return EX_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
unique_ptr<org::chromium::LibCrosServiceInterfaceProxy> call_proxy_;
|
||||
unique_ptr<org::chromium::CrashReporterLibcrosProxyResolvedInterfaceProxy>
|
||||
signal_proxy_;
|
||||
|
||||
const std::string source_url_;
|
||||
const std::string signal_interface_;
|
||||
const std::string signal_name_;
|
||||
base::TimeDelta timeout_;
|
||||
|
||||
std::vector<std::string> proxies_;
|
||||
base::WeakPtrFactory<ProxyResolver> weak_ptr_factory_;
|
||||
|
||||
base::CancelableClosure timeout_callback_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ProxyResolver);
|
||||
};
|
||||
|
||||
static bool ShowBrowserProxies(std::string url, base::TimeDelta timeout) {
|
||||
// Initialize and run the proxy resolver to watch for signals.
|
||||
ProxyResolver resolver(url,
|
||||
kLibCrosProxyResolvedSignalInterface,
|
||||
kLibCrosProxyResolvedName,
|
||||
timeout);
|
||||
resolver.Run();
|
||||
|
||||
std::vector<std::string> proxies = resolver.proxies();
|
||||
|
||||
// If proxies is empty, then the timeout was reached waiting for the proxy
|
||||
// resolved signal. If no proxies are defined, proxies will be populated
|
||||
// with "direct://".
|
||||
if (proxies.empty())
|
||||
return false;
|
||||
|
||||
for (const auto& proxy : proxies) {
|
||||
printf("%s\n", proxy.c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
base::CommandLine::Init(argc, argv);
|
||||
base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
|
||||
|
||||
if (cl->HasSwitch(kHelp)) {
|
||||
LOG(INFO) << kHelpMessage;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool quiet = cl->HasSwitch(kQuiet);
|
||||
bool verbose = cl->HasSwitch(kVerbose);
|
||||
|
||||
int timeout = kTimeoutDefaultSeconds;
|
||||
std::string str_timeout = cl->GetSwitchValueASCII(kTimeout);
|
||||
if (!str_timeout.empty() && !base::StringToInt(str_timeout, &timeout)) {
|
||||
LOG(ERROR) << "Invalid timeout value: " << str_timeout;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Default to logging to syslog.
|
||||
int init_flags = chromeos::kLogToSyslog;
|
||||
// Log to stderr if a TTY (and "-quiet" wasn't passed), or if "-verbose"
|
||||
// was passed.
|
||||
|
||||
if ((!quiet && isatty(STDERR_FILENO)) || verbose)
|
||||
init_flags |= chromeos::kLogToStderr;
|
||||
chromeos::InitLog(init_flags);
|
||||
|
||||
std::string url;
|
||||
base::CommandLine::StringVector urls = cl->GetArgs();
|
||||
if (!urls.empty()) {
|
||||
url = urls[0];
|
||||
LOG(INFO) << "Resolving proxies for URL: " << url;
|
||||
} else {
|
||||
LOG(INFO) << "Resolving proxies without URL";
|
||||
}
|
||||
|
||||
if (!ShowBrowserProxies(url, base::TimeDelta::FromSeconds(timeout))) {
|
||||
LOG(ERROR) << "Error resolving proxies via the browser";
|
||||
LOG(INFO) << "Assuming direct proxy";
|
||||
printf("%s\n", kNoProxy);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
11
crash_reporter/testrunner.cc
Normal file
11
crash_reporter/testrunner.cc
Normal file
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2015 The Chromium OS 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 <chromeos/test_helpers.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
SetUpTests(&argc, argv, true);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
232
crash_reporter/udev_collector.cc
Normal file
232
crash_reporter/udev_collector.cc
Normal file
|
@ -0,0 +1,232 @@
|
|||
// Copyright (c) 2012 The Chromium OS 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 "crash-reporter/udev_collector.h"
|
||||
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <base/files/file_enumerator.h>
|
||||
#include <base/files/file_util.h>
|
||||
#include <base/logging.h>
|
||||
#include <base/strings/string_number_conversions.h>
|
||||
#include <base/strings/string_split.h>
|
||||
#include <base/strings/string_util.h>
|
||||
#include <base/strings/stringprintf.h>
|
||||
#include <chromeos/process.h>
|
||||
|
||||
using base::FilePath;
|
||||
|
||||
namespace {
|
||||
|
||||
const char kCollectUdevSignature[] = "crash_reporter-udev-collection";
|
||||
const char kGzipPath[] = "/bin/gzip";
|
||||
const char kUdevExecName[] = "udev";
|
||||
const char kUdevSignatureKey[] = "sig";
|
||||
const char kUdevSubsystemDevCoredump[] = "devcoredump";
|
||||
const char kDefaultDevCoredumpDirectory[] = "/sys/class/devcoredump";
|
||||
const char kDevCoredumpFilePrefixFormat[] = "devcoredump_%s";
|
||||
|
||||
} // namespace
|
||||
|
||||
UdevCollector::UdevCollector()
|
||||
: dev_coredump_directory_(kDefaultDevCoredumpDirectory) {}
|
||||
|
||||
UdevCollector::~UdevCollector() {}
|
||||
|
||||
bool UdevCollector::HandleCrash(const std::string &udev_event) {
|
||||
if (IsDeveloperImage()) {
|
||||
LOG(INFO) << "developer image - collect udev crash info.";
|
||||
} else if (is_feedback_allowed_function_()) {
|
||||
LOG(INFO) << "Consent given - collect udev crash info.";
|
||||
} else {
|
||||
LOG(INFO) << "Ignoring - Non-developer image and no consent given.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Process the udev event string.
|
||||
// First get all the key-value pairs.
|
||||
std::vector<std::pair<std::string, std::string>> udev_event_keyval;
|
||||
base::SplitStringIntoKeyValuePairs(udev_event, '=', ':', &udev_event_keyval);
|
||||
std::vector<std::pair<std::string, std::string>>::const_iterator iter;
|
||||
std::map<std::string, std::string> udev_event_map;
|
||||
for (iter = udev_event_keyval.begin();
|
||||
iter != udev_event_keyval.end();
|
||||
++iter) {
|
||||
udev_event_map[iter->first] = iter->second;
|
||||
}
|
||||
|
||||
// Make sure the crash directory exists, or create it if it doesn't.
|
||||
FilePath crash_directory;
|
||||
if (!GetCreatedCrashDirectoryByEuid(0, &crash_directory, nullptr)) {
|
||||
LOG(ERROR) << "Could not get crash directory.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (udev_event_map["SUBSYSTEM"] == kUdevSubsystemDevCoredump) {
|
||||
int instance_number;
|
||||
if (!base::StringToInt(udev_event_map["KERNEL_NUMBER"], &instance_number)) {
|
||||
LOG(ERROR) << "Invalid kernel number: "
|
||||
<< udev_event_map["KERNEL_NUMBER"];
|
||||
return false;
|
||||
}
|
||||
return ProcessDevCoredump(crash_directory, instance_number);
|
||||
}
|
||||
|
||||
return ProcessUdevCrashLogs(crash_directory,
|
||||
udev_event_map["ACTION"],
|
||||
udev_event_map["KERNEL"],
|
||||
udev_event_map["SUBSYSTEM"]);
|
||||
}
|
||||
|
||||
bool UdevCollector::ProcessUdevCrashLogs(const FilePath& crash_directory,
|
||||
const std::string& action,
|
||||
const std::string& kernel,
|
||||
const std::string& subsystem) {
|
||||
// Construct the basename string for crash_reporter_logs.conf:
|
||||
// "crash_reporter-udev-collection-[action]-[name]-[subsystem]"
|
||||
// If a udev field is not provided, "" is used in its place, e.g.:
|
||||
// "crash_reporter-udev-collection-[action]--[subsystem]"
|
||||
// Hence, "" is used as a wildcard name string.
|
||||
// TODO(sque, crosbug.com/32238): Implement wildcard checking.
|
||||
std::string basename = action + "-" + kernel + "-" + subsystem;
|
||||
std::string udev_log_name = std::string(kCollectUdevSignature) + '-' +
|
||||
basename;
|
||||
|
||||
// Create the destination path.
|
||||
std::string log_file_name =
|
||||
FormatDumpBasename(basename, time(nullptr), 0);
|
||||
FilePath crash_path = GetCrashPath(crash_directory, log_file_name, "log");
|
||||
|
||||
// Handle the crash.
|
||||
bool result = GetLogContents(log_config_path_, udev_log_name, crash_path);
|
||||
if (!result) {
|
||||
LOG(ERROR) << "Error reading udev log info " << udev_log_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compress the output using gzip.
|
||||
chromeos::ProcessImpl gzip_process;
|
||||
gzip_process.AddArg(kGzipPath);
|
||||
gzip_process.AddArg(crash_path.value());
|
||||
int process_result = gzip_process.Run();
|
||||
FilePath crash_path_zipped = FilePath(crash_path.value() + ".gz");
|
||||
// If the zip file was not created, use the uncompressed file.
|
||||
if (process_result != 0 || !base::PathExists(crash_path_zipped))
|
||||
LOG(ERROR) << "Could not create zip file " << crash_path_zipped.value();
|
||||
else
|
||||
crash_path = crash_path_zipped;
|
||||
|
||||
std::string exec_name = std::string(kUdevExecName) + "-" + subsystem;
|
||||
AddCrashMetaData(kUdevSignatureKey, udev_log_name);
|
||||
WriteCrashMetaData(GetCrashPath(crash_directory, log_file_name, "meta"),
|
||||
exec_name, crash_path.value());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UdevCollector::ProcessDevCoredump(const FilePath& crash_directory,
|
||||
int instance_number) {
|
||||
FilePath coredump_path =
|
||||
FilePath(base::StringPrintf("%s/devcd%d/data",
|
||||
dev_coredump_directory_.c_str(),
|
||||
instance_number));
|
||||
if (!base::PathExists(coredump_path)) {
|
||||
LOG(ERROR) << "Device coredump file " << coredump_path.value()
|
||||
<< " does not exist";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add coredump file to the crash directory.
|
||||
if (!AppendDevCoredump(crash_directory, coredump_path, instance_number)) {
|
||||
ClearDevCoredump(coredump_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear the coredump data to allow generation of future device coredumps
|
||||
// without having to wait for the 5-minutes timeout.
|
||||
return ClearDevCoredump(coredump_path);
|
||||
}
|
||||
|
||||
bool UdevCollector::AppendDevCoredump(const FilePath& crash_directory,
|
||||
const FilePath& coredump_path,
|
||||
int instance_number) {
|
||||
// Retrieve the driver name of the failing device.
|
||||
std::string driver_name = GetFailingDeviceDriverName(instance_number);
|
||||
if (driver_name.empty()) {
|
||||
LOG(ERROR) << "Failed to obtain driver name for instance: "
|
||||
<< instance_number;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string coredump_prefix =
|
||||
base::StringPrintf(kDevCoredumpFilePrefixFormat, driver_name.c_str());
|
||||
|
||||
std::string dump_basename = FormatDumpBasename(coredump_prefix,
|
||||
time(nullptr),
|
||||
instance_number);
|
||||
FilePath core_path = GetCrashPath(crash_directory, dump_basename, "devcore");
|
||||
FilePath log_path = GetCrashPath(crash_directory, dump_basename, "log");
|
||||
FilePath meta_path = GetCrashPath(crash_directory, dump_basename, "meta");
|
||||
|
||||
// Collect coredump data.
|
||||
if (!base::CopyFile(coredump_path, core_path)) {
|
||||
LOG(ERROR) << "Failed to copy device coredumpm file from "
|
||||
<< coredump_path.value() << " to " << core_path.value();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Collect additional logs if one is specified in the config file.
|
||||
std::string udev_log_name = std::string(kCollectUdevSignature) + '-' +
|
||||
kUdevSubsystemDevCoredump + '-' + driver_name;
|
||||
bool result = GetLogContents(log_config_path_, udev_log_name, log_path);
|
||||
if (result) {
|
||||
AddCrashMetaUploadFile("logs", log_path.value());
|
||||
}
|
||||
|
||||
WriteCrashMetaData(meta_path, coredump_prefix, core_path.value());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UdevCollector::ClearDevCoredump(const FilePath& coredump_path) {
|
||||
if (!base::WriteFile(coredump_path, "0", 1)) {
|
||||
LOG(ERROR) << "Failed to delete the coredump data file "
|
||||
<< coredump_path.value();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string UdevCollector::GetFailingDeviceDriverName(int instance_number) {
|
||||
FilePath failing_uevent_path =
|
||||
FilePath(base::StringPrintf("%s/devcd%d/failing_device/uevent",
|
||||
dev_coredump_directory_.c_str(),
|
||||
instance_number));
|
||||
if (!base::PathExists(failing_uevent_path)) {
|
||||
LOG(ERROR) << "Failing uevent path " << failing_uevent_path.value()
|
||||
<< " does not exist";
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string uevent_content;
|
||||
if (!base::ReadFileToString(failing_uevent_path, &uevent_content)) {
|
||||
LOG(ERROR) << "Failed to read uevent file " << failing_uevent_path.value();
|
||||
return "";
|
||||
}
|
||||
|
||||
// Parse uevent file contents as key-value pairs.
|
||||
std::vector<std::pair<std::string, std::string>> uevent_keyval;
|
||||
base::SplitStringIntoKeyValuePairs(uevent_content, '=', '\n', &uevent_keyval);
|
||||
std::vector<std::pair<std::string, std::string>>::const_iterator iter;
|
||||
for (iter = uevent_keyval.begin();
|
||||
iter != uevent_keyval.end();
|
||||
++iter) {
|
||||
if (iter->first == "DRIVER") {
|
||||
return iter->second;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
64
crash_reporter/udev_collector.h
Normal file
64
crash_reporter/udev_collector.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CRASH_REPORTER_UDEV_COLLECTOR_H_
|
||||
#define CRASH_REPORTER_UDEV_COLLECTOR_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <base/files/file_path.h>
|
||||
#include <base/macros.h>
|
||||
#include <gtest/gtest_prod.h> // for FRIEND_TEST
|
||||
|
||||
#include "crash-reporter/crash_collector.h"
|
||||
|
||||
// Udev crash collector.
|
||||
class UdevCollector : public CrashCollector {
|
||||
public:
|
||||
UdevCollector();
|
||||
|
||||
~UdevCollector() override;
|
||||
|
||||
// The udev event string should be formatted as follows:
|
||||
// "ACTION=[action]:KERNEL=[name]:SUBSYSTEM=[subsystem]"
|
||||
// The values don't have to be in any particular order. One or more of them
|
||||
// could be omitted, in which case it would be treated as a wildcard (*).
|
||||
bool HandleCrash(const std::string& udev_event);
|
||||
|
||||
protected:
|
||||
std::string dev_coredump_directory_;
|
||||
|
||||
private:
|
||||
friend class UdevCollectorTest;
|
||||
|
||||
// Process udev crash logs, collecting log files according to the config
|
||||
// file (crash_reporter_logs.conf).
|
||||
bool ProcessUdevCrashLogs(const base::FilePath& crash_directory,
|
||||
const std::string& action,
|
||||
const std::string& kernel,
|
||||
const std::string& subsystem);
|
||||
// Process device coredump, collecting device coredump file.
|
||||
// |instance_number| is the kernel number of the virtual device for the device
|
||||
// coredump instance.
|
||||
bool ProcessDevCoredump(const base::FilePath& crash_directory,
|
||||
int instance_number);
|
||||
// Copy device coredump file to crash directory, and perform necessary
|
||||
// coredump file management.
|
||||
bool AppendDevCoredump(const base::FilePath& crash_directory,
|
||||
const base::FilePath& coredump_path,
|
||||
int instance_number);
|
||||
// Clear the device coredump file by performing a dummy write to it.
|
||||
bool ClearDevCoredump(const base::FilePath& coredump_path);
|
||||
// Return the driver name of the device that generates the coredump.
|
||||
std::string GetFailingDeviceDriverName(int instance_number);
|
||||
|
||||
// Mutator for unit testing.
|
||||
void set_log_config_path(const std::string& path) {
|
||||
log_config_path_ = base::FilePath(path);
|
||||
}
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(UdevCollector);
|
||||
};
|
||||
|
||||
#endif // CRASH_REPORTER_UDEV_COLLECTOR_H_
|
171
crash_reporter/udev_collector_test.cc
Normal file
171
crash_reporter/udev_collector_test.cc
Normal file
|
@ -0,0 +1,171 @@
|
|||
// Copyright (c) 2012 The Chromium OS 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 <base/files/file_enumerator.h>
|
||||
#include <base/files/file_util.h>
|
||||
#include <base/files/scoped_temp_dir.h>
|
||||
#include <base/strings/stringprintf.h>
|
||||
#include <chromeos/syslog_logging.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "crash-reporter/udev_collector.h"
|
||||
|
||||
using base::FilePath;
|
||||
|
||||
namespace {
|
||||
|
||||
// Dummy log config file name.
|
||||
const char kLogConfigFileName[] = "log_config_file";
|
||||
|
||||
// Dummy directory for storing device coredumps.
|
||||
const char kDevCoredumpDirectory[] = "devcoredump";
|
||||
|
||||
// A bunch of random rules to put into the dummy log config file.
|
||||
const char kLogConfigFileContents[] =
|
||||
"crash_reporter-udev-collection-change-card0-drm=echo change card0 drm\n"
|
||||
"crash_reporter-udev-collection-add-state0-cpu=echo change state0 cpu\n"
|
||||
"crash_reporter-udev-collection-devcoredump-iwlwifi=echo devcoredump\n"
|
||||
"cros_installer=echo not for udev";
|
||||
|
||||
const char kCrashLogFilePattern[] = "*.log.gz";
|
||||
const char kDevCoredumpFilePattern[] = "*.devcore";
|
||||
|
||||
// Dummy content for device coredump data file.
|
||||
const char kDevCoredumpDataContents[] = "coredump";
|
||||
|
||||
// Content for failing device's uevent file.
|
||||
const char kFailingDeviceUeventContents[] = "DRIVER=iwlwifi\n";
|
||||
|
||||
void CountCrash() {}
|
||||
|
||||
bool s_consent_given = true;
|
||||
|
||||
bool IsMetrics() {
|
||||
return s_consent_given;
|
||||
}
|
||||
|
||||
// Returns the number of files found in the given path that matches the
|
||||
// specified file name pattern.
|
||||
int GetNumFiles(const FilePath& path, const std::string& file_pattern) {
|
||||
base::FileEnumerator enumerator(path, false, base::FileEnumerator::FILES,
|
||||
file_pattern);
|
||||
int num_files = 0;
|
||||
for (FilePath file_path = enumerator.Next();
|
||||
!file_path.value().empty();
|
||||
file_path = enumerator.Next()) {
|
||||
num_files++;
|
||||
}
|
||||
return num_files;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class UdevCollectorMock : public UdevCollector {
|
||||
public:
|
||||
MOCK_METHOD0(SetUpDBus, void());
|
||||
};
|
||||
|
||||
class UdevCollectorTest : public ::testing::Test {
|
||||
protected:
|
||||
base::ScopedTempDir temp_dir_generator_;
|
||||
|
||||
void HandleCrash(const std::string &udev_event) {
|
||||
collector_.HandleCrash(udev_event);
|
||||
}
|
||||
|
||||
void GenerateDevCoredump(const std::string& device_name) {
|
||||
// Generate coredump data file.
|
||||
ASSERT_TRUE(CreateDirectory(
|
||||
FilePath(base::StringPrintf("%s/%s",
|
||||
collector_.dev_coredump_directory_.c_str(),
|
||||
device_name.c_str()))));
|
||||
FilePath data_path =
|
||||
FilePath(base::StringPrintf("%s/%s/data",
|
||||
collector_.dev_coredump_directory_.c_str(),
|
||||
device_name.c_str()));
|
||||
ASSERT_EQ(strlen(kDevCoredumpDataContents),
|
||||
base::WriteFile(data_path,
|
||||
kDevCoredumpDataContents,
|
||||
strlen(kDevCoredumpDataContents)));
|
||||
// Generate uevent file for failing device.
|
||||
ASSERT_TRUE(CreateDirectory(
|
||||
FilePath(base::StringPrintf("%s/%s/failing_device",
|
||||
collector_.dev_coredump_directory_.c_str(),
|
||||
device_name.c_str()))));
|
||||
FilePath uevent_path =
|
||||
FilePath(base::StringPrintf("%s/%s/failing_device/uevent",
|
||||
collector_.dev_coredump_directory_.c_str(),
|
||||
device_name.c_str()));
|
||||
ASSERT_EQ(strlen(kFailingDeviceUeventContents),
|
||||
base::WriteFile(uevent_path,
|
||||
kFailingDeviceUeventContents,
|
||||
strlen(kFailingDeviceUeventContents)));
|
||||
}
|
||||
|
||||
private:
|
||||
void SetUp() override {
|
||||
s_consent_given = true;
|
||||
|
||||
EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
|
||||
|
||||
collector_.Initialize(CountCrash, IsMetrics);
|
||||
|
||||
ASSERT_TRUE(temp_dir_generator_.CreateUniqueTempDir());
|
||||
|
||||
FilePath log_config_path =
|
||||
temp_dir_generator_.path().Append(kLogConfigFileName);
|
||||
collector_.log_config_path_ = log_config_path;
|
||||
collector_.ForceCrashDirectory(temp_dir_generator_.path());
|
||||
|
||||
FilePath dev_coredump_path =
|
||||
temp_dir_generator_.path().Append(kDevCoredumpDirectory);
|
||||
collector_.dev_coredump_directory_ = dev_coredump_path.value();
|
||||
|
||||
// Write to a dummy log config file.
|
||||
ASSERT_EQ(strlen(kLogConfigFileContents),
|
||||
base::WriteFile(log_config_path,
|
||||
kLogConfigFileContents,
|
||||
strlen(kLogConfigFileContents)));
|
||||
|
||||
chromeos::ClearLog();
|
||||
}
|
||||
|
||||
UdevCollectorMock collector_;
|
||||
};
|
||||
|
||||
TEST_F(UdevCollectorTest, TestNoConsent) {
|
||||
s_consent_given = false;
|
||||
HandleCrash("ACTION=change:KERNEL=card0:SUBSYSTEM=drm");
|
||||
EXPECT_EQ(0, GetNumFiles(temp_dir_generator_.path(), kCrashLogFilePattern));
|
||||
}
|
||||
|
||||
TEST_F(UdevCollectorTest, TestNoMatch) {
|
||||
// No rule should match this.
|
||||
HandleCrash("ACTION=change:KERNEL=foo:SUBSYSTEM=bar");
|
||||
EXPECT_EQ(0, GetNumFiles(temp_dir_generator_.path(), kCrashLogFilePattern));
|
||||
}
|
||||
|
||||
TEST_F(UdevCollectorTest, TestMatches) {
|
||||
// Try multiple udev events in sequence. The number of log files generated
|
||||
// should increase.
|
||||
HandleCrash("ACTION=change:KERNEL=card0:SUBSYSTEM=drm");
|
||||
EXPECT_EQ(1, GetNumFiles(temp_dir_generator_.path(), kCrashLogFilePattern));
|
||||
HandleCrash("ACTION=add:KERNEL=state0:SUBSYSTEM=cpu");
|
||||
EXPECT_EQ(2, GetNumFiles(temp_dir_generator_.path(), kCrashLogFilePattern));
|
||||
}
|
||||
|
||||
TEST_F(UdevCollectorTest, TestDevCoredump) {
|
||||
GenerateDevCoredump("devcd0");
|
||||
HandleCrash("ACTION=add:KERNEL_NUMBER=0:SUBSYSTEM=devcoredump");
|
||||
EXPECT_EQ(1, GetNumFiles(temp_dir_generator_.path(),
|
||||
kDevCoredumpFilePattern));
|
||||
GenerateDevCoredump("devcd1");
|
||||
HandleCrash("ACTION=add:KERNEL_NUMBER=1:SUBSYSTEM=devcoredump");
|
||||
EXPECT_EQ(2, GetNumFiles(temp_dir_generator_.path(),
|
||||
kDevCoredumpFilePattern));
|
||||
}
|
||||
|
||||
// TODO(sque, crosbug.com/32238) - test wildcard cases, multiple identical udev
|
||||
// events.
|
81
crash_reporter/unclean_shutdown_collector.cc
Normal file
81
crash_reporter/unclean_shutdown_collector.cc
Normal file
|
@ -0,0 +1,81 @@
|
|||
// Copyright (c) 2010 The Chromium OS 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 "crash-reporter/unclean_shutdown_collector.h"
|
||||
|
||||
#include <base/files/file_util.h>
|
||||
#include <base/logging.h>
|
||||
|
||||
static const char kUncleanShutdownFile[] =
|
||||
"/var/lib/crash_reporter/pending_clean_shutdown";
|
||||
|
||||
// Files created by power manager used for crash reporting.
|
||||
static const char kPowerdTracePath[] = "/var/lib/power_manager";
|
||||
// Presence of this file indicates that the system was suspended
|
||||
static const char kPowerdSuspended[] = "powerd_suspended";
|
||||
|
||||
using base::FilePath;
|
||||
|
||||
UncleanShutdownCollector::UncleanShutdownCollector()
|
||||
: unclean_shutdown_file_(kUncleanShutdownFile),
|
||||
powerd_trace_path_(kPowerdTracePath),
|
||||
powerd_suspended_file_(powerd_trace_path_.Append(kPowerdSuspended)) {
|
||||
}
|
||||
|
||||
UncleanShutdownCollector::~UncleanShutdownCollector() {
|
||||
}
|
||||
|
||||
bool UncleanShutdownCollector::Enable() {
|
||||
FilePath file_path(unclean_shutdown_file_);
|
||||
base::CreateDirectory(file_path.DirName());
|
||||
if (base::WriteFile(file_path, "", 0) != 0) {
|
||||
LOG(ERROR) << "Unable to create shutdown check file";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UncleanShutdownCollector::DeleteUncleanShutdownFiles() {
|
||||
if (!base::DeleteFile(FilePath(unclean_shutdown_file_), false)) {
|
||||
LOG(ERROR) << "Failed to delete unclean shutdown file "
|
||||
<< unclean_shutdown_file_;
|
||||
return false;
|
||||
}
|
||||
// Delete power manager state file if it exists.
|
||||
base::DeleteFile(powerd_suspended_file_, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UncleanShutdownCollector::Collect() {
|
||||
FilePath unclean_file_path(unclean_shutdown_file_);
|
||||
if (!base::PathExists(unclean_file_path)) {
|
||||
return false;
|
||||
}
|
||||
LOG(WARNING) << "Last shutdown was not clean";
|
||||
if (DeadBatteryCausedUncleanShutdown()) {
|
||||
DeleteUncleanShutdownFiles();
|
||||
return false;
|
||||
}
|
||||
DeleteUncleanShutdownFiles();
|
||||
|
||||
if (is_feedback_allowed_function_()) {
|
||||
count_crash_function_();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UncleanShutdownCollector::Disable() {
|
||||
LOG(INFO) << "Clean shutdown signalled";
|
||||
return DeleteUncleanShutdownFiles();
|
||||
}
|
||||
|
||||
bool UncleanShutdownCollector::DeadBatteryCausedUncleanShutdown() {
|
||||
// Check for case of battery running out while suspended.
|
||||
if (base::PathExists(powerd_suspended_file_)) {
|
||||
LOG(INFO) << "Unclean shutdown occurred while suspended. Not counting "
|
||||
<< "toward unclean shutdown statistic.";
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
50
crash_reporter/unclean_shutdown_collector.h
Normal file
50
crash_reporter/unclean_shutdown_collector.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CRASH_REPORTER_UNCLEAN_SHUTDOWN_COLLECTOR_H_
|
||||
#define CRASH_REPORTER_UNCLEAN_SHUTDOWN_COLLECTOR_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <base/files/file_path.h>
|
||||
#include <base/macros.h>
|
||||
#include <gtest/gtest_prod.h> // for FRIEND_TEST
|
||||
|
||||
#include "crash-reporter/crash_collector.h"
|
||||
|
||||
// Unclean shutdown collector.
|
||||
class UncleanShutdownCollector : public CrashCollector {
|
||||
public:
|
||||
UncleanShutdownCollector();
|
||||
~UncleanShutdownCollector() override;
|
||||
|
||||
// Enable collection - signal that a boot has started.
|
||||
bool Enable();
|
||||
|
||||
// Collect if there is was an unclean shutdown. Returns true if
|
||||
// there was, false otherwise.
|
||||
bool Collect();
|
||||
|
||||
// Disable collection - signal that the system has been shutdown cleanly.
|
||||
bool Disable();
|
||||
|
||||
private:
|
||||
friend class UncleanShutdownCollectorTest;
|
||||
FRIEND_TEST(UncleanShutdownCollectorTest, EnableCannotWrite);
|
||||
FRIEND_TEST(UncleanShutdownCollectorTest, CollectDeadBatterySuspended);
|
||||
|
||||
bool DeleteUncleanShutdownFiles();
|
||||
|
||||
// Check for unclean shutdown due to battery running out by analyzing powerd
|
||||
// trace files.
|
||||
bool DeadBatteryCausedUncleanShutdown();
|
||||
|
||||
const char *unclean_shutdown_file_;
|
||||
base::FilePath powerd_trace_path_;
|
||||
base::FilePath powerd_suspended_file_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(UncleanShutdownCollector);
|
||||
};
|
||||
|
||||
#endif // CRASH_REPORTER_UNCLEAN_SHUTDOWN_COLLECTOR_H_
|
135
crash_reporter/unclean_shutdown_collector_test.cc
Normal file
135
crash_reporter/unclean_shutdown_collector_test.cc
Normal file
|
@ -0,0 +1,135 @@
|
|||
// Copyright (c) 2010 The Chromium OS 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 "crash-reporter/unclean_shutdown_collector.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <base/files/file_util.h>
|
||||
#include <base/strings/string_util.h>
|
||||
#include <chromeos/syslog_logging.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using base::FilePath;
|
||||
using ::chromeos::FindLog;
|
||||
|
||||
namespace {
|
||||
|
||||
int s_crashes = 0;
|
||||
bool s_metrics = true;
|
||||
|
||||
const char kTestDirectory[] = "test";
|
||||
const char kTestSuspended[] = "test/suspended";
|
||||
const char kTestUnclean[] = "test/unclean";
|
||||
|
||||
void CountCrash() {
|
||||
++s_crashes;
|
||||
}
|
||||
|
||||
bool IsMetrics() {
|
||||
return s_metrics;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class UncleanShutdownCollectorMock : public UncleanShutdownCollector {
|
||||
public:
|
||||
MOCK_METHOD0(SetUpDBus, void());
|
||||
};
|
||||
|
||||
class UncleanShutdownCollectorTest : public ::testing::Test {
|
||||
void SetUp() {
|
||||
s_crashes = 0;
|
||||
|
||||
EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
|
||||
|
||||
collector_.Initialize(CountCrash,
|
||||
IsMetrics);
|
||||
rmdir(kTestDirectory);
|
||||
test_unclean_ = FilePath(kTestUnclean);
|
||||
collector_.unclean_shutdown_file_ = kTestUnclean;
|
||||
base::DeleteFile(test_unclean_, true);
|
||||
// Set up an alternate power manager state file as well
|
||||
collector_.powerd_suspended_file_ = FilePath(kTestSuspended);
|
||||
chromeos::ClearLog();
|
||||
}
|
||||
|
||||
protected:
|
||||
void WriteStringToFile(const FilePath &file_path,
|
||||
const char *data) {
|
||||
ASSERT_EQ(strlen(data), base::WriteFile(file_path, data, strlen(data)));
|
||||
}
|
||||
|
||||
UncleanShutdownCollectorMock collector_;
|
||||
FilePath test_unclean_;
|
||||
};
|
||||
|
||||
TEST_F(UncleanShutdownCollectorTest, EnableWithoutParent) {
|
||||
ASSERT_TRUE(collector_.Enable());
|
||||
ASSERT_TRUE(base::PathExists(test_unclean_));
|
||||
}
|
||||
|
||||
TEST_F(UncleanShutdownCollectorTest, EnableWithParent) {
|
||||
mkdir(kTestDirectory, 0777);
|
||||
ASSERT_TRUE(collector_.Enable());
|
||||
ASSERT_TRUE(base::PathExists(test_unclean_));
|
||||
}
|
||||
|
||||
TEST_F(UncleanShutdownCollectorTest, EnableCannotWrite) {
|
||||
collector_.unclean_shutdown_file_ = "/bad/path";
|
||||
ASSERT_FALSE(collector_.Enable());
|
||||
ASSERT_TRUE(FindLog("Unable to create shutdown check file"));
|
||||
}
|
||||
|
||||
TEST_F(UncleanShutdownCollectorTest, CollectTrue) {
|
||||
ASSERT_TRUE(collector_.Enable());
|
||||
ASSERT_TRUE(base::PathExists(test_unclean_));
|
||||
ASSERT_TRUE(collector_.Collect());
|
||||
ASSERT_FALSE(base::PathExists(test_unclean_));
|
||||
ASSERT_EQ(1, s_crashes);
|
||||
ASSERT_TRUE(FindLog("Last shutdown was not clean"));
|
||||
}
|
||||
|
||||
TEST_F(UncleanShutdownCollectorTest, CollectFalse) {
|
||||
ASSERT_FALSE(collector_.Collect());
|
||||
ASSERT_EQ(0, s_crashes);
|
||||
}
|
||||
|
||||
TEST_F(UncleanShutdownCollectorTest, CollectDeadBatterySuspended) {
|
||||
ASSERT_TRUE(collector_.Enable());
|
||||
ASSERT_TRUE(base::PathExists(test_unclean_));
|
||||
base::WriteFile(collector_.powerd_suspended_file_, "", 0);
|
||||
ASSERT_FALSE(collector_.Collect());
|
||||
ASSERT_FALSE(base::PathExists(test_unclean_));
|
||||
ASSERT_FALSE(base::PathExists(collector_.powerd_suspended_file_));
|
||||
ASSERT_EQ(0, s_crashes);
|
||||
ASSERT_TRUE(FindLog("Unclean shutdown occurred while suspended."));
|
||||
}
|
||||
|
||||
TEST_F(UncleanShutdownCollectorTest, Disable) {
|
||||
ASSERT_TRUE(collector_.Enable());
|
||||
ASSERT_TRUE(base::PathExists(test_unclean_));
|
||||
ASSERT_TRUE(collector_.Disable());
|
||||
ASSERT_FALSE(base::PathExists(test_unclean_));
|
||||
ASSERT_FALSE(collector_.Collect());
|
||||
}
|
||||
|
||||
TEST_F(UncleanShutdownCollectorTest, DisableWhenNotEnabled) {
|
||||
ASSERT_TRUE(collector_.Disable());
|
||||
}
|
||||
|
||||
TEST_F(UncleanShutdownCollectorTest, CantDisable) {
|
||||
mkdir(kTestDirectory, 0700);
|
||||
if (mkdir(kTestUnclean, 0700)) {
|
||||
ASSERT_EQ(EEXIST, errno)
|
||||
<< "Error while creating directory '" << kTestUnclean
|
||||
<< "': " << strerror(errno);
|
||||
}
|
||||
ASSERT_EQ(0, base::WriteFile(test_unclean_.Append("foo"), "", 0))
|
||||
<< "Error while creating empty file '"
|
||||
<< test_unclean_.Append("foo").value() << "': " << strerror(errno);
|
||||
ASSERT_FALSE(collector_.Disable());
|
||||
rmdir(kTestUnclean);
|
||||
}
|
673
crash_reporter/user_collector.cc
Normal file
673
crash_reporter/user_collector.cc
Normal file
|
@ -0,0 +1,673 @@
|
|||
// Copyright (c) 2012 The Chromium OS 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 "crash-reporter/user_collector.h"
|
||||
|
||||
#include <bits/wordsize.h>
|
||||
#include <elf.h>
|
||||
#include <fcntl.h>
|
||||
#include <grp.h> // For struct group.
|
||||
#include <pcrecpp.h>
|
||||
#include <pwd.h> // For struct passwd.
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h> // For getpwuid_r, getgrnam_r, WEXITSTATUS.
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <base/files/file_util.h>
|
||||
#include <base/logging.h>
|
||||
#include <base/posix/eintr_wrapper.h>
|
||||
#include <base/stl_util.h>
|
||||
#include <base/strings/string_split.h>
|
||||
#include <base/strings/string_util.h>
|
||||
#include <base/strings/stringprintf.h>
|
||||
#include <chromeos/process.h>
|
||||
#include <chromeos/syslog_logging.h>
|
||||
|
||||
static const char kCollectionErrorSignature[] =
|
||||
"crash_reporter-user-collection";
|
||||
// This procfs file is used to cause kernel core file writing to
|
||||
// instead pipe the core file into a user space process. See
|
||||
// core(5) man page.
|
||||
static const char kCorePatternFile[] = "/proc/sys/kernel/core_pattern";
|
||||
static const char kCorePipeLimitFile[] = "/proc/sys/kernel/core_pipe_limit";
|
||||
// Set core_pipe_limit to 4 so that we can catch a few unrelated concurrent
|
||||
// crashes, but finite to avoid infinitely recursing on crash handling.
|
||||
static const char kCorePipeLimit[] = "4";
|
||||
static const char kCoreToMinidumpConverterPath[] = "/usr/bin/core2md";
|
||||
|
||||
static const char kStatePrefix[] = "State:\t";
|
||||
|
||||
// Define an otherwise invalid value that represents an unknown UID.
|
||||
static const uid_t kUnknownUid = -1;
|
||||
|
||||
const char *UserCollector::kUserId = "Uid:\t";
|
||||
const char *UserCollector::kGroupId = "Gid:\t";
|
||||
|
||||
using base::FilePath;
|
||||
using base::StringPrintf;
|
||||
|
||||
UserCollector::UserCollector()
|
||||
: generate_diagnostics_(false),
|
||||
core_pattern_file_(kCorePatternFile),
|
||||
core_pipe_limit_file_(kCorePipeLimitFile),
|
||||
initialized_(false) {
|
||||
}
|
||||
|
||||
void UserCollector::Initialize(
|
||||
UserCollector::CountCrashFunction count_crash_function,
|
||||
const std::string &our_path,
|
||||
UserCollector::IsFeedbackAllowedFunction is_feedback_allowed_function,
|
||||
bool generate_diagnostics,
|
||||
bool core2md_failure,
|
||||
bool directory_failure,
|
||||
const std::string &filter_in) {
|
||||
CrashCollector::Initialize(count_crash_function,
|
||||
is_feedback_allowed_function);
|
||||
our_path_ = our_path;
|
||||
initialized_ = true;
|
||||
generate_diagnostics_ = generate_diagnostics;
|
||||
core2md_failure_ = core2md_failure;
|
||||
directory_failure_ = directory_failure;
|
||||
filter_in_ = filter_in;
|
||||
}
|
||||
|
||||
UserCollector::~UserCollector() {
|
||||
}
|
||||
|
||||
std::string UserCollector::GetErrorTypeSignature(ErrorType error_type) const {
|
||||
switch (error_type) {
|
||||
case kErrorSystemIssue:
|
||||
return "system-issue";
|
||||
case kErrorReadCoreData:
|
||||
return "read-core-data";
|
||||
case kErrorUnusableProcFiles:
|
||||
return "unusable-proc-files";
|
||||
case kErrorInvalidCoreFile:
|
||||
return "invalid-core-file";
|
||||
case kErrorUnsupported32BitCoreFile:
|
||||
return "unsupported-32bit-core-file";
|
||||
case kErrorCore2MinidumpConversion:
|
||||
return "core2md-conversion";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Return the string that should be used for the kernel's core_pattern file.
|
||||
// Note that if you change the format of the enabled pattern, you'll probably
|
||||
// also need to change the ParseCrashAttributes() function below, the
|
||||
// user_collector_test.cc unittest, and the logging_UserCrash.py autotest.
|
||||
std::string UserCollector::GetPattern(bool enabled) const {
|
||||
if (enabled) {
|
||||
// Combine the four crash attributes into one parameter to try to reduce
|
||||
// the size of the invocation line for crash_reporter, since the kernel
|
||||
// has a fixed-sized (128B) buffer for it (before parameter expansion).
|
||||
// Note that the kernel does not support quoted arguments in core_pattern.
|
||||
return StringPrintf("|%s --user=%%P:%%s:%%u:%%e", our_path_.c_str());
|
||||
} else {
|
||||
return "core";
|
||||
}
|
||||
}
|
||||
|
||||
bool UserCollector::SetUpInternal(bool enabled) {
|
||||
CHECK(initialized_);
|
||||
LOG(INFO) << (enabled ? "Enabling" : "Disabling") << " user crash handling";
|
||||
|
||||
if (base::WriteFile(FilePath(core_pipe_limit_file_), kCorePipeLimit,
|
||||
strlen(kCorePipeLimit)) !=
|
||||
static_cast<int>(strlen(kCorePipeLimit))) {
|
||||
PLOG(ERROR) << "Unable to write " << core_pipe_limit_file_;
|
||||
return false;
|
||||
}
|
||||
std::string pattern = GetPattern(enabled);
|
||||
if (base::WriteFile(FilePath(core_pattern_file_), pattern.c_str(),
|
||||
pattern.length()) != static_cast<int>(pattern.length())) {
|
||||
PLOG(ERROR) << "Unable to write " << core_pattern_file_;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UserCollector::GetFirstLineWithPrefix(
|
||||
const std::vector<std::string> &lines,
|
||||
const char *prefix, std::string *line) {
|
||||
std::vector<std::string>::const_iterator line_iterator;
|
||||
for (line_iterator = lines.begin(); line_iterator != lines.end();
|
||||
++line_iterator) {
|
||||
if (line_iterator->find(prefix) == 0) {
|
||||
*line = *line_iterator;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UserCollector::GetIdFromStatus(
|
||||
const char *prefix, IdKind kind,
|
||||
const std::vector<std::string> &status_lines, int *id) {
|
||||
// From fs/proc/array.c:task_state(), this file contains:
|
||||
// \nUid:\t<uid>\t<euid>\t<suid>\t<fsuid>\n
|
||||
std::string id_line;
|
||||
if (!GetFirstLineWithPrefix(status_lines, prefix, &id_line)) {
|
||||
return false;
|
||||
}
|
||||
std::string id_substring = id_line.substr(strlen(prefix), std::string::npos);
|
||||
std::vector<std::string> ids;
|
||||
base::SplitString(id_substring, '\t', &ids);
|
||||
if (ids.size() != kIdMax || kind < 0 || kind >= kIdMax) {
|
||||
return false;
|
||||
}
|
||||
const char *number = ids[kind].c_str();
|
||||
char *end_number = nullptr;
|
||||
*id = strtol(number, &end_number, 10);
|
||||
if (*end_number != '\0') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UserCollector::GetStateFromStatus(
|
||||
const std::vector<std::string> &status_lines, std::string *state) {
|
||||
std::string state_line;
|
||||
if (!GetFirstLineWithPrefix(status_lines, kStatePrefix, &state_line)) {
|
||||
return false;
|
||||
}
|
||||
*state = state_line.substr(strlen(kStatePrefix), std::string::npos);
|
||||
return true;
|
||||
}
|
||||
|
||||
void UserCollector::EnqueueCollectionErrorLog(pid_t pid,
|
||||
ErrorType error_type,
|
||||
const std::string &exec) {
|
||||
FilePath crash_path;
|
||||
LOG(INFO) << "Writing conversion problems as separate crash report.";
|
||||
if (!GetCreatedCrashDirectoryByEuid(0, &crash_path, nullptr)) {
|
||||
LOG(ERROR) << "Could not even get log directory; out of space?";
|
||||
return;
|
||||
}
|
||||
AddCrashMetaData("sig", kCollectionErrorSignature);
|
||||
AddCrashMetaData("error_type", GetErrorTypeSignature(error_type));
|
||||
std::string dump_basename = FormatDumpBasename(exec, time(nullptr), pid);
|
||||
std::string error_log = chromeos::GetLog();
|
||||
FilePath diag_log_path = GetCrashPath(crash_path, dump_basename, "diaglog");
|
||||
if (GetLogContents(FilePath(log_config_path_), kCollectionErrorSignature,
|
||||
diag_log_path)) {
|
||||
// We load the contents of diag_log into memory and append it to
|
||||
// the error log. We cannot just append to files because we need
|
||||
// to always create new files to prevent attack.
|
||||
std::string diag_log_contents;
|
||||
base::ReadFileToString(diag_log_path, &diag_log_contents);
|
||||
error_log.append(diag_log_contents);
|
||||
base::DeleteFile(diag_log_path, false);
|
||||
}
|
||||
FilePath log_path = GetCrashPath(crash_path, dump_basename, "log");
|
||||
FilePath meta_path = GetCrashPath(crash_path, dump_basename, "meta");
|
||||
// We must use WriteNewFile instead of base::WriteFile as we do
|
||||
// not want to write with root access to a symlink that an attacker
|
||||
// might have created.
|
||||
if (WriteNewFile(log_path, error_log.data(), error_log.length()) < 0) {
|
||||
LOG(ERROR) << "Error writing new file " << log_path.value();
|
||||
return;
|
||||
}
|
||||
WriteCrashMetaData(meta_path, exec, log_path.value());
|
||||
}
|
||||
|
||||
bool UserCollector::CopyOffProcFiles(pid_t pid,
|
||||
const FilePath &container_dir) {
|
||||
if (!base::CreateDirectory(container_dir)) {
|
||||
PLOG(ERROR) << "Could not create " << container_dir.value().c_str();
|
||||
return false;
|
||||
}
|
||||
FilePath process_path = GetProcessPath(pid);
|
||||
if (!base::PathExists(process_path)) {
|
||||
LOG(ERROR) << "Path " << process_path.value() << " does not exist";
|
||||
return false;
|
||||
}
|
||||
static const char *proc_files[] = {
|
||||
"auxv",
|
||||
"cmdline",
|
||||
"environ",
|
||||
"maps",
|
||||
"status"
|
||||
};
|
||||
for (unsigned i = 0; i < arraysize(proc_files); ++i) {
|
||||
if (!base::CopyFile(process_path.Append(proc_files[i]),
|
||||
container_dir.Append(proc_files[i]))) {
|
||||
LOG(ERROR) << "Could not copy " << proc_files[i] << " file";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UserCollector::ValidateProcFiles(const FilePath &container_dir) const {
|
||||
// Check if the maps file is empty, which could be due to the crashed
|
||||
// process being reaped by the kernel before finishing a core dump.
|
||||
int64_t file_size = 0;
|
||||
if (!base::GetFileSize(container_dir.Append("maps"), &file_size)) {
|
||||
LOG(ERROR) << "Could not get the size of maps file";
|
||||
return false;
|
||||
}
|
||||
if (file_size == 0) {
|
||||
LOG(ERROR) << "maps file is empty";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
UserCollector::ErrorType UserCollector::ValidateCoreFile(
|
||||
const FilePath &core_path) const {
|
||||
int fd = HANDLE_EINTR(open(core_path.value().c_str(), O_RDONLY));
|
||||
if (fd < 0) {
|
||||
PLOG(ERROR) << "Could not open core file " << core_path.value();
|
||||
return kErrorInvalidCoreFile;
|
||||
}
|
||||
|
||||
char e_ident[EI_NIDENT];
|
||||
bool read_ok = base::ReadFromFD(fd, e_ident, sizeof(e_ident));
|
||||
IGNORE_EINTR(close(fd));
|
||||
if (!read_ok) {
|
||||
LOG(ERROR) << "Could not read header of core file";
|
||||
return kErrorInvalidCoreFile;
|
||||
}
|
||||
|
||||
if (e_ident[EI_MAG0] != ELFMAG0 || e_ident[EI_MAG1] != ELFMAG1 ||
|
||||
e_ident[EI_MAG2] != ELFMAG2 || e_ident[EI_MAG3] != ELFMAG3) {
|
||||
LOG(ERROR) << "Invalid core file";
|
||||
return kErrorInvalidCoreFile;
|
||||
}
|
||||
|
||||
#if __WORDSIZE == 64
|
||||
// TODO(benchan, mkrebs): Remove this check once core2md can
|
||||
// handles both 32-bit and 64-bit ELF on a 64-bit platform.
|
||||
if (e_ident[EI_CLASS] == ELFCLASS32) {
|
||||
LOG(ERROR) << "Conversion of 32-bit core file on 64-bit platform is "
|
||||
<< "currently not supported";
|
||||
return kErrorUnsupported32BitCoreFile;
|
||||
}
|
||||
#endif
|
||||
|
||||
return kErrorNone;
|
||||
}
|
||||
|
||||
bool UserCollector::GetCreatedCrashDirectory(pid_t pid, uid_t supplied_ruid,
|
||||
FilePath *crash_file_path,
|
||||
bool *out_of_capacity) {
|
||||
FilePath process_path = GetProcessPath(pid);
|
||||
std::string status;
|
||||
if (directory_failure_) {
|
||||
LOG(ERROR) << "Purposefully failing to create spool directory";
|
||||
return false;
|
||||
}
|
||||
|
||||
uid_t uid;
|
||||
if (base::ReadFileToString(process_path.Append("status"), &status)) {
|
||||
std::vector<std::string> status_lines;
|
||||
base::SplitString(status, '\n', &status_lines);
|
||||
|
||||
std::string process_state;
|
||||
if (!GetStateFromStatus(status_lines, &process_state)) {
|
||||
LOG(ERROR) << "Could not find process state in status file";
|
||||
return false;
|
||||
}
|
||||
LOG(INFO) << "State of crashed process [" << pid << "]: " << process_state;
|
||||
|
||||
// Get effective UID of crashing process.
|
||||
int id;
|
||||
if (!GetIdFromStatus(kUserId, kIdEffective, status_lines, &id)) {
|
||||
LOG(ERROR) << "Could not find euid in status file";
|
||||
return false;
|
||||
}
|
||||
uid = id;
|
||||
} else if (supplied_ruid != kUnknownUid) {
|
||||
LOG(INFO) << "Using supplied UID " << supplied_ruid
|
||||
<< " for crashed process [" << pid
|
||||
<< "] due to error reading status file";
|
||||
uid = supplied_ruid;
|
||||
} else {
|
||||
LOG(ERROR) << "Could not read status file and kernel did not supply UID";
|
||||
LOG(INFO) << "Path " << process_path.value() << " DirectoryExists: "
|
||||
<< base::DirectoryExists(process_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GetCreatedCrashDirectoryByEuid(uid, crash_file_path, out_of_capacity)) {
|
||||
LOG(ERROR) << "Could not create crash directory";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UserCollector::CopyStdinToCoreFile(const FilePath &core_path) {
|
||||
// Copy off all stdin to a core file.
|
||||
FilePath stdin_path("/dev/fd/0");
|
||||
if (base::CopyFile(stdin_path, core_path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PLOG(ERROR) << "Could not write core file";
|
||||
// If the file system was full, make sure we remove any remnants.
|
||||
base::DeleteFile(core_path, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UserCollector::RunCoreToMinidump(const FilePath &core_path,
|
||||
const FilePath &procfs_directory,
|
||||
const FilePath &minidump_path,
|
||||
const FilePath &temp_directory) {
|
||||
FilePath output_path = temp_directory.Append("output");
|
||||
chromeos::ProcessImpl core2md;
|
||||
core2md.RedirectOutput(output_path.value());
|
||||
core2md.AddArg(kCoreToMinidumpConverterPath);
|
||||
core2md.AddArg(core_path.value());
|
||||
core2md.AddArg(procfs_directory.value());
|
||||
|
||||
if (!core2md_failure_) {
|
||||
core2md.AddArg(minidump_path.value());
|
||||
} else {
|
||||
// To test how core2md errors are propagaged, cause an error
|
||||
// by forgetting a required argument.
|
||||
}
|
||||
|
||||
int errorlevel = core2md.Run();
|
||||
|
||||
std::string output;
|
||||
base::ReadFileToString(output_path, &output);
|
||||
if (errorlevel != 0) {
|
||||
LOG(ERROR) << "Problem during " << kCoreToMinidumpConverterPath
|
||||
<< " [result=" << errorlevel << "]: " << output;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!base::PathExists(minidump_path)) {
|
||||
LOG(ERROR) << "Minidump file " << minidump_path.value()
|
||||
<< " was not created";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
UserCollector::ErrorType UserCollector::ConvertCoreToMinidump(
|
||||
pid_t pid,
|
||||
const FilePath &container_dir,
|
||||
const FilePath &core_path,
|
||||
const FilePath &minidump_path) {
|
||||
// If proc files are unuable, we continue to read the core file from stdin,
|
||||
// but only skip the core-to-minidump conversion, so that we may still use
|
||||
// the core file for debugging.
|
||||
bool proc_files_usable =
|
||||
CopyOffProcFiles(pid, container_dir) && ValidateProcFiles(container_dir);
|
||||
|
||||
if (!CopyStdinToCoreFile(core_path)) {
|
||||
return kErrorReadCoreData;
|
||||
}
|
||||
|
||||
if (!proc_files_usable) {
|
||||
LOG(INFO) << "Skipped converting core file to minidump due to "
|
||||
<< "unusable proc files";
|
||||
return kErrorUnusableProcFiles;
|
||||
}
|
||||
|
||||
ErrorType error = ValidateCoreFile(core_path);
|
||||
if (error != kErrorNone) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (!RunCoreToMinidump(core_path,
|
||||
container_dir, // procfs directory
|
||||
minidump_path,
|
||||
container_dir)) { // temporary directory
|
||||
return kErrorCore2MinidumpConversion;
|
||||
}
|
||||
|
||||
LOG(INFO) << "Stored minidump to " << minidump_path.value();
|
||||
return kErrorNone;
|
||||
}
|
||||
|
||||
UserCollector::ErrorType UserCollector::ConvertAndEnqueueCrash(
|
||||
pid_t pid, const std::string &exec, uid_t supplied_ruid,
|
||||
bool *out_of_capacity) {
|
||||
FilePath crash_path;
|
||||
if (!GetCreatedCrashDirectory(pid, supplied_ruid, &crash_path,
|
||||
out_of_capacity)) {
|
||||
LOG(ERROR) << "Unable to find/create process-specific crash path";
|
||||
return kErrorSystemIssue;
|
||||
}
|
||||
|
||||
// Directory like /tmp/crash_reporter/1234 which contains the
|
||||
// procfs entries and other temporary files used during conversion.
|
||||
FilePath container_dir(StringPrintf("/tmp/crash_reporter/%d", pid));
|
||||
// Delete a pre-existing directory from crash reporter that may have
|
||||
// been left around for diagnostics from a failed conversion attempt.
|
||||
// If we don't, existing files can cause forking to fail.
|
||||
base::DeleteFile(container_dir, true);
|
||||
std::string dump_basename = FormatDumpBasename(exec, time(nullptr), pid);
|
||||
FilePath core_path = GetCrashPath(crash_path, dump_basename, "core");
|
||||
FilePath meta_path = GetCrashPath(crash_path, dump_basename, "meta");
|
||||
FilePath minidump_path = GetCrashPath(crash_path, dump_basename, "dmp");
|
||||
FilePath log_path = GetCrashPath(crash_path, dump_basename, "log");
|
||||
|
||||
if (GetLogContents(FilePath(log_config_path_), exec, log_path))
|
||||
AddCrashMetaData("log", log_path.value());
|
||||
|
||||
ErrorType error_type =
|
||||
ConvertCoreToMinidump(pid, container_dir, core_path, minidump_path);
|
||||
if (error_type != kErrorNone) {
|
||||
LOG(INFO) << "Leaving core file at " << core_path.value()
|
||||
<< " due to conversion error";
|
||||
return error_type;
|
||||
}
|
||||
|
||||
// Here we commit to sending this file. We must not return false
|
||||
// after this point or we will generate a log report as well as a
|
||||
// crash report.
|
||||
WriteCrashMetaData(meta_path,
|
||||
exec,
|
||||
minidump_path.value());
|
||||
|
||||
if (!IsDeveloperImage()) {
|
||||
base::DeleteFile(core_path, false);
|
||||
} else {
|
||||
LOG(INFO) << "Leaving core file at " << core_path.value()
|
||||
<< " due to developer image";
|
||||
}
|
||||
|
||||
base::DeleteFile(container_dir, true);
|
||||
return kErrorNone;
|
||||
}
|
||||
|
||||
bool UserCollector::ParseCrashAttributes(const std::string &crash_attributes,
|
||||
pid_t *pid, int *signal, uid_t *uid,
|
||||
std::string *kernel_supplied_name) {
|
||||
pcrecpp::RE re("(\\d+):(\\d+):(\\d+):(.*)");
|
||||
if (re.FullMatch(crash_attributes, pid, signal, uid, kernel_supplied_name))
|
||||
return true;
|
||||
|
||||
LOG(INFO) << "Falling back to parsing crash attributes '"
|
||||
<< crash_attributes << "' without UID";
|
||||
pcrecpp::RE re_without_uid("(\\d+):(\\d+):(.*)");
|
||||
*uid = kUnknownUid;
|
||||
return re_without_uid.FullMatch(crash_attributes, pid, signal,
|
||||
kernel_supplied_name);
|
||||
}
|
||||
|
||||
// Returns true if the given executable name matches that of Chrome. This
|
||||
// includes checks for threads that Chrome has renamed.
|
||||
static bool IsChromeExecName(const std::string &exec) {
|
||||
static const char *kChromeNames[] = {
|
||||
"chrome",
|
||||
// These are additional thread names seen in http://crash/
|
||||
"MediaPipeline",
|
||||
// These come from the use of base::PlatformThread::SetName() directly
|
||||
"CrBrowserMain", "CrRendererMain", "CrUtilityMain", "CrPPAPIMain",
|
||||
"CrPPAPIBrokerMain", "CrPluginMain", "CrWorkerMain", "CrGpuMain",
|
||||
"BrokerEvent", "CrVideoRenderer", "CrShutdownDetector",
|
||||
"UsbEventHandler", "CrNaClMain", "CrServiceMain",
|
||||
// These thread names come from the use of base::Thread
|
||||
"Gamepad polling thread", "Chrome_InProcGpuThread",
|
||||
"Chrome_DragDropThread", "Renderer::FILE", "VC manager",
|
||||
"VideoCaptureModuleImpl", "JavaBridge", "VideoCaptureManagerThread",
|
||||
"Geolocation", "Geolocation_wifi_provider",
|
||||
"Device orientation polling thread", "Chrome_InProcRendererThread",
|
||||
"NetworkChangeNotifier", "Watchdog", "inotify_reader",
|
||||
"cf_iexplore_background_thread", "BrowserWatchdog",
|
||||
"Chrome_HistoryThread", "Chrome_SyncThread", "Chrome_ShellDialogThread",
|
||||
"Printing_Worker", "Chrome_SafeBrowsingThread", "SimpleDBThread",
|
||||
"D-Bus thread", "AudioThread", "NullAudioThread", "V4L2Thread",
|
||||
"ChromotingClientDecodeThread", "Profiling_Flush",
|
||||
"worker_thread_ticker", "AudioMixerAlsa", "AudioMixerCras",
|
||||
"FakeAudioRecordingThread", "CaptureThread",
|
||||
"Chrome_WebSocketproxyThread", "ProcessWatcherThread",
|
||||
"Chrome_CameraThread", "import_thread", "NaCl_IOThread",
|
||||
"Chrome_CloudPrintJobPrintThread", "Chrome_CloudPrintProxyCoreThread",
|
||||
"DaemonControllerFileIO", "ChromotingMainThread",
|
||||
"ChromotingEncodeThread", "ChromotingDesktopThread",
|
||||
"ChromotingIOThread", "ChromotingFileIOThread",
|
||||
"Chrome_libJingle_WorkerThread", "Chrome_ChildIOThread",
|
||||
"GLHelperThread", "RemotingHostPlugin",
|
||||
// "PAC thread #%d", // not easy to check because of "%d"
|
||||
"Chrome_DBThread", "Chrome_WebKitThread", "Chrome_FileThread",
|
||||
"Chrome_FileUserBlockingThread", "Chrome_ProcessLauncherThread",
|
||||
"Chrome_CacheThread", "Chrome_IOThread", "Cache Thread", "File Thread",
|
||||
"ServiceProcess_IO", "ServiceProcess_File",
|
||||
"extension_crash_uploader", "gpu-process_crash_uploader",
|
||||
"plugin_crash_uploader", "renderer_crash_uploader",
|
||||
// These come from the use of webkit_glue::WebThreadImpl
|
||||
"Compositor", "Browser Compositor",
|
||||
// "WorkerPool/%d", // not easy to check because of "%d"
|
||||
// These come from the use of base::Watchdog
|
||||
"Startup watchdog thread Watchdog", "Shutdown watchdog thread Watchdog",
|
||||
// These come from the use of AudioDeviceThread::Start
|
||||
"AudioDevice", "AudioInputDevice", "AudioOutputDevice",
|
||||
// These come from the use of MessageLoopFactory::GetMessageLoop
|
||||
"GpuVideoDecoder", "RtcVideoDecoderThread", "PipelineThread",
|
||||
"AudioDecoderThread", "VideoDecoderThread",
|
||||
// These come from the use of MessageLoopFactory::GetMessageLoopProxy
|
||||
"CaptureVideoDecoderThread", "CaptureVideoDecoder",
|
||||
// These come from the use of base::SimpleThread
|
||||
"LocalInputMonitor/%d", // "%d" gets lopped off for kernel-supplied
|
||||
// These come from the use of base::DelegateSimpleThread
|
||||
"ipc_channel_nacl reader thread/%d", "plugin_audio_input_thread/%d",
|
||||
"plugin_audio_thread/%d",
|
||||
// These come from the use of base::SequencedWorkerPool
|
||||
"BrowserBlockingWorker%d/%d", // "%d" gets lopped off for kernel-supplied
|
||||
};
|
||||
static std::set<std::string> chrome_names;
|
||||
|
||||
// Initialize a set of chrome names, for efficient lookup
|
||||
if (chrome_names.empty()) {
|
||||
for (size_t i = 0; i < arraysize(kChromeNames); i++) {
|
||||
std::string check_name(kChromeNames[i]);
|
||||
chrome_names.insert(check_name);
|
||||
// When checking a kernel-supplied name, it should be truncated to 15
|
||||
// chars. See PR_SET_NAME in
|
||||
// http://www.kernel.org/doc/man-pages/online/pages/man2/prctl.2.html,
|
||||
// although that page misleads by saying "16 bytes".
|
||||
chrome_names.insert("supplied_" + std::string(check_name, 0, 15));
|
||||
}
|
||||
}
|
||||
|
||||
return ContainsKey(chrome_names, exec);
|
||||
}
|
||||
|
||||
bool UserCollector::ShouldDump(bool has_owner_consent,
|
||||
bool is_developer,
|
||||
bool handle_chrome_crashes,
|
||||
const std::string &exec,
|
||||
std::string *reason) {
|
||||
reason->clear();
|
||||
|
||||
// Treat Chrome crashes as if the user opted-out. We stop counting Chrome
|
||||
// crashes towards user crashes, so user crashes really mean non-Chrome
|
||||
// user-space crashes.
|
||||
if (!handle_chrome_crashes && IsChromeExecName(exec)) {
|
||||
*reason = "ignoring call by kernel - chrome crash; "
|
||||
"waiting for chrome to call us directly";
|
||||
return false;
|
||||
}
|
||||
|
||||
// For developer builds, we always want to keep the crash reports unless
|
||||
// we're testing the crash facilities themselves. This overrides
|
||||
// feedback. Crash sending still obeys consent.
|
||||
if (is_developer) {
|
||||
*reason = "developer build - not testing - always dumping";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!has_owner_consent) {
|
||||
*reason = "ignoring - no consent";
|
||||
return false;
|
||||
}
|
||||
|
||||
*reason = "handling";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UserCollector::HandleCrash(const std::string &crash_attributes,
|
||||
const char *force_exec) {
|
||||
CHECK(initialized_);
|
||||
pid_t pid = 0;
|
||||
int signal = 0;
|
||||
uid_t supplied_ruid = kUnknownUid;
|
||||
std::string kernel_supplied_name;
|
||||
|
||||
if (!ParseCrashAttributes(crash_attributes, &pid, &signal, &supplied_ruid,
|
||||
&kernel_supplied_name)) {
|
||||
LOG(ERROR) << "Invalid parameter: --user=" << crash_attributes;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string exec;
|
||||
if (force_exec) {
|
||||
exec.assign(force_exec);
|
||||
} else if (!GetExecutableBaseNameFromPid(pid, &exec)) {
|
||||
// If we cannot find the exec name, use the kernel supplied name.
|
||||
// We don't always use the kernel's since it truncates the name to
|
||||
// 16 characters.
|
||||
exec = StringPrintf("supplied_%s", kernel_supplied_name.c_str());
|
||||
}
|
||||
|
||||
// Allow us to test the crash reporting mechanism successfully even if
|
||||
// other parts of the system crash.
|
||||
if (!filter_in_.empty() &&
|
||||
(filter_in_ == "none" ||
|
||||
filter_in_ != exec)) {
|
||||
// We use a different format message to make it more obvious in tests
|
||||
// which crashes are test generated and which are real.
|
||||
LOG(WARNING) << "Ignoring crash from " << exec << "[" << pid << "] while "
|
||||
<< "filter_in=" << filter_in_ << ".";
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string reason;
|
||||
bool dump = ShouldDump(is_feedback_allowed_function_(),
|
||||
IsDeveloperImage(),
|
||||
ShouldHandleChromeCrashes(),
|
||||
exec,
|
||||
&reason);
|
||||
|
||||
LOG(WARNING) << "Received crash notification for " << exec << "[" << pid
|
||||
<< "] sig " << signal << ", user " << supplied_ruid
|
||||
<< " (" << reason << ")";
|
||||
|
||||
if (dump) {
|
||||
count_crash_function_();
|
||||
|
||||
if (generate_diagnostics_) {
|
||||
bool out_of_capacity = false;
|
||||
ErrorType error_type =
|
||||
ConvertAndEnqueueCrash(pid, exec, supplied_ruid, &out_of_capacity);
|
||||
if (error_type != kErrorNone) {
|
||||
if (!out_of_capacity)
|
||||
EnqueueCollectionErrorLog(pid, error_type, exec);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
192
crash_reporter/user_collector.h
Normal file
192
crash_reporter/user_collector.h
Normal file
|
@ -0,0 +1,192 @@
|
|||
// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CRASH_REPORTER_USER_COLLECTOR_H_
|
||||
#define CRASH_REPORTER_USER_COLLECTOR_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <base/files/file_path.h>
|
||||
#include <base/macros.h>
|
||||
#include <gtest/gtest_prod.h> // for FRIEND_TEST
|
||||
|
||||
#include "crash-reporter/crash_collector.h"
|
||||
|
||||
class SystemLogging;
|
||||
|
||||
// User crash collector.
|
||||
class UserCollector : public CrashCollector {
|
||||
public:
|
||||
UserCollector();
|
||||
|
||||
// Initialize the user crash collector for detection of crashes,
|
||||
// given a crash counting function, the path to this executable,
|
||||
// metrics collection enabled oracle, and system logger facility.
|
||||
// Crash detection/reporting is not enabled until Enable is called.
|
||||
// |generate_diagnostics| is used to indicate whether or not to try
|
||||
// to generate a minidump from crashes.
|
||||
void Initialize(CountCrashFunction count_crash,
|
||||
const std::string &our_path,
|
||||
IsFeedbackAllowedFunction is_metrics_allowed,
|
||||
bool generate_diagnostics,
|
||||
bool core2md_failure,
|
||||
bool directory_failure,
|
||||
const std::string &filter_in);
|
||||
|
||||
~UserCollector() override;
|
||||
|
||||
// Enable collection.
|
||||
bool Enable() { return SetUpInternal(true); }
|
||||
|
||||
// Disable collection.
|
||||
bool Disable() { return SetUpInternal(false); }
|
||||
|
||||
// Handle a specific user crash. Returns true on success.
|
||||
bool HandleCrash(const std::string &crash_attributes,
|
||||
const char *force_exec);
|
||||
|
||||
// Set (override the default) core file pattern.
|
||||
void set_core_pattern_file(const std::string &pattern) {
|
||||
core_pattern_file_ = pattern;
|
||||
}
|
||||
|
||||
// Set (override the default) core pipe limit file.
|
||||
void set_core_pipe_limit_file(const std::string &path) {
|
||||
core_pipe_limit_file_ = path;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class UserCollectorTest;
|
||||
FRIEND_TEST(UserCollectorTest, CopyOffProcFilesBadPath);
|
||||
FRIEND_TEST(UserCollectorTest, CopyOffProcFilesBadPid);
|
||||
FRIEND_TEST(UserCollectorTest, CopyOffProcFilesOK);
|
||||
FRIEND_TEST(UserCollectorTest, GetExecutableBaseNameFromPid);
|
||||
FRIEND_TEST(UserCollectorTest, GetFirstLineWithPrefix);
|
||||
FRIEND_TEST(UserCollectorTest, GetIdFromStatus);
|
||||
FRIEND_TEST(UserCollectorTest, GetStateFromStatus);
|
||||
FRIEND_TEST(UserCollectorTest, GetProcessPath);
|
||||
FRIEND_TEST(UserCollectorTest, GetSymlinkTarget);
|
||||
FRIEND_TEST(UserCollectorTest, GetUserInfoFromName);
|
||||
FRIEND_TEST(UserCollectorTest, ParseCrashAttributes);
|
||||
FRIEND_TEST(UserCollectorTest, ShouldDumpChromeOverridesDeveloperImage);
|
||||
FRIEND_TEST(UserCollectorTest, ShouldDumpDeveloperImageOverridesConsent);
|
||||
FRIEND_TEST(UserCollectorTest, ShouldDumpUseConsentProductionImage);
|
||||
FRIEND_TEST(UserCollectorTest, ValidateProcFiles);
|
||||
FRIEND_TEST(UserCollectorTest, ValidateCoreFile);
|
||||
|
||||
// Enumeration to pass to GetIdFromStatus. Must match the order
|
||||
// that the kernel lists IDs in the status file.
|
||||
enum IdKind {
|
||||
kIdReal = 0, // uid and gid
|
||||
kIdEffective = 1, // euid and egid
|
||||
kIdSet = 2, // suid and sgid
|
||||
kIdFileSystem = 3, // fsuid and fsgid
|
||||
kIdMax
|
||||
};
|
||||
|
||||
enum ErrorType {
|
||||
kErrorNone,
|
||||
kErrorSystemIssue,
|
||||
kErrorReadCoreData,
|
||||
kErrorUnusableProcFiles,
|
||||
kErrorInvalidCoreFile,
|
||||
kErrorUnsupported32BitCoreFile,
|
||||
kErrorCore2MinidumpConversion,
|
||||
};
|
||||
|
||||
static const int kForkProblem = 255;
|
||||
|
||||
// Returns an error type signature for a given |error_type| value,
|
||||
// which is reported to the crash server along with the
|
||||
// crash_reporter-user-collection signature.
|
||||
std::string GetErrorTypeSignature(ErrorType error_type) const;
|
||||
|
||||
std::string GetPattern(bool enabled) const;
|
||||
bool SetUpInternal(bool enabled);
|
||||
|
||||
// Returns, via |line|, the first line in |lines| that starts with |prefix|.
|
||||
// Returns true if a line is found, or false otherwise.
|
||||
bool GetFirstLineWithPrefix(const std::vector<std::string> &lines,
|
||||
const char *prefix, std::string *line);
|
||||
|
||||
// Returns the identifier of |kind|, via |id|, found in |status_lines| on
|
||||
// the line starting with |prefix|. |status_lines| contains the lines in
|
||||
// the status file. Returns true if the identifier can be determined.
|
||||
bool GetIdFromStatus(const char *prefix,
|
||||
IdKind kind,
|
||||
const std::vector<std::string> &status_lines,
|
||||
int *id);
|
||||
|
||||
// Returns the process state, via |state|, found in |status_lines|, which
|
||||
// contains the lines in the status file. Returns true if the process state
|
||||
// can be determined.
|
||||
bool GetStateFromStatus(const std::vector<std::string> &status_lines,
|
||||
std::string *state);
|
||||
|
||||
void LogCollectionError(const std::string &error_message);
|
||||
void EnqueueCollectionErrorLog(pid_t pid, ErrorType error_type,
|
||||
const std::string &exec_name);
|
||||
|
||||
bool CopyOffProcFiles(pid_t pid, const base::FilePath &container_dir);
|
||||
|
||||
// Validates the proc files at |container_dir| and returns true if they
|
||||
// are usable for the core-to-minidump conversion later. For instance, if
|
||||
// a process is reaped by the kernel before the copying of its proc files
|
||||
// takes place, some proc files like /proc/<pid>/maps may contain nothing
|
||||
// and thus become unusable.
|
||||
bool ValidateProcFiles(const base::FilePath &container_dir) const;
|
||||
|
||||
// Validates the core file at |core_path| and returns kErrorNone if
|
||||
// the file contains the ELF magic bytes and an ELF class that matches the
|
||||
// platform (i.e. 32-bit ELF on a 32-bit platform or 64-bit ELF on a 64-bit
|
||||
// platform), which is due to the limitation in core2md. It returns an error
|
||||
// type otherwise.
|
||||
ErrorType ValidateCoreFile(const base::FilePath &core_path) const;
|
||||
|
||||
// Determines the crash directory for given pid based on pid's owner,
|
||||
// and creates the directory if necessary with appropriate permissions.
|
||||
// Returns true whether or not directory needed to be created, false on
|
||||
// any failure.
|
||||
bool GetCreatedCrashDirectory(pid_t pid, uid_t supplied_ruid,
|
||||
base::FilePath *crash_file_path,
|
||||
bool *out_of_capacity);
|
||||
bool CopyStdinToCoreFile(const base::FilePath &core_path);
|
||||
bool RunCoreToMinidump(const base::FilePath &core_path,
|
||||
const base::FilePath &procfs_directory,
|
||||
const base::FilePath &minidump_path,
|
||||
const base::FilePath &temp_directory);
|
||||
ErrorType ConvertCoreToMinidump(pid_t pid,
|
||||
const base::FilePath &container_dir,
|
||||
const base::FilePath &core_path,
|
||||
const base::FilePath &minidump_path);
|
||||
ErrorType ConvertAndEnqueueCrash(pid_t pid, const std::string &exec_name,
|
||||
uid_t supplied_ruid, bool *out_of_capacity);
|
||||
bool ParseCrashAttributes(const std::string &crash_attributes,
|
||||
pid_t *pid, int *signal, uid_t *uid,
|
||||
std::string *kernel_supplied_name);
|
||||
|
||||
bool ShouldDump(bool has_owner_consent,
|
||||
bool is_developer,
|
||||
bool handle_chrome_crashes,
|
||||
const std::string &exec,
|
||||
std::string *reason);
|
||||
|
||||
bool generate_diagnostics_;
|
||||
std::string core_pattern_file_;
|
||||
std::string core_pipe_limit_file_;
|
||||
std::string our_path_;
|
||||
bool initialized_;
|
||||
|
||||
bool core2md_failure_;
|
||||
bool directory_failure_;
|
||||
std::string filter_in_;
|
||||
|
||||
static const char *kUserId;
|
||||
static const char *kGroupId;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(UserCollector);
|
||||
};
|
||||
|
||||
#endif // CRASH_REPORTER_USER_COLLECTOR_H_
|
553
crash_reporter/user_collector_test.cc
Normal file
553
crash_reporter/user_collector_test.cc
Normal file
|
@ -0,0 +1,553 @@
|
|||
// Copyright (c) 2012 The Chromium OS 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 "crash-reporter/user_collector.h"
|
||||
|
||||
#include <bits/wordsize.h>
|
||||
#include <elf.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <base/files/file_util.h>
|
||||
#include <base/files/scoped_temp_dir.h>
|
||||
#include <base/strings/string_split.h>
|
||||
#include <chromeos/syslog_logging.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using base::FilePath;
|
||||
using chromeos::FindLog;
|
||||
|
||||
namespace {
|
||||
|
||||
int s_crashes = 0;
|
||||
bool s_metrics = false;
|
||||
|
||||
const char kFilePath[] = "/my/path";
|
||||
|
||||
// Keep in sync with UserCollector::ShouldDump.
|
||||
const char kChromeIgnoreMsg[] =
|
||||
"ignoring call by kernel - chrome crash; "
|
||||
"waiting for chrome to call us directly";
|
||||
|
||||
void CountCrash() {
|
||||
++s_crashes;
|
||||
}
|
||||
|
||||
bool IsMetrics() {
|
||||
return s_metrics;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class UserCollectorMock : public UserCollector {
|
||||
public:
|
||||
MOCK_METHOD0(SetUpDBus, void());
|
||||
};
|
||||
|
||||
class UserCollectorTest : public ::testing::Test {
|
||||
void SetUp() {
|
||||
s_crashes = 0;
|
||||
|
||||
EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
|
||||
|
||||
collector_.Initialize(CountCrash,
|
||||
kFilePath,
|
||||
IsMetrics,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
"");
|
||||
base::DeleteFile(FilePath("test"), true);
|
||||
mkdir("test", 0777);
|
||||
collector_.set_core_pattern_file("test/core_pattern");
|
||||
collector_.set_core_pipe_limit_file("test/core_pipe_limit");
|
||||
pid_ = getpid();
|
||||
chromeos::ClearLog();
|
||||
}
|
||||
|
||||
protected:
|
||||
void ExpectFileEquals(const char *golden,
|
||||
const FilePath &file_path) {
|
||||
std::string contents;
|
||||
EXPECT_TRUE(base::ReadFileToString(file_path, &contents));
|
||||
EXPECT_EQ(golden, contents);
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitLines(const std::string &lines) const {
|
||||
std::vector<std::string> result;
|
||||
base::SplitString(lines, '\n', &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
UserCollectorMock collector_;
|
||||
pid_t pid_;
|
||||
};
|
||||
|
||||
TEST_F(UserCollectorTest, EnableOK) {
|
||||
ASSERT_TRUE(collector_.Enable());
|
||||
ExpectFileEquals("|/my/path --user=%P:%s:%u:%e",
|
||||
FilePath("test/core_pattern"));
|
||||
ExpectFileEquals("4", FilePath("test/core_pipe_limit"));
|
||||
ASSERT_EQ(s_crashes, 0);
|
||||
EXPECT_TRUE(FindLog("Enabling user crash handling"));
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, EnableNoPatternFileAccess) {
|
||||
collector_.set_core_pattern_file("/does_not_exist");
|
||||
ASSERT_FALSE(collector_.Enable());
|
||||
ASSERT_EQ(s_crashes, 0);
|
||||
EXPECT_TRUE(FindLog("Enabling user crash handling"));
|
||||
EXPECT_TRUE(FindLog("Unable to write /does_not_exist"));
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, EnableNoPipeLimitFileAccess) {
|
||||
collector_.set_core_pipe_limit_file("/does_not_exist");
|
||||
ASSERT_FALSE(collector_.Enable());
|
||||
ASSERT_EQ(s_crashes, 0);
|
||||
// Core pattern should not be written if we cannot access the pipe limit
|
||||
// or otherwise we may set a pattern that results in infinite recursion.
|
||||
ASSERT_FALSE(base::PathExists(FilePath("test/core_pattern")));
|
||||
EXPECT_TRUE(FindLog("Enabling user crash handling"));
|
||||
EXPECT_TRUE(FindLog("Unable to write /does_not_exist"));
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, DisableOK) {
|
||||
ASSERT_TRUE(collector_.Disable());
|
||||
ExpectFileEquals("core", FilePath("test/core_pattern"));
|
||||
ASSERT_EQ(s_crashes, 0);
|
||||
EXPECT_TRUE(FindLog("Disabling user crash handling"));
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, DisableNoFileAccess) {
|
||||
collector_.set_core_pattern_file("/does_not_exist");
|
||||
ASSERT_FALSE(collector_.Disable());
|
||||
ASSERT_EQ(s_crashes, 0);
|
||||
EXPECT_TRUE(FindLog("Disabling user crash handling"));
|
||||
EXPECT_TRUE(FindLog("Unable to write /does_not_exist"));
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, ParseCrashAttributes) {
|
||||
pid_t pid;
|
||||
int signal;
|
||||
uid_t uid;
|
||||
std::string exec_name;
|
||||
EXPECT_TRUE(collector_.ParseCrashAttributes("123456:11:1000:foobar",
|
||||
&pid, &signal, &uid, &exec_name));
|
||||
EXPECT_EQ(123456, pid);
|
||||
EXPECT_EQ(11, signal);
|
||||
EXPECT_EQ(1000, uid);
|
||||
EXPECT_EQ("foobar", exec_name);
|
||||
EXPECT_TRUE(collector_.ParseCrashAttributes("4321:6:barfoo",
|
||||
&pid, &signal, &uid, &exec_name));
|
||||
EXPECT_EQ(4321, pid);
|
||||
EXPECT_EQ(6, signal);
|
||||
EXPECT_EQ(-1, uid);
|
||||
EXPECT_EQ("barfoo", exec_name);
|
||||
|
||||
EXPECT_FALSE(collector_.ParseCrashAttributes("123456:11",
|
||||
&pid, &signal, &uid, &exec_name));
|
||||
|
||||
EXPECT_TRUE(collector_.ParseCrashAttributes("123456:11:exec:extra",
|
||||
&pid, &signal, &uid, &exec_name));
|
||||
EXPECT_EQ("exec:extra", exec_name);
|
||||
|
||||
EXPECT_FALSE(collector_.ParseCrashAttributes("12345p:11:foobar",
|
||||
&pid, &signal, &uid, &exec_name));
|
||||
|
||||
EXPECT_FALSE(collector_.ParseCrashAttributes("123456:1 :foobar",
|
||||
&pid, &signal, &uid, &exec_name));
|
||||
|
||||
EXPECT_FALSE(collector_.ParseCrashAttributes("123456::foobar",
|
||||
&pid, &signal, &uid, &exec_name));
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, ShouldDumpDeveloperImageOverridesConsent) {
|
||||
std::string reason;
|
||||
EXPECT_TRUE(collector_.ShouldDump(false, true, false,
|
||||
"chrome-wm", &reason));
|
||||
EXPECT_EQ("developer build - not testing - always dumping", reason);
|
||||
|
||||
// When running a crash test, behave as normal.
|
||||
EXPECT_FALSE(collector_.ShouldDump(false, false, false,
|
||||
"chrome-wm", &reason));
|
||||
EXPECT_EQ("ignoring - no consent", reason);
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, ShouldDumpChromeOverridesDeveloperImage) {
|
||||
std::string reason;
|
||||
// When running a crash test, behave as normal.
|
||||
EXPECT_FALSE(collector_.ShouldDump(false, false, false,
|
||||
"chrome", &reason));
|
||||
EXPECT_EQ(kChromeIgnoreMsg, reason);
|
||||
EXPECT_FALSE(collector_.ShouldDump(false, false, false,
|
||||
"supplied_Compositor", &reason));
|
||||
EXPECT_EQ(kChromeIgnoreMsg, reason);
|
||||
EXPECT_FALSE(collector_.ShouldDump(false, false, false,
|
||||
"supplied_PipelineThread", &reason));
|
||||
EXPECT_EQ(kChromeIgnoreMsg, reason);
|
||||
EXPECT_FALSE(collector_.ShouldDump(false, false, false,
|
||||
"Chrome_ChildIOThread", &reason));
|
||||
EXPECT_EQ(kChromeIgnoreMsg, reason);
|
||||
EXPECT_FALSE(collector_.ShouldDump(false, false, false,
|
||||
"supplied_Chrome_ChildIOT", &reason));
|
||||
EXPECT_EQ(kChromeIgnoreMsg, reason);
|
||||
EXPECT_FALSE(collector_.ShouldDump(false, false, false,
|
||||
"supplied_ChromotingClien", &reason));
|
||||
EXPECT_EQ(kChromeIgnoreMsg, reason);
|
||||
EXPECT_FALSE(collector_.ShouldDump(false, false, false,
|
||||
"supplied_LocalInputMonit", &reason));
|
||||
EXPECT_EQ(kChromeIgnoreMsg, reason);
|
||||
|
||||
// When running a developer image, test that chrome crashes are handled
|
||||
// when the "handle_chrome_crashes" flag is set.
|
||||
EXPECT_TRUE(collector_.ShouldDump(false, true, true,
|
||||
"chrome", &reason));
|
||||
EXPECT_EQ("developer build - not testing - always dumping",
|
||||
reason);
|
||||
EXPECT_TRUE(collector_.ShouldDump(false, true, true,
|
||||
"supplied_Compositor", &reason));
|
||||
EXPECT_EQ("developer build - not testing - always dumping",
|
||||
reason);
|
||||
EXPECT_TRUE(collector_.ShouldDump(false, true, true,
|
||||
"supplied_PipelineThread", &reason));
|
||||
EXPECT_EQ("developer build - not testing - always dumping",
|
||||
reason);
|
||||
EXPECT_TRUE(collector_.ShouldDump(false, true, true,
|
||||
"Chrome_ChildIOThread", &reason));
|
||||
EXPECT_EQ("developer build - not testing - always dumping",
|
||||
reason);
|
||||
EXPECT_TRUE(collector_.ShouldDump(false, true, true,
|
||||
"supplied_Chrome_ChildIOT", &reason));
|
||||
EXPECT_EQ("developer build - not testing - always dumping",
|
||||
reason);
|
||||
EXPECT_TRUE(collector_.ShouldDump(false, true, true,
|
||||
"supplied_ChromotingClien", &reason));
|
||||
EXPECT_EQ("developer build - not testing - always dumping",
|
||||
reason);
|
||||
EXPECT_TRUE(collector_.ShouldDump(false, true, true,
|
||||
"supplied_LocalInputMonit", &reason));
|
||||
EXPECT_EQ("developer build - not testing - always dumping",
|
||||
reason);
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, ShouldDumpUseConsentProductionImage) {
|
||||
std::string result;
|
||||
EXPECT_FALSE(collector_.ShouldDump(false, false, false,
|
||||
"chrome-wm", &result));
|
||||
EXPECT_EQ("ignoring - no consent", result);
|
||||
|
||||
EXPECT_TRUE(collector_.ShouldDump(true, false, false,
|
||||
"chrome-wm", &result));
|
||||
EXPECT_EQ("handling", result);
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, HandleCrashWithoutConsent) {
|
||||
s_metrics = false;
|
||||
collector_.HandleCrash("20:10:ignored", "foobar");
|
||||
EXPECT_TRUE(FindLog(
|
||||
"Received crash notification for foobar[20] sig 10"));
|
||||
ASSERT_EQ(s_crashes, 0);
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, HandleNonChromeCrashWithConsent) {
|
||||
s_metrics = true;
|
||||
collector_.HandleCrash("5:2:ignored", "chromeos-wm");
|
||||
EXPECT_TRUE(FindLog(
|
||||
"Received crash notification for chromeos-wm[5] sig 2"));
|
||||
ASSERT_EQ(s_crashes, 1);
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, HandleChromeCrashWithConsent) {
|
||||
s_metrics = true;
|
||||
collector_.HandleCrash("5:2:ignored", "chrome");
|
||||
EXPECT_TRUE(FindLog(
|
||||
"Received crash notification for chrome[5] sig 2"));
|
||||
EXPECT_TRUE(FindLog(kChromeIgnoreMsg));
|
||||
ASSERT_EQ(s_crashes, 0);
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, HandleSuppliedChromeCrashWithConsent) {
|
||||
s_metrics = true;
|
||||
collector_.HandleCrash("0:2:chrome", nullptr);
|
||||
EXPECT_TRUE(FindLog(
|
||||
"Received crash notification for supplied_chrome[0] sig 2"));
|
||||
EXPECT_TRUE(FindLog(kChromeIgnoreMsg));
|
||||
ASSERT_EQ(s_crashes, 0);
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, GetProcessPath) {
|
||||
FilePath path = collector_.GetProcessPath(100);
|
||||
ASSERT_EQ("/proc/100", path.value());
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, GetSymlinkTarget) {
|
||||
FilePath result;
|
||||
ASSERT_FALSE(collector_.GetSymlinkTarget(FilePath("/does_not_exist"),
|
||||
&result));
|
||||
ASSERT_TRUE(FindLog(
|
||||
"Readlink failed on /does_not_exist with 2"));
|
||||
std::string long_link;
|
||||
for (int i = 0; i < 50; ++i)
|
||||
long_link += "0123456789";
|
||||
long_link += "/gold";
|
||||
|
||||
for (size_t len = 1; len <= long_link.size(); ++len) {
|
||||
std::string this_link;
|
||||
static const char kLink[] = "test/this_link";
|
||||
this_link.assign(long_link.c_str(), len);
|
||||
ASSERT_EQ(len, this_link.size());
|
||||
unlink(kLink);
|
||||
ASSERT_EQ(0, symlink(this_link.c_str(), kLink));
|
||||
ASSERT_TRUE(collector_.GetSymlinkTarget(FilePath(kLink), &result));
|
||||
ASSERT_EQ(this_link, result.value());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, GetExecutableBaseNameFromPid) {
|
||||
std::string base_name;
|
||||
EXPECT_FALSE(collector_.GetExecutableBaseNameFromPid(0, &base_name));
|
||||
EXPECT_TRUE(FindLog(
|
||||
"Readlink failed on /proc/0/exe with 2"));
|
||||
EXPECT_TRUE(FindLog(
|
||||
"GetSymlinkTarget failed - Path /proc/0 DirectoryExists: 0"));
|
||||
EXPECT_TRUE(FindLog("stat /proc/0/exe failed: -1 2"));
|
||||
|
||||
chromeos::ClearLog();
|
||||
pid_t my_pid = getpid();
|
||||
EXPECT_TRUE(collector_.GetExecutableBaseNameFromPid(my_pid, &base_name));
|
||||
EXPECT_FALSE(FindLog("Readlink failed"));
|
||||
EXPECT_EQ("crash_reporter_test", base_name);
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, GetFirstLineWithPrefix) {
|
||||
std::vector<std::string> lines;
|
||||
std::string line;
|
||||
|
||||
EXPECT_FALSE(collector_.GetFirstLineWithPrefix(lines, "Name:", &line));
|
||||
EXPECT_EQ("", line);
|
||||
|
||||
lines.push_back("Name:\tls");
|
||||
lines.push_back("State:\tR (running)");
|
||||
lines.push_back(" Foo:\t1000");
|
||||
|
||||
line.clear();
|
||||
EXPECT_TRUE(collector_.GetFirstLineWithPrefix(lines, "Name:", &line));
|
||||
EXPECT_EQ(lines[0], line);
|
||||
|
||||
line.clear();
|
||||
EXPECT_TRUE(collector_.GetFirstLineWithPrefix(lines, "State:", &line));
|
||||
EXPECT_EQ(lines[1], line);
|
||||
|
||||
line.clear();
|
||||
EXPECT_FALSE(collector_.GetFirstLineWithPrefix(lines, "Foo:", &line));
|
||||
EXPECT_EQ("", line);
|
||||
|
||||
line.clear();
|
||||
EXPECT_TRUE(collector_.GetFirstLineWithPrefix(lines, " Foo:", &line));
|
||||
EXPECT_EQ(lines[2], line);
|
||||
|
||||
line.clear();
|
||||
EXPECT_FALSE(collector_.GetFirstLineWithPrefix(lines, "Bar:", &line));
|
||||
EXPECT_EQ("", line);
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, GetIdFromStatus) {
|
||||
int id = 1;
|
||||
EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
|
||||
UserCollector::kIdEffective,
|
||||
SplitLines("nothing here"),
|
||||
&id));
|
||||
EXPECT_EQ(id, 1);
|
||||
|
||||
// Not enough parameters.
|
||||
EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
|
||||
UserCollector::kIdReal,
|
||||
SplitLines("line 1\nUid:\t1\n"),
|
||||
&id));
|
||||
|
||||
const std::vector<std::string> valid_contents =
|
||||
SplitLines("\nUid:\t1\t2\t3\t4\nGid:\t5\t6\t7\t8\n");
|
||||
EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
|
||||
UserCollector::kIdReal,
|
||||
valid_contents,
|
||||
&id));
|
||||
EXPECT_EQ(1, id);
|
||||
|
||||
EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
|
||||
UserCollector::kIdEffective,
|
||||
valid_contents,
|
||||
&id));
|
||||
EXPECT_EQ(2, id);
|
||||
|
||||
EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
|
||||
UserCollector::kIdFileSystem,
|
||||
valid_contents,
|
||||
&id));
|
||||
EXPECT_EQ(4, id);
|
||||
|
||||
EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kGroupId,
|
||||
UserCollector::kIdEffective,
|
||||
valid_contents,
|
||||
&id));
|
||||
EXPECT_EQ(6, id);
|
||||
|
||||
EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kGroupId,
|
||||
UserCollector::kIdSet,
|
||||
valid_contents,
|
||||
&id));
|
||||
EXPECT_EQ(7, id);
|
||||
|
||||
EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kGroupId,
|
||||
UserCollector::IdKind(5),
|
||||
valid_contents,
|
||||
&id));
|
||||
EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kGroupId,
|
||||
UserCollector::IdKind(-1),
|
||||
valid_contents,
|
||||
&id));
|
||||
|
||||
// Fail if junk after number
|
||||
EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
|
||||
UserCollector::kIdReal,
|
||||
SplitLines("Uid:\t1f\t2\t3\t4\n"),
|
||||
&id));
|
||||
EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
|
||||
UserCollector::kIdReal,
|
||||
SplitLines("Uid:\t1\t2\t3\t4\n"),
|
||||
&id));
|
||||
EXPECT_EQ(1, id);
|
||||
|
||||
// Fail if more than 4 numbers.
|
||||
EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
|
||||
UserCollector::kIdReal,
|
||||
SplitLines("Uid:\t1\t2\t3\t4\t5\n"),
|
||||
&id));
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, GetStateFromStatus) {
|
||||
std::string state;
|
||||
EXPECT_FALSE(collector_.GetStateFromStatus(SplitLines("nothing here"),
|
||||
&state));
|
||||
EXPECT_EQ("", state);
|
||||
|
||||
EXPECT_TRUE(collector_.GetStateFromStatus(SplitLines("State:\tR (running)"),
|
||||
&state));
|
||||
EXPECT_EQ("R (running)", state);
|
||||
|
||||
EXPECT_TRUE(collector_.GetStateFromStatus(
|
||||
SplitLines("Name:\tls\nState:\tZ (zombie)\n"), &state));
|
||||
EXPECT_EQ("Z (zombie)", state);
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, GetUserInfoFromName) {
|
||||
gid_t gid = 100;
|
||||
uid_t uid = 100;
|
||||
EXPECT_TRUE(collector_.GetUserInfoFromName("root", &uid, &gid));
|
||||
EXPECT_EQ(0, uid);
|
||||
EXPECT_EQ(0, gid);
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, CopyOffProcFilesBadPath) {
|
||||
// Try a path that is not writable.
|
||||
ASSERT_FALSE(collector_.CopyOffProcFiles(pid_, FilePath("/bad/path")));
|
||||
EXPECT_TRUE(FindLog("Could not create /bad/path"));
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, CopyOffProcFilesBadPid) {
|
||||
FilePath container_path("test/container");
|
||||
ASSERT_FALSE(collector_.CopyOffProcFiles(0, container_path));
|
||||
EXPECT_TRUE(FindLog("Path /proc/0 does not exist"));
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, CopyOffProcFilesOK) {
|
||||
FilePath container_path("test/container");
|
||||
ASSERT_TRUE(collector_.CopyOffProcFiles(pid_, container_path));
|
||||
EXPECT_FALSE(FindLog("Could not copy"));
|
||||
static struct {
|
||||
const char *name;
|
||||
bool exists;
|
||||
} expectations[] = {
|
||||
{ "auxv", true },
|
||||
{ "cmdline", true },
|
||||
{ "environ", true },
|
||||
{ "maps", true },
|
||||
{ "mem", false },
|
||||
{ "mounts", false },
|
||||
{ "sched", false },
|
||||
{ "status", true }
|
||||
};
|
||||
for (unsigned i = 0; i < sizeof(expectations)/sizeof(expectations[0]); ++i) {
|
||||
EXPECT_EQ(expectations[i].exists,
|
||||
base::PathExists(
|
||||
container_path.Append(expectations[i].name)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, ValidateProcFiles) {
|
||||
base::ScopedTempDir temp_dir;
|
||||
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
|
||||
FilePath container_dir = temp_dir.path();
|
||||
|
||||
// maps file not exists (i.e. GetFileSize fails)
|
||||
EXPECT_FALSE(collector_.ValidateProcFiles(container_dir));
|
||||
|
||||
// maps file is empty
|
||||
FilePath maps_file = container_dir.Append("maps");
|
||||
ASSERT_EQ(0, base::WriteFile(maps_file, nullptr, 0));
|
||||
ASSERT_TRUE(base::PathExists(maps_file));
|
||||
EXPECT_FALSE(collector_.ValidateProcFiles(container_dir));
|
||||
|
||||
// maps file is not empty
|
||||
const char data[] = "test data";
|
||||
ASSERT_EQ(sizeof(data), base::WriteFile(maps_file, data, sizeof(data)));
|
||||
ASSERT_TRUE(base::PathExists(maps_file));
|
||||
EXPECT_TRUE(collector_.ValidateProcFiles(container_dir));
|
||||
}
|
||||
|
||||
TEST_F(UserCollectorTest, ValidateCoreFile) {
|
||||
base::ScopedTempDir temp_dir;
|
||||
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
|
||||
FilePath container_dir = temp_dir.path();
|
||||
FilePath core_file = container_dir.Append("core");
|
||||
|
||||
// Core file does not exist
|
||||
EXPECT_EQ(UserCollector::kErrorInvalidCoreFile,
|
||||
collector_.ValidateCoreFile(core_file));
|
||||
char e_ident[EI_NIDENT];
|
||||
e_ident[EI_MAG0] = ELFMAG0;
|
||||
e_ident[EI_MAG1] = ELFMAG1;
|
||||
e_ident[EI_MAG2] = ELFMAG2;
|
||||
e_ident[EI_MAG3] = ELFMAG3;
|
||||
#if __WORDSIZE == 32
|
||||
e_ident[EI_CLASS] = ELFCLASS32;
|
||||
#elif __WORDSIZE == 64
|
||||
e_ident[EI_CLASS] = ELFCLASS64;
|
||||
#else
|
||||
#error Unknown/unsupported value of __WORDSIZE.
|
||||
#endif
|
||||
|
||||
// Core file has the expected header
|
||||
ASSERT_TRUE(base::WriteFile(core_file, e_ident, sizeof(e_ident)));
|
||||
EXPECT_EQ(UserCollector::kErrorNone,
|
||||
collector_.ValidateCoreFile(core_file));
|
||||
|
||||
#if __WORDSIZE == 64
|
||||
// 32-bit core file on 64-bit platform
|
||||
e_ident[EI_CLASS] = ELFCLASS32;
|
||||
ASSERT_TRUE(base::WriteFile(core_file, e_ident, sizeof(e_ident)));
|
||||
EXPECT_EQ(UserCollector::kErrorUnsupported32BitCoreFile,
|
||||
collector_.ValidateCoreFile(core_file));
|
||||
e_ident[EI_CLASS] = ELFCLASS64;
|
||||
#endif
|
||||
|
||||
// Invalid core files
|
||||
ASSERT_TRUE(base::WriteFile(core_file, e_ident, sizeof(e_ident) - 1));
|
||||
EXPECT_EQ(UserCollector::kErrorInvalidCoreFile,
|
||||
collector_.ValidateCoreFile(core_file));
|
||||
|
||||
e_ident[EI_MAG0] = 0;
|
||||
ASSERT_TRUE(base::WriteFile(core_file, e_ident, sizeof(e_ident)));
|
||||
EXPECT_EQ(UserCollector::kErrorInvalidCoreFile,
|
||||
collector_.ValidateCoreFile(core_file));
|
||||
}
|
322
crash_reporter/warn_collector.l
Normal file
322
crash_reporter/warn_collector.l
Normal file
|
@ -0,0 +1,322 @@
|
|||
/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*
|
||||
* This flex program reads /var/log/messages as it grows and saves kernel
|
||||
* warnings to files. It keeps track of warnings it has seen (based on
|
||||
* file/line only, ignoring differences in the stack trace), and reports only
|
||||
* the first warning of each kind, but maintains a count of all warnings by
|
||||
* using their hashes as buckets in a UMA sparse histogram. It also invokes
|
||||
* the crash collector, which collects the warnings and prepares them for later
|
||||
* shipment to the crash server.
|
||||
*/
|
||||
|
||||
%{
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <pwd.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "metrics/c_metrics_library.h"
|
||||
|
||||
int WarnStart(void);
|
||||
void WarnEnd(void);
|
||||
void WarnInput(char *buf, yy_size_t *result, size_t max_size);
|
||||
|
||||
#define YY_INPUT(buf, result, max_size) WarnInput(buf, &result, max_size)
|
||||
|
||||
%}
|
||||
|
||||
/* Define a few useful regular expressions. */
|
||||
|
||||
D [0-9]
|
||||
PREFIX .*" kernel: [ "*{D}+"."{D}+"]"
|
||||
CUT_HERE {PREFIX}" ------------[ cut here".*
|
||||
WARNING {PREFIX}" WARNING: at "
|
||||
END_TRACE {PREFIX}" ---[ end trace".*
|
||||
|
||||
/* Use exclusive start conditions. */
|
||||
%x PRE_WARN WARN
|
||||
|
||||
%%
|
||||
/* The scanner itself. */
|
||||
|
||||
^{CUT_HERE}\n{WARNING} BEGIN(PRE_WARN);
|
||||
.|\n /* ignore all other input in state 0 */
|
||||
<PRE_WARN>[^ ]+.[^ ]+\n if (WarnStart()) {
|
||||
/* yytext is
|
||||
"file:line func+offset/offset()\n" */
|
||||
BEGIN(WARN); ECHO;
|
||||
} else {
|
||||
BEGIN(0);
|
||||
}
|
||||
|
||||
/* Assume the warning ends at the "end trace" line */
|
||||
<WARN>^{END_TRACE}\n ECHO; BEGIN(0); WarnEnd();
|
||||
<WARN>^.*\n ECHO;
|
||||
|
||||
%%
|
||||
|
||||
#define HASH_BITMAP_SIZE (1 << 15) /* size in bits */
|
||||
#define HASH_BITMAP_MASK (HASH_BITMAP_SIZE - 1)
|
||||
|
||||
const char warn_hist_name[] = "Platform.KernelWarningHashes";
|
||||
uint32_t hash_bitmap[HASH_BITMAP_SIZE / 32];
|
||||
CMetricsLibrary metrics_library;
|
||||
|
||||
const char *prog_name; /* the name of this program */
|
||||
int yyin_fd; /* instead of FILE *yyin to avoid buffering */
|
||||
int i_fd; /* for inotify, to detect file changes */
|
||||
int testing; /* 1 if running test */
|
||||
int filter; /* 1 when using as filter (for development) */
|
||||
int fifo; /* 1 when reading from fifo (for devel) */
|
||||
int draining; /* 1 when draining renamed log file */
|
||||
|
||||
const char *msg_path = "/var/log/messages";
|
||||
const char warn_dump_dir[] = "/var/run/kwarn";
|
||||
const char *warn_dump_path = "/var/run/kwarn/warning";
|
||||
const char *crash_reporter_command;
|
||||
|
||||
__attribute__((__format__(__printf__, 1, 2)))
|
||||
static void Die(const char *format, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
fprintf(stderr, "%s: ", prog_name);
|
||||
vfprintf(stderr, format, ap);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void RunCrashReporter(void) {
|
||||
int status = system(crash_reporter_command);
|
||||
if (status != 0)
|
||||
Die("%s exited with status %d\n", crash_reporter_command, status);
|
||||
}
|
||||
|
||||
static uint32_t StringHash(const char *string) {
|
||||
uint32_t hash = 0;
|
||||
while (*string != '\0') {
|
||||
hash = (hash << 5) + hash + *string++;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/* We expect only a handful of different warnings per boot session, so the
|
||||
* probability of a collision is very low, and statistically it won't matter
|
||||
* (unless warnings with the same hash also happens in tandem, which is even
|
||||
* rarer).
|
||||
*/
|
||||
static int HashSeen(uint32_t hash) {
|
||||
int word_index = (hash & HASH_BITMAP_MASK) / 32;
|
||||
int bit_index = (hash & HASH_BITMAP_MASK) % 32;
|
||||
return hash_bitmap[word_index] & 1 << bit_index;
|
||||
}
|
||||
|
||||
static void SetHashSeen(uint32_t hash) {
|
||||
int word_index = (hash & HASH_BITMAP_MASK) / 32;
|
||||
int bit_index = (hash & HASH_BITMAP_MASK) % 32;
|
||||
hash_bitmap[word_index] |= 1 << bit_index;
|
||||
}
|
||||
|
||||
int WarnStart(void) {
|
||||
uint32_t hash;
|
||||
char *spacep;
|
||||
|
||||
if (filter)
|
||||
return 1;
|
||||
|
||||
hash = StringHash(yytext);
|
||||
if (!(testing || fifo || filter)) {
|
||||
CMetricsLibrarySendSparseToUMA(metrics_library, warn_hist_name, (int) hash);
|
||||
}
|
||||
if (HashSeen(hash))
|
||||
return 0;
|
||||
SetHashSeen(hash);
|
||||
|
||||
yyout = fopen(warn_dump_path, "w");
|
||||
if (yyout == NULL)
|
||||
Die("fopen %s failed: %s\n", warn_dump_path, strerror(errno));
|
||||
spacep = index(yytext, ' ');
|
||||
if (spacep == NULL || spacep[1] == '\0')
|
||||
spacep = "unknown-function";
|
||||
fprintf(yyout, "%08x-%s\n", hash, spacep + 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void WarnEnd(void) {
|
||||
if (filter)
|
||||
return;
|
||||
fclose(yyout);
|
||||
yyout = stdout; /* for debugging */
|
||||
RunCrashReporter();
|
||||
}
|
||||
|
||||
static void WarnOpenInput(const char *path) {
|
||||
yyin_fd = open(path, O_RDONLY);
|
||||
if (yyin_fd < 0)
|
||||
Die("could not open %s: %s\n", path, strerror(errno));
|
||||
if (!fifo) {
|
||||
/* Go directly to the end of the file. We don't want to parse the same
|
||||
* warnings multiple times on reboot/restart. We might miss some
|
||||
* warnings, but so be it---it's too hard to keep track reliably of the
|
||||
* last parsed position in the syslog.
|
||||
*/
|
||||
if (lseek(yyin_fd, 0, SEEK_END) < 0)
|
||||
Die("could not lseek %s: %s\n", path, strerror(errno));
|
||||
/* Set up notification of file growth and rename. */
|
||||
i_fd = inotify_init();
|
||||
if (i_fd < 0)
|
||||
Die("inotify_init: %s\n", strerror(errno));
|
||||
if (inotify_add_watch(i_fd, path, IN_MODIFY | IN_MOVE_SELF) < 0)
|
||||
Die("inotify_add_watch: %s\n", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
/* We replace the default YY_INPUT() for the following reasons:
|
||||
*
|
||||
* 1. We want to read data as soon as it becomes available, but the default
|
||||
* YY_INPUT() uses buffered I/O.
|
||||
*
|
||||
* 2. We want to block on end of input and wait for the file to grow.
|
||||
*
|
||||
* 3. We want to detect log rotation, and reopen the input file as needed.
|
||||
*/
|
||||
void WarnInput(char *buf, yy_size_t *result, size_t max_size) {
|
||||
while (1) {
|
||||
ssize_t ret = read(yyin_fd, buf, max_size);
|
||||
if (ret < 0)
|
||||
Die("read: %s", strerror(errno));
|
||||
*result = ret;
|
||||
if (*result > 0 || fifo || filter)
|
||||
return;
|
||||
if (draining) {
|
||||
/* Assume we're done with this log, and move to next
|
||||
* log. Rsyslogd may keep writing to the old log file
|
||||
* for a while, but we don't care since we don't have
|
||||
* to be exact.
|
||||
*/
|
||||
close(yyin_fd);
|
||||
if (YYSTATE == WARN) {
|
||||
/* Be conservative in case we lose the warn
|
||||
* terminator during the switch---or we may
|
||||
* collect personally identifiable information.
|
||||
*/
|
||||
WarnEnd();
|
||||
}
|
||||
BEGIN(0); /* see above comment */
|
||||
sleep(1); /* avoid race with log rotator */
|
||||
WarnOpenInput(msg_path);
|
||||
draining = 0;
|
||||
continue;
|
||||
}
|
||||
/* Nothing left to read, so we must wait. */
|
||||
struct inotify_event event;
|
||||
while (1) {
|
||||
int n = read(i_fd, &event, sizeof(event));
|
||||
if (n <= 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
else
|
||||
Die("inotify: %s\n", strerror(errno));
|
||||
} else
|
||||
break;
|
||||
}
|
||||
if (event.mask & IN_MOVE_SELF) {
|
||||
/* The file has been renamed. Before switching
|
||||
* to the new one, we process any remaining
|
||||
* content of this file.
|
||||
*/
|
||||
draining = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int result;
|
||||
struct passwd *user;
|
||||
prog_name = argv[0];
|
||||
|
||||
if (argc == 2 && strcmp(argv[1], "--test") == 0)
|
||||
testing = 1;
|
||||
else if (argc == 2 && strcmp(argv[1], "--filter") == 0)
|
||||
filter = 1;
|
||||
else if (argc == 2 && strcmp(argv[1], "--fifo") == 0) {
|
||||
fifo = 1;
|
||||
} else if (argc != 1) {
|
||||
fprintf(stderr,
|
||||
"usage: %s [single-flag]\n"
|
||||
"flags (for testing only):\n"
|
||||
"--fifo\tinput is fifo \"fifo\", output is stdout\n"
|
||||
"--filter\tinput is stdin, output is stdout\n"
|
||||
"--test\trun self-test\n",
|
||||
prog_name);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
metrics_library = CMetricsLibraryNew();
|
||||
CMetricsLibraryInit(metrics_library);
|
||||
|
||||
crash_reporter_command = testing ?
|
||||
"./warn_collector_test_reporter.sh" :
|
||||
"/sbin/crash_reporter --kernel_warning";
|
||||
|
||||
/* When filtering with --filter (for development) use stdin for input.
|
||||
* Otherwise read input from a file or a fifo.
|
||||
*/
|
||||
yyin_fd = fileno(stdin);
|
||||
if (testing) {
|
||||
msg_path = "messages";
|
||||
warn_dump_path = "warning";
|
||||
}
|
||||
if (fifo) {
|
||||
msg_path = "fifo";
|
||||
}
|
||||
if (!filter) {
|
||||
WarnOpenInput(msg_path);
|
||||
}
|
||||
|
||||
/* Create directory for dump file. Still need to be root here. */
|
||||
unlink(warn_dump_path);
|
||||
if (!testing && !fifo && !filter) {
|
||||
rmdir(warn_dump_dir);
|
||||
result = mkdir(warn_dump_dir, 0755);
|
||||
if (result < 0)
|
||||
Die("could not create %s: %s\n",
|
||||
warn_dump_dir, strerror(errno));
|
||||
}
|
||||
|
||||
if (0) {
|
||||
/* TODO(semenzato): put this back in once we decide it's safe
|
||||
* to make /var/spool/crash rwxrwxrwx root, or use a different
|
||||
* owner and setuid for the crash reporter as well.
|
||||
*/
|
||||
|
||||
/* Get low privilege uid, gid. */
|
||||
user = getpwnam("chronos");
|
||||
if (user == NULL)
|
||||
Die("getpwnam failed\n");
|
||||
|
||||
/* Change dump directory ownership. */
|
||||
if (chown(warn_dump_dir, user->pw_uid, user->pw_gid) < 0)
|
||||
Die("chown: %s\n", strerror(errno));
|
||||
|
||||
/* Drop privileges. */
|
||||
if (setuid(user->pw_uid) < 0) {
|
||||
Die("setuid: %s\n", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
/* Go! */
|
||||
return yylex();
|
||||
}
|
||||
|
||||
/* Flex should really know not to generate these functions.
|
||||
*/
|
||||
void UnusedFunctionWarningSuppressor(void) {
|
||||
yyunput(0, 0);
|
||||
(void) input();
|
||||
}
|
14
crash_reporter/warn_collector_test.c
Normal file
14
crash_reporter/warn_collector_test.c
Normal file
|
@ -0,0 +1,14 @@
|
|||
/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Test driver for the warn_collector daemon.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int ac, char **av) {
|
||||
int status = system("exec \"${SRC}\"/warn_collector_test.sh");
|
||||
return status < 0 ? EXIT_FAILURE : WEXITSTATUS(status);
|
||||
}
|
79
crash_reporter/warn_collector_test.sh
Executable file
79
crash_reporter/warn_collector_test.sh
Executable file
|
@ -0,0 +1,79 @@
|
|||
#! /bin/bash
|
||||
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
# Test for warn_collector. Run the warn collector in the background, emulate
|
||||
# the kernel by appending lines to the log file "messages", and observe the log
|
||||
# of the (fake) crash reporter each time is run by the warn collector daemon.
|
||||
|
||||
set -e
|
||||
|
||||
fail() {
|
||||
printf '[ FAIL ] %b\n' "$*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [[ -z ${SYSROOT} ]]; then
|
||||
fail "SYSROOT must be set for this test to work"
|
||||
fi
|
||||
: ${OUT:=${PWD}}
|
||||
cd "${OUT}"
|
||||
PATH=${OUT}:${PATH}
|
||||
TESTLOG="${OUT}/warn-test-log"
|
||||
|
||||
echo "Testing: $(which warn_collector)"
|
||||
|
||||
cleanup() {
|
||||
# Kill daemon (if started) on exit
|
||||
kill %
|
||||
}
|
||||
|
||||
check_log() {
|
||||
local n_expected=$1
|
||||
if [[ ! -f ${TESTLOG} ]]; then
|
||||
fail "${TESTLOG} was not created"
|
||||
fi
|
||||
if [[ $(wc -l < "${TESTLOG}") -ne ${n_expected} ]]; then
|
||||
fail "expected ${n_expected} lines in ${TESTLOG}, found this instead:
|
||||
$(<"${TESTLOG}")"
|
||||
fi
|
||||
if egrep -qv '^[0-9a-f]{8}' "${TESTLOG}"; then
|
||||
fail "found bad lines in ${TESTLOG}:
|
||||
$(<"${TESTLOG}")"
|
||||
fi
|
||||
}
|
||||
|
||||
rm -f "${TESTLOG}"
|
||||
cp "${SRC}/warn_collector_test_reporter.sh" .
|
||||
cp "${SRC}/TEST_WARNING" .
|
||||
cp TEST_WARNING messages
|
||||
|
||||
# Start the collector daemon. With the --test option, the daemon reads input
|
||||
# from ./messages, writes the warning into ./warning, and invokes
|
||||
# ./warn_collector_test_reporter.sh to report the warning.
|
||||
warn_collector --test &
|
||||
trap cleanup EXIT
|
||||
|
||||
# After a while, check that the first warning has been collected.
|
||||
sleep 1
|
||||
check_log 1
|
||||
|
||||
# Add the same warning to messages, verify that it is NOT collected
|
||||
cat TEST_WARNING >> messages
|
||||
sleep 1
|
||||
check_log 1
|
||||
|
||||
# Add a slightly different warning to messages, check that it is collected.
|
||||
sed s/intel_dp.c/intel_xx.c/ < TEST_WARNING >> messages
|
||||
sleep 1
|
||||
check_log 2
|
||||
|
||||
# Emulate log rotation, add a warning, and check.
|
||||
mv messages messages.1
|
||||
sed s/intel_dp.c/intel_xy.c/ < TEST_WARNING > messages
|
||||
sleep 2
|
||||
check_log 3
|
||||
|
||||
# Success!
|
||||
exit 0
|
16
crash_reporter/warn_collector_test_reporter.sh
Executable file
16
crash_reporter/warn_collector_test_reporter.sh
Executable file
|
@ -0,0 +1,16 @@
|
|||
#! /bin/sh
|
||||
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
# Replacement for the crash reporter, for testing. Log the first line of the
|
||||
# "warning" file, which by convention contains the warning hash, and remove the
|
||||
# file.
|
||||
|
||||
set -e
|
||||
|
||||
exec 1>> warn-test-log
|
||||
exec 2>> warn-test-log
|
||||
|
||||
head -1 warning
|
||||
rm warning
|
Loading…
Reference in a new issue