blob: 0d77405540719490e628c8a6ae0e5753fc9a8fab [file] [log] [blame]
// Copyright 2018 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/crashpad_agent/crashpad_agent.h"
#include <fuchsia/crash/cpp/fidl.h>
#include <fuchsia/feedback/cpp/fidl.h>
#include <fuchsia/mem/cpp/fidl.h>
#include <lib/fit/bridge.h>
#include <lib/fsl/handles/object_info.h>
#include <lib/syslog/cpp/logger.h>
#include <stdio.h>
#include <unistd.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/object.h>
#include <map>
#include <string>
#include <utility>
#include "src/developer/crashpad_agent/config.h"
#include "src/developer/crashpad_agent/crash_server.h"
#include "src/developer/crashpad_agent/report_annotations.h"
#include "src/developer/crashpad_agent/report_attachments.h"
#include "src/developer/crashpad_agent/scoped_unlink.h"
#include "src/lib/files/directory.h"
#include "src/lib/files/file.h"
#include "src/lib/files/unique_fd.h"
#include "third_party/crashpad/client/crash_report_database.h"
#include "third_party/crashpad/client/prune_crash_reports.h"
#include "third_party/crashpad/client/settings.h"
#include "third_party/crashpad/handler/fuchsia/crash_report_exception_handler.h"
#include "third_party/crashpad/handler/minidump_to_upload_parameters.h"
#include "third_party/crashpad/snapshot/minidump/process_snapshot_minidump.h"
#include "third_party/crashpad/third_party/mini_chromium/mini_chromium/base/files/file_path.h"
#include "third_party/crashpad/third_party/mini_chromium/mini_chromium/base/files/scoped_file.h"
#include "third_party/crashpad/util/file/file_io.h"
#include "third_party/crashpad/util/file/file_reader.h"
#include "third_party/crashpad/util/misc/metrics.h"
#include "third_party/crashpad/util/misc/uuid.h"
#include "third_party/crashpad/util/net/http_body.h"
#include "third_party/crashpad/util/net/http_headers.h"
#include "third_party/crashpad/util/net/http_multipart_builder.h"
#include "third_party/crashpad/util/net/http_transport.h"
#include "third_party/crashpad/util/net/url.h"
namespace fuchsia {
namespace crash {
namespace {
using fuchsia::feedback::Data;
const char kDefaultConfigPath[] = "/pkg/data/default_config.json";
const char kOverrideConfigPath[] = "/config/data/override_config.json";
} // namespace
std::unique_ptr<CrashpadAgent> CrashpadAgent::TryCreate(
async_dispatcher_t* dispatcher,
std::shared_ptr<::sys::ServiceDirectory> services) {
Config config;
if (files::IsFile(kOverrideConfigPath)) {
const zx_status_t status = ParseConfig(kOverrideConfigPath, &config);
if (status == ZX_OK) {
return CrashpadAgent::TryCreate(dispatcher, std::move(services),
std::move(config));
}
FX_LOGS(ERROR) << "failed to read override config file at "
<< kOverrideConfigPath << ": " << status << " ("
<< zx_status_get_string(status)
<< "); falling back to default config file";
}
// We try to load the default config included in the package if no override
// config was specified or we failed to parse it.
const zx_status_t status = ParseConfig(kDefaultConfigPath, &config);
if (status == ZX_OK) {
return CrashpadAgent::TryCreate(dispatcher, std::move(services),
std::move(config));
}
FX_LOGS(ERROR) << "failed to read default config file at "
<< kDefaultConfigPath << ": " << status << " ("
<< zx_status_get_string(status) << ")";
FX_LOGS(FATAL) << "failed to set up crash analyzer";
return nullptr;
}
std::unique_ptr<CrashpadAgent> CrashpadAgent::TryCreate(
async_dispatcher_t* dispatcher,
std::shared_ptr<::sys::ServiceDirectory> services, Config config) {
std::unique_ptr<CrashServer> crash_server;
if (config.enable_upload_to_crash_server && config.crash_server_url) {
crash_server = std::make_unique<CrashServer>(*config.crash_server_url);
}
return CrashpadAgent::TryCreate(dispatcher, std::move(services),
std::move(config), std::move(crash_server));
}
std::unique_ptr<CrashpadAgent> CrashpadAgent::TryCreate(
async_dispatcher_t* dispatcher,
std::shared_ptr<::sys::ServiceDirectory> services, Config config,
std::unique_ptr<CrashServer> crash_server) {
if (!files::IsDirectory(config.local_crashpad_database_path)) {
files::CreateDirectory(config.local_crashpad_database_path);
}
std::unique_ptr<crashpad::CrashReportDatabase> database(
crashpad::CrashReportDatabase::Initialize(
base::FilePath(config.local_crashpad_database_path)));
if (!database) {
FX_LOGS(ERROR) << "error initializing local crash report database at "
<< config.local_crashpad_database_path;
FX_LOGS(FATAL) << "failed to set up crash analyzer";
return nullptr;
}
// Today we enable uploads here. In the future, this will most likely be set
// in some external settings.
database->GetSettings()->SetUploadsEnabled(
config.enable_upload_to_crash_server);
return std::unique_ptr<CrashpadAgent>(
new CrashpadAgent(dispatcher, std::move(services), std::move(config),
std::move(database), std::move(crash_server)));
}
CrashpadAgent::CrashpadAgent(
async_dispatcher_t* dispatcher,
std::shared_ptr<::sys::ServiceDirectory> services, Config config,
std::unique_ptr<crashpad::CrashReportDatabase> database,
std::unique_ptr<CrashServer> crash_server)
: executor_(dispatcher),
services_(services),
config_(std::move(config)),
database_(std::move(database)),
crash_server_(std::move(crash_server)) {
FXL_DCHECK(services_);
FXL_DCHECK(database_);
if (config.enable_upload_to_crash_server) {
FXL_DCHECK(crash_server_);
}
}
void CrashpadAgent::OnNativeException(zx::process process, zx::thread thread,
zx::port exception_port,
OnNativeExceptionCallback callback) {
auto promise =
OnNativeException(std::move(process), std::move(thread),
std::move(exception_port))
.and_then([] {
Analyzer_OnNativeException_Result result;
Analyzer_OnNativeException_Response response;
result.set_response(response);
return fit::ok(std::move(result));
})
.or_else([] {
FX_LOGS(ERROR) << "Failed to handle native exception. Won't retry.";
Analyzer_OnNativeException_Result result;
result.set_err(ZX_ERR_INTERNAL);
return fit::ok(std::move(result));
})
.and_then([callback = std::move(callback),
this](Analyzer_OnNativeException_Result& result) {
callback(std::move(result));
PruneDatabase();
});
executor_.schedule_task(std::move(promise));
}
void CrashpadAgent::OnManagedRuntimeException(
std::string component_url, ManagedRuntimeException exception,
OnManagedRuntimeExceptionCallback callback) {
auto promise =
OnManagedRuntimeException(component_url, std::move(exception))
.and_then([] {
Analyzer_OnManagedRuntimeException_Result result;
Analyzer_OnManagedRuntimeException_Response response;
result.set_response(response);
return fit::ok(std::move(result));
})
.or_else([] {
FX_LOGS(ERROR)
<< "Failed to handle managed runtime exception. Won't retry.";
Analyzer_OnManagedRuntimeException_Result result;
result.set_err(ZX_ERR_INTERNAL);
return fit::ok(std::move(result));
})
.and_then([callback = std::move(callback),
this](Analyzer_OnManagedRuntimeException_Result& result) {
callback(std::move(result));
PruneDatabase();
});
executor_.schedule_task(std::move(promise));
}
void CrashpadAgent::OnKernelPanicCrashLog(
fuchsia::mem::Buffer crash_log, OnKernelPanicCrashLogCallback callback) {
auto promise =
OnKernelPanicCrashLog(std::move(crash_log))
.and_then([] {
Analyzer_OnKernelPanicCrashLog_Result result;
Analyzer_OnKernelPanicCrashLog_Response response;
result.set_response(response);
return fit::ok(std::move(result));
})
.or_else(
[] {
FX_LOGS(ERROR)
<< "Failed to process kernel panic crash log. Won't retry.";
Analyzer_OnKernelPanicCrashLog_Result result;
result.set_err(ZX_ERR_INTERNAL);
return fit::ok(std::move(result));
})
.and_then([callback = std::move(callback),
this](Analyzer_OnKernelPanicCrashLog_Result& result) {
callback(std::move(result));
PruneDatabase();
});
executor_.schedule_task(std::move(promise));
}
fit::promise<Data> CrashpadAgent::GetFeedbackData() {
// We use a fit::bridge to turn GetData() callback into a fit::promise.
//
// We use a share_ptr to share the bridge between the return value owned by
// this and GetData() callback.
//
// TODO(DX-1469): add a timeout to the fit::bridge completion.
std::shared_ptr<fit::bridge<Data>> get_data_done =
std::make_shared<fit::bridge<Data>>();
const uint64_t id = next_feedback_data_provider_id_++;
// TODO(DX-1469): set up set_error_handler() and call complete_error() on
// the pending fit::bridge.
feedback_data_providers_[id] =
services_->Connect<fuchsia::feedback::DataProvider>();
feedback_data_providers_[id]->GetData(
[this, id, get_data_done](
fuchsia::feedback::DataProvider_GetData_Result out_result) {
CloseFeedbackDataProvider(id);
if (out_result.is_err()) {
FX_LOGS(WARNING) << "Failed to fetch feedback data: "
<< out_result.err() << " ("
<< zx_status_get_string(out_result.err()) << ")";
get_data_done->completer.complete_error();
return;
}
get_data_done->completer.complete_ok(
std::move(out_result.response().data));
});
return get_data_done->consumer.promise_or(fit::error());
}
namespace {
std::map<std::string, fuchsia::mem::Buffer> MakeAttachments(
Data* feedback_data) {
std::map<std::string, fuchsia::mem::Buffer> attachments;
if (feedback_data->has_attachments()) {
for (auto& attachment : *feedback_data->mutable_attachments()) {
attachments[attachment.key] = std::move(attachment.value);
}
}
return attachments;
}
} // namespace
fit::promise<void> CrashpadAgent::OnNativeException(zx::process process,
zx::thread thread,
zx::port exception_port) {
const std::string process_name = fsl::GetObjectName(process.get());
FX_LOGS(INFO) << "generating crash report for exception thrown by "
<< process_name;
// Prepare annotations and attachments.
return GetFeedbackData().then(
[this, process = std::move(process), thread = std::move(thread),
exception_port = std::move(exception_port),
process_name](fit::result<Data>& result) mutable -> fit::result<void> {
Data feedback_data;
if (result.is_ok()) {
feedback_data = result.take_value();
}
const std::map<std::string, std::string> annotations =
MakeDefaultAnnotations(feedback_data, process_name);
const std::map<std::string, fuchsia::mem::Buffer> attachments =
MakeAttachments(&feedback_data);
// Set minidump and create local crash report.
// * The annotations will be stored in the minidump of the report
// and augmented with modules' annotations.
// * The attachments will be stored in the report.
// We don't pass an upload_thread so we can do the upload ourselves
// synchronously.
crashpad::CrashReportExceptionHandler exception_handler(
database_.get(), /*upload_thread=*/nullptr, &annotations,
&attachments,
/*user_stream_data_sources=*/nullptr);
crashpad::UUID local_report_id;
if (!exception_handler.HandleExceptionHandles(
process, thread, zx::unowned_port(exception_port),
&local_report_id)) {
database_->SkipReportUpload(
local_report_id,
crashpad::Metrics::CrashSkippedReason::kPrepareForUploadFailed);
FX_LOGS(ERROR)
<< "error handling exception for local crash report, ID "
<< local_report_id.ToString();
return fit::error();
}
// For userspace, we read back the annotations from the minidump
// instead of passing them as argument like for kernel crashes because
// the Crashpad handler augmented them with the modules' annotations.
if (UploadReport(local_report_id, /*annotations=*/nullptr,
/*read_annotations_from_minidump=*/true) != ZX_OK) {
return fit::error();
}
return fit::ok();
});
}
fit::promise<void> CrashpadAgent::OnManagedRuntimeException(
std::string component_url, ManagedRuntimeException exception) {
FX_LOGS(INFO) << "generating crash report for exception thrown by "
<< component_url;
// Create local crash report.
std::unique_ptr<crashpad::CrashReportDatabase::NewReport> report;
const crashpad::CrashReportDatabase::OperationStatus database_status =
database_->PrepareNewCrashReport(&report);
if (database_status != crashpad::CrashReportDatabase::kNoError) {
FX_LOGS(ERROR) << "error creating local crash report (" << database_status
<< ")";
return fit::make_error_promise();
}
// Prepare annotations and attachments.
return GetFeedbackData().then(
[this, component_url, exception = std::move(exception),
report = std::move(report)](
fit::result<Data>& result) mutable -> fit::result<void> {
Data feedback_data;
if (result.is_ok()) {
feedback_data = result.take_value();
}
const std::map<std::string, std::string> annotations =
MakeManagedRuntimeExceptionAnnotations(feedback_data, component_url,
&exception);
AddManagedRuntimeExceptionAttachments(report.get(), feedback_data,
&exception);
// Finish new local crash report.
crashpad::UUID local_report_id;
const crashpad::CrashReportDatabase::OperationStatus database_status =
database_->FinishedWritingCrashReport(std::move(report),
&local_report_id);
if (database_status != crashpad::CrashReportDatabase::kNoError) {
FX_LOGS(ERROR) << "error writing local crash report ("
<< database_status << ")";
return fit::error();
}
if (UploadReport(local_report_id, &annotations,
/*read_annotations_from_minidump=*/false) != ZX_OK) {
return fit::error();
}
return fit::ok();
});
}
fit::promise<void> CrashpadAgent::OnKernelPanicCrashLog(
fuchsia::mem::Buffer crash_log) {
FX_LOGS(INFO) << "generating crash report for previous kernel panic";
// Create local crash report.
std::unique_ptr<crashpad::CrashReportDatabase::NewReport> report;
const crashpad::CrashReportDatabase::OperationStatus database_status =
database_->PrepareNewCrashReport(&report);
if (database_status != crashpad::CrashReportDatabase::kNoError) {
FX_LOGS(ERROR) << "error creating local crash report (" << database_status
<< ")";
return fit::make_error_promise();
}
// Prepare annotations and attachments.
return GetFeedbackData().then(
[this, crash_log = std::move(crash_log), report = std::move(report)](
fit::result<Data>& result) mutable -> fit::result<void> {
Data feedback_data;
if (result.is_ok()) {
feedback_data = result.take_value();
}
const std::map<std::string, std::string> annotations =
MakeDefaultAnnotations(feedback_data,
/*package_name=*/"kernel");
AddKernelPanicAttachments(report.get(), feedback_data,
std::move(crash_log));
// Finish new local crash report.
crashpad::UUID local_report_id;
const crashpad::CrashReportDatabase::OperationStatus database_status =
database_->FinishedWritingCrashReport(std::move(report),
&local_report_id);
if (database_status != crashpad::CrashReportDatabase::kNoError) {
FX_LOGS(ERROR) << "error writing local crash report ("
<< database_status << ")";
return fit::error();
}
if (UploadReport(local_report_id, &annotations,
/*read_annotations_from_minidump=*/false) != ZX_OK) {
return fit::error();
}
return fit::ok();
});
}
zx_status_t CrashpadAgent::UploadReport(
const crashpad::UUID& local_report_id,
const std::map<std::string, std::string>* annotations,
bool read_annotations_from_minidump) {
bool uploads_enabled;
if ((!database_->GetSettings()->GetUploadsEnabled(&uploads_enabled) ||
!uploads_enabled)) {
FX_LOGS(INFO)
<< "upload to remote crash server disabled. Local crash report, ID "
<< local_report_id.ToString() << ", available under "
<< config_.local_crashpad_database_path;
database_->SkipReportUpload(
local_report_id,
crashpad::Metrics::CrashSkippedReason::kUploadsDisabled);
return ZX_OK;
}
// Read local crash report as an "upload" report.
std::unique_ptr<const crashpad::CrashReportDatabase::UploadReport> report;
const crashpad::CrashReportDatabase::OperationStatus database_status =
database_->GetReportForUploading(local_report_id, &report);
if (database_status != crashpad::CrashReportDatabase::kNoError) {
FX_LOGS(ERROR) << "error loading local crash report, ID "
<< local_report_id.ToString() << " (" << database_status
<< ")";
return ZX_ERR_INTERNAL;
}
// Set annotations, either from argument or from minidump.
FXL_CHECK((annotations != nullptr) ^ read_annotations_from_minidump);
const std::map<std::string, std::string>* final_annotations = annotations;
std::map<std::string, std::string> minidump_annotations;
if (read_annotations_from_minidump) {
crashpad::FileReader* reader = report->Reader();
crashpad::FileOffset start_offset = reader->SeekGet();
crashpad::ProcessSnapshotMinidump minidump_process_snapshot;
if (!minidump_process_snapshot.Initialize(reader)) {
report.reset();
database_->SkipReportUpload(
local_report_id,
crashpad::Metrics::CrashSkippedReason::kPrepareForUploadFailed);
FX_LOGS(ERROR) << "error processing minidump for local crash report, ID "
<< local_report_id.ToString();
return ZX_ERR_INTERNAL;
}
minidump_annotations = crashpad::BreakpadHTTPFormParametersFromMinidump(
&minidump_process_snapshot);
final_annotations = &minidump_annotations;
if (!reader->SeekSet(start_offset)) {
report.reset();
database_->SkipReportUpload(
local_report_id,
crashpad::Metrics::CrashSkippedReason::kPrepareForUploadFailed);
FX_LOGS(ERROR) << "error processing minidump for local crash report, ID "
<< local_report_id.ToString();
return ZX_ERR_INTERNAL;
}
}
// We have to build the MIME multipart message ourselves as all the public
// Crashpad helpers are asynchronous and we won't be able to know the upload
// status nor the server report ID.
crashpad::HTTPMultipartBuilder http_multipart_builder;
http_multipart_builder.SetGzipEnabled(true);
for (const auto& kv : *final_annotations) {
http_multipart_builder.SetFormData(kv.first, kv.second);
}
for (const auto& kv : report->GetAttachments()) {
http_multipart_builder.SetFileAttachment(kv.first, kv.first, kv.second,
"application/octet-stream");
}
http_multipart_builder.SetFileAttachment(
"uploadFileMinidump", report->uuid.ToString() + ".dmp", report->Reader(),
"application/octet-stream");
crashpad::HTTPHeaders content_headers;
http_multipart_builder.PopulateContentHeaders(&content_headers);
std::string server_report_id;
if (!crash_server_->MakeRequest(content_headers,
http_multipart_builder.GetBodyStream(),
&server_report_id)) {
report.reset();
database_->SkipReportUpload(
local_report_id, crashpad::Metrics::CrashSkippedReason::kUploadFailed);
FX_LOGS(ERROR) << "error uploading local crash report, ID "
<< local_report_id.ToString();
return ZX_ERR_INTERNAL;
}
database_->RecordUploadComplete(std::move(report), server_report_id);
FX_LOGS(INFO) << "successfully uploaded crash report at "
"https://crash.corp.google.com/"
<< server_report_id;
return ZX_OK;
}
void CrashpadAgent::PruneDatabase() {
// We need to create a new condition every time we prune as it internally
// maintains a cumulated total size as it iterates over the reports in the
// database and we want to reset that cumulated total size every time we
// prune.
crashpad::DatabaseSizePruneCondition pruning_condition(
config_.max_crashpad_database_size_in_kb);
crashpad::PruneCrashReportDatabase(database_.get(), &pruning_condition);
}
void CrashpadAgent::CloseFeedbackDataProvider(const uint64_t id) {
if (feedback_data_providers_.erase(id) == 0) {
FX_LOGS(ERROR)
<< "No fuchsia.feedback.DataProvider connection to close with id "
<< id;
}
}
} // namespace crash
} // namespace fuchsia