| // 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/launcher.h" |
| |
| #include <fidl/fuchsia.component.decl/cpp/fidl.h> |
| #include <fidl/fuchsia.component/cpp/fidl.h> |
| #include <fidl/fuchsia.debugger/cpp/fidl.h> |
| #include <lib/async/default.h> |
| #include <lib/component/incoming/cpp/protocol.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <zircon/processargs.h> |
| |
| #include <charconv> |
| #include <deque> |
| #include <random> |
| |
| #include "lib/async/cpp/task.h" |
| #include "lib/fit/defer.h" |
| |
| namespace { |
| |
| constexpr char kChildUrl[] = "fuchsia-pkg://fuchsia.com/debug_agent#meta/debug_agent.cm"; |
| constexpr char kCollectionName[] = "agents"; |
| constexpr char kAgentPrefix[] = "agent-"; |
| |
| // Convert various result types to zx_status_t. |
| template <typename ResultType> |
| zx_status_t ResultToStatus(const ResultType& result) { |
| zx_status_t error = ZX_OK; |
| if (result.is_error()) { |
| if (result.error_value().is_domain_error()) { |
| error = fidl::ToUnderlying(result.error_value().domain_error()); |
| } else { |
| error = result.error_value().framework_error().status(); |
| } |
| } |
| return error; |
| } |
| |
| std::string GetRandomHexString() { |
| std::mt19937 prng{std::random_device{}()}; |
| uint32_t num = prng(); |
| |
| std::string s; |
| s.resize(8); |
| auto [end, ec] = std::to_chars(s.data(), s.data() + s.size(), num, 16); |
| FX_CHECK(ec == std::errc()) << std::make_error_code(ec).message(); |
| |
| return std::string{s.data(), end}; |
| } |
| |
| fidl::ClientEnd<fuchsia_component::Realm> ConnectToRealmQuery() { |
| zx::result realm_client_end = component::Connect<fuchsia_component::Realm>(); |
| FX_CHECK(realm_client_end.is_ok()); |
| return std::move(*realm_client_end); |
| } |
| |
| class AgentIterator; |
| |
| void ConnectToAgentAt(const std::string& name, |
| fidl::ServerEnd<fuchsia_debugger::DebugAgent> server_end) { |
| fidl::Client<fuchsia_component::Realm> realm(ConnectToRealmQuery(), |
| async_get_default_dispatcher()); |
| |
| fuchsia_component_decl::ChildRef child_ref(name, kCollectionName); |
| |
| auto [dir_client_end, dir_server_end] = fidl::Endpoints<fuchsia_io::Directory>::Create(); |
| |
| realm->OpenExposedDir({{.child = child_ref, .exposed_dir = std::move(dir_server_end)}}) |
| .Then([name, server_end = std::move(server_end), dir_client_end = std::move(dir_client_end), |
| realm = std::move(realm)](auto& result) mutable { |
| if (auto status = ResultToStatus(result); status != ZX_OK) { |
| FX_LOGS(WARNING) << "OpenExposedDir failed: " << result.error_value(); |
| return; |
| } |
| |
| auto connect_at_result = component::ConnectAt<fuchsia_debugger::DebugAgent>( |
| dir_client_end, std::move(server_end)); |
| if (connect_at_result.is_error()) { |
| FX_LOGS(WARNING) << "Failed to connect to " << name << ": " |
| << connect_at_result.error_value(); |
| return; |
| } |
| }); |
| } |
| |
| class AgentIterator : public fidl::Server<fuchsia_debugger::AgentIterator> { |
| public: |
| void GetNext(GetNextCompleter::Sync& completer) override { |
| GetNextCompleter::Async async_completer = completer.ToAsync(); |
| |
| // First call, get the remote iterator from ComponentManager. |
| if (!child_iterator_.is_valid()) { |
| fidl::Client<fuchsia_component::Realm> realm(ConnectToRealmQuery(), |
| async_get_default_dispatcher()); |
| |
| fuchsia_component_decl::CollectionRef collection(kCollectionName); |
| auto [client_end, server_end] = fidl::Endpoints<fuchsia_component::ChildIterator>::Create(); |
| |
| child_iterator_ = fidl::Client(std::move(client_end), async_get_default_dispatcher()); |
| |
| realm->ListChildren({collection, std::move(server_end)}) |
| .Then([this, completer = &async_completer, realm = std::move(realm)]( |
| fidl::Result<fuchsia_component::Realm::ListChildren>& result) mutable { |
| if (auto status = ResultToStatus(result); status != ZX_OK) { |
| FX_LOGS(WARNING) << "ListChildren failed: " << result.error_value(); |
| if (auto unbind_result = child_iterator_.UnbindMaybeGetEndpoint(); |
| unbind_result.is_error()) { |
| FX_LOGS(WARNING) << "Failed to unbind iterator: " << unbind_result.error_value(); |
| } |
| completer->Close(status); |
| } |
| }); |
| } else if (!agents_.empty()) { |
| async::PostTask(async_get_default_dispatcher(), |
| [this, completer = std::move(async_completer)]() mutable { |
| completer.Reply({{std::move(agents_)}}); |
| }); |
| return; |
| } |
| |
| // Get the next batch of results, |children| will be an empty vector when there are no more |
| // child instances. It is possible for this call to return more results than we can send in a |
| // single response. |
| if (child_iterator_.is_valid()) { |
| child_iterator_->Next().Then( |
| [this, completer = std::move(async_completer)]( |
| fidl::Result<fuchsia_component::ChildIterator::Next>& children) mutable { |
| if (children.is_error()) { |
| FX_LOGS(WARNING) << "Child iterator returned error: " << children.error_value(); |
| completer.Close(ZX_ERR_UNAVAILABLE); |
| return; |
| } else if (children->children().empty()) { |
| // No more children, reply with what we have an hang up. |
| completer.Reply(std::move(agents_)); |
| completer.Close(ZX_OK); |
| return; |
| } |
| |
| OnChildrenReady(children->children(), std::move(completer)); |
| }); |
| } |
| } |
| |
| private: |
| void OnChildrenReady(const std::vector<fuchsia_component_decl::ChildRef>& children, |
| GetNextCompleter::Async completer) { |
| std::vector<fuchsia_debugger::Agent> batch; |
| |
| // There's no way to query the completer to know that we've given a response already, but we can |
| // still collate data from the child iterator while we wait for another request from the client. |
| bool completer_called = false; |
| |
| for (auto it = children.begin(); it != children.end(); ++it) { |
| auto [client_end, server_end] = fidl::Endpoints<fuchsia_debugger::DebugAgent>::Create(); |
| |
| if (!completer_called) { |
| batch.emplace_back(it->name(), std::move(client_end)); |
| |
| if (batch.size() == kMaxBatchedAgents || std::next(it) == children.end()) { |
| completer.Reply(std::move(batch)); |
| completer_called = true; |
| } |
| } else { |
| // Buffer the rest of the agents to be returned in the next |GetNext| call from the client. |
| agents_.emplace_back(it->name(), std::move(client_end)); |
| } |
| |
| // This will finish asynchronously, but we don't need to worry about the result here. If an |
| // error occurs, the client_end will see a PEER_CLOSED. |
| ConnectToAgentAt(it->name(), std::move(server_end)); |
| } |
| } |
| |
| // Each struct is a string and a handle, the string is the child component's name which can be up |
| // to 1024 bytes, which is conveniently the same as the 64 handle limit per message. |
| static constexpr size_t kMaxBatchedAgents = 64; |
| |
| fidl::Client<fuchsia_component::ChildIterator> child_iterator_; |
| std::vector<fuchsia_debugger::Agent> agents_; |
| }; |
| |
| } // namespace |
| |
| void DebugAgentLauncher::Launch(LaunchRequest& request, LaunchCompleter::Sync& completer) { |
| std::string name(kAgentPrefix); |
| name += GetRandomHexString(); |
| |
| LaunchDebugAgent(std::move(request.agent()), name, |
| [completer = completer.ToAsync()](zx_status_t status) mutable { |
| completer.Reply(zx::make_result(status)); |
| }); |
| } |
| |
| void DebugAgentLauncher::GetAgents(GetAgentsRequest& request, GetAgentsCompleter::Sync& completer) { |
| fidl::BindServer(async_get_default_dispatcher(), |
| fidl::ServerEnd<fuchsia_debugger::AgentIterator>(std::move(request.iterator())), |
| std::make_unique<AgentIterator>()); |
| } |
| |
| void DebugAgentLauncher::LaunchDebugAgent(fidl::ServerEnd<fuchsia_debugger::DebugAgent> server_end, |
| const std::string& name, |
| fit::callback<void(zx_status_t)> cb) { |
| zx::result client_end = component::Connect<fuchsia_component::Realm>(); |
| fidl::Client<fuchsia_component::Realm> realm(std::move(*client_end), |
| async_get_default_dispatcher()); |
| |
| auto [controller_client_end, controller_server_end] = |
| fidl::Endpoints<fuchsia_component::Controller>::Create(); |
| |
| fidl::Client<fuchsia_component::Controller> controller(std::move(controller_client_end), |
| async_get_default_dispatcher()); |
| |
| // Create the child decl and all the starting parameters. |
| fuchsia_component_decl::Child child_decl; |
| child_decl.name(name); |
| child_decl.url(kChildUrl); |
| child_decl.startup(fuchsia_component_decl::StartupMode::kLazy); |
| |
| fuchsia_component_decl::CollectionRef collection(kCollectionName); |
| |
| std::vector<fuchsia_process::HandleInfo> handles; |
| handles.emplace_back(server_end.TakeHandle(), PA_HND(PA_USER0, 0)); |
| |
| fuchsia_component::CreateChildArgs args; |
| args.numbered_handles() = std::move(handles); |
| args.controller(std::move(controller_server_end)); |
| |
| // Create and Start the child. |
| realm->CreateChild({collection, child_decl, std::move(args)}) |
| .Then([realm = std::move(realm), controller = std::move(controller), cb = std::move(cb)]( |
| fidl::Result<fuchsia_component::Realm::CreateChild>& result) mutable { |
| if (auto status = ResultToStatus(result); status != ZX_OK) { |
| return cb(status); |
| } |
| |
| controller->IsStarted().Then( |
| [controller = std::move(controller), cb = std::move(cb)]( |
| fidl::Result<fuchsia_component::Controller::IsStarted>& result) mutable { |
| if (auto status = ResultToStatus(result); status != ZX_OK) { |
| return cb(status); |
| } |
| |
| // If the component is already started, then we're done. This is |
| // not guaranteed by the Realm protocol. |
| if (result.value().is_started()) { |
| return cb(ZX_OK); |
| } |
| |
| // Otherwise, we need to explicitly start the child. |
| auto [exec_controller_client, exec_controller_server] = |
| fidl::Endpoints<fuchsia_component::ExecutionController>::Create(); |
| fidl::Client<fuchsia_component::ExecutionController> execution_controller( |
| std::move(exec_controller_client), async_get_default_dispatcher()); |
| |
| controller->Start({{.execution_controller = std::move(exec_controller_server)}}) |
| .Then([controller = std::move(controller), cb = std::move(cb)]( |
| fidl::Result<fuchsia_component::Controller::Start>& result) mutable { |
| if (auto status = ResultToStatus(result); status != ZX_OK) { |
| return cb(status); |
| } |
| |
| cb(ZX_OK); |
| }); |
| }); |
| }); |
| } |