blob: 7889dccc2e8c066f480319eb96cc65887ec7f8a5 [file] [log] [blame]
// Copyright 2020 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/exceptions/handler/crash_reporter.h"
#include <lib/async/cpp/task.h>
#include <lib/fidl/cpp/string.h>
#include <lib/fpromise/bridge.h>
#include <lib/fpromise/promise.h>
#include <lib/fpromise/result.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/exception.h>
#include <zircon/syscalls/object.h>
#include <optional>
#include <utility>
#include "src/developer/forensics/exceptions/constants.h"
#include "src/developer/forensics/exceptions/handler/component_lookup.h"
#include "src/developer/forensics/exceptions/handler/minidump.h"
#include "src/developer/forensics/exceptions/handler/report_builder.h"
#include "src/developer/forensics/exceptions/handler/wake_lease.h"
#include "src/developer/forensics/utils/fidl_oneshot.h"
#include "src/lib/fsl/handles/object_info.h"
namespace forensics {
namespace exceptions {
namespace handler {
namespace {
using fuchsia_power_system::LeaseToken;
// Either resets the exception immediately if the process only has one thread or with a 5s delay
// otherwise.
void ResetException(async_dispatcher_t* dispatcher, zx::exception exception,
const zx::process& process) {
if (!exception.is_valid()) {
return;
}
if (!process.is_valid()) {
FX_LOGS(ERROR) << "Process for exception is invalid";
exception.reset();
return;
}
size_t num_threads{0};
if (const zx_status_t status = zx_object_get_info(process.get(), ZX_INFO_PROCESS_THREADS, nullptr,
0u, nullptr, &num_threads);
status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to get thread info from process " << process.get();
exception.reset();
return;
}
if (num_threads > 1) {
// If the process has multiple threads, delay resetting |exception| for 5 seconds. If one of the
// other threads is in an exception, releasing |exception| immediately may result in the process
// being terminated by the kernel before the minidump for the other thread is generated.
async::PostDelayedTask(
dispatcher, [exception = std::move(exception)]() mutable { exception.reset(); },
zx::sec(5));
} else {
exception.reset();
}
}
// Returns true if the process is terminated.
bool IsProcessTerminated(zx::process& crashed_process) {
zx_info_process_t process_info;
if (const zx_status_t status = crashed_process.get_info(ZX_INFO_PROCESS, &process_info,
sizeof(process_info), nullptr, nullptr);
status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to get info for process " << crashed_process.get();
return false;
}
return process_info.flags & ZX_INFO_PROCESS_FLAG_EXITED;
}
fpromise::promise<> Delay(async_dispatcher_t* dispatcher, const zx::duration duration) {
fpromise::bridge<> bridge;
if (const zx_status_t status = async::PostDelayedTask(
dispatcher,
[completer = std::move(bridge.completer)]() mutable { completer.complete_ok(); },
zx::sec(5));
status != ZX_OK) {
// When |bridge.completer| gets destroyed, the completer is considered abandoned and the
// consumer will receive an error result.
FX_PLOGS(ERROR, status) << "Failed to delay connecting to the crash reporter, connecting now";
}
return bridge.consumer.promise_or(fpromise::error());
}
} // namespace
CrashReporter::CrashReporter(
async_dispatcher_t* dispatcher, std::shared_ptr<sys::ServiceDirectory> services,
zx::duration component_lookup_timeout, std::unique_ptr<WakeLeaseBase> wake_lease,
fidl::ClientEnd<fuchsia_driver_crash::CrashIntrospect> driver_crash_introspect)
: dispatcher_(dispatcher),
executor_(dispatcher_),
services_(std::move(services)),
component_lookup_timeout_(component_lookup_timeout),
wake_lease_(std::move(wake_lease)) {
if (driver_crash_introspect) {
driver_crash_introspect_.Bind(std::move(driver_crash_introspect), dispatcher_);
}
}
void CrashReporter::Send(zx::exception exception, zx::process crashed_process,
zx::thread crashed_thread, SendCallback callback) {
CrashReportBuilder builder;
builder.SetProcess(crashed_process).SetThread(crashed_thread);
if (exception.is_valid()) {
std::optional<ExceptionReason> exception_reason{std::nullopt};
std::optional<std::string> gwp_asan_exception_type;
zx::vmo minidump = GenerateMinidump(exception, &exception_reason, &gwp_asan_exception_type);
if (minidump.is_valid()) {
builder.SetMinidump(std::move(minidump));
} else {
builder.SetProcessTerminated();
}
builder.SetExceptionReason(exception_reason);
if (gwp_asan_exception_type.has_value()) {
builder.SetGwpAsanExceptionType(gwp_asan_exception_type.value());
}
} else {
builder.SetExceptionExpired();
}
if (IsProcessTerminated(crashed_process)) {
builder.SetProcessTerminated();
}
// If suspend is enabled, acquire a wake lease before releasing the exception. The wake lease
// should be kept in scope until after we're done filing the crash report.
fpromise::promise<LeaseToken, Error> wake_lease_promise =
wake_lease_ != nullptr
? wake_lease_->Acquire(kWakeLeaseAcquisitionTimeout)
: fpromise::make_result_promise<LeaseToken, Error>(fpromise::ok(LeaseToken()));
const auto thread_koid = fsl::GetKoid(crashed_thread.get());
const auto process_koid = fsl::GetKoid(crashed_process.get());
auto join = fpromise::join_promises(
std::move(wake_lease_promise),
GetComponentInfo(dispatcher_, services_, driver_crash_introspect_, component_lookup_timeout_,
process_koid, thread_koid));
auto join_promise = join.and_then(
[dispatcher = dispatcher_, services = services_, builder = std::move(builder),
exception = std::move(exception), crashed_process = std::move(crashed_process),
callback = std::move(callback)](
std::tuple<fpromise::result<LeaseToken, Error>, fpromise::result<ComponentInfo>>&
results) mutable {
fpromise::result<LeaseToken, Error>& wake_lease_result = std::get<0>(results);
fpromise::result<ComponentInfo>& component_info_result = std::get<1>(results);
if (wake_lease_result.is_error()) {
FX_LOGS(ERROR) << "Wake lease not acquired: " + ToString(wake_lease_result.error());
}
// Don't release the exception until after we attempted to acquire a wake lease, if
// applicable.
ResetException(dispatcher, std::move(exception), crashed_process);
ComponentInfo component_info;
if (component_info_result.is_ok()) {
component_info = component_info_result.take_value();
}
builder.SetComponentInfo(component_info);
fpromise::promise<> delay = (builder.ProcessName() != "feedback.cm")
? fpromise::make_ok_promise()
: Delay(dispatcher, /*duration=*/zx::sec(5));
return delay
.then([dispatcher, services,
builder = std::move(builder)](const fpromise::result<>& result) mutable {
return OneShotCall<fuchsia::feedback::CrashReporter,
&fuchsia::feedback::CrashReporter::FileReport>(
dispatcher, services, kFileReportTimeout, builder.Consume());
})
.then([wake_lease_result = std::move(wake_lease_result),
component_info = std::move(component_info), callback = std::move(callback)](
const fpromise::result<fuchsia::feedback::CrashReporter_FileReport_Result,
forensics::Error>& result) {
::fidl::StringPtr moniker = std::nullopt;
if (!component_info.moniker.empty()) {
moniker = component_info.moniker;
}
callback(std::move(moniker));
return ::fpromise::ok();
});
});
executor_.schedule_task(std::move(join_promise));
}
} // namespace handler
} // namespace exceptions
} // namespace forensics