Skip to content
Snippets Groups Projects
Commit 5f92e26b authored by Android Build Coastguard Worker's avatar Android Build Coastguard Worker
Browse files

Snap for 13206524 from c4245755 to busytown-mac-infra-release

Change-Id: Ie047fab7d15636a65b367aa8756c5505964412ff
parents b036c23c c4245755
No related branches found
No related tags found
No related merge requests found
// 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"],
}
OWNERS 0 → 100644
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
main.cpp 0 → 100644
/*
* 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;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment