blob: d82c93d90848898e97d064f33e661349d3ceb925 [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 "crashpad_analyzer_impl.h"
#include <map>
#include <string>
#include <utility>
#include <fbl/type_support.h>
#include <fuchsia/crash/cpp/fidl.h>
#include <lib/fdio/io.h>
#include <lib/fxl/files/directory.h>
#include <lib/fxl/files/file.h>
#include <lib/fxl/files/path.h>
#include <lib/fxl/logging.h>
#include <lib/fxl/strings/concatenate.h>
#include <lib/syslog/cpp/logger.h>
#include <lib/zx/log.h>
#include <lib/zx/time.h>
#include <lib/zx/vmo.h>
#include <stdio.h>
#include <third_party/crashpad/client/crash_report_database.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/minidump/minidump_file_writer.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>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/log.h>
#include <zircon/syscalls/object.h>
#include "report_annotations.h"
namespace fuchsia {
namespace crash {
namespace {
const char kLocalCrashDatabase[] = "/data/crashes";
const char kURL[] = "https://clients2.google.com/cr/report";
class ScopedStoppable {
public:
ScopedStoppable() = default;
~ScopedStoppable() {
if (stoppable_) {
stoppable_->Stop();
}
}
void Reset(crashpad::Stoppable* stoppable) { stoppable_.reset(stoppable); }
crashpad::Stoppable* Get() { return stoppable_.get(); }
private:
std::unique_ptr<crashpad::Stoppable> stoppable_;
DISALLOW_COPY_AND_ASSIGN(ScopedStoppable);
};
class ScopedUnlink {
public:
ScopedUnlink(const std::string& filename) : filename_(filename) {}
~ScopedUnlink() { unlink(filename_.c_str()); }
bool is_valid() const { return !filename_.empty(); }
const std::string& get() const { return filename_; }
private:
std::string filename_;
DISALLOW_COPY_AND_ASSIGN(ScopedUnlink);
};
std::string WriteKernelLogToFile() {
std::string filename = files::SimplifyPath(
fxl::Concatenate({kLocalCrashDatabase, "/kernel_log.XXXXXX"}));
base::ScopedFD fd(mkstemp(filename.data()));
if (fd.get() < 0) {
FX_LOGS(ERROR) << "could not create temp file";
return std::string();
}
zx::log log;
zx_status_t status = zx::log::create(ZX_LOG_FLAG_READABLE, &log);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "zx::log::create failed " << status;
return std::string();
}
char buf[ZX_LOG_RECORD_MAX + 1];
zx_log_record_t* rec = (zx_log_record_t*)buf;
while (log.read(ZX_LOG_RECORD_MAX, rec, 0) > 0) {
if (rec->datalen && (rec->data[rec->datalen - 1] == '\n')) {
rec->datalen--;
}
rec->data[rec->datalen] = 0;
dprintf(fd.get(), "[%05d.%03d] %05" PRIu64 ".%05" PRIu64 "> %s\n",
(int)(rec->timestamp / 1000000000ULL),
(int)((rec->timestamp / 1000000ULL) % 1000ULL), rec->pid, rec->tid,
rec->data);
}
return filename;
}
std::string GetPackageName(const zx::process& process) {
char name[ZX_MAX_NAME_LEN];
if (process.get_property(ZX_PROP_NAME, name, sizeof(name)) == ZX_OK) {
return std::string(name);
}
return std::string("unknown-package");
}
} // namespace
int CrashpadAnalyzerImpl::UploadReport(
std::unique_ptr<const crashpad::CrashReportDatabase::UploadReport> report,
const std::map<std::string, std::string>& annotations) {
// 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 : 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(
"upload_file_minidump", report->uuid.ToString() + ".dmp",
report->Reader(), "application/octet-stream");
std::unique_ptr<crashpad::HTTPTransport> http_transport(
crashpad::HTTPTransport::Create());
crashpad::HTTPHeaders content_headers;
http_multipart_builder.PopulateContentHeaders(&content_headers);
for (const auto& content_header : content_headers) {
http_transport->SetHeader(content_header.first, content_header.second);
}
http_transport->SetBodyStream(http_multipart_builder.GetBodyStream());
http_transport->SetTimeout(60.0); // 1 minute.
http_transport->SetURL(kURL);
std::string server_report_id;
if (!http_transport->ExecuteSynchronously(&server_report_id)) {
database_->SkipReportUpload(
report->uuid,
crashpad::Metrics::CrashSkippedReason::kPrepareForUploadFailed);
FX_LOGS(ERROR) << "error uploading local crash report, ID "
<< report->uuid.ToString();
return EXIT_FAILURE;
}
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 EXIT_SUCCESS;
}
std::unique_ptr<const crashpad::CrashReportDatabase::UploadReport>
CrashpadAnalyzerImpl::GetUploadReport(const crashpad::UUID& local_report_id) {
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 nullptr;
}
return report;
}
int CrashpadAnalyzerImpl::HandleException(zx::process process,
zx::thread thread,
zx::port exception_port) {
const std::string package_name = GetPackageName(process);
FX_LOGS(INFO) << "generating crash report for exception thrown by "
<< package_name;
// Prepare annotations and attachments.
const std::map<std::string, std::string> annotations =
MakeAnnotations(package_name);
std::map<std::string, base::FilePath> attachments;
ScopedUnlink temp_kernel_log_file(WriteKernelLogToFile());
if (temp_kernel_log_file.is_valid()) {
attachments["kernel_log"] = base::FilePath(temp_kernel_log_file.get());
}
// 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 EXIT_FAILURE;
}
// Read local crash report as an "upload" report.
std::unique_ptr<const crashpad::CrashReportDatabase::UploadReport> report =
GetUploadReport(local_report_id);
if (!report) {
return EXIT_FAILURE;
}
// 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.
crashpad::FileReader* reader = report->Reader();
crashpad::FileOffset start_offset = reader->SeekGet();
crashpad::ProcessSnapshotMinidump minidump_process_snapshot;
if (!minidump_process_snapshot.Initialize(reader)) {
database_->SkipReportUpload(
report->uuid,
crashpad::Metrics::CrashSkippedReason::kPrepareForUploadFailed);
FX_LOGS(ERROR) << "error processing minidump for local crash report, ID "
<< local_report_id.ToString();
return EXIT_FAILURE;
}
const std::map<std::string, std::string> augmented_annotations =
crashpad::BreakpadHTTPFormParametersFromMinidump(
&minidump_process_snapshot);
if (!reader->SeekSet(start_offset)) {
database_->SkipReportUpload(
report->uuid,
crashpad::Metrics::CrashSkippedReason::kPrepareForUploadFailed);
FX_LOGS(ERROR) << "error processing minidump for local crash report, ID "
<< local_report_id.ToString();
return EXIT_FAILURE;
}
return UploadReport(std::move(report), annotations);
}
int CrashpadAnalyzerImpl::ProcessCrashlog(fuchsia::mem::Buffer crashlog) {
FX_LOGS(INFO) << "generating crash report for previous kernel panic";
crashpad::CrashReportDatabase::OperationStatus database_status;
// Create local crash report.
std::unique_ptr<crashpad::CrashReportDatabase::NewReport> report;
database_status = database_->PrepareNewCrashReport(&report);
if (database_status != crashpad::CrashReportDatabase::kNoError) {
FX_LOGS(ERROR) << "error creating local crash report (" << database_status
<< ")";
return EXIT_FAILURE;
}
// Prepare annotations and attachments.
const std::map<std::string, std::string> annotations =
MakeAnnotations(/*package_name=*/"kernel");
crashpad::FileWriter* writer = report->AddAttachment("log");
if (!writer) {
return EXIT_FAILURE;
}
// TODO(frousseau): make crashpad::FileWriter VMO-aware.
std::unique_ptr<void, decltype(&free)> buffer(malloc(crashlog.size), &free);
zx_status_t status = crashlog.vmo.read(buffer.get(), 0u, crashlog.size);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "error writing VMO crashlog to buffer: "
<< zx_status_get_string(status);
return EXIT_FAILURE;
}
writer->Write(buffer.get(), crashlog.size);
// Finish new local crash report.
crashpad::UUID local_report_id;
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 EXIT_FAILURE;
}
// Read local crash report as an "upload" report and upload it.
std::unique_ptr<const crashpad::CrashReportDatabase::UploadReport>
upload_report = GetUploadReport(local_report_id);
if (!upload_report) {
return EXIT_FAILURE;
}
return UploadReport(std::move(upload_report), annotations);
}
CrashpadAnalyzerImpl::CrashpadAnalyzerImpl(
std::unique_ptr<crashpad::CrashReportDatabase> database)
: database_(std::move(database)) {
FXL_DCHECK(database_);
}
void CrashpadAnalyzerImpl::HandleException(zx::process process,
zx::thread thread,
zx::port exception_port,
HandleExceptionCallback callback) {
callback();
if (HandleException(std::move(process), std::move(thread),
std::move(exception_port)) != EXIT_SUCCESS) {
FX_LOGS(ERROR) << "failed to handle exception. Won't retry.";
}
}
void CrashpadAnalyzerImpl::ProcessCrashlog(fuchsia::mem::Buffer crashlog,
ProcessCrashlogCallback callback) {
callback();
if (ProcessCrashlog(fbl::move(crashlog)) != EXIT_SUCCESS) {
FX_LOGS(ERROR) << "failed to process VMO crashlog. Won't retry.";
}
}
std::unique_ptr<CrashpadAnalyzerImpl> CrashpadAnalyzerImpl::TryCreate() {
if (!files::IsDirectory(kLocalCrashDatabase)) {
files::CreateDirectory(kLocalCrashDatabase);
}
std::unique_ptr<crashpad::CrashReportDatabase> database(
crashpad::CrashReportDatabase::Initialize(
base::FilePath(kLocalCrashDatabase)));
if (!database) {
FX_LOGS(ERROR) << "error initializing local crash report database at "
<< kLocalCrashDatabase;
return nullptr;
}
// Today we enable uploads here. In the future, this will most likely be set
// in some external settings.
database->GetSettings()->SetUploadsEnabled(true);
return std::unique_ptr<CrashpadAnalyzerImpl>(
new CrashpadAnalyzerImpl(std::move(database)));
}
} // namespace crash
} // namespace fuchsia