blob: bbe4382e7c850cc6a04cab71926c54084b1698fa [file]
// Copyright 2018 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_system_interface.h"
#include <string_view>
#include <gtest/gtest.h>
#include "fidl/fuchsia.component/cpp/fidl.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/testing/runtime_dir_helper.h"
#include "src/developer/debug/debug_agent/zircon_process_handle.h"
#include "src/developer/debug/debug_agent/zircon_utils.h"
#include "src/developer/debug/ipc/filter_utils.h"
#include "src/developer/debug/ipc/records.h"
#include "src/developer/debug/shared/event_pair_watcher.h"
#include "src/developer/debug/shared/message_loop.h"
#include "src/developer/debug/shared/message_loop_fuchsia.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
// 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, std::vector<debug_ipc::ComponentInfo>* component_info) {
if (record.koid == koid_to_find) {
*process_name = record.name;
return true;
}
for (const auto& child : record.children) {
if (FindProcess(child, koid_to_find, process_name, component_info)) {
if (!record.components.empty()) {
component_info->insert(component_info->end(), record.components.begin(),
record.components.end());
}
return true;
}
}
return false;
}
} // namespace
class ZirconSystemInterfaceTest : public debug::TestWithLoop, public debug::EventPairWatcher {
public:
debug::MessageLoopFuchsia& loop() {
return reinterpret_cast<debug::MessageLoopFuchsia&>(debug::TestWithLoop::loop());
}
// EventPairWatcher implementation.
void OnPeerClosed(zx_handle_t) override { loop().QuitNow(); }
};
TEST_F(ZirconSystemInterfaceTest, DISABLED_GetProcessTree) {
ZirconSystemInterface system_interface;
system_interface.zircon_component_manager().SetReadyCallback([&]() { loop().QuitNow(); });
loop().Run();
debug_ipc::ProcessTreeRecord root = system_interface.GetProcessTree();
// The root node should be a job with some children.
EXPECT_EQ(debug_ipc::TaskType::kJob, root.type);
EXPECT_FALSE(root.children.empty());
// Query ourself.
auto self = zx::process::self();
zx_koid_t self_koid = zircon::KoidForObject(*self);
ASSERT_NE(ZX_KOID_INVALID, self_koid);
// Our koid should be somewhere in the tree.
std::string process_name;
std::vector<debug_ipc::ComponentInfo> all_component_info;
EXPECT_TRUE(FindProcess(root, self_koid, &process_name, &all_component_info));
// The process_name and component info should match
EXPECT_EQ(zircon::NameForObject(*self), process_name);
ASSERT_FALSE(all_component_info.empty());
ASSERT_EQ(all_component_info.size(), 1ull);
const auto& component_info = all_component_info[0];
EXPECT_EQ(".", component_info.moniker);
// The url will include a hash that cannot be compared.
ASSERT_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";
ASSERT_GE(component_info.url.size(), prefix.size() + suffix.size());
EXPECT_EQ(prefix, component_info.url.substr(0, prefix.size()));
EXPECT_EQ(suffix, component_info.url.substr(component_info.url.size() - suffix.size()));
}
TEST_F(ZirconSystemInterfaceTest, DISABLED_FindComponentInfo) {
ZirconSystemInterface system_interface;
system_interface.zircon_component_manager().SetReadyCallback([&]() { loop().QuitNow(); });
loop().Run();
zx::process handle;
zx::process::self()->duplicate(ZX_RIGHT_SAME_RIGHTS, &handle);
ZirconProcessHandle self(std::move(handle));
auto components = system_interface.GetComponentManager().FindComponentInfo(self);
ASSERT_EQ(components.size(), 1ull);
auto component_info = components[0];
EXPECT_EQ(".", component_info.moniker);
// The url will include a hash that cannot be compared.
ASSERT_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";
ASSERT_GE(component_info.url.size(), prefix.size() + suffix.size());
EXPECT_EQ(prefix, component_info.url.substr(0, prefix.size()));
EXPECT_EQ(suffix, component_info.url.substr(component_info.url.size() - suffix.size()));
}
TEST_F(ZirconSystemInterfaceTest, ComponentEvents) {
ZirconSystemInterface system_interface;
// Need a concrete instance to call |OnComponentEvent| directly.
ZirconComponentManager* zircon_component_manager =
reinterpret_cast<ZirconComponentManager*>(&system_interface.GetComponentManager());
constexpr char kElfMoniker[] = "some/moniker";
constexpr char kElfUrl[] = "fuchsia-pkg://fuchsia.com/package1#meta/component.cm";
constexpr char kNonElfMoniker[] = "some/non_elf/moniker";
constexpr char kNonElfUrl[] = "fuchsia-pkg://fuchsia.com/package2#meta/non_elf.cm";
constexpr zx_koid_t kExpectedKoid = 23;
testing::RuntimeDirHelper helper;
helper.AddJobIdFile(kExpectedKoid);
helper.Start(loop().dispatcher());
// Inject an ELF component.
fuchsia_component::EventHeader header;
header.component_url(kElfUrl);
header.moniker(kElfMoniker);
header.event_type(fuchsia_component::EventType::kDebugStarted);
fuchsia_component::Event event;
event.header() = std::move(header);
zx::eventpair left, right;
ASSERT_EQ(zx_eventpair_create(0, left.reset_and_get_address(), right.reset_and_get_address()),
ZX_OK);
fuchsia_component::DebugStartedPayload payload;
payload.runtime_dir() = helper.GetScopedDirectoryHandle(kExpectedKoid);
payload.break_on_start() = std::move(right);
event.payload() = fuchsia_component::EventPayload::WithDebugStarted(std::move(payload));
// Inject the event.
zircon_component_manager->OnComponentEvent(std::move(event));
debug::MessageLoop::WatchHandle handle;
loop().WatchEventPair(left.get(), this, &handle);
loop().Run();
const auto& info = system_interface.GetComponentManager().FindComponentInfo(kExpectedKoid);
EXPECT_EQ(info.size(), 1u);
EXPECT_EQ(info[0].moniker, kElfMoniker);
EXPECT_EQ(info[0].url, kElfUrl);
fuchsia_component::EventHeader stopped_header;
stopped_header.component_url(kElfUrl);
stopped_header.moniker(kElfMoniker);
stopped_header.event_type(fuchsia_component::EventType::kStopped);
fuchsia_component::Event stopped_event;
stopped_event.header() = std::move(stopped_header);
fuchsia_component::StoppedPayload stopped_payload;
stopped_payload.exit_code(0);
stopped_event.payload() =
fuchsia_component::EventPayload::WithStopped(std::move(stopped_payload));
zircon_component_manager->OnComponentEvent(std::move(stopped_event));
EXPECT_TRUE(system_interface.GetComponentManager().FindComponentInfo(kExpectedKoid).empty());
// Inject a non-ELF component.
fuchsia_component::EventHeader non_elf_header;
non_elf_header.component_url(kNonElfUrl);
non_elf_header.moniker(kNonElfMoniker);
non_elf_header.event_type(fuchsia_component::EventType::kDebugStarted);
fuchsia_component::Event non_elf_event;
non_elf_event.header() = std::move(non_elf_header);
ASSERT_EQ(zx_eventpair_create(0, left.reset_and_get_address(), right.reset_and_get_address()),
ZX_OK);
fuchsia_component::DebugStartedPayload non_elf_payload;
non_elf_payload.runtime_dir() = std::nullopt;
non_elf_payload.break_on_start() = std::move(right);
non_elf_event.payload() =
fuchsia_component::EventPayload::WithDebugStarted(std::move(non_elf_payload));
zircon_component_manager->OnComponentEvent(std::move(non_elf_event));
loop().WatchEventPair(left.get(), this, &handle);
loop().Run();
// Still shouldn't have any ELF component info.
EXPECT_TRUE(system_interface.GetComponentManager().FindComponentInfo(kExpectedKoid).empty());
ASSERT_EQ(system_interface.GetComponentManager().GetNonElfComponentInfo().size(), 1u);
const auto& it = system_interface.GetComponentManager().GetNonElfComponentInfo().begin();
ASSERT_NE(it, system_interface.GetComponentManager().GetNonElfComponentInfo().end());
const auto& component_info = it->second;
EXPECT_EQ(component_info.moniker, kNonElfMoniker);
EXPECT_EQ(component_info.url, kNonElfUrl);
fuchsia_component::EventHeader non_elf_stopped_header;
non_elf_stopped_header.component_url(kNonElfUrl);
non_elf_stopped_header.moniker(kNonElfMoniker);
non_elf_stopped_header.event_type(fuchsia_component::EventType::kStopped);
fuchsia_component::Event non_elf_stopped_event;
non_elf_stopped_event.header() = std::move(non_elf_stopped_header);
fuchsia_component::StoppedPayload non_elf_stopped_payload;
non_elf_stopped_payload.exit_code(0);
non_elf_stopped_event.payload() =
fuchsia_component::EventPayload::WithStopped(std::move(non_elf_stopped_payload));
zircon_component_manager->OnComponentEvent(std::move(non_elf_stopped_event));
loop().RunUntilNoTasks();
EXPECT_TRUE(zircon_component_manager->GetNonElfComponentInfo().empty());
}
TEST_F(ZirconSystemInterfaceTest, FilterMatchComponents) {
ZirconSystemInterface system_interface;
// Create a job tree like this:
// 1: root-job
// 2: child_job1 fake/moniker
// ...
// 5: child_job2 other/moniker
// ...
constexpr zx_koid_t kRootJobKoid = 1;
constexpr zx_koid_t kChildJob1Koid = 2;
constexpr zx_koid_t kChildJob2Koid = 5;
auto& parent_jobs = system_interface.parent_jobs_;
parent_jobs.insert({kChildJob1Koid, kRootJobKoid});
parent_jobs.insert({kChildJob2Koid, kRootJobKoid});
auto& component_info = system_interface.zircon_component_manager().running_component_info_;
component_info.insert(
{kChildJob1Koid, {"fake/moniker", "fuchsia-pkg://fuchsia.com/component1#meta/component.cm"}});
component_info.insert(
{kChildJob2Koid,
{"other/moniker", "fuchsia-pkg://fuchsia.com/some_other#meta/other_component.cm"}});
system_interface.zircon_component_manager().SetReadyCallback([&]() { loop().QuitNow(); });
loop().Run();
debug_ipc::Filter filter{.type = debug_ipc::Filter::Type::kComponentName,
.pattern = "component.cm"};
auto components = system_interface.GetComponentManager().FindComponentInfo(kChildJob1Koid);
EXPECT_EQ(components.size(), 1u);
EXPECT_TRUE(debug_ipc::FilterMatches(filter, "", components));
filter = {.type = debug_ipc::Filter::Type::kComponentUrl,
.pattern = "fuchsia-pkg://fuchsia.com/some_other#meta/other_component.cm"};
components = system_interface.GetComponentManager().FindComponentInfo(kChildJob2Koid);
EXPECT_EQ(components.size(), 1u);
EXPECT_TRUE(debug_ipc::FilterMatches(filter, "", components));
}
TEST_F(ZirconSystemInterfaceTest, FilterMatchComponentsNonElf) {
ZirconSystemInterface system_interface;
constexpr char kMoniker[] = "some/absolute/moniker";
constexpr char kUrl[] = "fuchsia-pkg://fuchsia.com/component2#meta/component2.cm";
// Inject a non-ELF component.
fuchsia_component::EventHeader header;
header.component_url(kUrl);
header.moniker(kMoniker);
header.event_type(fuchsia_component::EventType::kDebugStarted);
fuchsia_component::Event event;
event.header() = std::move(header);
fuchsia_component::DebugStartedPayload payload;
payload.runtime_dir() = std::nullopt;
event.payload() = fuchsia_component::EventPayload::WithDebugStarted(std::move(payload));
system_interface.zircon_component_manager().OnComponentEvent(std::move(event));
std::vector<debug_ipc::ComponentInfo> component_info;
std::ranges::transform(system_interface.GetComponentManager().GetNonElfComponentInfo(),
std::back_inserter(component_info),
[](const auto& pair) -> debug_ipc::ComponentInfo { return pair.second; });
ASSERT_EQ(component_info.size(), 1u);
debug_ipc::Filter moniker_filter;
moniker_filter.type = debug_ipc::Filter::Type::kComponentMoniker;
moniker_filter.pattern = kMoniker;
EXPECT_TRUE(debug_ipc::FilterMatches(moniker_filter, "", component_info));
debug_ipc::Filter url_filter;
url_filter.type = debug_ipc::Filter::Type::kComponentUrl;
url_filter.pattern = kUrl;
EXPECT_TRUE(debug_ipc::FilterMatches(url_filter, "", component_info));
}
} // namespace debug_agent