Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • clo/la/platform/system/acpi/bert_collector
1 result
Show changes
Commits on Source (6)
  • Rob Barnes's avatar
    bert_collector: Add ACPI bert_collector implementation · 473ca751
    Rob Barnes authored
    bert_collector collects ACPI BERT table and data and sends it to
    DropBox. When an error is detected by firmware during boot, a BERT
    table and data sysfs file will be present at
    /sys/firmware/acpi/tables/BERT and /sys/firmware/acpi/tables/data/BERT.
    bert_collector does a basic validity check before sending to DropBox.
    
    bert_collector was ported from the ChromiumOS version of the same name.
    Reference: https://chromium.googlesource.com/chromiumos/platform2/+/31746c0d/crash-reporter/bert_collector.cc
    
    Bug: 357626966
    Test: atest bert_collector_test
    Change-Id: Ibc808704bc6c4e80caafeac2c76f771a35777a96
    473ca751
  • Rob Barnes's avatar
    bert_collector: pass report by reference · 1923db25
    Rob Barnes authored
    Pass report as a const reference so it's not copied.
    
    Bug: None
    Test: atest bert_collector_test
    Change-Id: I95652105b495a7fa5fa3ead4a019f5a95a7ae433
    1923db25
  • Rob Barnes's avatar
    bert_collector: Revise report format · e3637203
    Rob Barnes authored
    Update the bert_collector to align with the latest schema format.
    Entries are delimited by ===<filename>===.
    
    Added and improved tests related to report format.
    
    Bug: 375241111
    Test: Bert report collected in expected format
    Change-Id: I35fefa8e34afc9e6b9bea86b475c8e9016524bf9
    e3637203
  • Rob Barnes's avatar
    bert_collector: Add init rc script · ce072021
    Rob Barnes authored
    Add init rc script to start bert_collector at boot. Runs at late_start
    since timing isn't critical.
    
    Bug: 357626966
    Test: atest bert_collector_test
    Test: dropbox report generated after boot
    Change-Id: If11f6baa9a60e1efe76a5f069c0ab809fe8a7a62
    ce072021
  • Xin Li's avatar
    Merge 25Q1 (ab/12770256) to aosp-main-future · 43f030de
    Xin Li authored
    Bug: 385190204
    Merged-In: If11f6baa9a60e1efe76a5f069c0ab809fe8a7a62
    Change-Id: Ia204385363a835b1ecb75d88ebc72fed7e82b147
    43f030de
  • Xin Li's avatar
    Merge 25Q1 (ab/BP1A.250305.020) to AOSP main · 1ec63e18
    Xin Li authored
    Bug: 385190204
    Merged-In: I988c95fbcfd51da2466ee28b0e9093b18c854ce5
    Change-Id: I0986272050a1a1d383a2bdeac5012febe95fb0c4
    1ec63e18
