[debugger] "ps" to show component info
Bug: 94893
Change-Id: I51fbf8b556d1f71a275d0f8983f5c141f98657af
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/692005
Commit-Queue: Dangyi Liu <dangyi@google.com>
Reviewed-by: Brett Wilson <brettw@google.com>
Reviewed-by: Xyan Bhatnagar <xbhatnag@google.com>
diff --git a/src/developer/debug/debug_agent/BUILD.gn b/src/developer/debug/debug_agent/BUILD.gn
index bc21422..598ed76 100644
--- a/src/developer/debug/debug_agent/BUILD.gn
+++ b/src/developer/debug/debug_agent/BUILD.gn
@@ -110,7 +110,9 @@
public_deps = [
"//sdk/fidl/fuchsia.exception",
+ "//sdk/fidl/fuchsia.io",
"//sdk/fidl/fuchsia.kernel",
+ "//sdk/fidl/fuchsia.sys2",
"//sdk/lib/fit",
"//sdk/lib/syslog/cpp",
"//src/developer/debug/ipc:agent",
diff --git a/src/developer/debug/debug_agent/component_manager.h b/src/developer/debug/debug_agent/component_manager.h
index b9fce91..419ebe4 100644
--- a/src/developer/debug/debug_agent/component_manager.h
+++ b/src/developer/debug/debug_agent/component_manager.h
@@ -26,6 +26,14 @@
public:
virtual ~ComponentManager() = default;
+ struct ComponentInfo {
+ std::string url;
+ std::string moniker;
+ };
+
+ // Find the component information if the job is the root job of an ELF component.
+ virtual std::optional<ComponentInfo> FindComponentInfo(zx_koid_t job_koid) const = 0;
+
// Launches the component with the given command line.
//
// The root_job is the job for the attached component or system root job. The requirement is
diff --git a/src/developer/debug/debug_agent/debugged_job.cc b/src/developer/debug/debug_agent/debugged_job.cc
index d6d418d..3be45a5 100644
--- a/src/developer/debug/debug_agent/debugged_job.cc
+++ b/src/developer/debug/debug_agent/debugged_job.cc
@@ -40,7 +40,8 @@
// Tools like fx serve will connect every second or so to the target, spamming logging for this
// with a lot of "/boot/bin/sh" starting. We filter this out as it makes debugging much harder.
if (proc_name != "/boot/bin/sh") {
- DEBUG_LOG(Job) << "Debugged job " << koid() << ": Process " << proc_name << " starting.";
+ DEBUG_LOG(Job) << "Debugged job " << koid() << ": Process starting pid=" << process->GetKoid()
+ << " name=" << proc_name;
}
// Search through the available filters. If the regex is not valid, fallback to checking if
diff --git a/src/developer/debug/debug_agent/meta/debug_agent.cml b/src/developer/debug/debug_agent/meta/debug_agent.cml
index ffa6dfd..cdb548f 100644
--- a/src/developer/debug/debug_agent/meta/debug_agent.cml
+++ b/src/developer/debug/debug_agent/meta/debug_agent.cml
@@ -27,7 +27,21 @@
// For running v1 components.
"fuchsia.sys.Launcher",
+
+ // To watch and attach to v2 components.
+ "fuchsia.sys2.EventSource",
+ "fuchsia.sys2.RealmExplorer.root",
+ "fuchsia.sys2.RealmQuery.root",
],
+ from: "parent",
+ },
+ {
+ // To watch and attach to v2 components.
+ event: [
+ "debug_started",
+ "stopped",
+ ],
+ from: "parent",
},
{
// Some tests depend on "run /boot/bin/crasher".
diff --git a/src/developer/debug/debug_agent/meta/debug_agent.core_shard.cml b/src/developer/debug/debug_agent/meta/debug_agent.core_shard.cml
index 8e69de4..c392500 100644
--- a/src/developer/debug/debug_agent/meta/debug_agent.core_shard.cml
+++ b/src/developer/debug/debug_agent/meta/debug_agent.core_shard.cml
@@ -15,6 +15,17 @@
"fuchsia.kernel.RootJob",
"fuchsia.logger.LogSink",
"fuchsia.process.Launcher",
+ "fuchsia.sys2.EventSource",
+ "fuchsia.sys2.RealmExplorer.root",
+ "fuchsia.sys2.RealmQuery.root",
+ ],
+ from: "parent",
+ to: "#debug_agent",
+ },
+ {
+ event: [
+ "debug_started",
+ "stopped",
],
from: "parent",
to: "#debug_agent",
diff --git a/src/developer/debug/debug_agent/meta/debug_agent_integration_tests.cml b/src/developer/debug/debug_agent/meta/debug_agent_integration_tests.cml
index 556d0cf..73fd1fc 100644
--- a/src/developer/debug/debug_agent/meta/debug_agent_integration_tests.cml
+++ b/src/developer/debug/debug_agent/meta/debug_agent_integration_tests.cml
@@ -15,7 +15,25 @@
protocol: [
"fuchsia.kernel.RootJob",
"fuchsia.process.Launcher",
+ "fuchsia.sys2.EventSource",
],
},
+ {
+ protocol: "fuchsia.sys2.RealmExplorer",
+ from: "framework",
+ path: "/svc/fuchsia.sys2.RealmExplorer.root",
+ },
+ {
+ protocol: "fuchsia.sys2.RealmQuery",
+ from: "framework",
+ path: "/svc/fuchsia.sys2.RealmQuery.root",
+ },
+ {
+ event: [
+ "debug_started",
+ "stopped",
+ ],
+ from: "framework",
+ },
],
}
diff --git a/src/developer/debug/debug_agent/meta/debug_agent_unit_tests.cml b/src/developer/debug/debug_agent/meta/debug_agent_unit_tests.cml
index d0fb518..2211a78 100644
--- a/src/developer/debug/debug_agent/meta/debug_agent_unit_tests.cml
+++ b/src/developer/debug/debug_agent/meta/debug_agent_unit_tests.cml
@@ -15,7 +15,28 @@
protocol: [
"fuchsia.kernel.RootJob",
"fuchsia.process.Launcher",
+ "fuchsia.sys2.EventSource",
],
},
+
+ // In tests, use the component API from the framework for ourself.
+ // The result is we can only observe ourself as the only component.
+ {
+ protocol: "fuchsia.sys2.RealmExplorer",
+ from: "framework",
+ path: "/svc/fuchsia.sys2.RealmExplorer.root",
+ },
+ {
+ protocol: "fuchsia.sys2.RealmQuery",
+ from: "framework",
+ path: "/svc/fuchsia.sys2.RealmQuery.root",
+ },
+ {
+ event: [
+ "debug_started",
+ "stopped",
+ ],
+ from: "framework",
+ },
],
}
diff --git a/src/developer/debug/debug_agent/mock_component_manager.cc b/src/developer/debug/debug_agent/mock_component_manager.cc
index c301f87..f8b7f54 100644
--- a/src/developer/debug/debug_agent/mock_component_manager.cc
+++ b/src/developer/debug/debug_agent/mock_component_manager.cc
@@ -6,6 +6,11 @@
namespace debug_agent {
+std::optional<ComponentManager::ComponentInfo> MockComponentManager::FindComponentInfo(
+ zx_koid_t job_koid) const {
+ return std::nullopt;
+}
+
debug::Status MockComponentManager::LaunchComponent(DebuggedJob* root_job,
const std::vector<std::string>& argv,
uint64_t* component_id) {
diff --git a/src/developer/debug/debug_agent/mock_component_manager.h b/src/developer/debug/debug_agent/mock_component_manager.h
index 512e6a90..5540647 100644
--- a/src/developer/debug/debug_agent/mock_component_manager.h
+++ b/src/developer/debug/debug_agent/mock_component_manager.h
@@ -15,6 +15,7 @@
~MockComponentManager() override = default;
// ComponentManager implementation.
+ std::optional<ComponentInfo> FindComponentInfo(zx_koid_t job_koid) const override;
debug::Status LaunchComponent(DebuggedJob* root_job, const std::vector<std::string>& argv,
uint64_t* component_id) override;
uint64_t OnProcessStart(const std::string& filter, StdioHandles& out_stdio) override;
diff --git a/src/developer/debug/debug_agent/system_interface.cc b/src/developer/debug/debug_agent/system_interface.cc
index a69bf09..4ab5367 100644
--- a/src/developer/debug/debug_agent/system_interface.cc
+++ b/src/developer/debug/debug_agent/system_interface.cc
@@ -4,17 +4,24 @@
#include "src/developer/debug/debug_agent/system_interface.h"
+#include "src/developer/debug/debug_agent/component_manager.h"
+
namespace debug_agent {
namespace {
using debug_ipc::ProcessTreeRecord;
-ProcessTreeRecord GetProcessTreeFrom(const JobHandle& job) {
+ProcessTreeRecord GetProcessTreeFrom(const JobHandle& job,
+ const ComponentManager& component_manager) {
ProcessTreeRecord result;
result.type = ProcessTreeRecord::Type::kJob;
result.koid = job.GetKoid();
result.name = job.GetName();
+ if (auto component_info = component_manager.FindComponentInfo(job.GetKoid())) {
+ result.component_url = component_info->url;
+ result.component_moniker = component_info->moniker;
+ }
for (const auto& child_process : job.GetChildProcesses()) {
ProcessTreeRecord& proc_record = result.children.emplace_back();
@@ -24,16 +31,17 @@
}
for (const auto& child_job : job.GetChildJobs())
- result.children.push_back(GetProcessTreeFrom(*child_job));
+ result.children.push_back(GetProcessTreeFrom(*child_job, component_manager));
return result;
}
} // namespace
-ProcessTreeRecord SystemInterface::GetProcessTree() const {
+ProcessTreeRecord SystemInterface::GetProcessTree() {
+ const ComponentManager& component_manager = GetComponentManager();
if (std::unique_ptr<JobHandle> root_job = GetRootJob())
- return GetProcessTreeFrom(*root_job);
+ return GetProcessTreeFrom(*root_job, component_manager);
return ProcessTreeRecord();
}
diff --git a/src/developer/debug/debug_agent/system_interface.h b/src/developer/debug/debug_agent/system_interface.h
index 7d7fc87..268f7bb 100644
--- a/src/developer/debug/debug_agent/system_interface.h
+++ b/src/developer/debug/debug_agent/system_interface.h
@@ -57,7 +57,7 @@
// These all use the virtual interface above to implement their functionality.
// Collects the process tree starting from the given job handle.
- debug_ipc::ProcessTreeRecord GetProcessTree() const;
+ debug_ipc::ProcessTreeRecord GetProcessTree();
// Returns a handle to the job/process with the given koid. Returns an empty pointer if it was not
// found. This can also happen if the debug_agent doesn't have permission to see it.
diff --git a/src/developer/debug/debug_agent/zircon_component_manager.cc b/src/developer/debug/debug_agent/zircon_component_manager.cc
index 712a2e1..75d9806 100644
--- a/src/developer/debug/debug_agent/zircon_component_manager.cc
+++ b/src/developer/debug/debug_agent/zircon_component_manager.cc
@@ -4,12 +4,16 @@
#include "src/developer/debug/debug_agent/zircon_component_manager.h"
+#include <fuchsia/io/cpp/fidl.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 <zircon/types.h>
+
+#include <memory>
+#include <optional>
+#include <string>
#include "src/developer/debug/debug_agent/debugged_job.h"
#include "src/developer/debug/shared/component_utils.h"
@@ -48,13 +52,38 @@
return local;
}
+// Read the content of "elf/job_id" in the runtime directory of an ELF component.
+// Return ZX_KOID_INVALID if no such file.
+zx_koid_t ReadElfJobId(fuchsia::io::DirectoryHandle runtime_dir_handle) {
+ fuchsia::io::DirectorySyncPtr runtime_dir = runtime_dir_handle.BindSync();
+ fuchsia::io::FileSyncPtr job_id_file;
+ runtime_dir->Open(
+ fuchsia::io::OpenFlags::RIGHT_READABLE, 0, "elf/job_id",
+ fidl::InterfaceRequest<fuchsia::io::Node>(job_id_file.NewRequest().TakeChannel()));
+ fuchsia::io::File2_Read_Result job_id_res;
+ job_id_file->Read(fuchsia::io::MAX_TRANSFER_SIZE, &job_id_res);
+ if (job_id_res.is_err()) {
+ return ZX_KOID_INVALID;
+ }
+ std::string job_id_str(reinterpret_cast<const char*>(job_id_res.response().data.data()),
+ job_id_res.response().data.size());
+ // We use std::strtoull here because std::stoull is not exception-safe.
+ char* end;
+ zx_koid_t job_id = std::strtoull(job_id_str.c_str(), &end, 10);
+ if (end != job_id_str.c_str() + job_id_str.size()) {
+ FX_LOGS(ERROR) << "Invalid elf/job_id: " << job_id_str;
+ return ZX_KOID_INVALID;
+ }
+ return job_id;
+}
+
// 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 {
+class V1ComponentLauncher {
public:
- explicit ComponentLauncher(std::shared_ptr<sys::ServiceDirectory> services)
+ explicit V1ComponentLauncher(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.
@@ -71,9 +100,9 @@
fuchsia::sys::LaunchInfo launch_info_;
};
-zx_status_t ComponentLauncher::Prepare(std::vector<std::string> argv,
- ZirconComponentManager::ComponentDescription* description,
- StdioHandles* handles) {
+zx_status_t V1ComponentLauncher::Prepare(std::vector<std::string> argv,
+ ZirconComponentManager::ComponentDescription* description,
+ StdioHandles* handles) {
FX_DCHECK(services_);
FX_DCHECK(!argv.empty());
@@ -106,7 +135,7 @@
return ZX_OK;
}
-fuchsia::sys::ComponentControllerPtr ComponentLauncher::Launch() {
+fuchsia::sys::ComponentControllerPtr V1ComponentLauncher::Launch() {
FX_DCHECK(services_);
fuchsia::sys::LauncherSyncPtr launcher;
@@ -125,20 +154,120 @@
} // namespace
ZirconComponentManager::ZirconComponentManager(std::shared_ptr<sys::ServiceDirectory> services)
- : services_(std::move(services)), weak_factory_(this) {}
+ : services_(std::move(services)), event_stream_binding_(this), weak_factory_(this) {
+ // 1. Subscribe to "debug_started" and "stopped" events.
+ fuchsia::sys2::EventSourceSyncPtr event_source;
+ services_->Connect(event_source.NewRequest());
+ std::vector<fuchsia::sys2::EventSubscription> subscriptions;
+ subscriptions.resize(2);
+ subscriptions[0].set_event_name("debug_started");
+ subscriptions[1].set_event_name("stopped");
+ fuchsia::sys2::EventStreamHandle stream;
+ event_stream_binding_.Bind(stream.NewRequest());
+ fuchsia::sys2::EventSource_Subscribe_Result subscribe_res;
+ event_source->Subscribe(std::move(subscriptions), std::move(stream), &subscribe_res);
+ if (subscribe_res.is_err()) {
+ FX_LOGS(ERROR) << "Failed to Subscribe: " << static_cast<uint32_t>(subscribe_res.err());
+ }
+
+ // 2. List existing components via fuchsia.sys2.RealmExplorer and fuchsia.sys2.RealmQuery.
+ fuchsia::sys2::RealmExplorerSyncPtr realm_explorer;
+ fuchsia::sys2::RealmQuerySyncPtr realm_query;
+ services_->Connect(realm_explorer.NewRequest(), "fuchsia.sys2.RealmExplorer.root");
+ services_->Connect(realm_query.NewRequest(), "fuchsia.sys2.RealmQuery.root");
+
+ fuchsia::sys2::RealmExplorer_GetAllInstanceInfos_Result all_instance_infos_res;
+ realm_explorer->GetAllInstanceInfos(&all_instance_infos_res);
+ if (all_instance_infos_res.is_err()) {
+ FX_LOGS(ERROR) << "Failed to GetAllInstanceInfos: "
+ << static_cast<uint32_t>(all_instance_infos_res.err());
+ return;
+ }
+ fuchsia::sys2::InstanceInfoIteratorSyncPtr instance_it =
+ all_instance_infos_res.response().iterator.BindSync();
+
+ while (true) {
+ std::vector<fuchsia::sys2::InstanceInfo> infos;
+ instance_it->Next(&infos);
+ if (infos.empty()) {
+ break;
+ }
+ for (auto& info : infos) {
+ if (info.state != fuchsia::sys2::InstanceState::STARTED || info.moniker.empty()) {
+ continue;
+ }
+ fuchsia::sys2::RealmQuery_GetInstanceInfo_Result instace_info_res;
+ realm_query->GetInstanceInfo(info.moniker, &instace_info_res);
+ if (instace_info_res.is_err() || !instace_info_res.response().resolved->started ||
+ !instace_info_res.response().resolved->started->runtime_dir) {
+ continue;
+ }
+ zx_koid_t job_id =
+ ReadElfJobId(std::move(instace_info_res.response().resolved->started->runtime_dir));
+ if (job_id != ZX_KOID_INVALID) {
+ // Remove the "." at the beginning of the moniker. It's safe because moniker is not empty.
+ running_component_info_[job_id] = {.url = info.url, .moniker = info.moniker.substr(1)};
+ }
+ }
+ }
+}
+
+void ZirconComponentManager::OnEvent(fuchsia::sys2::Event event) {
+ if (!event.has_header() || !event.header().has_moniker() || event.header().moniker().empty() ||
+ !event.has_event_result() || !event.event_result().is_payload()) {
+ return;
+ }
+ switch (event.header().event_type()) {
+ case fuchsia::sys2::EventType::DEBUG_STARTED:
+ if (event.event_result().payload().is_debug_started() &&
+ event.event_result().payload().debug_started().has_runtime_dir()) {
+ zx_koid_t job_id = ReadElfJobId(std::move(
+ *event.mutable_event_result()->payload().debug_started().mutable_runtime_dir()));
+ if (job_id != ZX_KOID_INVALID) {
+ running_component_info_[job_id] = {.url = event.header().component_url(),
+ .moniker = event.header().moniker().substr(1)};
+ DEBUG_LOG(Process) << "Component started job_id=" << job_id
+ << " moniker=" << running_component_info_[job_id].moniker
+ << " url=" << running_component_info_[job_id].url;
+ }
+ }
+ break;
+ case fuchsia::sys2::EventType::STOPPED: {
+ std::string moniker = event.header().moniker().substr(1);
+ for (auto it = running_component_info_.begin(); it != running_component_info_.end(); it++) {
+ if (it->second.moniker == moniker) {
+ DEBUG_LOG(Process) << "Component stopped job_id=" << it->first
+ << " moniker=" << it->second.moniker << " url=" << it->second.url;
+ running_component_info_.erase(it);
+ break;
+ }
+ }
+ break;
+ }
+ default:
+ FX_NOTREACHED();
+ }
+}
+
+std::optional<ZirconComponentManager::ComponentInfo> ZirconComponentManager::FindComponentInfo(
+ zx_koid_t job_koid) const {
+ if (auto it = running_component_info_.find(job_koid); it != running_component_info_.end())
+ return it->second;
+ return std::nullopt;
+}
debug::Status ZirconComponentManager::LaunchComponent(DebuggedJob* root_job,
const std::vector<std::string>& argv,
uint64_t* component_id) {
*component_id = 0;
- ComponentLauncher launcher(services_);
+ V1ComponentLauncher 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);
+ FX_DCHECK(expected_v1_components_.count(description.filter) == 0);
root_job->AppendFilter(description.filter);
@@ -177,35 +306,35 @@
fuchsia::sys::TerminationReason reason) {
// If the agent is gone, there isn't anything more to do.
if (mgr)
- mgr->OnComponentTerminated(return_code, description, reason);
+ mgr->OnV1ComponentTerminated(return_code, description, reason);
};
- ExpectedComponent expected_component;
+ ExpectedV1Component 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);
+ expected_v1_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()) {
+ if (auto it = expected_v1_components_.find(filter); it != expected_v1_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);
+ running_v1_components_[component_id] = std::move(it->second.controller);
- expected_components_.erase(it);
+ expected_v1_components_.erase(it);
return component_id;
}
return 0;
}
-void ZirconComponentManager::OnComponentTerminated(int64_t return_code,
- const ComponentDescription& description,
- fuchsia::sys::TerminationReason reason) {
+void ZirconComponentManager::OnV1ComponentTerminated(int64_t return_code,
+ const ComponentDescription& description,
+ fuchsia::sys::TerminationReason reason) {
DEBUG_LOG(Process) << "Component " << description.url << " exited with "
<< sys::HumanReadableTerminationReason(reason);
@@ -217,12 +346,12 @@
// 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);
+ expected_v1_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 << "Still expecting the following components: " << expected_v1_components_.size();
+ for (auto& expected : expected_v1_components_) {
ss << std::endl << "* " << expected.first;
}
DEBUG_LOG(Process) << ss.str();
diff --git a/src/developer/debug/debug_agent/zircon_component_manager.h b/src/developer/debug/debug_agent/zircon_component_manager.h
index 3674e20..bd061e2 100644
--- a/src/developer/debug/debug_agent/zircon_component_manager.h
+++ b/src/developer/debug/debug_agent/zircon_component_manager.h
@@ -6,6 +6,8 @@
#define SRC_DEVELOPER_DEBUG_DEBUG_AGENT_ZIRCON_COMPONENT_MANAGER_H_
#include <fuchsia/sys/cpp/fidl.h>
+#include <fuchsia/sys2/cpp/fidl.h>
+#include <lib/fidl/cpp/binding.h>
#include <lib/sys/cpp/service_directory.h>
#include "src/developer/debug/debug_agent/component_manager.h"
@@ -13,7 +15,7 @@
namespace debug_agent {
-class ZirconComponentManager : public ComponentManager {
+class ZirconComponentManager : public ComponentManager, public fuchsia::sys2::EventStream {
public:
struct ComponentDescription {
uint64_t component_id = 0; // 0 is invalid.
@@ -26,29 +28,36 @@
~ZirconComponentManager() override = default;
// ComponentManager implementation.
+ std::optional<ComponentInfo> FindComponentInfo(zx_koid_t job_koid) const override;
debug::Status LaunchComponent(DebuggedJob* root_job, const std::vector<std::string>& argv,
uint64_t* component_id) override;
uint64_t OnProcessStart(const std::string& filter, StdioHandles& out_stdio) override;
+ // fuchsia::sys2::EventStream implementation.
+ void OnEvent(fuchsia::sys2::Event event) override;
+
private:
- void OnComponentTerminated(int64_t return_code, const ComponentDescription& description,
- fuchsia::sys::TerminationReason reason);
+ void OnV1ComponentTerminated(int64_t return_code, const ComponentDescription& description,
+ fuchsia::sys::TerminationReason reason);
std::shared_ptr<sys::ServiceDirectory> services_;
+ std::map<zx_koid_t, ComponentInfo> running_component_info_;
+ fidl::Binding<fuchsia::sys2::EventStream> event_stream_binding_;
+
// Each component launch is assigned an unique filter and id. This is because new components are
// attached via the job filter mechanism. When a particular filter attached, we use this id to
// know which component launch just happened and we can communicate it to the client.
- struct ExpectedComponent {
+ struct ExpectedV1Component {
ComponentDescription description;
StdioHandles handles;
fuchsia::sys::ComponentControllerPtr controller;
};
- std::map<std::string, ExpectedComponent> expected_components_;
+ std::map<std::string, ExpectedV1Component> expected_v1_components_;
// References to the running components. These need to be kept alive to keep the components
// running.
- std::map<uint64_t, fuchsia::sys::ComponentControllerPtr> running_components_;
+ std::map<uint64_t, fuchsia::sys::ComponentControllerPtr> running_v1_components_;
fxl::WeakPtrFactory<ZirconComponentManager> weak_factory_;
};
diff --git a/src/developer/debug/debug_agent/zircon_system_interface.cc b/src/developer/debug/debug_agent/zircon_system_interface.cc
index ac1d9d9..f143d12 100644
--- a/src/developer/debug/debug_agent/zircon_system_interface.cc
+++ b/src/developer/debug/debug_agent/zircon_system_interface.cc
@@ -5,37 +5,31 @@
#include "src/developer/debug/debug_agent/zircon_system_interface.h"
#include <fuchsia/kernel/cpp/fidl.h>
-#include <lib/fdio/directory.h>
-#include <lib/fdio/fdio.h>
#include "src/developer/debug/debug_agent/zircon_binary_launcher.h"
#include "src/developer/debug/debug_agent/zircon_job_handle.h"
#include "src/developer/debug/debug_agent/zircon_process_handle.h"
#include "src/developer/debug/debug_agent/zircon_utils.h"
-#include "src/lib/files/file.h"
namespace debug_agent {
namespace {
// Returns an !is_valid() job object on failure.
-zx::job GetRootZxJob() {
+zx::job GetRootZxJob(const sys::ServiceDirectory& services) {
zx::job root_job;
fuchsia::kernel::RootJobSyncPtr root_job_ptr;
- std::string root_job_path("/svc/");
- root_job_path.append(fuchsia::kernel::RootJob::Name_);
+ zx_status_t status = services.Connect(root_job_ptr.NewRequest());
- zx_status_t status = fdio_service_connect(root_job_path.c_str(),
- root_job_ptr.NewRequest().TakeChannel().release());
if (status != ZX_OK) {
- FX_NOTREACHED();
+ FX_PLOGS(ERROR, status) << "Cannot connect to fuchsia.kernel.RootJob";
return zx::job();
}
status = root_job_ptr->Get(&root_job);
if (status != ZX_OK) {
- FX_NOTREACHED();
+ FX_PLOGS(ERROR, status) << "Cannot get root job handle";
return zx::job();
}
return root_job;
@@ -47,7 +41,7 @@
: services_(sys::ServiceDirectory::CreateFromNamespace()),
component_manager_(services_),
limbo_provider_(services_) {
- if (zx::job zx_root = GetRootZxJob(); zx_root.is_valid())
+ if (zx::job zx_root = GetRootZxJob(*services_); zx_root.is_valid())
root_job_ = std::make_unique<ZirconJobHandle>(std::move(zx_root));
}
@@ -58,8 +52,6 @@
std::unique_ptr<JobHandle> ZirconSystemInterface::GetRootJob() const {
if (root_job_)
return std::make_unique<ZirconJobHandle>(*root_job_);
-
- FX_LOGS(WARNING) << "Failed to get the root job";
return nullptr;
}
diff --git a/src/developer/debug/debug_agent/zircon_system_interface_unittest.cc b/src/developer/debug/debug_agent/zircon_system_interface_unittest.cc
index b2044c5..bd2e1c98 100644
--- a/src/developer/debug/debug_agent/zircon_system_interface_unittest.cc
+++ b/src/developer/debug/debug_agent/zircon_system_interface_unittest.cc
@@ -2,34 +2,48 @@
// 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/system_interface.h"
+#include "src/developer/debug/debug_agent/zircon_system_interface.h"
+
+#include <string_view>
#include <gtest/gtest.h>
+#include "src/developer/debug/debug_agent/component_manager.h"
+#include "src/developer/debug/debug_agent/system_interface.h"
#include "src/developer/debug/debug_agent/zircon_job_handle.h"
-#include "src/developer/debug/debug_agent/zircon_system_interface.h"
#include "src/developer/debug/debug_agent/zircon_utils.h"
+#include "src/developer/debug/shared/test_with_loop.h"
namespace debug_agent {
namespace {
// Recursively walks the process tree and returns true if there is a process
-// with the given name and koid.
-bool FindProcess(const debug_ipc::ProcessTreeRecord& record, const std::string& name_to_find,
- zx_koid_t koid_to_find) {
- if (record.name == name_to_find && record.koid == koid_to_find)
+// matching the given koid. Fills the process_name if such process can be found.
+// Fills the component_info if the process belongs to some component.
+bool FindProcess(const debug_ipc::ProcessTreeRecord& record, zx_koid_t koid_to_find,
+ std::string* process_name, ComponentManager::ComponentInfo* component_info) {
+ if (record.koid == koid_to_find) {
+ *process_name = record.name;
return true;
+ }
for (const auto& child : record.children) {
- if (FindProcess(child, name_to_find, koid_to_find))
+ if (FindProcess(child, koid_to_find, process_name, component_info)) {
+ if (component_info->moniker.empty() && !record.component_url.empty()) {
+ component_info->moniker = record.component_moniker;
+ component_info->url = record.component_url;
+ }
return true;
+ }
}
return false;
}
+class SystemInfoTest : public debug::TestWithLoop {};
+
} // namespace
-TEST(SystemInfo, GetProcessTree) {
+TEST_F(SystemInfoTest, GetProcessTree) {
ZirconSystemInterface system_interface;
debug_ipc::ProcessTreeRecord root = system_interface.GetProcessTree();
@@ -38,15 +52,26 @@
EXPECT_EQ(debug_ipc::ProcessTreeRecord::Type::kJob, root.type);
EXPECT_FALSE(root.children.empty());
- // Compute our own process name and koid.
+ // Query ourself.
auto self = zx::process::self();
- std::string self_name = zircon::NameForObject(*self);
- EXPECT_FALSE(self_name.empty());
zx_koid_t self_koid = zircon::KoidForObject(*self);
- ASSERT_NE(0u, self_koid);
+ ASSERT_NE(ZX_KOID_INVALID, self_koid);
- // Our name and koid should be somewhere in the tree.
- EXPECT_TRUE(FindProcess(root, self_name, self_koid));
+ // Our koid should be somewhere in the tree.
+ std::string process_name;
+ ComponentManager::ComponentInfo component_info;
+ EXPECT_TRUE(FindProcess(root, self_koid, &process_name, &component_info));
+
+ // The process_name and component info should match
+ EXPECT_EQ(zircon::NameForObject(*self), process_name);
+ // The moniker is empty because it's actually "." in the test environment and the "." is stripped.
+ EXPECT_EQ("", component_info.moniker);
+ // The url will include a hash that cannot be compared.
+ EXPECT_FALSE(component_info.url.empty());
+ std::string_view prefix = "fuchsia-pkg://fuchsia.com/debug_agent_unit_tests";
+ std::string_view suffix = "#meta/debug_agent_unit_tests.cm";
+ EXPECT_EQ(prefix, component_info.url.substr(0, prefix.size()));
+ EXPECT_EQ(suffix, component_info.url.substr(component_info.url.size() - suffix.size()));
}
} // namespace debug_agent
diff --git a/src/developer/debug/ipc/agent_protocol.cc b/src/developer/debug/ipc/agent_protocol.cc
index 7224d03..8a9cfc1 100644
--- a/src/developer/debug/ipc/agent_protocol.cc
+++ b/src/developer/debug/ipc/agent_protocol.cc
@@ -125,7 +125,11 @@
writer->WriteUint32(static_cast<uint32_t>(record.type));
writer->WriteUint64(record.koid);
writer->WriteString(record.name);
- Serialize(record.children, writer);
+ if (record.type == ProcessTreeRecord::Type::kJob) {
+ writer->WriteString(record.component_url);
+ writer->WriteString(record.component_moniker);
+ Serialize(record.children, writer);
+ }
}
void Serialize(const ThreadRecord& record, MessageWriter* writer) {
diff --git a/src/developer/debug/ipc/client_protocol.cc b/src/developer/debug/ipc/client_protocol.cc
index 89fc20d..56821f1 100644
--- a/src/developer/debug/ipc/client_protocol.cc
+++ b/src/developer/debug/ipc/client_protocol.cc
@@ -21,7 +21,15 @@
return false;
if (!reader->ReadString(&record->name))
return false;
- return Deserialize(reader, &record->children);
+ if (record->type == ProcessTreeRecord::Type::kJob) {
+ if (!reader->ReadString(&record->component_url))
+ return false;
+ if (!reader->ReadString(&record->component_moniker))
+ return false;
+ if (!Deserialize(reader, &record->children))
+ return false;
+ }
+ return true;
}
bool Deserialize(MessageReader* reader, ThreadRecord* record) {
diff --git a/src/developer/debug/ipc/protocol.h b/src/developer/debug/ipc/protocol.h
index d45a59c6..344d4f0 100644
--- a/src/developer/debug/ipc/protocol.h
+++ b/src/developer/debug/ipc/protocol.h
@@ -12,7 +12,7 @@
namespace debug_ipc {
-constexpr uint32_t kProtocolVersion = 38;
+constexpr uint32_t kProtocolVersion = 39;
// This is so that it's obvious if the timestamp wasn't properly set (that number should be at
// least 30,000 years) but it's not the max so that if things add to it then time keeps moving
diff --git a/src/developer/debug/ipc/protocol_unittests.cc b/src/developer/debug/ipc/protocol_unittests.cc
index ad15f84..4b8004c 100644
--- a/src/developer/debug/ipc/protocol_unittests.cc
+++ b/src/developer/debug/ipc/protocol_unittests.cc
@@ -377,6 +377,8 @@
initial.root.type = ProcessTreeRecord::Type::kJob;
initial.root.koid = 1234;
initial.root.name = "root";
+ initial.root.component_url = "fuchsia-pkg://package#meta/component.cm";
+ initial.root.component_moniker = "/moniker";
initial.root.children.resize(1);
initial.root.children[0].type = ProcessTreeRecord::Type::kProcess;
@@ -389,6 +391,8 @@
EXPECT_EQ(initial.root.type, second.root.type);
EXPECT_EQ(initial.root.koid, second.root.koid);
EXPECT_EQ(initial.root.name, second.root.name);
+ EXPECT_EQ(initial.root.component_moniker, second.root.component_moniker);
+ EXPECT_EQ(initial.root.component_url, second.root.component_url);
ASSERT_EQ(initial.root.children.size(), second.root.children.size());
EXPECT_EQ(initial.root.children[0].type, second.root.children[0].type);
EXPECT_EQ(initial.root.children[0].koid, second.root.children[0].koid);
diff --git a/src/developer/debug/ipc/records.h b/src/developer/debug/ipc/records.h
index b194cd6..c9b9ef4 100644
--- a/src/developer/debug/ipc/records.h
+++ b/src/developer/debug/ipc/records.h
@@ -142,6 +142,12 @@
uint64_t koid = 0;
std::string name;
+ // The following fields are only valid on kJob and will be skipped if type is kProcess.
+
+ // The component information if the current job is the root job of an ELF component.
+ std::string component_url;
+ std::string component_moniker;
+
std::vector<ProcessTreeRecord> children;
};
diff --git a/src/developer/debug/zxdb/console/commands/verb_ps.cc b/src/developer/debug/zxdb/console/commands/verb_ps.cc
index f9e7375..47966d1 100644
--- a/src/developer/debug/zxdb/console/commands/verb_ps.cc
+++ b/src/developer/debug/zxdb/console/commands/verb_ps.cc
@@ -8,6 +8,7 @@
#include <optional>
#include <set>
#include <sstream>
+#include <string_view>
#include "src/developer/debug/zxdb/client/job.h"
#include "src/developer/debug/zxdb/client/process.h"
@@ -73,7 +74,13 @@
output->Append(syntax, prefix);
output->Append(Syntax::kSpecial, std::to_string(rec.koid));
- output->Append(syntax, " " + rec.name + "\n");
+ if (!rec.name.empty())
+ output->Append(syntax, " " + rec.name);
+ if (!rec.component_moniker.empty())
+ output->Append(" " + rec.component_moniker, TextForegroundColor::kCyan);
+ if (!rec.component_url.empty())
+ output->Append(" " + rec.component_url, TextForegroundColor::kGray);
+ output->Append(syntax, "\n");
for (const auto& child : rec.children)
OutputProcessTreeRecord(child, indent + 1, attached, output);
@@ -86,16 +93,32 @@
const debug_ipc::ProcessTreeRecord& rec, const std::string& filter) {
debug_ipc::ProcessTreeRecord result;
- for (const auto& child : rec.children) {
- if (auto matched_child = FilterProcessTree(child, filter))
- result.children.push_back(*matched_child);
+ // A record matches if its name or component_moniker matches.
+ bool matched = rec.name.find(filter) != std::string::npos;
+ if (!matched && !rec.component_moniker.empty()) {
+ // Use the last segment of the moniker as the "component name".
+ std::string_view moniker = rec.component_moniker;
+ std::string_view name = moniker.substr(moniker.find_last_of('/') + 1);
+ matched = name.find(filter) != std::string_view::npos;
+ }
+
+ // If a record matches, show all its children.
+ if (matched) {
+ result.children = rec.children;
+ } else {
+ for (const auto& child : rec.children) {
+ if (auto matched_child = FilterProcessTree(child, filter))
+ result.children.push_back(*matched_child);
+ }
}
// Return the node when it matches or any of its children do.
- if (rec.name.find(filter) != std::string::npos || !result.children.empty()) {
+ if (matched || !result.children.empty()) {
result.type = rec.type;
result.koid = rec.koid;
result.name = rec.name;
+ result.component_url = rec.component_url;
+ result.component_moniker = rec.component_moniker;
return result;
}
@@ -132,6 +155,9 @@
If a filter-string is provided only jobs and processes whose names contain the
given case-sensitive substring. It does not support regular expressions.
+ If a job is the root job of a component, the component information will also
+ be printed.
+
Jobs are annotated with "j: <job koid>"
Processes are annotated with "p: <process koid>")";
diff --git a/src/developer/debug/zxdb/console/commands/verb_ps_unittest.cc b/src/developer/debug/zxdb/console/commands/verb_ps_unittest.cc
index f3db53d..ef4eaed 100644
--- a/src/developer/debug/zxdb/console/commands/verb_ps_unittest.cc
+++ b/src/developer/debug/zxdb/console/commands/verb_ps_unittest.cc
@@ -24,6 +24,8 @@
reply.root.children.emplace_back();
reply.root.children[0].koid = 100;
reply.root.children[0].name = "j1";
+ reply.root.children[0].component_moniker = "/some/moniker";
+ reply.root.children[0].component_url = "schema://url";
reply.root.children[0].children.emplace_back();
reply.root.children[0].children[0].koid = 101;
@@ -79,9 +81,9 @@
TEST_F(VerbPSTest, Filter) {
// "ps" by itself should show everything.
- EXPECT_EQ(RunCommandAndGetOutput("ps b"),
+ EXPECT_EQ(RunCommandAndGetOutput("ps"),
" j: 1 root\n"
- " j: 100 j1\n"
+ " j: 100 j1 /some/moniker schema://url\n"
" j: 101 j2\n"
" p: 102 baz\n"
"▶ p: 875123541 foo bar\n");
@@ -89,7 +91,7 @@
// Both processes have a "b" in them, this should match everything.
EXPECT_EQ(RunCommandAndGetOutput("ps b"),
" j: 1 root\n"
- " j: 100 j1\n"
+ " j: 100 j1 /some/moniker schema://url\n"
" j: 101 j2\n"
" p: 102 baz\n"
"▶ p: 875123541 foo bar\n");
@@ -97,8 +99,16 @@
// Look for just a job name.
EXPECT_EQ(RunCommandAndGetOutput("ps j2"),
" j: 1 root\n"
- " j: 100 j1\n"
- " j: 101 j2\n");
+ " j: 100 j1 /some/moniker schema://url\n"
+ " j: 101 j2\n"
+ " p: 102 baz\n");
+
+ // Look for a component name.
+ EXPECT_EQ(RunCommandAndGetOutput("ps moniker"),
+ " j: 1 root\n"
+ " j: 100 j1 /some/moniker schema://url\n"
+ " j: 101 j2\n"
+ " p: 102 baz\n");
// Look for just one process name with a space in it (matches "foo bar").
EXPECT_EQ(RunCommandAndGetOutput("ps o b"),
@@ -107,6 +117,7 @@
// Matches nothing.
EXPECT_EQ(RunCommandAndGetOutput("ps zzz"), "No processes or jobs matching \"zzz\".\n");
+ EXPECT_EQ(RunCommandAndGetOutput("ps some"), "No processes or jobs matching \"some\".\n");
}
} // namespace zxdb
diff --git a/src/sys/root/root-base.shard.cml b/src/sys/root/root-base.shard.cml
index ca34d38..20614c5 100644
--- a/src/sys/root/root-base.shard.cml
+++ b/src/sys/root/root-base.shard.cml
@@ -267,6 +267,16 @@
to: "#core",
},
+ // Offer events to `core` for debugger usage.
+ {
+ event: [
+ "debug_started",
+ "stopped",
+ ],
+ from: "framework",
+ to: "#core",
+ },
+
// Offer boot resolver to `bootstrap` for driver usage.
{
resolver: "boot_resolver",