Merge branch 'rewrite-crash-reporter' into merge-crash-reporter

BUG:22773266
This commit is contained in:
Bill Yi 2015-07-28 09:32:10 -07:00
commit 5cab52906c
42 changed files with 7478 additions and 0 deletions

View file

@ -0,0 +1 @@
crash

View 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
View file

@ -0,0 +1,2 @@
set noparent
vapier@chromium.org

View 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+

View 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";

View 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_

View 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);
}

View 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',
],
},
],
}],
],
}

View 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(&timestamp, &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();
}

View 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_

View 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);
}

View 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_

View 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);
}

View 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

View 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
View 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}"

View 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>

View 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

View 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

View 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

View 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, &timestamp, 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,
&timestamp,
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;
}

View 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_

View 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();
}

View 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_

View 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"

View 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;
}

View 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_

View 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;
}

View 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();
}

View 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 "";
}

View 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_

View 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.

View 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;
}

View 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_

View 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);
}

View 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;
}

View 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_

View 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));
}

View 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();
}

View 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);
}

View 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

View 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