blob: 9f752e3a12dbaf33d873419731f7d5698de2f906 [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 <variant>
#include "src/developer/forensics/crash_reports/constants.h"
#include "src/developer/forensics/crash_reports/dart_module_parser.h"
#include "src/developer/forensics/crash_reports/errors.h"
#include "src/developer/forensics/crash_reports/report.h"
#include "src/lib/fsl/vmo/strings.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;
}
// Remove everything leading up to the last colon.
name.erase(name.begin(), name.begin() + last_colon + 1);
// Determine if there's a ".cm" suffix in |name|.
const size_t cm_suffix = name.rfind(".cm");
if (cm_suffix == std::string::npos) {
return name;
}
// Erase the ".cm" and everything after it.
name.erase(name.begin() + cm_suffix, name.end());
return name;
}
namespace {
// The crash server expects certain keys from the client for certain fields.
const char kProgramUptimeMillisKey[] = "ptime";
const char kEventIdKey[] = "comments";
const char kCrashSignatureKey[] = "signature";
const char kDartTypeKey[] = "type";
const char kDartTypeValue[] = "DartError";
const char kDartExceptionMessageKey[] = "error_message";
const char kDartExceptionRuntimeTypeKey[] = "error_runtime_type";
const char kDartExceptionStackTraceKey[] = "DartError";
const char kDartModulesKey[] = "dart_modules";
const char kReportTimeMillis[] = "reportTimeMillis";
const char kIsFatalKey[] = "isFatal";
const char kProcessNameKey[] = "crash.process.name";
const char kThreadNameKey[] = "crash.thread.name";
// Extra keys that the crash server does *not* have a dependency on.
const char kProcessKoidKey[] = "crash.process.koid";
const char kThreadKoidKey[] = "crash.thread.koid";
std::pair<bool, std::optional<std::string>> ParseDartModules(
const fuchsia::mem::Buffer& stack_trace) {
if (!stack_trace.vmo.is_valid()) {
return {false, std::nullopt};
}
std::string text_stack_trace(stack_trace.size, '\0');
if (!fsl::StringFromVmo(stack_trace, &text_stack_trace)) {
FX_LOGS(ERROR) << "Failed to read Dart stack trace vmo";
return {false, std::nullopt};
}
return ParseDartModulesFromStackTrace(text_stack_trace);
}
void ExtractAnnotationsAndAttachments(fuchsia::feedback::CrashReport report,
AnnotationMap* annotations,
std::map<std::string, fuchsia::mem::Buffer>* attachments,
std::optional<fuchsia::mem::Buffer>* minidump) {
// 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());
}
// 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";
}
if (dart_report.has_exception_stack_trace()) {
if (const auto [is_unsymbolicated, dart_modules] =
ParseDartModules(dart_report.exception_stack_trace());
dart_modules.has_value()) {
annotations->Set(kDartModulesKey, *dart_modules);
} else if (is_unsymbolicated) {
FX_LOGS(WARNING) << "Failed to parse Dart modules from stack trace";
}
}
}
// Native-specific annotations.
if (report.has_specific_report() && report.specific_report().is_native()) {
const auto& native_report = report.specific_report().native();
if (native_report.has_process_name()) {
annotations->Set(kProcessNameKey, native_report.process_name());
}
if (native_report.has_process_koid()) {
annotations->Set(kProcessKoidKey, native_report.process_koid());
}
if (native_report.has_thread_name()) {
annotations->Set(kThreadNameKey, native_report.thread_name());
}
if (native_report.has_thread_koid()) {
annotations->Set(kThreadKoidKey, native_report.thread_koid());
}
// 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());
} else {
FX_LOGS(WARNING) << "no minidump to attach to Crashpad report";
// We don't want to overwrite the client-provided signature.
if (!report.has_crash_signature()) {
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());
} 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) {
// The underlying snapshot may have been garbage collected or its collection timed out
// (possibly due to shutdown). Add the annotations from the snapshot manager could collect itself
// and annotations indicating why the annotations and archive collected from
// fuchsia.feedback.DataProvider aren't present.
//
// Snapshots will not be missing due to reasons like not being persisted or not having a valid
// snapshot uuid because neither can occur without a report entering the store and this flow is
// triggered before the store is used.
if (std::holds_alternative<MissingSnapshot>(snapshot)) {
const auto& s = std::get<MissingSnapshot>(snapshot);
annotations->Set(s.Annotations());
annotations->Set(s.PresenceAnnotations());
return;
}
// The underlying snapshot was successfully collected and not all of its data was dropped by the
// snapshot manager (due to garbage collection). Add the annotations collected from
// fuchsia.feedback.DataProvider and any annotations about why the collected archive may be
// missing.
const auto& s = std::get<ManagedSnapshot>(snapshot);
annotations->Set(s.Annotations());
annotations->Set(s.PresenceAnnotations());
}
void AddCrashServerAnnotations(const std::string& program_name,
const std::optional<timekeeper::time_utc>& current_time,
const ErrorOr<std::string>& device_id, const Product& product,
AnnotationMap* annotations) {
// Product.
annotations->Set("product", product.name)
.Set("version", product.version)
.Set("channel", product.channel);
// Program.
// TODO(fxbug.dev/57502): for historical reasons, we used ptype to benefit from Chrome's
// "Process type" handling in the crash server UI. Remove once the UI can fallback on "Program".
annotations->Set("ptype", program_name);
annotations->Set("program", 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.HasValue()) {
annotations->Set("guid", device_id.Value());
} else {
annotations->Set("debug.guid.set", false).Set("debug.device-id.error", device_id.Error());
}
}
} // namespace
std::optional<Report> MakeReport(fuchsia::feedback::CrashReport report, const ReportId report_id,
const SnapshotUuid& snapshot_uuid, const Snapshot& snapshot,
const std::optional<timekeeper::time_utc>& current_time,
const ErrorOr<std::string>& 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;
// Optional annotations and attachments filled by the client.
ExtractAnnotationsAndAttachments(std::move(report), &annotations, &attachments, &minidump);
// 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, &annotations);
return Report::MakeReport(report_id, shortname, annotations, std::move(attachments),
snapshot_uuid, std::move(minidump), is_hourly_report);
}
} // namespace crash_reports
} // namespace forensics