blob: 712a2e1feafe5dbf11a4c9b8fcc3041a3820f40a [file] [log] [blame]
// Copyright 2021 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_component_manager.h"
#include <fuchsia/sys/cpp/fidl.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/io.h>
#include <lib/sys/cpp/termination_reason.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/processargs.h>
#include "src/developer/debug/debug_agent/debugged_job.h"
#include "src/developer/debug/shared/component_utils.h"
#include "src/developer/debug/shared/logging/logging.h"
namespace debug_agent {
namespace {
uint64_t next_component_id = 1;
// Attempts to link a zircon socket into the new component's file descriptor number represented by
// |fd|. If successful, the socket will be connected and a (one way) communication channel with that
// file descriptor will be made.
zx::socket AddStdio(int fd, fuchsia::sys::LaunchInfo* launch_info) {
zx::socket local;
zx::socket target;
zx_status_t status = zx::socket::create(0, &local, &target);
if (status != ZX_OK)
return zx::socket();
auto io = std::make_unique<fuchsia::sys::FileDescriptor>();
io->type0 = PA_HND(PA_FD, fd);
io->handle0 = std::move(target);
if (fd == STDOUT_FILENO) {
launch_info->out = std::move(io);
} else if (fd == STDERR_FILENO) {
launch_info->err = std::move(io);
} else {
FX_NOTREACHED() << "Invalid file descriptor: " << fd;
return zx::socket();
}
return local;
}
// Class designed to help setup a component and then launch it. These setups are necessary because
// the agent needs some information about how the component will be launch before it actually
// launches it. This is because the debugger will set itself to "catch" the component when it starts
// as a process.
class ComponentLauncher {
public:
explicit ComponentLauncher(std::shared_ptr<sys::ServiceDirectory> services)
: services_(std::move(services)) {}
// Will fail if |argv| is invalid. The first element should be the component url needed to launch.
zx_status_t Prepare(std::vector<std::string> argv,
ZirconComponentManager::ComponentDescription* description,
StdioHandles* handles);
// The launcher has to be already successfully prepared. The lifetime of the controller is bound
// to the lifetime of the component.
fuchsia::sys::ComponentControllerPtr Launch();
private:
std::shared_ptr<sys::ServiceDirectory> services_;
fuchsia::sys::LaunchInfo launch_info_;
};
zx_status_t ComponentLauncher::Prepare(std::vector<std::string> argv,
ZirconComponentManager::ComponentDescription* description,
StdioHandles* handles) {
FX_DCHECK(services_);
FX_DCHECK(!argv.empty());
auto pkg_url = argv.front();
debug::ComponentDescription url_desc;
if (!debug::ExtractComponentFromPackageUrl(pkg_url, &url_desc)) {
FX_LOGS(WARNING) << "Invalid package url: " << pkg_url;
return ZX_ERR_INVALID_ARGS;
}
// Prepare the launch info. The parameters to the component do not include
// the component URL.
launch_info_.url = argv.front();
if (argv.size() > 1) {
launch_info_.arguments.emplace();
for (size_t i = 1; i < argv.size(); i++)
launch_info_.arguments->push_back(std::move(argv[i]));
}
*description = {};
description->component_id = next_component_id++;
description->url = pkg_url;
description->process_name = url_desc.component_name;
description->filter = url_desc.component_name;
*handles = {};
handles->out = AddStdio(STDOUT_FILENO, &launch_info_);
handles->err = AddStdio(STDERR_FILENO, &launch_info_);
return ZX_OK;
}
fuchsia::sys::ComponentControllerPtr ComponentLauncher::Launch() {
FX_DCHECK(services_);
fuchsia::sys::LauncherSyncPtr launcher;
services_->Connect(launcher.NewRequest());
// Controller is a way to manage the newly created component. We need it in
// order to receive the terminated events. Sadly, there is no component
// started event. This also makes us need an async::Loop so that the fidl
// plumbing can work.
fuchsia::sys::ComponentControllerPtr controller;
launcher->CreateComponent(std::move(launch_info_), controller.NewRequest());
return controller;
}
} // namespace
ZirconComponentManager::ZirconComponentManager(std::shared_ptr<sys::ServiceDirectory> services)
: services_(std::move(services)), weak_factory_(this) {}
debug::Status ZirconComponentManager::LaunchComponent(DebuggedJob* root_job,
const std::vector<std::string>& argv,
uint64_t* component_id) {
*component_id = 0;
ComponentLauncher launcher(services_);
ComponentDescription description;
StdioHandles handles;
if (zx_status_t status = launcher.Prepare(argv, &description, &handles); status != ZX_OK) {
return debug::ZxStatus(status);
}
FX_DCHECK(expected_components_.count(description.filter) == 0);
root_job->AppendFilter(description.filter);
if (debug::IsDebugLoggingActive()) {
std::stringstream ss;
ss << "Launching component. " << std::endl
<< "Url: " << description.url << std::endl
<< ", name: " << description.process_name << std::endl
<< ", filter: " << description.filter << std::endl
<< ", component_id: " << description.component_id << std::endl;
auto& filters = root_job->filters();
ss << "Current component filters: " << filters.size();
for (auto& filter : filters) {
ss << std::endl << "* " << filter.filter;
}
DEBUG_LOG(Process) << ss.str();
}
*component_id = description.component_id;
// Launch the component.
auto controller = launcher.Launch();
if (!controller) {
FX_LOGS(WARNING) << "Could not launch component " << description.url;
return debug::Status("Could not launch component.");
}
// TODO(donosoc): This should hook into the debug agent so it can correctly
// shutdown the state associated with waiting for this
// component.
controller.events().OnTerminated = [mgr = weak_factory_.GetWeakPtr(), description](
int64_t return_code,
fuchsia::sys::TerminationReason reason) {
// If the agent is gone, there isn't anything more to do.
if (mgr)
mgr->OnComponentTerminated(return_code, description, reason);
};
ExpectedComponent expected_component;
expected_component.description = description;
expected_component.handles = std::move(handles);
expected_component.controller = std::move(controller);
expected_components_[description.filter] = std::move(expected_component);
return debug::Status();
}
uint64_t ZirconComponentManager::OnProcessStart(const std::string& filter,
StdioHandles& out_stdio) {
if (auto it = expected_components_.find(filter); it != expected_components_.end()) {
out_stdio = std::move(it->second.handles);
uint64_t component_id = it->second.description.component_id;
running_components_[component_id] = std::move(it->second.controller);
expected_components_.erase(it);
return component_id;
}
return 0;
}
void ZirconComponentManager::OnComponentTerminated(int64_t return_code,
const ComponentDescription& description,
fuchsia::sys::TerminationReason reason) {
DEBUG_LOG(Process) << "Component " << description.url << " exited with "
<< sys::HumanReadableTerminationReason(reason);
// TODO(donosoc): This need to be communicated over to the client.
if (reason != fuchsia::sys::TerminationReason::EXITED) {
FX_LOGS(WARNING) << "Component " << description.url << " exited with "
<< sys::HumanReadableTerminationReason(reason);
}
// We look for the filter and remove it.
// If we couldn't find it, the component was already caught and cleaned.
expected_components_.erase(description.filter);
if (debug::IsDebugLoggingActive()) {
std::stringstream ss;
ss << "Still expecting the following components: " << expected_components_.size();
for (auto& expected : expected_components_) {
ss << std::endl << "* " << expected.first;
}
DEBUG_LOG(Process) << ss.str();
}
}
} // namespace debug_agent