// Copyright 2024, The ChromiumOS Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
cc_defaults {
name: "bert_collector_defaults",
cflags: [
"-Wall",
"-Wextra",
"-Werror",
"-Wno-unused-argument",
"-Wno-unused-function",
"-Wno-nullability-completeness",
"-Os",
],
shared_libs: [
"libbase",
"libbrillo",
"liblog",
"libservices",
"libutils",
],
}
cc_binary {
name: "bert_collector",
defaults: [
"bert_collector_defaults",
],
srcs: [
"main.cpp",
"bert_collector.cpp",
],
init_rc: ["bert_collector.rc"],
}
cc_test {
name: "bert_collector_test",
defaults: [
"bert_collector_defaults",
],
srcs: [
"bert_collector_test.cpp",
],
test_suites: ["general-tests"],
}
hirthanan@google.com
jongahn@google.com
robbarnes@google.com
timvp@google.com
[Builtin Hooks]
bpfmt = true
clang_format = true
[Builtin Hooks Options]
clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
/*
* Copyright 2024, The ChromiumOS Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* bert_collector collects ACPI BERT table and data if it exists and sends it to
* DropBoxManager. The BERT table is only created when there is a critical error
* detected by the BIOS during boot.
*
* This implementation was adapted for Android from the ChromOS version:
* https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform2/crash-reporter/bert_collector.cc
*/
#include "bert_collector.h"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android/os/DropBoxManager.h>
#include <brillo/data_encoding.h>
#include <regex>
#if defined(__BIG_ENDIAN__) || \
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
#error "bert_collector does not support big endian"
#endif
namespace bert_collector {
// Validate BERT table signature, length and region length.
static bool BertCheckTable(const struct acpi_table_bert &bert_table) {
if (memcmp(bert_table.signature, ACPI_SIG_BERT, ACPI_NAME_SIZE) != 0)
return false;
if (bert_table.length != sizeof(struct acpi_table_bert))
return false;
if (bert_table.region_length != 0 &&
bert_table.region_length < ACPI_BERT_REGION_STRUCT_SIZE)
return false;
return true;
}
// Read BERT table and data files.
static bool BertRead(const std::filesystem::path &bert_table_path,
const std::filesystem::path &bert_data_path,
struct acpi_table_bert &bert_table,
std::string &bert_table_contents,
std::string &bert_data_contents) {
// Read BERT table file.
if (!android::base::ReadFileToString(bert_table_path, &bert_table_contents)) {
LOG(ERROR) << "Failed to read BERT table at " << bert_table_path;
return false;
}
if (bert_table_contents.length() != sizeof(bert_table)) {
LOG(ERROR) << std::format("BERT table length is {}, expected {}",
bert_table_contents.length(), sizeof(bert_table));
return false;
}
memcpy(&bert_table, bert_table_contents.data(), sizeof(bert_table));
if (!BertCheckTable(bert_table)) {
LOG(ERROR) << "Bad data in BERT table";
return false;
}
// Read BERT data file.
if (!android::base::ReadFileToString(bert_data_path, &bert_data_contents)) {
LOG(ERROR) << "Failed to read BERT data at " << bert_data_path;
return false;
}
return true;
}
// Default value for unknown header values
const std::string kUnknown = "UNKNOWN";
static std::string FormatHeaderLine(const std::string &key,
const std::string &value) {
return android::base::Trim(key) + ": " + android::base::Trim(value) + "\n";
}
static std::string GetOnePropertyHeader(const std::string &header_key,
const std::string &property_name) {
return FormatHeaderLine(header_key,
android::base::GetProperty(property_name, kUnknown));
};
static std::string GetReportHeader() {
std::string ret = GetOnePropertyHeader("Build", "ro.build.fingerprint");
ret += GetOnePropertyHeader("Hardware", "ro.product.device");
ret += GetOnePropertyHeader("Revision", "ro.revision");
ret += FormatHeaderLine("Subsystem", kSubsystem);
std::string version;
if (!android::base::ReadFileToString("/proc/version", &version))
version = kUnknown;
ret += FormatHeaderLine("Kernel", version);
ret += "\n"; // report header ends with extra newline
return ret;
}
const std::string kDelimiterPrefix = "===";
const std::string kDelimiterSuffix = "===";
const std::string kEscapedDelimiterPrefix = "====";
static std::string EscapeDelmiter(const std::string &text) {
std::regex re("^" + kDelimiterPrefix, std::regex::multiline);
return std::regex_replace(text, re, kEscapedDelimiterPrefix);
}
static void AppendReportEntry(std::string &report, const std::string &name,
const std::string &value) {
report += std::format("{}{}{}\n", kDelimiterPrefix, name, kDelimiterSuffix);
report += std::format("{}\n", EscapeDelmiter(value));
}
bool CollectReport(std::string &report,
const std::filesystem::path &bert_table_path,
const std::filesystem::path &bert_data_path) {
if (!std::filesystem::exists(bert_table_path)) {
LOG(INFO) << "No BERT table found";
return false;
}
if (!std::filesystem::exists(bert_data_path)) {
LOG(ERROR) << "BERT data missing";
return false;
}
LOG(INFO) << "BERT error from previous boot (handling)";
report = GetReportHeader();
std::string bert_table_contents;
std::string bert_data_contents;
struct acpi_table_bert bert_table;
if (!BertRead(bert_table_path, bert_data_path, bert_table,
bert_table_contents, bert_data_contents)) {
return false;
}
const std::string bert_table_contents_encoded =
brillo::data_encoding::Base64Encode(bert_table_contents);
AppendReportEntry(report, bert_table_path, bert_table_contents_encoded);
const std::string bert_data_contents_encoded =
brillo::data_encoding::Base64Encode(bert_data_contents);
AppendReportEntry(report, bert_data_path, bert_data_contents_encoded);
return true;
}
bool DumpReport(const std::string &report, const android::String16 tag) {
android::sp<android::os::DropBoxManager> dropbox(
new android::os::DropBoxManager());
android::binder::Status status = dropbox->addText(tag, report);
if (!status.isOk()) {
LOG(ERROR) << "Failed to write " << tag
<< " to DropBox: " << status.exceptionMessage();
return false;
}
LOG(INFO) << tag << " sent to DropBox";
return true;
}
} // namespace bert_collector
/*
* Copyright 2024, The ChromiumOS Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <sys/types.h>
#include <utils/String16.h>
#include <filesystem>
namespace bert_collector {
const android::String16 kBertDropBoxTag("DesktopFirmwareCrash");
const std::string kSubsystem("bert");
const std::filesystem::path kAcpiTablesPath = "/sys/firmware/acpi/tables";
const std::filesystem::path kBertTablePath = kAcpiTablesPath / "BERT";
const std::filesystem::path kBertDataPath = kAcpiTablesPath / "data" / "BERT";
#define ACPI_NAME_SIZE 4
#define ACPI_SIG_BERT "BERT"
#define ACPI_BERT_REGION_STRUCT_SIZE (5 * sizeof(uint32_t))
// BERT (Boot Error Record Table) as defined in ACPI spec, APEI chapter at
// http://www.uefi.org/sites/default/files/resources/ACPI%206_2_A_Sept29.pdf.
struct acpi_table_bert {
char signature[ACPI_NAME_SIZE];
uint32_t length;
uint8_t revision;
uint8_t checksum;
char oem_id[6];
char oem_table_id[8];
uint32_t oem_revision;
char asl_compiler_id[ACPI_NAME_SIZE];
uint32_t asl_compiler_revision;
uint32_t region_length;
uint64_t address;
};
// Collect BERT table and data and encode for dumping.
// Return false if bert table is unable to be generated for any reason.
bool CollectReport(
std::string &report,
const std::filesystem::path &bert_table_path = kBertTablePath,
const std::filesystem::path &bert_data_path = kBertDataPath);
// Dump BERT report to DropBox
bool DumpReport(const std::string &report,
const android::String16 tag = kBertDropBoxTag);
} // namespace bert_collector
# Copyright (C) 2024 The ChromiumOS Authors
service bert_collector /system/bin/bert_collector
user system
group system
class late_start
oneshot
/*
* Copyright 2024, The ChromiumOS Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Including file under test so internal functions can remain static.
// This technique is described in
// https://google.github.io/googletest/advanced.html#testing-private-code
#include "bert_collector.cpp"
#include <brillo/strings/string_utils.h>
#include <gtest/gtest.h>
using std::string_literals::operator""s;
namespace bert_collector {
class BERTCollectorTest : public ::testing::Test {
protected:
const TemporaryDir tmp_dir_;
std::filesystem::path test_bert_table_path_;
std::filesystem::path test_bert_data_path_;
enum BertTestDataType {
BAD_DATA,
CONSTRUCTED_DATA,
ORGANIC_DATA,
};
void PrepareBertDataTest(enum BertTestDataType data_type) {
std::string test_bert_table;
std::string test_bert_data;
switch (data_type) {
case BAD_DATA:
test_bert_table = "Bad Bert Table";
test_bert_data = "Bad Bert Data";
break;
case ORGANIC_DATA:
test_bert_table =
"\x42\x45\x52\x54\x30\x00\x00\x00\x01\x22\x43\x4f\x52\x45\x76\x34"s
"\x43\x4f\x52\x45\x42\x4f\x4f\x54\x00\x00\x00\x00\x43\x4f\x52\x45"s
"\x28\x06\x23\x20\xf0\x00\x00\x00\x00\xc0\x90\x76\x00\x00\x00\x00"s;
test_bert_data =
"\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdc\x00\x00\x00"s
"\x01\x00\x00\x00\x96\x2a\x21\x81\xed\x09\x96\x49\x94\x71\x8d\x72"s
"\x9c\x8e\x69\xed\x01\x00\x00\x00\x00\x03\x04\x00\x20\x00\x00\x00"s
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"s
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"s
"\x00\x00\x00\x00\x47\x41\x14\x01\x12\x09\x24\x20\x02\x02\x00\x00"s
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\xf3\x87\x8f"s
"\x98\xc9\x9e\x4d\xa0\xc4\x60\x65\x51\x8c\x4f\x6d\x96\x2a\x21\x81"s
"\xed\x09\x96\x49\x94\x71\x8d\x72\x9c\x8e\x69\xed\x01\x00\x00\x00"s
"\x00\x03\x04\x00\x2c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"s
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"s
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x47\x41\x14\x01"s
"\x12\x09\x24\x20\x02\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"s
"\x00\x00\x00\x00\x11\xf3\x87\x8f\x98\xc9\x9e\x4d\xa0\xc4\x60\x65"s
"\x51\x8c\x4f\x6d\x48\x65\x6c\x6c\x6f\x20\x45\x72\x72\x6f\x72\x21"s;
break;
case CONSTRUCTED_DATA:
// TODO(b/368163368): Construct valid BERT data
test_bert_data = "TODO: Construct BERT data";
const struct acpi_table_bert constructed_bert_table = {
.signature = {'B', 'E', 'R', 'T'},
.length = sizeof(constructed_bert_table),
.revision = 'A',
.checksum = 'D',
.oem_id = "OEMID",
.oem_table_id = "TABLEID",
.oem_revision = 0xFFFFFFFF,
.asl_compiler_id = "ACP",
.asl_compiler_revision = 0xEEEEEEEE,
.region_length = (uint32_t)test_bert_data.size(),
.address = 0x000000000001234,
};
test_bert_table =
std::string(reinterpret_cast<const char *>(&constructed_bert_table),
sizeof(constructed_bert_table));
break;
}
ASSERT_TRUE(android::base::WriteStringToFile(test_bert_table,
test_bert_table_path_));
ASSERT_TRUE(
android::base::WriteStringToFile(test_bert_data, test_bert_data_path_));
}
public:
void SetUp() override {
std::filesystem::path tmp_dir_path(tmp_dir_.path);
ASSERT_TRUE(std::filesystem::create_directory(tmp_dir_path / "data"));
test_bert_table_path_ = std::filesystem::path(tmp_dir_path / "BERT");
test_bert_data_path_ = tmp_dir_path / "data" / "BERT";
}
};
TEST_F(BERTCollectorTest, TestNoBERTData) {
std::string report;
ASSERT_FALSE(
CollectReport(report, test_bert_table_path_, test_bert_data_path_));
}
TEST_F(BERTCollectorTest, TestBadBERTData) {
std::string report;
PrepareBertDataTest(BAD_DATA);
ASSERT_FALSE(
CollectReport(report, test_bert_table_path_, test_bert_data_path_));
}
TEST_F(BERTCollectorTest, TestConstructedBERTData) {
std::string bert_table_contents;
std::string bert_data_contents;
struct bert_collector::acpi_table_bert bert_table;
PrepareBertDataTest(CONSTRUCTED_DATA);
ASSERT_TRUE(BertRead(test_bert_table_path_, test_bert_data_path_, bert_table,
bert_table_contents, bert_data_contents));
ASSERT_EQ(bert_table.length, sizeof(bert_table));
ASSERT_EQ(bert_table.length, bert_table_contents.size());
ASSERT_EQ(bert_table.region_length, bert_data_contents.size());
}
TEST_F(BERTCollectorTest, TestOrganicBERTData) {
std::string bert_table_contents;
std::string bert_data_contents;
struct bert_collector::acpi_table_bert bert_table;
PrepareBertDataTest(ORGANIC_DATA);
ASSERT_TRUE(BertRead(test_bert_table_path_, test_bert_data_path_, bert_table,
bert_table_contents, bert_data_contents));
ASSERT_EQ(bert_table.length, sizeof(bert_table));
ASSERT_EQ(bert_table.length, bert_table_contents.size());
ASSERT_EQ(bert_table.region_length, bert_data_contents.size());
}
TEST_F(BERTCollectorTest, TestEscapeDelimiter) {
std::string text = "foo===bar\n"
"===foobar===\n"
"==foobar\n"
"====foobar\n";
std::string expected = "foo===bar\n"
"====foobar===\n"
"==foobar\n"
"=====foobar\n";
ASSERT_EQ(expected, EscapeDelmiter(text));
}
TEST_F(BERTCollectorTest, TestBERTReportHeader) {
std::string header = GetReportHeader();
ASSERT_FALSE(header.empty());
ASSERT_TRUE(header.ends_with("\n\n"));
bool found_subsystem = false;
for (std::string line : brillo::string_utils::Split(header, "\n")) {
std::cout << "Header Line: " << std::endl << line << std::endl;
std::regex header_line_re("^([^:]+): (.*)$", std::regex::multiline);
std::smatch header_line_match;
ASSERT_TRUE(std::regex_search(line, header_line_match, header_line_re));
ASSERT_EQ(header_line_match.size(), 3ul);
if (header_line_match[1] == "Subsystem") {
ASSERT_EQ(header_line_match[2], kSubsystem);
found_subsystem = true;
}
}
ASSERT_TRUE(found_subsystem);
}
TEST_F(BERTCollectorTest, TestDecodeReport) {
std::string bert_table_contents;
std::string bert_data_contents;
struct bert_collector::acpi_table_bert bert_table;
PrepareBertDataTest(ORGANIC_DATA);
ASSERT_TRUE(BertRead(test_bert_table_path_, test_bert_data_path_, bert_table,
bert_table_contents, bert_data_contents));
std::string report;
ASSERT_TRUE(
CollectReport(report, test_bert_table_path_, test_bert_data_path_));
std::pair<std::string, std::string> header_body =
brillo::string_utils::SplitAtFirst(report, "\n\n");
std::string body = header_body.second;
// Extract entries from report
std::regex entry_re("===([^=]+)===\n((?:\n|.)*?)(?=(?:\n===[^=]|$))");
std::smatch entry_match;
// Decode BERT table from report
ASSERT_TRUE(std::regex_search(body, entry_match, entry_re));
ASSERT_EQ(entry_match.size(), 3ul);
std::string table_entry_name = entry_match[1].str();
std::string table_encoded = entry_match[2].str();
ASSERT_EQ(table_entry_name, test_bert_table_path_);
std::string table_decoded;
ASSERT_TRUE(
brillo::data_encoding::Base64Decode(table_encoded, &table_decoded));
ASSERT_EQ(table_decoded, bert_table_contents);
// Decode BERT data from report
body = entry_match.suffix();
ASSERT_TRUE(std::regex_search(body, entry_match, entry_re));
ASSERT_EQ(entry_match.size(), 3ul);
std::string data_entry_name = entry_match[1].str();
std::string data_encoded = entry_match[2].str();
ASSERT_EQ(data_entry_name, test_bert_data_path_);
std::string data_decoded;
ASSERT_TRUE(brillo::data_encoding::Base64Decode(data_encoded, &data_decoded));
ASSERT_EQ(data_decoded, bert_data_contents);
}
} // namespace bert_collector
/*
* Copyright 2024, The ChromiumOS Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "bert_collector.h"
int main() {
std::string report;
if (!bert_collector::CollectReport(report)) {
return 1;
}
if (!bert_collector::DumpReport(report)) {
return 1;
}
return 0;
}