blob: 05b9b89d7bb6d7198ada3d5a03d273493178a3bf [file] [log] [blame]
// Copyright 2019 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/debug/debug_agent/zircon_limbo_provider.h"
#include <zircon/status.h>
#include "src/developer/debug/debug_agent/arch.h"
#include "src/developer/debug/debug_agent/zircon_exception_handle.h"
#include "src/developer/debug/debug_agent/zircon_process_handle.h"
#include "src/developer/debug/debug_agent/zircon_thread_handle.h"
#include "src/developer/debug/debug_agent/zircon_utils.h"
using namespace fuchsia::exception;
namespace debug_agent {
namespace {
LimboProvider::Record MetadataToRecord(ProcessExceptionMetadata metadata) {
return ZirconLimboProvider::Record{
.process = std::make_unique<ZirconProcessHandle>(std::move(*metadata.mutable_process())),
.thread = std::make_unique<ZirconThreadHandle>(std::move(*metadata.mutable_thread()))};
}
} // namespace
ZirconLimboProvider::ZirconLimboProvider(std::shared_ptr<sys::ServiceDirectory> services)
: services_(std::move(services)) {
// Get the initial state of the hanging gets.
ProcessLimboSyncPtr process_limbo;
if (zx_status_t status = services_->Connect(process_limbo.NewRequest()); status != ZX_OK)
return;
// Check if the limbo is active.
bool is_limbo_active = false;
if (zx_status_t status = process_limbo->WatchActive(&is_limbo_active); status != ZX_OK)
return;
is_limbo_active_ = is_limbo_active;
if (is_limbo_active_) {
// Get the current set of process in exceptions.
ProcessLimbo_WatchProcessesWaitingOnException_Result result;
if (zx_status_t status = process_limbo->WatchProcessesWaitingOnException(&result);
status != ZX_OK || result.is_err())
return;
// Add all the current exceptions.
for (auto& exception : result.response().exception_list)
limbo_[exception.info().process_koid] = MetadataToRecord(std::move(exception));
}
// Now that we were able to get the current state of the limbo, we move to an async binding.
connection_.Bind(process_limbo.Unbind().TakeChannel());
// |this| owns the connection, so it's guaranteed to outlive it.
connection_.set_error_handler([this](zx_status_t status) {
FX_LOGS(ERROR) << "Got error from limbo: " << zx_status_get_string(status);
limbo_.clear();
is_limbo_active_ = false;
connection_ = {};
});
WatchActive();
WatchLimbo();
valid_ = true;
}
void ZirconLimboProvider::WatchActive() {
// |this| owns the connection, so it's guaranteed to outlive it.
connection_->WatchActive([this](bool is_active) {
if (!is_active)
limbo_.clear();
is_limbo_active_ = is_active;
// Re-issue the hanging get.
WatchActive();
});
}
void ZirconLimboProvider::WatchLimbo() {
connection_->WatchProcessesWaitingOnException(
// |this| owns the connection, so we're guaranteed to outlive it.
[this](ProcessLimbo_WatchProcessesWaitingOnException_Result result) {
if (result.is_err())
return; // Limbo likely not enabled, give up.
// The callback provides the full current list every time.
RecordMap new_limbo;
std::vector<zx_koid_t> new_exceptions;
for (ProcessExceptionMetadata& exception : result.response().exception_list) {
zx_koid_t process_koid = exception.info().process_koid;
// Record if this is a new one we don't have yet.
if (auto it = limbo_.find(process_koid); it == limbo_.end())
new_exceptions.push_back(process_koid);
new_limbo.insert({process_koid, MetadataToRecord(std::move(exception))});
}
limbo_ = std::move(new_limbo);
if (on_enter_limbo_) {
// Notify for the new exceptions.
for (zx_koid_t process_koid : new_exceptions) {
// Even though we added the exception above and expect it to be in limbo_, re-check
// that we found it in case the callee consumed the exception out from under us.
if (auto found = limbo_.find(process_koid); found != limbo_.end())
on_enter_limbo_(found->second);
}
}
// Re-issue the hanging get.
WatchLimbo();
});
}
bool ZirconLimboProvider::IsProcessInLimbo(zx_koid_t process_koid) const {
const auto& records = GetLimboRecords();
return records.find(process_koid) != records.end();
}
fitx::result<debug::Status, ZirconLimboProvider::RetrievedException>
ZirconLimboProvider::RetrieveException(zx_koid_t process_koid) {
ProcessLimboSyncPtr process_limbo;
if (zx_status_t status = services_->Connect(process_limbo.NewRequest()); status != ZX_OK)
return fitx::error(debug::ZxStatus(status));
ProcessLimbo_RetrieveException_Result result = {};
if (zx_status_t status = process_limbo->RetrieveException(process_koid, &result);
status != ZX_OK) {
return fitx::error(debug::ZxStatus(status));
}
if (result.is_err())
return fitx::error(debug::ZxStatus(result.err()));
fuchsia::exception::ProcessException exception = result.response().ResultValue_();
// Convert from the FIDL ExceptionInfo to the kernel zx_exception_info_t.
const auto& source_info = exception.info();
zx_exception_info_t info = {};
info.pid = source_info.process_koid;
info.tid = source_info.thread_koid;
info.type = static_cast<zx_excp_type_t>(source_info.type);
RetrievedException retrieved;
retrieved.process =
std::make_unique<ZirconProcessHandle>(std::move(*exception.mutable_process()));
retrieved.thread = std::make_unique<ZirconThreadHandle>(std::move(*exception.mutable_thread()));
retrieved.exception =
std::make_unique<ZirconExceptionHandle>(std::move(*exception.mutable_exception()), info);
return fitx::ok(std::move(retrieved));
}
debug::Status ZirconLimboProvider::ReleaseProcess(zx_koid_t process_koid) {
ProcessLimboSyncPtr process_limbo;
if (zx_status_t status = services_->Connect(process_limbo.NewRequest()); status != ZX_OK)
return debug::ZxStatus(status);
ProcessLimbo_ReleaseProcess_Result result;
if (zx_status_t status = process_limbo->ReleaseProcess(process_koid, &result);
status != ZX_OK || result.is_err())
return debug::ZxStatus(status);
limbo_.erase(process_koid);
return debug::Status();
}
} // namespace debug_agent