blob: f39cb4c417b13ce3c0ac94d5ea075c86c1560c13 [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/sys/appmgr/crash_introspector.h"
#include <fuchsia/sys/internal/cpp/fidl.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/syscalls/exception.h>
#include <zircon/types.h>
#include <memory>
#include <utility>
#include "lib/async/cpp/task.h"
#include "lib/async/default.h"
#include "lib/fidl/cpp/clone.h"
#include "lib/fit/defer.h"
#include "lib/fitx/result.h"
#include "src/lib/fsl/handles/object_info.h"
namespace component {
using fuchsia::sys::internal::CrashIntrospect_FindComponentByThreadKoid_Response;
using fuchsia::sys::internal::CrashIntrospect_FindComponentByThreadKoid_Result;
// Exceptions have a TTL of 5 minutes in the exception handler so this timeout should be higher
// to preserve the mapping for exceptions that have expired. There is still a chance that exception
// handling takes longer, but we at least want to cover expired exceptions.
const zx::duration kDefaultThreadCacheTimeout = zx::min(10);
CrashIntrospector::CrashIntrospector() : weak_ptr_factory_(this) {}
CrashIntrospector::~CrashIntrospector() = default;
void CrashIntrospector::AddBinding(
fidl::InterfaceRequest<fuchsia::sys::internal::CrashIntrospect> request) {
bindings_.AddBinding(this, std::move(request));
}
void CrashIntrospector::FindComponentByThreadKoid(zx_koid_t thread_koid,
FindComponentByThreadKoidCallback callback) {
CrashIntrospect_FindComponentByThreadKoid_Result result;
auto status = RemoveThreadFromCache(thread_koid);
if (status.is_ok()) {
CrashIntrospect_FindComponentByThreadKoid_Response response;
response.component_info = std::move(status.value());
result.set_response(std::move(response));
} else {
result.set_err(ZX_ERR_NOT_FOUND);
}
callback(std::move(result));
}
void CrashIntrospector::RegisterExceptionChannel(
zx::channel exception_channel, fuchsia::sys::internal::SourceIdentity component_info) {
auto monitor = std::make_unique<CrashMonitor>(
weak_ptr_factory_.GetWeakPtr(), std::move(exception_channel), std::move(component_info));
monitors_.emplace(monitor.get(), std::move(monitor));
}
std::unique_ptr<CrashIntrospector::CrashMonitor> CrashIntrospector::ExtractMonitor(
const CrashIntrospector::CrashMonitor* monitor) {
auto it = monitors_.find(monitor);
if (it == monitors_.end()) {
return nullptr;
}
auto obj = std::move(it->second);
monitors_.erase(it);
return obj;
}
fitx::result<bool, fuchsia::sys::internal::SourceIdentity> CrashIntrospector::RemoveThreadFromCache(
zx_koid_t thread_koid) {
auto it = thread_cache_.find(thread_koid);
if (it == thread_cache_.end()) {
return fitx::error(false);
}
auto obj = std::move(it->second);
// already removed, cancel auto removal task if pending
obj.first->Cancel();
thread_cache_.erase(it);
return fitx::ok(std::move(obj.second));
}
void CrashIntrospector::AddThreadToCache(
const zx::thread& thread, const fuchsia::sys::internal::SourceIdentity& component_info) {
const auto thread_koid = fsl::GetKoid(thread.get());
if (thread_cache_.count(thread_koid) > 0) {
FX_LOGS(ERROR) << "Thread " << thread_koid << " already in map.";
return;
}
auto timeout_task = std::make_unique<async::TaskClosure>([this, thread_koid]() {
auto result = RemoveThreadFromCache(thread_koid);
// result will die at end of this statement
});
timeout_task->PostDelayed(async_get_default_dispatcher(), kDefaultThreadCacheTimeout);
thread_cache_.emplace(thread_koid,
std::make_pair(std::move(timeout_task), fidl::Clone(component_info)));
}
CrashIntrospector::CrashMonitor::CrashMonitor(fxl::WeakPtr<CrashIntrospector> introspector,
zx::channel exception_channel,
fuchsia::sys::internal::SourceIdentity component_info)
: introspector_(std::move(introspector)),
component_info_(std::move(component_info)),
exception_channel_(std::move(exception_channel)),
wait_(this, exception_channel_.get(), ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED) {
wait_.Begin(async_get_default_dispatcher());
}
void CrashIntrospector::CrashMonitor::CrashHandler(async_dispatcher_t* dispatcher,
async::WaitBase* wait, zx_status_t status,
const zx_packet_signal* signal) {
FX_CHECK(status == ZX_OK) << "status: " << status;
if (signal->observed & ZX_CHANNEL_READABLE) {
// wait for next signal
auto run_again = fit::defer([&wait, &dispatcher] { wait->Begin(dispatcher); });
zx_exception_info_t info;
zx::exception exception;
if (const zx_status_t status = exception_channel_.read(
0, &info, exception.reset_and_get_address(), sizeof(info), 1, nullptr, nullptr);
status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to read from the exception channel: " << status;
return;
}
zx::thread thread;
if (const zx_status_t status = exception.get_thread(&thread); status != ZX_OK) {
FX_LOGS(ERROR) << "Could not get thread for exception: " << status;
return;
}
if (introspector_) {
introspector_->AddThreadToCache(thread, component_info_);
} else {
run_again.cancel();
// parent is already dead
wait_.Cancel();
}
return;
}
FX_CHECK(signal->observed & ZX_CHANNEL_PEER_CLOSED) << "signal observed: " << signal->observed;
// job died, stop monitoring
wait_.Cancel();
if (introspector_) {
auto self = introspector_->ExtractMonitor(this);
// |self| will die when this block finishes
}
// this object will die when this function returns
}
CrashIntrospector::CrashMonitor::~CrashMonitor() = default;
} // namespace component