blob: 5ac870b960cca9e53dff2d331265b552abfdb721 [file] [log] [blame]
// Copyright 2023 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/debug_agent_server.h"
#include <lib/fit/result.h>
#include <utility>
#include "src/developer/debug/debug_agent/backtrace_utils.h"
#include "src/developer/debug/debug_agent/component_manager.h"
#include "src/developer/debug/debug_agent/debug_agent.h"
#include "src/developer/debug/debug_agent/debugged_process.h"
#include "src/developer/debug/debug_agent/debugged_thread.h"
#include "src/developer/debug/debug_agent/process_info_iterator.h"
#include "src/developer/debug/debug_agent/system_interface.h"
#include "src/developer/debug/ipc/filter_utils.h"
#include "src/developer/debug/ipc/protocol.h"
#include "src/developer/debug/ipc/records.h"
namespace debug_agent {
namespace {
// Process names are short, just 32 bytes, and fidl messages have 64k to work with. So we can
// include 2048 process names in a single message. Realistically, DebugAgent will never be attached
// to that many processes at once, so we don't need to hit the absolute limit.
constexpr size_t kMaxBatchedProcessNames = 1024;
class AttachedProcessIterator : public fidl::Server<fuchsia_debugger::AttachedProcessIterator> {
public:
explicit AttachedProcessIterator(fxl::WeakPtr<DebugAgent> debug_agent)
: debug_agent_(std::move(debug_agent)) {}
void GetNext(GetNextCompleter::Sync& completer) override {
// First request, get the attached processes. This is unbounded, so we will always receive all
// of the processes that DebugAgent is attached to.
if (reply_.processes.empty()) {
FX_CHECK(debug_agent_);
debug_ipc::StatusRequest request;
debug_agent_->OnStatus(request, &reply_);
it_ = reply_.processes.begin();
}
std::vector<std::string> names;
for (; it_ != reply_.processes.end() && names.size() < kMaxBatchedProcessNames; ++it_) {
names.push_back(it_->process_name);
}
completer.Reply(fuchsia_debugger::AttachedProcessIteratorGetNextResponse{
{.process_names = std::move(names)}});
}
private:
fxl::WeakPtr<DebugAgent> debug_agent_;
debug_ipc::StatusReply reply_ = {};
std::vector<debug_ipc::ProcessRecord>::iterator it_;
};
// Converts a FIDL filter to a debug_ipc filter or a FilterError if there was an error.
debug::Result<debug_ipc::Filter, fuchsia_debugger::FilterError> ToDebugIpcFilter(
const fuchsia_debugger::Filter& request) {
debug_ipc::Filter filter;
if (request.pattern().empty()) {
return fuchsia_debugger::FilterError::kNoPattern;
}
switch (request.type()) {
case fuchsia_debugger::FilterType::kUrl:
filter.type = debug_ipc::Filter::Type::kComponentUrl;
break;
case fuchsia_debugger::FilterType::kMoniker:
filter.type = debug_ipc::Filter::Type::kComponentMoniker;
break;
case fuchsia_debugger::FilterType::kMonikerPrefix:
filter.type = debug_ipc::Filter::Type::kComponentMonikerPrefix;
break;
case fuchsia_debugger::FilterType::kMonikerSuffix:
filter.type = debug_ipc::Filter::Type::kComponentMonikerSuffix;
break;
default:
return fuchsia_debugger::FilterError::kUnknownType;
}
filter.pattern = request.pattern();
// Filters are always weak when attached via this interface.
filter.weak = true;
if (request.options().recursive()) {
filter.recursive = *request.options().recursive();
}
return filter;
}
} // namespace
// Static.
void DebugAgentServer::BindServer(async_dispatcher_t* dispatcher,
fidl::ServerEnd<fuchsia_debugger::DebugAgent> server_end,
fxl::WeakPtr<DebugAgent> debug_agent) {
auto server = std::make_unique<DebugAgentServer>(debug_agent, dispatcher);
auto impl_ptr = server.get();
impl_ptr->binding_ref_ =
fidl::BindServer(dispatcher, std::move(server_end), std::move(server),
cpp20::bind_front(&debug_agent::DebugAgentServer::OnUnboundFn, impl_ptr));
}
DebugAgentServer::DebugAgentServer(fxl::WeakPtr<DebugAgent> agent, async_dispatcher_t* dispatcher)
: debug_agent_(std::move(agent)), dispatcher_(dispatcher) {
debug_agent_->AddObserver(this);
}
void DebugAgentServer::GetAttachedProcesses(GetAttachedProcessesRequest& request,
GetAttachedProcessesCompleter::Sync& completer) {
FX_CHECK(debug_agent_);
// Create and bind the iterator.
fidl::BindServer(
dispatcher_,
fidl::ServerEnd<fuchsia_debugger::AttachedProcessIterator>(std::move(request.iterator())),
std::make_unique<AttachedProcessIterator>(debug_agent_->GetWeakPtr()), nullptr);
}
void DebugAgentServer::Connect(ConnectRequest& request, ConnectCompleter::Sync& completer) {
FX_CHECK(debug_agent_);
if (debug_agent_->is_connected()) {
completer.Reply(zx::make_result(ZX_ERR_ALREADY_BOUND));
return;
}
auto buffered_socket = std::make_unique<debug::BufferedZxSocket>(std::move(request.socket()));
// Hand ownership of the socket to DebugAgent and start listening.
debug_agent_->TakeAndConnectRemoteAPIStream(std::move(buffered_socket));
completer.Reply(zx::make_result(ZX_OK));
}
void DebugAgentServer::AttachTo(AttachToRequest& request, AttachToCompleter::Sync& completer) {
FX_DCHECK(debug_agent_);
auto result = AddFilter(request);
if (result.has_error()) {
completer.Reply(fit::error(result.err()));
return;
}
auto reply = result.take_value();
// The set removes potential duplicate koids from other filters.
std::set<zx_koid_t> koids_to_attach;
if (!reply.matched_processes_for_filter.empty()) {
for (const auto& f : reply.matched_processes_for_filter) {
koids_to_attach.insert(f.matched_pids.begin(), f.matched_pids.end());
}
}
completer.Reply(fit::success(AttachToKoids({koids_to_attach.begin(), koids_to_attach.end()})));
}
DebugAgentServer::AddFilterResult DebugAgentServer::AddFilter(
const fuchsia_debugger::Filter& fidl_filter) const {
auto result = ToDebugIpcFilter(fidl_filter);
if (result.has_error()) {
return result.err();
}
debug_ipc::UpdateFilterRequest ipc_request;
debug_ipc::StatusReply status;
debug_agent_->OnStatus({}, &status);
// OnUpdateFilter will clear all the filters before reinstalling the set that is present in the
// IPC request, so we must be sure to copy all of the filters that were already there before
// calling the method.
auto agent_filters = status.filters;
// Add in the new filter.
agent_filters.emplace_back(result.value());
ipc_request.filters.reserve(agent_filters.size());
for (const auto& filter : agent_filters) {
ipc_request.filters.push_back(filter);
}
debug_ipc::UpdateFilterReply reply;
debug_agent_->OnUpdateFilter(ipc_request, &reply);
return reply;
}
uint32_t DebugAgentServer::AttachToKoids(const std::vector<zx_koid_t>& koids) const {
// This is not a size_t because this count is eventually fed back through a FIDL type, which
// does not have support for size types.
uint32_t attaches = 0;
for (auto koid : koids) {
debug_ipc::AttachRequest attach_request;
attach_request.koid = koid;
attach_request.weak = true;
debug_ipc::AttachReply attach_reply;
debug_agent_->OnAttach(attach_request, &attach_reply);
// We may get an error if we're already attached to this process. DebugAgent already prints a
// trace log for this, and it's not a problem for clients if we're already attached, so this
// case is ignored. Other errors will produce a warning log.
if (attach_reply.status.has_error() &&
attach_reply.status.type() != debug::Status::Type::kAlreadyExists) {
FX_LOGS(WARNING) << " attach to koid " << koid
<< " failed: " << attach_reply.status.message();
} else {
// Normal case where we attached to something.
attaches++;
}
}
return attaches;
}
void DebugAgentServer::OnNotification(const debug_ipc::NotifyProcessStarting& notify) {
// Ignore launching notifications.
if (notify.type == debug_ipc::NotifyProcessStarting::Type::kLaunch) {
return;
}
AttachToKoids({notify.koid});
}
void DebugAgentServer::OnNotification(const debug_ipc::NotifyException& notify) {
// We always destruct ourselves whenever the client hangs up.
FX_DCHECK(binding_ref_);
if (debug_ipc::IsDebug(notify.type)) {
// Not the kind of exception that our clients are interested in.
return;
}
// The thread is in an exception, we don't need to suspend it, but we do need
// to resume it when we're done (if there isn't a debug_ipc client).
auto thread = debug_agent_->GetDebuggedThread(notify.thread.id);
fuchsia_debugger::DebugAgentOnFatalExceptionRequest event;
event.thread(notify.thread.id.thread);
event.backtrace(
GetBacktraceMarkupForThread(thread->process()->process_handle(), thread->thread_handle()));
fit::result result = fidl::SendEvent(*binding_ref_)->OnFatalException(event);
if (!result.is_ok()) {
FX_LOGS(WARNING) << "Error sending event: " << result.error_value();
}
// Asynchronously detach from the process so the system can handle the exception as normal if
// there is no debug_ipc client. This must be asynchronous so that the low level exception handler
// doesn't have the process removed out from under it when we should be synchronously handling the
// exception.
//
// |this| is owned by the async dispatcher associated with this message loop, so it's safe to
// capture. Similarly, DebugAgent is allocated in main, so our reference should also always be
// valid here.
debug::MessageLoop::Current()->PostTask(FROM_HERE, [=]() {
FX_DCHECK(this);
FX_DCHECK(debug_agent_);
if (!debug_agent_->is_connected()) {
debug_ipc::DetachRequest request;
request.koid = thread->process()->koid();
debug_ipc::DetachReply reply;
debug_agent_->OnDetach(request, &reply);
if (reply.status.has_error()) {
FX_LOGS(WARNING) << "Failed to detach from process " << std::hex
<< thread->process()->koid() << ": " << reply.status.message();
}
}
});
}
DebugAgentServer::GetMatchingProcessesResult DebugAgentServer::GetMatchingProcesses(
std::optional<fuchsia_debugger::Filter> filter) const {
FX_DCHECK(debug_agent_);
std::vector<DebuggedProcess*> processes;
const auto& attached_processes = debug_agent_->GetAllProcesses();
if (filter) {
auto result = ToDebugIpcFilter(*filter);
if (result.has_error()) {
return result.err();
}
const auto& component_manager = debug_agent_->system_interface().GetComponentManager();
for (const auto& [_koid, process] : attached_processes) {
const auto& components = component_manager.FindComponentInfo(process->process_handle());
if (debug_ipc::FilterMatches(result.value(), process->process_handle().GetName(),
components)) {
processes.push_back(process.get());
}
}
} else {
for (const auto& [_koid, process] : attached_processes) {
processes.push_back(process.get());
}
}
return processes;
}
void DebugAgentServer::GetProcessInfo(GetProcessInfoRequest& request,
GetProcessInfoCompleter::Sync& completer) {
FX_DCHECK(debug_agent_);
std::vector<DebuggedProcess*> processes;
auto result = GetMatchingProcesses(request.options().filter());
if (result.has_error()) {
completer.Reply(fit::error(result.err()));
return;
}
// At this point it is invalid to have either filtered out all of the attached processes (or be
// attached to nothing). The first GetNext call on the iterator will produce this error, which is
// more appropriate than for this method.
fidl::BindServer(
dispatcher_,
fidl::ServerEnd<fuchsia_debugger::ProcessInfoIterator>(std::move(request.iterator())),
std::make_unique<ProcessInfoIterator>(debug_agent_, result.take_value(),
std::move(request.options().interest())));
completer.Reply(fit::success());
}
void DebugAgentServer::OnUnboundFn(DebugAgentServer* impl, fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_debugger::DebugAgent> server_end) {
// DebugAgent will be destructed before the server bound to the outgoing directory, so it is
// possible for DebugAgent to be null here.
if (!debug_agent_)
return;
debug_agent_->RemoveObserver(this);
}
void DebugAgentServer::handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_debugger::DebugAgent> metadata,
fidl::UnknownMethodCompleter::Sync& completer) {
FX_LOGS(WARNING) << "Unknown method: " << metadata.method_ordinal;
}
} // namespace debug_agent