| // 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 <cinttypes> |
| #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 <brillo/syslog_logging.h> |
| #include <gtest/gtest.h> |
| |
| #include "crash-reporter/test_util.h" |
| |
| using base::FilePath; |
| using base::StringPrintf; |
| using brillo::FindLog; |
| using brillo::GetLog; |
| |
| namespace { |
| |
| const int kMaxEfiParts = 100; |
| |
| } // namespace |
| |
| class KernelCollectorTest : public ::testing::Test { |
| protected: |
| void SetUpSuccessfulCollect(); |
| void SetUpSuccessfulWatchdog(const FilePath&); |
| void WatchdogOptedOutHelper(const FilePath&); |
| void WatchdogOKHelper(const FilePath&); |
| void WatchdogOnlyLastBootHelper(const FilePath&); |
| |
| const FilePath& console_ramoops_file() const { return test_console_ramoops_; } |
| const FilePath& console_ramoops_file_old() const { |
| return test_console_ramoops_old_; |
| } |
| const FilePath& eventlog_file() const { return test_eventlog_; } |
| const FilePath& bios_log_file() const { return test_bios_log_; } |
| const FilePath& kcrash_file() const { return test_kcrash_; } |
| const FilePath& efikcrash_file(int part) const { |
| return test_efikcrash_[part]; |
| } |
| const FilePath& test_crash_directory() const { return test_crash_directory_; } |
| |
| KernelCollectorMock collector_; |
| |
| private: |
| void SetUp() override { |
| EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return()); |
| |
| collector_.Initialize(false); |
| ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir()); |
| test_kcrash_ = scoped_temp_dir_.GetPath().Append("kcrash"); |
| ASSERT_TRUE(base::CreateDirectory(test_kcrash_)); |
| collector_.OverridePreservedDumpPath(test_kcrash_); |
| |
| test_console_ramoops_ = test_kcrash_.Append("console-ramoops-0"); |
| ASSERT_FALSE(base::PathExists(test_console_ramoops_)); |
| test_console_ramoops_old_ = test_kcrash_.Append("console-ramoops"); |
| ASSERT_FALSE(base::PathExists(test_console_ramoops_old_)); |
| for (int i = 0; i < kMaxEfiParts; i++) { |
| test_efikcrash_[i] = test_kcrash_.Append(StringPrintf( |
| "dmesg-efi-%" PRIu64, (9876543210 * 100 + i) * 1000 + 1)); |
| ASSERT_FALSE(base::PathExists(test_efikcrash_[i])); |
| } |
| test_kcrash_ = test_kcrash_.Append("dmesg-ramoops-0"); |
| ASSERT_FALSE(base::PathExists(test_kcrash_)); |
| |
| test_crash_directory_ = |
| scoped_temp_dir_.GetPath().Append("crash_directory"); |
| ASSERT_TRUE(base::CreateDirectory(test_crash_directory_)); |
| |
| test_eventlog_ = scoped_temp_dir_.GetPath().Append("eventlog.txt"); |
| ASSERT_FALSE(base::PathExists(test_eventlog_)); |
| collector_.OverrideEventLogPath(test_eventlog_); |
| |
| test_bios_log_ = scoped_temp_dir_.GetPath().Append("bios_log"); |
| ASSERT_FALSE(base::PathExists(test_bios_log_)); |
| collector_.OverrideBiosLogPath(test_bios_log_); |
| brillo::ClearLog(); |
| } |
| |
| FilePath test_console_ramoops_; |
| FilePath test_console_ramoops_old_; |
| FilePath test_eventlog_; |
| FilePath test_bios_log_; |
| FilePath test_kcrash_; |
| FilePath test_efikcrash_[kMaxEfiParts]; |
| FilePath test_crash_directory_; |
| base::ScopedTempDir scoped_temp_dir_; |
| }; |
| |
| TEST_F(KernelCollectorTest, ParseEfiCrashId) { |
| uint64_t test_efi_crash_id = 150989600314002; |
| EXPECT_EQ(1509896003, |
| KernelCollector::EfiCrash::GetTimestamp(test_efi_crash_id)); |
| EXPECT_EQ(14, KernelCollector::EfiCrash::GetPart(test_efi_crash_id)); |
| EXPECT_EQ(2, KernelCollector::EfiCrash::GetCrashCount(test_efi_crash_id)); |
| EXPECT_EQ(test_efi_crash_id, |
| KernelCollector::EfiCrash::GenerateId(1509896003, 14, 2)); |
| } |
| |
| TEST_F(KernelCollectorTest, GetEfiCrashType) { |
| ASSERT_FALSE(base::PathExists(efikcrash_file(1))); |
| std::string type; |
| uint64_t test_efi_crash_id; |
| sscanf(efikcrash_file(1).BaseName().value().c_str(), "%*10s%" PRIu64, |
| &test_efi_crash_id); |
| // Write header. |
| ASSERT_TRUE(test_util::CreateFile(efikcrash_file(1), "Panic#1 Part#20")); |
| KernelCollector::EfiCrash efi_crash(test_efi_crash_id, collector_); |
| ASSERT_TRUE(efi_crash.GetType(&type)); |
| EXPECT_EQ("Panic", type); |
| } |
| |
| TEST_F(KernelCollectorTest, LoadEfiCrash) { |
| int efi_part_count = kMaxEfiParts - 1; |
| std::string efi_part[kMaxEfiParts]; |
| std::string expected_dump; |
| std::string dump; |
| uint64_t test_efi_crash_id; |
| sscanf(efikcrash_file(1).BaseName().value().c_str(), "%*10s%" PRIu64, |
| &test_efi_crash_id); |
| |
| for (int i = 1; i <= efi_part_count; i++) { |
| ASSERT_FALSE(base::PathExists(efikcrash_file(i))); |
| efi_part[i] = StringPrintf("Panic#100 Part#%d\n", i); |
| for (int j = 0; j < i; j++) { |
| efi_part[i].append(StringPrintf("random blob %d\n", j)); |
| } |
| ASSERT_TRUE(test_util::CreateFile(efikcrash_file(i), efi_part[i].c_str())); |
| } |
| KernelCollector::EfiCrash efi_crash(test_efi_crash_id, collector_); |
| efi_crash.UpdateMaxPart(efi_crash.GetIdForPart(efi_part_count)); |
| ASSERT_TRUE(efi_crash.Load(&dump)); |
| |
| // Stitch parts in reverse order. |
| for (int i = efi_part_count; i > 0; i--) { |
| // Strip first line since it contains header. |
| expected_dump.append(efi_part[i], efi_part[i].find('\n') + 1, |
| std::string::npos); |
| } |
| EXPECT_EQ(expected_dump, dump); |
| } |
| |
| TEST_F(KernelCollectorTest, ComputeKernelStackSignatureBase) { |
| // Make sure the normal build architecture is detected |
| EXPECT_NE(kernel_util::kArchUnknown, collector_.arch()); |
| } |
| |
| TEST_F(KernelCollectorTest, LoadPreservedDump) { |
| ASSERT_FALSE(base::PathExists(kcrash_file())); |
| std::string dump; |
| dump.clear(); |
| |
| ASSERT_TRUE(test_util::CreateFile( |
| kcrash_file(), "CrashRecordWithoutRamoopsHeader\n<6>[ 0.078852]")); |
| ASSERT_TRUE(collector_.LoadParameters()); |
| ASSERT_TRUE(collector_.LoadPreservedDump(&dump)); |
| ASSERT_EQ("CrashRecordWithoutRamoopsHeader\n<6>[ 0.078852]", dump); |
| |
| ASSERT_TRUE(test_util::CreateFile(kcrash_file(), "====1.1\nsomething")); |
| ASSERT_TRUE(collector_.LoadParameters()); |
| ASSERT_TRUE(collector_.LoadPreservedDump(&dump)); |
| ASSERT_EQ("something", dump); |
| |
| ASSERT_TRUE( |
| test_util::CreateFile(kcrash_file(), "\x01\x02\xfe\xff random blob")); |
| ASSERT_TRUE(collector_.LoadParameters()); |
| ASSERT_FALSE(collector_.LoadPreservedDump(&dump)); |
| ASSERT_EQ("", dump); |
| |
| std::string large(1024 * 1024 + 1, 'x'); // 1MiB + 1 byte. |
| ASSERT_TRUE(test_util::CreateFile(kcrash_file(), large)); |
| ASSERT_TRUE(collector_.LoadParameters()); |
| ASSERT_FALSE(collector_.LoadPreservedDump(&dump)); |
| } |
| |
| TEST_F(KernelCollectorTest, LoadBiosLog) { |
| std::string dump; |
| dump.clear(); |
| |
| std::string bootblock_boot_1 = |
| "\n\ncoreboot-dc417eb Tue Nov 2 20:47:41 UTC 2016 bootblock starting" |
| " (log level: 7)...\n" |
| "This is boot 1 bootblock!\n" |
| "\n\ncoreboot-dc417eb Tue Nov 2 20:47:41 UTC 2016 verstage starting" |
| " (log level: 7)...\n" |
| "This is boot 1 verstage!\n"; |
| std::string romstage_boot_1 = |
| "\n\ncoreboot-e8dd2d8 Tue Mar 14 23:29:43 UTC 2017 romstage starting...\n" |
| "This is boot 1 romstage!\n" |
| "\n\ncoreboot-e8dd2d8 Tue Mar 14 23:29:43 UTC 2017 ramstage starting...\n" |
| "This is boot 1 ramstage!\n" |
| "\n\nStarting depthcharge on kevin...\n" |
| "This is boot 1 depthcharge!\n" |
| "jumping to kernel\n" |
| "Some more messages logged at runtime, maybe without terminating newline"; |
| std::string bootblock_boot_2 = |
| "\n\ncoreboot-dc417eb Tue Nov 2 20:47:41 UTC 2016 bootblock starting...\n" |
| "This is boot 2 bootblock!\n" |
| "\n\ncoreboot-dc417eb Tue Nov 2 20:47:41 UTC 2016 verstage starting...\n" |
| "This is boot 2 verstage!\n"; |
| std::string romstage_boot_2 = |
| "\n\ncoreboot-e8dd2d8 Tue Mar 14 23:29:43 UTC 2017 romstage starting...\n" |
| "This is boot 2 romstage!\n" |
| "\n\ncoreboot-e8dd2d8 Tue Mar 14 23:29:43 UTC 2017 ramstage starting...\n" |
| "This is boot 2 ramstage!\n" |
| "\n\nStarting depthcharge on kevin...\n" |
| "This is boot 2 depthcharge!\n" |
| "jumping to kernel\n" |
| "Some more messages logged at runtime, maybe without terminating newline"; |
| |
| // Normal situation of multiple boots in log. |
| ASSERT_TRUE(test_util::CreateFile( |
| bios_log_file(), |
| ("Some old lines from boot N-3\n" + // N-3 |
| bootblock_boot_2 + romstage_boot_2 + // N-2 |
| bootblock_boot_1 + romstage_boot_1 + // N-1 (the "last" boot) |
| bootblock_boot_2 + romstage_boot_2) // N ("current" boot, after crash) |
| .c_str())); |
| ASSERT_TRUE(collector_.LoadLastBootBiosLog(&dump)); |
| ASSERT_EQ(bootblock_boot_1 + romstage_boot_1, "\n" + dump); |
| |
| // Same on a board that cannot log pre-romstage. |
| ASSERT_TRUE(test_util::CreateFile( |
| bios_log_file(), |
| (romstage_boot_2 + romstage_boot_1 + romstage_boot_2).c_str())); |
| ASSERT_TRUE(collector_.LoadLastBootBiosLog(&dump)); |
| ASSERT_EQ(romstage_boot_1, "\n" + dump); |
| |
| // Logs from previous boot were lost. |
| ASSERT_TRUE(test_util::CreateFile( |
| bios_log_file(), (bootblock_boot_1 + romstage_boot_1).c_str())); |
| ASSERT_FALSE(collector_.LoadLastBootBiosLog(&dump)); |
| ASSERT_EQ("", dump); |
| |
| // No recognizable BIOS log. |
| ASSERT_TRUE(test_util::CreateFile(bios_log_file(), "random crud\n")); |
| ASSERT_FALSE(collector_.LoadLastBootBiosLog(&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")); |
| } |
| |
| TEST_F(KernelCollectorTest, EnableOK) { |
| ASSERT_TRUE(test_util::CreateFile(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")); |
| } |
| |
| TEST_F(KernelCollectorTest, CollectPreservedFileMissing) { |
| ASSERT_FALSE(collector_.Collect()); |
| ASSERT_FALSE(FindLog("Stored kcrash to ")); |
| } |
| |
| TEST_F(KernelCollectorTest, CollectBadDirectory) { |
| ASSERT_TRUE(test_util::CreateFile(kcrash_file(), "====1.1\nsomething")); |
| ASSERT_TRUE(collector_.Collect()); |
| ASSERT_TRUE(FindLog("Unable to create crash directory")) |
| << "Did not find expected error string in log: {\n" |
| << GetLog() << "}"; |
| } |
| |
| void KernelCollectorTest::SetUpSuccessfulCollect() { |
| collector_.set_crash_directory_for_test(test_crash_directory()); |
| ASSERT_TRUE(test_util::CreateFile(kcrash_file(), "====1.1\nsomething")); |
| } |
| |
| void KernelCollectorTest::SetUpSuccessfulWatchdog(const FilePath& path) { |
| collector_.set_crash_directory_for_test(test_crash_directory()); |
| ASSERT_TRUE(test_util::CreateFile( |
| eventlog_file(), |
| "112 | 2016-03-24 15:09:39 | System boot | 0\n" |
| "113 | 2016-03-24 15:11:20 | System boot | 0\n" |
| "114 | 2016-03-24 15:11:20 | Hardware watchdog reset\n")); |
| ASSERT_TRUE(test_util::CreateFile(path, "\n[ 0.0000] I can haz boot!")); |
| } |
| |
| TEST_F(KernelCollectorTest, CollectOK) { |
| SetUpSuccessfulCollect(); |
| ASSERT_TRUE(test_util::CreateFile( |
| bios_log_file(), |
| "BIOS Messages" |
| "\n\ncoreboot-dc417eb Tue Nov 2 bootblock starting...\n")); |
| ASSERT_TRUE(collector_.Collect()); |
| ASSERT_TRUE(FindLog("(handling)")); |
| static const char kNamePrefix[] = "Stored kcrash to "; |
| std::string log = brillo::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())); |
| FilePath path(filename); |
| ASSERT_TRUE(base::PathExists(path)); |
| std::string contents; |
| ASSERT_TRUE(base::ReadFileToString(path, &contents)); |
| ASSERT_EQ("something", contents); |
| // Check that BIOS log was collected as well. |
| path = path.ReplaceExtension("bios_log"); |
| ASSERT_TRUE(base::PathExists(path)); |
| ASSERT_TRUE(base::ReadFileToString(path, &contents)); |
| ASSERT_EQ("BIOS Messages", contents); |
| // Confirm that files are correctly described in .meta file. |
| path = path.ReplaceExtension("meta"); |
| ASSERT_TRUE(base::PathExists(path)); |
| ASSERT_TRUE(base::ReadFileToString(path, &contents)); |
| ASSERT_TRUE( |
| contents.find("payload=" + |
| path.ReplaceExtension("kcrash").BaseName().value()) != |
| std::string::npos); |
| ASSERT_TRUE( |
| contents.find("upload_file_bios_log=" + |
| path.ReplaceExtension("bios_log").BaseName().value()) != |
| std::string::npos); |
| } |
| |
| void KernelCollectorTest::WatchdogOKHelper(const FilePath& path) { |
| SetUpSuccessfulWatchdog(path); |
| ASSERT_TRUE(collector_.Collect()); |
| ASSERT_TRUE(FindLog("(handling)")); |
| ASSERT_TRUE(FindLog("kernel-(WATCHDOG)-I can haz")); |
| } |
| |
| TEST_F(KernelCollectorTest, BiosCrashArmOK) { |
| collector_.set_crash_directory_for_test(test_crash_directory()); |
| collector_.set_arch(kernel_util::kArchArm); |
| ASSERT_TRUE(test_util::CreateFile( |
| bios_log_file(), |
| "PANIC in EL3 at x30 = 0x00003698" |
| "\n\ncoreboot-dc417eb Tue Nov 2 bootblock starting...\n")); |
| ASSERT_TRUE(collector_.Collect()); |
| ASSERT_TRUE(FindLog("(handling)")); |
| ASSERT_TRUE(FindLog("bios-(PANIC)-0x00003698")); |
| } |
| |
| TEST_F(KernelCollectorTest, WatchdogOK) { |
| WatchdogOKHelper(console_ramoops_file()); |
| } |
| |
| TEST_F(KernelCollectorTest, WatchdogOKOld) { |
| WatchdogOKHelper(console_ramoops_file_old()); |
| } |
| |
| void KernelCollectorTest::WatchdogOnlyLastBootHelper(const FilePath& path) { |
| char next[] = "115 | 2016-03-24 15:24:27 | System boot | 0"; |
| SetUpSuccessfulWatchdog(path); |
| ASSERT_TRUE(test_util::CreateFile(eventlog_file(), next)); |
| ASSERT_FALSE(collector_.Collect()); |
| } |
| |
| TEST_F(KernelCollectorTest, WatchdogOnlyLastBoot) { |
| WatchdogOnlyLastBootHelper(console_ramoops_file()); |
| } |
| |
| TEST_F(KernelCollectorTest, WatchdogOnlyLastBootOld) { |
| WatchdogOnlyLastBootHelper(console_ramoops_file_old()); |
| } |