blob: 60418cf88f125fba68fc65fd564fe95118925824 [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 "src/developer/forensics/crash_reports/report_util.h"
#include <fuchsia/mem/cpp/fidl.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <zircon/errors.h>
#include <string>
#include "src/developer/forensics/crash_reports/constants.h"
#include "src/developer/forensics/crash_reports/errors.h"
#include "src/developer/forensics/crash_reports/report.h"
namespace forensics {
namespace crash_reports {
std::string Shorten(std::string program_name) {
// Remove leading whitespace
const size_t first_non_whitespace = program_name.find_first_not_of(" ");
if (first_non_whitespace == std::string::npos) {
return "";
}
program_name = program_name.substr(first_non_whitespace);
// Remove the "fuchsia-pkg://" prefix if present.
const std::string fuchsia_pkg_prefix("fuchsia-pkg://");
if (program_name.find(fuchsia_pkg_prefix) == 0) {
program_name.erase(/*pos=*/0u, /*len=*/fuchsia_pkg_prefix.size());
}
std::replace(program_name.begin(), program_name.end(), '/', ':');
// Remove all repeating ':'.
for (size_t idx = program_name.find("::"); idx != std::string::npos;
idx = program_name.find("::")) {
program_name.erase(idx, 1);
}
// Remove trailing white space
const size_t last_non_whitespace = program_name.find_last_not_of(" ");
return (last_non_whitespace == std::string::npos)
? ""
: program_name.substr(0, last_non_whitespace + 1);
}
std::string Logname(std::string name) {
// Normalize |name|.
name = Shorten(name);
// Find the last colon in |name|.
const size_t last_colon = name.find_last_of(":");
if (last_colon == std::string::npos) {
return name;
}
// Determine if there's a ".cmx" suffix in |name|.
const size_t cmx_suffix = name.find_last_of(".cmx");
if (cmx_suffix == std::string::npos || cmx_suffix <= last_colon) {
return name;
}
// Extract the string between the last colon and the ".cmx" suffix.
return name.substr(last_colon + 1, cmx_suffix - sizeof(".cmx") - last_colon + 1);
}
namespace {
// The crash server expects a specific key for client-provided program uptimes.
const char kProgramUptimeMillisKey[] = "ptime";
// The crash server expects a specific key for client-provided event keys.
const char kEventIdKey[] = "comments";
// The crash server expects a specific key for client-provided crash signatures.
const char kCrashSignatureKey[] = "signature";
// The crash server expects specific key and values for some annotations and attachments for Dart.
const char kDartTypeKey[] = "type";
const char kDartTypeValue[] = "DartError";
const char kDartExceptionMessageKey[] = "error_message";
const char kDartExceptionRuntimeTypeKey[] = "error_runtime_type";
const char kDartExceptionStackTraceKey[] = "DartError";
// The crash server expects a specific key for client-provided report time.
constexpr char kReportTimeMillis[] = "reportTimeMillis";
// The crash server expects a specific key for client-provided information about whether a crash is
// fatal.
const char kIsFatalKey[] = "isFatal";
void ExtractAnnotationsAndAttachments(fuchsia::feedback::CrashReport report,
AnnotationMap* annotations,
std::map<std::string, fuchsia::mem::Buffer>* attachments,
std::optional<fuchsia::mem::Buffer>* minidump,
bool* should_process) {
// Default annotations common to all crash reports.
if (report.has_annotations()) {
annotations->Set(report.annotations());
}
if (report.has_program_uptime()) {
annotations->Set(kProgramUptimeMillisKey, zx::duration(report.program_uptime()).to_msecs());
}
if (report.has_event_id()) {
annotations->Set(kEventIdKey, report.event_id());
}
if (report.has_crash_signature()) {
annotations->Set(kCrashSignatureKey, report.crash_signature());
}
if (report.has_is_fatal()) {
annotations->Set(kIsFatalKey, report.is_fatal());
}
// Generic-specific annotations.
if (report.has_specific_report() && report.specific_report().is_generic()) {
const auto& generic_report = report.specific_report().generic();
if (generic_report.has_crash_signature()) {
annotations->Set(kCrashSignatureKey, generic_report.crash_signature());
}
}
// Dart-specific annotations.
if (report.has_specific_report() && report.specific_report().is_dart()) {
annotations->Set(kDartTypeKey, kDartTypeValue);
const auto& dart_report = report.specific_report().dart();
if (dart_report.has_exception_type()) {
annotations->Set(kDartExceptionRuntimeTypeKey, dart_report.exception_type());
} else {
FX_LOGS(WARNING) << "no Dart exception type to attach to Crashpad report";
}
if (dart_report.has_exception_message()) {
annotations->Set(kDartExceptionMessageKey, dart_report.exception_message());
} else {
FX_LOGS(WARNING) << "no Dart exception message to attach to Crashpad report";
}
}
// Native-specific annotations.
// TODO(fxbug.dev/6564): add module annotations from minidump.
// Default attachments common to all crash reports.
if (report.has_attachments()) {
for (auto& attachment : *(report.mutable_attachments())) {
(*attachments)[attachment.key] = std::move(attachment.value);
}
}
// Native-specific attachment (minidump).
if (report.has_specific_report() && report.specific_report().is_native()) {
auto& native_report = report.mutable_specific_report()->native();
if (native_report.has_minidump()) {
*minidump = std::move(*native_report.mutable_minidump());
*should_process = true;
} else {
FX_LOGS(WARNING) << "no minidump to attach to Crashpad report";
annotations->Set(kCrashSignatureKey, "fuchsia-no-minidump");
}
}
// Dart-specific attachment (text stack trace).
if (report.has_specific_report() && report.specific_report().is_dart()) {
auto& dart_report = report.mutable_specific_report()->dart();
if (dart_report.has_exception_stack_trace()) {
(*attachments)[kDartExceptionStackTraceKey] =
std::move(*dart_report.mutable_exception_stack_trace());
*should_process = true;
} else {
FX_LOGS(WARNING) << "no Dart exception stack trace to attach to Crashpad report";
annotations->Set(kCrashSignatureKey, "fuchsia-no-dart-stack-trace");
}
}
}
void AddSnapshotAnnotations(const SnapshotUuid& snapshot_uuid, const Snapshot& snapshot,
AnnotationMap* annotations) {
if (const auto snapshot_annotations = snapshot.LockAnnotations(); snapshot_annotations) {
annotations->Set(*snapshot_annotations);
}
}
void AddCrashServerAnnotations(const std::string& program_name,
const std::optional<zx::time_utc>& current_time,
const ::fit::result<std::string, Error>& device_id,
const Product& product, const bool should_process,
AnnotationMap* annotations) {
// Product.
annotations->Set("product", product.name)
.Set("version", product.version)
.Set("channel", product.channel);
// Program.
// We use ptype to benefit from Chrome's "Process type" handling in the crash server UI.
annotations->Set("ptype", program_name);
// We set the report time only if we were able to get an accurate one.
if (current_time.has_value()) {
annotations->Set(kReportTimeMillis, current_time.value().get() / zx::msec(1).get());
} else {
annotations->Set("debug.report-time.set", false);
}
// We set the device's global unique identifier only if the device has one.
if (device_id.is_ok()) {
annotations->Set("guid", device_id.value());
} else {
annotations->Set("debug.guid.set", false).Set("debug.device-id.error", device_id.error());
}
// Not all reports need to be processed by the crash server.
// Typically only reports with a minidump or a Dart stack trace file need to be processed.
annotations->Set("should_process", should_process);
}
} // namespace
std::optional<Report> MakeReport(fuchsia::feedback::CrashReport report, const ReportId report_id,
const SnapshotUuid& snapshot_uuid, const Snapshot& snapshot,
const std::optional<zx::time_utc>& current_time,
const ::fit::result<std::string, Error>& device_id,
const AnnotationMap& default_annotations, const Product& product,
const bool is_hourly_report) {
const std::string program_name = report.program_name();
const std::string shortname = Shorten(program_name);
AnnotationMap annotations = default_annotations;
std::map<std::string, fuchsia::mem::Buffer> attachments;
std::optional<fuchsia::mem::Buffer> minidump;
bool should_process = false;
// Optional annotations and attachments filled by the client.
ExtractAnnotationsAndAttachments(std::move(report), &annotations, &attachments, &minidump,
&should_process);
// Snapshot annotations specific to this crash report.
AddSnapshotAnnotations(snapshot_uuid, snapshot, &annotations);
// Crash server annotations common to all crash reports.
AddCrashServerAnnotations(program_name, current_time, device_id, product, should_process,
&annotations);
return Report::MakeReport(report_id, shortname, annotations, std::move(attachments),
snapshot_uuid, std::move(minidump), is_hourly_report);
}
} // namespace crash_reports
} // namespace forensics