blob: d784517f2f058ac318fca9dddbf8e6ca66043c31 [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 <utility>
#include <fuchsia/crash/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/component/cpp/startup_context.h>
#include <lib/fdio/io.h>
#include <lib/fxl/files/file.h>
#include <lib/fxl/logging.h>
#include <lib/fxl/strings/trim.h>
#include <lib/zx/handle.h>
#include <lib/zx/log.h>
#include <lib/zx/time.h>
#include <lib/zx/vmo.h>
#include <stdio.h>
#include <third_party/crashpad/client/settings.h>
#include <third_party/crashpad/handler/fuchsia/crash_report_exception_handler.h>
#include <third_party/crashpad/minidump/minidump_file_writer.h>
#include <third_party/crashpad/third_party/mini_chromium/mini_chromium/base/files/scoped_file.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/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/log.h>
#include <zircon/syscalls/object.h>
namespace {
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 GetSystemLogToFile() {
char filename[] = "/data/crashes/log.XXXXXX";
base::ScopedFD fd(mkstemp(filename));
if (fd.get() < 0) {
FXL_LOG(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) {
FXL_LOG(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 std::string(filename);
}
std::string GetVersion() {
const char kFilepath[] = "/system/data/build/last-update";
std::string build_timestamp;
if (!files::ReadFileToString(kFilepath, &build_timestamp)) {
FXL_LOG(ERROR) << "Failed to read build timestamp from '" << kFilepath
<< "'.";
return "unknown";
}
return fxl::TrimString(build_timestamp, "\r\n").ToString();
}
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 HandleException(zx::process process, zx::thread thread) {
// On Fuchsia, the crash reporter does not stay resident, so we don't run
// crashpad_handler here. Instead, directly use CrashReportExceptionHandler
// and terminate when it has completed.
std::unique_ptr<crashpad::CrashReportDatabase> database(
crashpad::CrashReportDatabase::Initialize(
base::FilePath("/data/crashes")));
if (!database) {
return EXIT_FAILURE;
}
database->GetSettings()->SetUploadsEnabled(true);
ScopedStoppable upload_thread;
crashpad::CrashReportUploadThread::Options upload_thread_options;
upload_thread_options.identify_client_via_url = true;
upload_thread_options.rate_limit = false;
upload_thread_options.upload_gzip = true;
upload_thread_options.watch_pending_reports = true;
upload_thread.Reset(new crashpad::CrashReportUploadThread(
database.get(), kURL, upload_thread_options));
upload_thread.Get()->Start();
std::map<std::string, std::string> annotations;
annotations["product"] = "Fuchsia";
annotations["version"] = GetVersion();
// We use ptype to benefit from Chrome's "Process type" handling in the UI.
annotations["ptype"] = GetPackageName(process);
std::map<std::string, base::FilePath> attachments;
ScopedUnlink temp_log_file(GetSystemLogToFile());
if (temp_log_file.is_valid()) {
attachments["log"] = base::FilePath(temp_log_file.get());
}
crashpad::CrashReportExceptionHandler exception_handler(
database.get(),
static_cast<crashpad::CrashReportUploadThread*>(upload_thread.Get()),
&annotations, &attachments, nullptr);
return exception_handler.HandleExceptionHandles(process, thread)
? EXIT_SUCCESS
: EXIT_FAILURE;
}
int Process(fuchsia::crash::Buffer crashlog) {
std::unique_ptr<crashpad::CrashReportDatabase> database(
crashpad::CrashReportDatabase::Initialize(
base::FilePath("/data/kernel_crashes")));
if (!database) {
return EXIT_FAILURE;
}
database->GetSettings()->SetUploadsEnabled(true);
crashpad::CrashReportDatabase::OperationStatus database_status;
// Create report.
std::unique_ptr<crashpad::CrashReportDatabase::NewReport> report;
database_status = database->PrepareNewCrashReport(&report);
if (database_status != crashpad::CrashReportDatabase::kNoError) {
return EXIT_FAILURE;
}
// Add annotations.
std::map<std::string, std::string> annotations = {
{"product", "Fuchsia"},
// Technically the version after reboot, not when it crashed.
{"version", GetVersion()},
// We use ptype to benefit from Chrome's "Process type" handling in the
// UI.
{"ptype", "kernel"},
};
// Add attachments.
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) {
FXL_LOG(ERROR) << "error writing VMO crashlog to buffer: "
<< zx_status_get_string(status);
return EXIT_FAILURE;
}
writer->Write(buffer.get(), crashlog.size);
// Finish new report.
crashpad::UUID local_report_id;
database_status =
database->FinishedWritingCrashReport(std::move(report), &local_report_id);
if (database_status != crashpad::CrashReportDatabase::kNoError) {
return EXIT_FAILURE;
}
// Switch to an "upload" report.
std::unique_ptr<const crashpad::CrashReportDatabase::UploadReport>
upload_report;
database_status =
database->GetReportForUploading(local_report_id, &upload_report);
if (database_status != crashpad::CrashReportDatabase::kNoError) {
return EXIT_FAILURE;
}
// Upload report.
// We have to build the MIME multipart message ourselves as all the Crashpad
// helpers expect some process to build a minidump from and we don't have one.
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 : upload_report->GetAttachments()) {
http_multipart_builder.SetFileAttachment(kv.first, kv.first, kv.second,
"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(
local_report_id, crashpad::Metrics::CrashSkippedReason::kUploadFailed);
return EXIT_FAILURE;
}
database->RecordUploadComplete(std::move(upload_report), server_report_id);
FXL_LOG(INFO) << "Successfully uploaded crash report at "
"https://crash.corp.google.com/"
<< server_report_id;
return EXIT_SUCCESS;
}
class AnalyzerImpl : public fuchsia::crash::Analyzer {
public:
// fuchsia::crash::Analyzer:
void Analyze(::zx::process process, ::zx::thread thread,
AnalyzeCallback callback) override {
callback();
HandleException(std::move(process), std::move(thread));
}
void Process(fuchsia::crash::Buffer crashlog,
ProcessCallback callback) override {
callback();
if (::Process(fbl::move(crashlog)) == EXIT_FAILURE) {
FXL_LOG(ERROR) << "Failed to process VMO crashlog. Won't retry.";
}
}
};
int main(int argc, const char** argv) {
async::Loop loop(&kAsyncLoopConfigAttachToThread);
std::unique_ptr<component::StartupContext> app_context(
component::StartupContext::CreateFromStartupInfo());
AnalyzerImpl analyzer;
fidl::BindingSet<fuchsia::crash::Analyzer> bindings;
app_context->outgoing().AddPublicService(bindings.GetHandler(&analyzer));
loop.Run();
return 0;
}