blob: 5090b5c6bc5ea395bd7004b90d675bce295b99f2 [file] [log] [blame]
// Copyright 2021 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_util.h"
#include <algorithm>
#include <base/logging.h>
#include <base/stl_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <re2/re2.h>
#include "crash-reporter/util.h"
using base::StringPiece;
using base::StringPrintf;
namespace {
constexpr char kDefaultKernelStackSignature[] =
"kernel-UnspecifiedStackSignature";
// Byte length of maximum human readable portion of a kernel crash signature.
constexpr size_t kMaxHumanStringLength = 40;
// Time in seconds from the final kernel log message for a call stack
// to count towards the signature of the kcrash.
constexpr int kSignatureTimestampWindow = 2;
// Kernel log timestamp regular expression.
// Specify the multiline option so that ^ matches the start of lines, not just
// the start of the text.
constexpr char kTimestampRegex[] = "(?m)^<.*>\\[\\s*(\\d+\\.\\d+)\\]";
//
// These regular expressions enable to us capture the function name of
// 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" (arm32)
// "<4>[ 263.857834] pc : lkdtm_BUG+0xc/0x10" (arm64)
// 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"
// For x86_64:
// "<5>[ 1505.853254] RIP: 0010:[<ffffffff94fb0c27>] [<ffffffff94fb0c27>]
// list_del_init+0x8/0x1b" (v4.10-)
// "<4>[ 2358.194253] RIP: 0010:pick_task_fair+0x55/0x77" (v4.10+)
//
const char* const kPCFuncNameRegex[] = {
nullptr, R"( (?:PC is at |pc : )([^\+\[ ]+).*)",
R"( epc\s+:\s+\S+\s+([^\+ ]+).*)", // MIPS has an exception
// program counter
R"( EIP: \[<.*>\] ([^\+ ]+).*)", // X86 uses EIP for the
// program counter
R"( RIP: [[:xdigit:]]{4}:(?:\[<[[:xdigit:]]+>\] \[<[[:xdigit:]]+>\] )?)"
R"(([^\+ ]+)\+0x.*)", // X86_64 uses RIP
};
static_assert(base::size(kPCFuncNameRegex) == kernel_util::kArchCount,
"Missing Arch PC func_name RegExp");
void ProcessStackTrace(re2::StringPiece kernel_dump,
unsigned* hash,
float* last_stack_timestamp,
bool* is_watchdog_crash) {
RE2 line_re("(.+)");
RE2::Options opt;
opt.set_case_sensitive(false);
RE2 stack_trace_start_re(
std::string(kTimestampRegex) + " (Call Trace|Backtrace):$", opt);
// 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) (arm32 older)
// <4>[ 263.956936] lkdtm_do_action+0x24/0x40 (arm64 / arm32 newer)
//
// For MIPS:
// <5>[ 3378.656000] [<804010f0>] lkdtm_do_action+0x68/0x3f8
//
// For X86:
// <4>[ 6066.849504] [<7937bcee>] ? function_name+0x66/0x6c
// <4>[ 2358.194379] __schedule+0x83f/0xf92 (newer) like arm64 above
//
RE2 stack_entry_re(
std::string(kTimestampRegex) +
R"(\s+(?:\[<[[:xdigit:]]+>\])?)" // Matches " [<7937bcee>]" (if any)
R"(([\s?(]+))" // Matches " ? (" (ARM) or " ? " (X86)
R"(([^\+ )]+))"); // 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 (RE2::FindAndConsume(&kernel_dump, line_re, &line)) {
std::string certainty;
std::string function_name;
if (RE2::PartialMatch(line, stack_trace_start_re, last_stack_timestamp)) {
previous_hashable = hashable;
hashable.clear();
is_watchdog = false;
} else if (RE2::PartialMatch(line, stack_entry_re, last_stack_timestamp,
&certainty, &function_name)) {
bool is_certain = certainty.find('?') == std::string::npos;
// 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 = util::HashString(StringPiece(hashable));
*is_watchdog_crash = is_watchdog;
}
bool FindCrashingFunction(re2::StringPiece kernel_dump,
float stack_trace_timestamp,
kernel_util::ArchKind arch,
std::string* crashing_function) {
float timestamp = 0;
// Use the correct regex for this architecture.
if (kPCFuncNameRegex[arch] == nullptr) {
LOG(WARNING) << "PC func_name RegExp is not defined for this architecture";
return false;
}
RE2 func_re(std::string(kTimestampRegex) + kPCFuncNameRegex[arch]);
while (RE2::FindAndConsume(&kernel_dump, func_re, &timestamp,
crashing_function)) {
}
if (timestamp == 0) {
LOG(WARNING) << "Found no crashing function";
return false;
}
if (stack_trace_timestamp != 0 &&
abs(static_cast<int>(stack_trace_timestamp - timestamp)) >
kSignatureTimestampWindow) {
LOG(WARNING) << "Found crashing function but not within window";
return false;
}
return true;
}
bool FindPanicMessage(re2::StringPiece kernel_dump,
std::string* panic_message) {
// Match lines such as the following and grab out "Fatal exception"
// <0>[ 342.841135] Kernel panic - not syncing: Fatal exception
RE2 kernel_panic_re(std::string(kTimestampRegex) +
" Kernel panic[^\\:]*\\:\\s*(.*)");
float timestamp = 0;
while (RE2::FindAndConsume(&kernel_dump, kernel_panic_re, &timestamp,
panic_message)) {
}
if (timestamp == 0) {
LOG(WARNING) << "Found no panic message";
return false;
}
return true;
}
} // namespace
namespace kernel_util {
const char kKernelExecName[] = "kernel";
ArchKind 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
}
std::string ComputeKernelStackSignature(const std::string& kernel_dump,
ArchKind arch) {
unsigned stack_hash = 0;
float last_stack_timestamp = 0;
std::string human_string;
bool is_watchdog_crash;
ProcessStackTrace(kernel_dump, &stack_hash, &last_stack_timestamp,
&is_watchdog_crash);
if (!FindCrashingFunction(kernel_dump, last_stack_timestamp, arch,
&human_string)) {
if (!FindPanicMessage(kernel_dump, &human_string)) {
LOG(WARNING) << "Found no human readable string, using empty string";
human_string.clear();
}
}
if (human_string.empty() && stack_hash == 0) {
LOG(WARNING) << "Cannot find a stack or a human readable string";
return kDefaultKernelStackSignature;
}
human_string = human_string.substr(0, kMaxHumanStringLength);
return StringPrintf("%s-%s%s-%08X", kKernelExecName,
(is_watchdog_crash ? "(HANG)-" : ""),
human_string.c_str(), stack_hash);
}
std::string BiosCrashSignature(const std::string& dump) {
const char* type = "";
if (RE2::PartialMatch(dump, RE2("PANIC in EL3")))
type = "PANIC";
else if (RE2::PartialMatch(dump, RE2("Unhandled Exception in EL3")))
type = "EXCPT";
else if (RE2::PartialMatch(dump, RE2("Unhandled Interrupt Exception in")))
type = "INTR";
std::string elr;
RE2::PartialMatch(dump, RE2("x30 =\\s+(0x[0-9a-fA-F]+)"), &elr);
return StringPrintf("bios-(%s)-%s", type, elr.c_str());
}
std::string ComputeNoCErrorSignature(const std::string& dump) {
RE2 line_re("(.+)");
re2::StringPiece dump_piece = dump;
// Match lines such as the following and grab out the type of NoC (MMSS)
// and the register contents
//
// QTISECLIB [1727120e379]MMSS_NOC ERROR: ERRLOG0_LOW = 0x00000105
//
RE2 noc_entry_re(R"(QTISECLIB \[[[:xdigit:]]+\]([a-zA-Z]+)_NOC ERROR: )"
R"(ERRLOG[0-9]_(?:(LOW|HIGH)) = ([[:xdigit:]]+))");
std::string line;
std::string hashable;
std::string noc_name;
std::string first_noc;
std::string regval;
// Look at each line of the bios log for the NOC errors and compute a hash
// of all the registers
while (RE2::FindAndConsume(&dump_piece, line_re, &line)) {
if (RE2::PartialMatch(line, noc_entry_re, &noc_name, &regval)) {
if (!hashable.empty())
hashable.append("|");
if (first_noc.empty())
first_noc = noc_name;
hashable.append(noc_name);
hashable.append("|");
hashable.append(regval);
}
}
unsigned hash = util::HashString(StringPiece(hashable));
return StringPrintf("%s-(NOC-Error)-%s-%08X", kKernelExecName,
first_noc.c_str(), hash);
}
// Watchdog reboots leave no stack trace. Generate a poor man's signature out
// of the last log line instead (minus the timestamp ended by ']').
std::string WatchdogSignature(const std::string& console_ramoops) {
StringPiece line(console_ramoops);
constexpr char kTimestampEnd[] = "] ";
size_t timestamp_end_pos = line.rfind(kTimestampEnd);
if (timestamp_end_pos != StringPiece::npos) {
line = line.substr(timestamp_end_pos + strlen(kTimestampEnd));
}
size_t newline_pos = line.find("\n");
size_t end = (newline_pos == StringPiece::npos
? StringPiece::npos
: std::min(newline_pos, kMaxHumanStringLength));
return StringPrintf("%s-(WATCHDOG)-%s-%08X", kKernelExecName,
std::string(line.substr(0, end)).c_str(),
util::HashString(line));
}
} // namespace kernel_util