blob: bc5f7046d71f629a3362443c8eaa0fb70e6cab75 [file] [log] [blame]
// Copyright 2019 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.h"
#include <lib/syslog/cpp/macros.h>
#include <zircon/status.h>
#include <gtest/gtest.h>
#include "src/developer/debug/debug_agent/arch.h"
#include "src/developer/debug/debug_agent/mock_debug_agent_harness.h"
#include "src/developer/debug/debug_agent/mock_exception_handle.h"
#include "src/developer/debug/debug_agent/mock_process.h"
#include "src/developer/debug/debug_agent/mock_process_handle.h"
#include "src/developer/debug/debug_agent/mock_thread_handle.h"
#include "src/developer/debug/debug_agent/test_utils.h"
#include "src/developer/debug/ipc/filter_utils.h"
#include "src/developer/debug/ipc/message_writer.h"
#include "src/developer/debug/ipc/protocol.h"
#include "src/developer/debug/shared/logging/debug.h"
#include "src/developer/debug/shared/message_loop.h"
#include "src/developer/debug/shared/test_with_loop.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace debug_agent {
namespace {
bool HasAttachedProcessWithKoid(DebugAgent* debug_agent, zx_koid_t koid) {
DebuggedProcess* proc = debug_agent->GetDebuggedProcess(koid);
if (!proc)
return false;
// All of our process handles should be mock ones.
return static_cast<MockProcessHandle&>(proc->process_handle()).IsAttached();
}
// Setup -------------------------------------------------------------------------------------------
class DebugAgentMockProcess : public MockProcess {
public:
DebugAgentMockProcess(DebugAgent* debug_agent, zx_koid_t koid, std::string name)
: MockProcess(debug_agent, koid, std::move(name)) {}
~DebugAgentMockProcess() = default;
void SuspendAndSendModules() override {
// Send the modules over to the ipc.
debug_agent()->SendNotification(modules_to_send_);
}
void set_modules_to_send(debug_ipc::NotifyModules m) { modules_to_send_ = std::move(m); }
private:
debug_ipc::NotifyModules modules_to_send_;
};
} // namespace
// Tests -------------------------------------------------------------------------------------------
class DebugAgentTests : public debug::TestWithLoop {};
TEST_F(DebugAgentTests, OnGlobalStatus) {
MockDebugAgentHarness harness;
RemoteAPI* remote_api = harness.debug_agent();
debug_ipc::StatusRequest request = {};
debug_ipc::StatusReply reply = {};
remote_api->OnStatus(request, &reply);
ASSERT_EQ(reply.processes.size(), 0u);
constexpr uint64_t kProcessKoid1 = 0x1234;
const std::string kProcessName1 = "process-1";
constexpr uint64_t kProcess1ThreadKoid1 = 0x1;
auto process1 = std::make_unique<MockProcess>(nullptr, kProcessKoid1, kProcessName1);
process1->AddThread(kProcess1ThreadKoid1);
harness.debug_agent()->InjectProcessForTest(std::move(process1));
reply = {};
remote_api->OnStatus(request, &reply);
ASSERT_EQ(reply.processes.size(), 1u);
EXPECT_EQ(reply.processes[0].process_koid, kProcessKoid1);
EXPECT_EQ(reply.processes[0].process_name, kProcessName1);
ASSERT_EQ(reply.processes[0].threads.size(), 1u);
EXPECT_EQ(reply.processes[0].threads[0].id.process, kProcessKoid1);
EXPECT_EQ(reply.processes[0].threads[0].id.thread, kProcess1ThreadKoid1);
constexpr uint64_t kProcessKoid2 = 0x5678;
const std::string kProcessName2 = "process-2";
constexpr uint64_t kProcess2ThreadKoid1 = 0x1;
constexpr uint64_t kProcess2ThreadKoid2 = 0x2;
auto process2 = std::make_unique<MockProcess>(nullptr, kProcessKoid2, kProcessName2);
process2->AddThread(kProcess2ThreadKoid1);
process2->AddThread(kProcess2ThreadKoid2);
harness.debug_agent()->InjectProcessForTest(std::move(process2));
reply = {};
remote_api->OnStatus(request, &reply);
ASSERT_EQ(reply.processes.size(), 2u);
EXPECT_EQ(reply.processes[0].process_koid, kProcessKoid1);
EXPECT_EQ(reply.processes[0].process_name, kProcessName1);
ASSERT_EQ(reply.processes[0].threads.size(), 1u);
EXPECT_EQ(reply.processes[0].threads[0].id.process, kProcessKoid1);
EXPECT_EQ(reply.processes[0].threads[0].id.thread, kProcess1ThreadKoid1);
EXPECT_EQ(reply.processes[1].process_koid, kProcessKoid2);
EXPECT_EQ(reply.processes[1].process_name, kProcessName2);
ASSERT_EQ(reply.processes[1].threads.size(), 2u);
EXPECT_EQ(reply.processes[1].threads[0].id.process, kProcessKoid2);
EXPECT_EQ(reply.processes[1].threads[0].id.thread, kProcess2ThreadKoid1);
EXPECT_EQ(reply.processes[1].threads[1].id.process, kProcessKoid2);
EXPECT_EQ(reply.processes[1].threads[1].id.thread, kProcess2ThreadKoid2);
// Set a limbo provider.
constexpr zx_koid_t kProcKoid1 = 100;
constexpr zx_koid_t kThreadKoid1 = 101;
harness.system_interface()->mock_limbo_provider().AppendException(
MockProcessHandle(kProcKoid1, "proc1"), MockThreadHandle(kThreadKoid1, "thread1"),
MockExceptionHandle(kThreadKoid1));
constexpr zx_koid_t kProcKoid2 = 102;
constexpr zx_koid_t kThreadKoid2 = 103;
harness.system_interface()->mock_limbo_provider().AppendException(
MockProcessHandle(kProcKoid2, "proc2"), MockThreadHandle(kThreadKoid1, "thread2"),
MockExceptionHandle(kThreadKoid2));
reply = {};
remote_api->OnStatus(request, &reply);
// The attached processes should still be there.
ASSERT_EQ(reply.processes.size(), 2u);
// The limbo processes should be there.
ASSERT_EQ(reply.limbo.size(), 2u);
EXPECT_EQ(reply.limbo[0].process_koid, kProcKoid1);
EXPECT_EQ(reply.limbo[0].process_name, "proc1");
ASSERT_EQ(reply.limbo[0].threads.size(), 1u);
// TODO(donosoc): Add exception type.
}
TEST_F(DebugAgentTests, OnAttachNotFound) {
MockDebugAgentHarness harness;
RemoteAPI* remote_api = harness.debug_agent();
debug_ipc::AttachRequest attach_request;
attach_request.koid = -1;
// Invalid koid should fail.
debug_ipc::AttachReply attach_reply;
remote_api->OnAttach(attach_request, &attach_reply);
EXPECT_TRUE(attach_reply.status.has_error());
constexpr zx_koid_t kProcKoid1 = 100;
constexpr zx_koid_t kThreadKoid1 = 101;
harness.system_interface()->mock_limbo_provider().AppendException(
MockProcessHandle(kProcKoid1, "proc1"), MockThreadHandle(kThreadKoid1, "thread1"),
MockExceptionHandle(kThreadKoid1));
// Even with limbo it should fail.
remote_api->OnAttach(attach_request, &attach_reply);
EXPECT_TRUE(attach_reply.status.has_error());
}
TEST_F(DebugAgentTests, OnAttach) {
constexpr zx_koid_t kProcess1Koid = 11u; // Koid for job1-p2 from the GetMockJobTree() hierarchy.
MockDebugAgentHarness harness;
RemoteAPI* remote_api = harness.debug_agent();
debug_ipc::AttachRequest attach_request;
attach_request.koid = kProcess1Koid;
debug_ipc::AttachReply attach_reply;
remote_api->OnAttach(attach_request, &attach_reply);
// We should've received a watch command (which does the low level exception watching).
EXPECT_TRUE(HasAttachedProcessWithKoid(harness.debug_agent(), kProcess1Koid));
// We should've gotten an attach reply.
EXPECT_TRUE(attach_reply.status.ok());
EXPECT_EQ(attach_reply.koid, kProcess1Koid);
EXPECT_EQ(attach_reply.name, "job1-p2");
// Asking for some invalid process should fail.
attach_request.koid = 0x231315; // Some invalid value.
remote_api->OnAttach(attach_request, &attach_reply);
// We should've gotten an error reply.
EXPECT_TRUE(attach_reply.status.has_error());
// Attaching to a third process should work.
attach_request.koid = 21u;
remote_api->OnAttach(attach_request, &attach_reply);
EXPECT_TRUE(attach_reply.status.ok());
EXPECT_EQ(attach_reply.koid, 21u);
EXPECT_EQ(attach_reply.name, "job121-p2");
// Attaching again to a process should fail.
remote_api->OnAttach(attach_request, &attach_reply);
EXPECT_TRUE(attach_reply.status.has_error());
}
TEST_F(DebugAgentTests, AttachToLimbo) {
// debug::SetDebugMode(true);
// debug::SetLogCategories({debug::LogCategory::kAll});
MockDebugAgentHarness harness;
RemoteAPI* remote_api = harness.debug_agent();
constexpr zx_koid_t kProcKoid = 100;
constexpr zx_koid_t kThreadKoid = 101;
MockProcessHandle mock_process(kProcKoid, "proc");
MockThreadHandle mock_thread(kThreadKoid, "thread");
mock_process.set_threads({mock_thread});
harness.system_interface()->mock_limbo_provider().AppendException(
mock_process, mock_thread, MockExceptionHandle(kThreadKoid));
debug_ipc::AttachRequest attach_request = {};
attach_request.koid = kProcKoid;
debug_ipc::AttachReply attach_reply;
remote_api->OnAttach(attach_request, &attach_reply);
// The threads are only populated on the next tick.
debug::MessageLoop::Current()->RunUntilNoTasks();
// Process should be watching.
EXPECT_TRUE(HasAttachedProcessWithKoid(harness.debug_agent(), kProcKoid));
// We should've gotten an attach reply.
EXPECT_TRUE(attach_reply.status.ok());
EXPECT_EQ(attach_reply.koid, kProcKoid);
EXPECT_EQ(attach_reply.name, "proc");
{
DebuggedProcess* process = harness.debug_agent()->GetDebuggedProcess(kProcKoid);
ASSERT_TRUE(process);
auto threads = process->GetThreads();
ASSERT_EQ(threads.size(), 1u);
// Search for the exception thread.
DebuggedThread* exception_thread = nullptr;
for (DebuggedThread* thread : threads) {
if (thread->koid() == kThreadKoid) {
exception_thread = thread;
break;
}
}
ASSERT_TRUE(exception_thread);
ASSERT_TRUE(exception_thread->in_exception());
EXPECT_EQ(exception_thread->exception_handle()->GetThreadHandle()->GetKoid(), kThreadKoid);
}
}
TEST_F(DebugAgentTests, OnEnterLimbo) {
MockDebugAgentHarness harness;
constexpr zx_koid_t kProcKoid1 = 100;
constexpr zx_koid_t kThreadKoid1 = 101;
harness.system_interface()->mock_limbo_provider().AppendException(
MockProcessHandle(kProcKoid1, "proc1"), MockThreadHandle(kThreadKoid1, "thread1"),
MockExceptionHandle(kThreadKoid1));
// Call the limbo.
harness.system_interface()->mock_limbo_provider().CallOnEnterLimbo();
// Should've sent a notification.
{
ASSERT_EQ(harness.stream_backend()->process_starts().size(), 1u);
auto& process_start = harness.stream_backend()->process_starts()[0];
EXPECT_EQ(process_start.type, debug_ipc::NotifyProcessStarting::Type::kLimbo);
EXPECT_EQ(process_start.koid, kProcKoid1);
EXPECT_EQ(process_start.name, "proc1");
}
}
TEST_F(DebugAgentTests, DetachFromLimbo) {
MockDebugAgentHarness harness;
RemoteAPI* remote_api = harness.debug_agent();
constexpr zx_koid_t kProcKoid = 14; // MockJobTree job11-p1
// Attempting to detach to a process that doesn't exist should fail.
{
debug_ipc::DetachRequest request = {};
request.koid = kProcKoid;
debug_ipc::DetachReply reply = {};
remote_api->OnDetach(request, &reply);
ASSERT_TRUE(reply.status.has_error());
ASSERT_EQ(harness.system_interface()->mock_limbo_provider().release_calls().size(), 0u);
}
// Adding it should now find it and remove it.
constexpr zx_koid_t kProcKoid1 = 100;
constexpr zx_koid_t kThreadKoid1 = 101;
harness.system_interface()->mock_limbo_provider().AppendException(
MockProcessHandle(kProcKoid1, "proc1"), MockThreadHandle(kThreadKoid1, "thread1"),
MockExceptionHandle(kThreadKoid1));
{
debug_ipc::DetachRequest request = {};
request.koid = kProcKoid1;
debug_ipc::DetachReply reply = {};
remote_api->OnDetach(request, &reply);
ASSERT_TRUE(reply.status.ok());
ASSERT_EQ(harness.system_interface()->mock_limbo_provider().release_calls().size(), 1u);
EXPECT_EQ(harness.system_interface()->mock_limbo_provider().release_calls()[0], kProcKoid1);
}
// This should've remove it from limbo, trying it again should fail.
{
debug_ipc::DetachRequest request = {};
request.koid = kProcKoid1;
debug_ipc::DetachReply reply = {};
remote_api->OnDetach(request, &reply);
ASSERT_TRUE(reply.status.has_error());
ASSERT_EQ(harness.system_interface()->mock_limbo_provider().release_calls().size(), 1u);
EXPECT_EQ(harness.system_interface()->mock_limbo_provider().release_calls()[0], kProcKoid1);
}
}
TEST_F(DebugAgentTests, Kill) {
MockDebugAgentHarness harness;
RemoteAPI* remote_api = harness.debug_agent();
constexpr zx_koid_t kProcKoid = 14; // MockJobTree job11-p1
// Attempt to kill a process that's not there should fail.
{
debug_ipc::KillRequest kill_request = {};
kill_request.process_koid = kProcKoid;
debug_ipc::KillReply kill_reply = {};
remote_api->OnKill(kill_request, &kill_reply);
ASSERT_TRUE(kill_reply.status.has_error());
}
// Attach to a process so that the debugger knows about it.
{
debug_ipc::AttachRequest attach_request = {};
attach_request.koid = kProcKoid;
debug_ipc::AttachReply attach_reply;
remote_api->OnAttach(attach_request, &attach_reply);
// There should be a process.
ASSERT_EQ(harness.debug_agent()->procs_.size(), 1u);
// Should not come from limbo.
EXPECT_FALSE(harness.debug_agent()->procs_.begin()->second->from_limbo());
}
// Killing now should work.
{
debug_ipc::KillRequest kill_request = {};
kill_request.process_koid = kProcKoid;
debug_ipc::KillReply kill_reply = {};
remote_api->OnKill(kill_request, &kill_reply);
// There should be no more processes.
ASSERT_EQ(harness.debug_agent()->procs_.size(), 0u);
// Killing again should fail.
remote_api->OnKill(kill_request, &kill_reply);
ASSERT_TRUE(kill_reply.status.has_error());
}
// Add the process to the limbo.
constexpr zx_koid_t kLimboProcKoid = 100;
constexpr zx_koid_t kLimboThreadKoid = 101;
MockProcessHandle mock_process(kLimboProcKoid, "proc");
// This is a limbo process so we can not kill it.
mock_process.set_kill_status(debug::Status("Access denied"));
MockThreadHandle mock_thread(kLimboThreadKoid, "thread");
MockExceptionHandle mock_exception(kLimboThreadKoid);
mock_process.set_threads({mock_thread});
harness.system_interface()->mock_limbo_provider().AppendException(mock_process, mock_thread,
mock_exception);
// There should be no more processes.
ASSERT_EQ(harness.debug_agent()->procs_.size(), 0u);
// Killing now should release it.
{
debug_ipc::KillRequest kill_request = {};
kill_request.process_koid = kLimboProcKoid;
debug_ipc::KillReply kill_reply = {};
remote_api->OnKill(kill_request, &kill_reply);
ASSERT_TRUE(kill_reply.status.ok());
ASSERT_EQ(harness.system_interface()->mock_limbo_provider().release_calls().size(), 1u);
EXPECT_EQ(harness.system_interface()->mock_limbo_provider().release_calls()[0], kLimboProcKoid);
// Killing again should not find it.
remote_api->OnKill(kill_request, &kill_reply);
ASSERT_TRUE(kill_reply.status.has_error());
}
harness.system_interface()->mock_limbo_provider().AppendException(mock_process, mock_thread,
mock_exception);
debug_ipc::AttachRequest attach_request = {};
attach_request.koid = kLimboProcKoid;
debug_ipc::AttachReply attach_reply;
remote_api->OnAttach(attach_request, &attach_reply);
// There should be a process.
ASSERT_EQ(harness.debug_agent()->procs_.size(), 1u);
{
auto it = harness.debug_agent()->procs_.find(kLimboProcKoid);
ASSERT_NE(it, harness.debug_agent()->procs_.end());
EXPECT_TRUE(harness.debug_agent()->procs_.begin()->second->from_limbo());
// Killing it should free the process.
debug_ipc::KillRequest kill_request = {};
kill_request.process_koid = kLimboProcKoid;
debug_ipc::KillReply kill_reply = {};
remote_api->OnKill(kill_request, &kill_reply);
ASSERT_TRUE(kill_reply.status.ok());
ASSERT_EQ(harness.debug_agent()->procs_.size(), 0u);
// There should be a limbo process to be killed.
ASSERT_EQ(harness.debug_agent()->killed_limbo_procs_.size(), 1u);
EXPECT_EQ(harness.debug_agent()->killed_limbo_procs_.count(kLimboProcKoid), 1u);
// There should've have been more release calls (yet).
ASSERT_EQ(harness.system_interface()->mock_limbo_provider().release_calls().size(), 1u);
// When the process "re-enters" the limbo, it should be removed.
harness.system_interface()->mock_limbo_provider().AppendException(mock_process, mock_thread,
mock_exception);
harness.system_interface()->mock_limbo_provider().CallOnEnterLimbo();
// There should not be an additional proc in the agent.
ASSERT_EQ(harness.debug_agent()->procs_.size(), 0u);
// There should've been a release call.
ASSERT_EQ(harness.system_interface()->mock_limbo_provider().release_calls().size(), 2u);
EXPECT_EQ(harness.system_interface()->mock_limbo_provider().release_calls()[1], kLimboProcKoid);
}
}
TEST_F(DebugAgentTests, OnUpdateGlobalSettings) {
MockDebugAgentHarness harness;
RemoteAPI* remote_api = harness.debug_agent();
// The default strategy should be first chance for a type that has yet to be
// updated.
EXPECT_EQ(debug_ipc::ExceptionStrategy::kFirstChance,
harness.debug_agent()->GetExceptionStrategy(debug_ipc::ExceptionType::kGeneral));
EXPECT_EQ(debug_ipc::ExceptionStrategy::kFirstChance,
harness.debug_agent()->GetExceptionStrategy(debug_ipc::ExceptionType::kPageFault));
{
const debug_ipc::UpdateGlobalSettingsRequest request = {
.exception_strategies =
{
{
.type = debug_ipc::ExceptionType::kGeneral,
.value = debug_ipc::ExceptionStrategy::kSecondChance,
},
{
.type = debug_ipc::ExceptionType::kPageFault,
.value = debug_ipc::ExceptionStrategy::kSecondChance,
},
},
};
debug_ipc::UpdateGlobalSettingsReply reply;
remote_api->OnUpdateGlobalSettings(request, &reply);
EXPECT_TRUE(reply.status.ok());
}
EXPECT_EQ(debug_ipc::ExceptionStrategy::kSecondChance,
harness.debug_agent()->GetExceptionStrategy(debug_ipc::ExceptionType::kGeneral));
EXPECT_EQ(debug_ipc::ExceptionStrategy::kSecondChance,
harness.debug_agent()->GetExceptionStrategy(debug_ipc::ExceptionType::kPageFault));
{
const debug_ipc::UpdateGlobalSettingsRequest request = {
.exception_strategies =
{
{
.type = debug_ipc::ExceptionType::kGeneral,
.value = debug_ipc::ExceptionStrategy::kFirstChance,
},
},
};
debug_ipc::UpdateGlobalSettingsReply reply;
remote_api->OnUpdateGlobalSettings(request, &reply);
EXPECT_TRUE(reply.status.ok());
}
EXPECT_EQ(debug_ipc::ExceptionStrategy::kFirstChance,
harness.debug_agent()->GetExceptionStrategy(debug_ipc::ExceptionType::kGeneral));
EXPECT_EQ(debug_ipc::ExceptionStrategy::kSecondChance,
harness.debug_agent()->GetExceptionStrategy(debug_ipc::ExceptionType::kPageFault));
}
TEST_F(DebugAgentTests, WeakFilterMatchDoesNotSendModules) {
MockDebugAgentHarness harness;
RemoteAPI* remote_api = harness.debug_agent();
constexpr char kProcessName[] = "process-1";
constexpr uint64_t kProcessKoid = 0x12345;
debug_ipc::UpdateFilterRequest request;
auto& filter = request.filters.emplace_back();
filter.type = debug_ipc::Filter::Type::kProcessName;
filter.pattern = kProcessName;
filter.id = 1;
filter.config.weak = true;
debug_ipc::UpdateFilterReply reply;
remote_api->OnUpdateFilter(request, &reply);
EXPECT_TRUE(reply.matched_processes_for_filter.empty());
harness.debug_agent()->OnProcessChanged(
DebugAgent::ProcessChangedHow::kStarting,
std::make_unique<MockProcessHandle>(kProcessKoid, kProcessName));
// We should have sent a process starting notification, but no modules.
EXPECT_FALSE(harness.stream_backend()->process_starts().empty());
EXPECT_TRUE(harness.stream_backend()->modules().empty());
}
TEST_F(DebugAgentTests, RecursiveFilterAppliesImplicitFilter) {
MockDebugAgentHarness harness;
RemoteAPI* remote_api = harness.debug_agent();
constexpr char kRootComponentUrl[] = "fuchsia-pkg://devhost/root_package#meta/root_component.cm";
constexpr char kRootComponentMoniker[] = "some/moniker/root:test";
constexpr char kSubpackageUrl[] = "#meta/child.cm";
constexpr char kSubpackageMoniker[] = "some/moniker/root:test/driver";
debug_ipc::UpdateFilterRequest request;
auto& filter = request.filters.emplace_back();
filter.type = debug_ipc::Filter::Type::kComponentUrl;
filter.pattern = kRootComponentUrl;
filter.config.recursive = true;
debug_ipc::UpdateFilterReply reply;
remote_api->OnUpdateFilter(request, &reply);
harness.debug_agent()->OnComponentStarted(kRootComponentMoniker, kRootComponentUrl,
ZX_KOID_INVALID);
// There should now be TWO filters, one of which the frontend doesn't know about.
// Now we start the child component, which contains a program, the job's koid doesn't matter for
// this test.
harness.debug_agent()->OnComponentStarted(kSubpackageMoniker, kSubpackageUrl, ZX_KOID_INVALID);
EXPECT_EQ(harness.stream_backend()->component_starts().size(), 2u);
EXPECT_EQ(harness.stream_backend()->component_starts()[1].component.url, kSubpackageUrl);
EXPECT_EQ(harness.stream_backend()->component_starts()[1].component.moniker, kSubpackageMoniker);
}
TEST_F(DebugAgentTests, AttachToExistingJob) {
MockDebugAgentHarness harness;
RemoteAPI* remote_api = harness.debug_agent();
constexpr zx_koid_t kJobKoid = 8;
constexpr zx_koid_t kProcessKoid = 9;
debug_ipc::AttachRequest request;
request.koid = kJobKoid;
request.config.target = debug_ipc::AttachConfig::Target::kJob;
request.config.weak = true;
debug_ipc::AttachReply reply;
remote_api->OnAttach(request, &reply);
// Because we didn't inject all of the notifications.
auto debugged_job = harness.debug_agent()->GetDebuggedJob(kJobKoid);
ASSERT_TRUE(debugged_job);
// Simply attaching to a job won't necessarily create the DebuggedProcess objects (i.e. the client
// issued a direct attach command to an already running component instead of installing a filter
// and waiting for a matching component to start).
EXPECT_FALSE(harness.debug_agent()->GetDebuggedProcess(kProcessKoid));
}
TEST_F(DebugAgentTests, JobOnlyFilter) {
MockDebugAgentHarness harness;
RemoteAPI* remote_api = harness.debug_agent();
// Matches the default job tree in MockSystemInterface.
constexpr zx_koid_t kJobKoid = 25;
constexpr zx_koid_t kProcessKoid = 26;
constexpr char kComponentRootMoniker[] = "fixed/moniker";
debug_ipc::UpdateFilterRequest request;
auto& filter = request.filters.emplace_back();
filter.type = debug_ipc::Filter::Type::kComponentMonikerSuffix;
filter.pattern = kComponentRootMoniker;
filter.config.job_only = true;
// By specifying a strong attach, we'll claim the job's exception channel, not the debugger
// exception channel.
filter.config.weak = false;
debug_ipc::UpdateFilterReply reply;
remote_api->OnUpdateFilter(request, &reply);
// Should have matched the existing component.
EXPECT_EQ(reply.matched_processes_for_filter.size(), 1u);
EXPECT_EQ(reply.matched_processes_for_filter[0].matched_pids.size(), 1u);
EXPECT_EQ(reply.matched_processes_for_filter[0].matched_pids[0], kJobKoid);
debug_ipc::AttachRequest attach_request;
attach_request.koid = reply.matched_processes_for_filter[0].matched_pids[0];
attach_request.config.target = debug_ipc::AttachConfig::Target::kJob;
attach_request.config.weak = false;
debug_ipc::AttachReply attach_reply;
remote_api->OnAttach(attach_request, &attach_reply);
EXPECT_TRUE(attach_reply.status.ok()) << attach_reply.status.message();
// The job_only filter will cause the DebuggedProcess object to be created and propagate the
// process starting notification.
harness.debug_agent()->OnProcessChanged(
DebugAgent::ProcessChangedHow::kStarting,
harness.debug_agent()->system_interface().GetProcess(kProcessKoid));
// We should now have both a DebuggedJob and a DebuggedProcess for this job and process. The
// process should *not* have the exception channel bound, because the filter was configured as
// job_only.
EXPECT_EQ(harness.stream_backend()->process_starts().size(), 1u);
EXPECT_TRUE(harness.debug_agent()->GetDebuggedJob(kJobKoid));
// We should have a DebuggedProcess object, but should not be attached to its exception channel.
ASSERT_TRUE(harness.debug_agent()->GetDebuggedProcess(kProcessKoid));
EXPECT_FALSE(harness.debug_agent()->GetDebuggedProcess(kProcessKoid)->IsAttached());
}
TEST_F(DebugAgentTests, JobOnlyFilterDoesNotAttachToChildJobs) {
MockDebugAgentHarness harness;
RemoteAPI* remote_api = harness.debug_agent();
// Matches the default job tree in MockSystemInterface. This is "job1".
constexpr zx_koid_t kJobKoid = 8;
// Direct child process of "job1".
constexpr zx_koid_t kProcessKoid = 9;
// This is a child job of the above, "job11".
constexpr zx_koid_t kSecondJobKoid = 13;
// Child of job11.
constexpr zx_koid_t kSecondProcessKoid = 14;
// Component moniker associated with all above jobs.
constexpr char kComponentRootMoniker[] = "/moniker";
debug_ipc::UpdateFilterRequest request;
auto& filter = request.filters.emplace_back();
filter.type = debug_ipc::Filter::Type::kComponentMonikerSuffix;
filter.pattern = kComponentRootMoniker;
filter.config.job_only = true;
// By specifying a strong attach, we'll claim the job's exception channel, not the debugger
// exception channel.
filter.config.weak = false;
debug_ipc::UpdateFilterReply reply;
// We don't need to worry about the contents of the reply, it will have all of the child processes
// under the component. In a real system, the client will choose what to attach to.
remote_api->OnUpdateFilter(request, &reply);
// Attach to the root job of the component from the component starting event.
harness.debug_agent()->OnComponentStarted(kComponentRootMoniker, "some/url", kJobKoid);
// Send the notification that the child process of "job1" started. This one will always start
// first, and will create the DebuggedProcess objects.
harness.debug_agent()->OnProcessChanged(
DebugAgent::ProcessChangedHow::kStarting,
harness.debug_agent()->system_interface().GetProcess(kProcessKoid));
// Send the notification that the child process of "job11" started. This should _not_ attach to
harness.debug_agent()->OnProcessChanged(
DebugAgent::ProcessChangedHow::kStarting,
harness.debug_agent()->system_interface().GetProcess(kSecondProcessKoid));
// We should now have both processes specified above. Neither process should have the exception
// channel bound, because the filter was configured as job_only. Furthermore, the parent job
// _should_ be attached (i.e. we'll have a DebuggedJob object for it) and the child job should
// _not_ be attached.
EXPECT_EQ(harness.stream_backend()->process_starts().size(), 2u);
EXPECT_TRUE(harness.debug_agent()->GetDebuggedJob(kJobKoid));
// We are _not_ attached to this second job, even though it would also match the component filter.
EXPECT_FALSE(harness.debug_agent()->GetDebuggedJob(kSecondJobKoid));
}
// This test is very similar to the above test. The difference is in the component structure. This
// test's filter matches multiple components, which respectively in a real system would have unique
// job_ids, and therefore return multiple results in the filter matching logic. This is different
// from the above situation where one component (and its corresponding job) contains multiple jobs
// but no child components.
TEST_F(DebugAgentTests, DoNotAttachToChildJobs) {
MockDebugAgentHarness harness;
RemoteAPI* remote_api = harness.debug_agent();
// From the default MockSystemInterface.
constexpr zx_koid_t kParentJobKoid = 35;
constexpr zx_koid_t kChildJobKoid = 38;
constexpr char kRootComponentMoniker[] = "/some/moniker";
constexpr char kChildComponentMoniker[] = "/some/other/moniker";
// Start the components before the filter. The filter should match immediately in |OnUpdateFilter|
// below.
harness.debug_agent()->OnComponentStarted(kRootComponentMoniker, "some/url", kParentJobKoid);
harness.debug_agent()->OnComponentStarted(kChildComponentMoniker, "some/url", kParentJobKoid);
// This realm has two ELF components, one is the realm's root, the other is a child. They each
// have distinctive job_ids.
constexpr char kMonikerPrefix[] = "/some";
debug_ipc::UpdateFilterRequest request;
auto& filter = request.filters.emplace_back();
filter.type = debug_ipc::Filter::Type::kComponentMonikerPrefix;
filter.pattern = kMonikerPrefix;
filter.config.job_only = true;
// By specifying a strong attach, we'll claim the job's exception channel, not the debugger
// exception channel.
filter.config.weak = false;
debug_ipc::UpdateFilterReply reply;
remote_api->OnUpdateFilter(request, &reply);
// We should have 1 filter match and two job koids.
EXPECT_FALSE(reply.matched_processes_for_filter.empty());
EXPECT_EQ(reply.matched_processes_for_filter.size(), 1u);
EXPECT_EQ(reply.matched_processes_for_filter[0].matched_pids.size(), 2u);
auto configs =
debug_ipc::GetAttachConfigsForFilterMatches(reply.matched_processes_for_filter, {filter});
// We will attempt to attach to both.
EXPECT_EQ(configs.size(), 2u);
for (const auto& [koid, config] : configs) {
debug_ipc::AttachRequest request;
request.koid = koid;
request.config = config;
debug_ipc::AttachReply reply;
remote_api->OnAttach(request, &reply);
if (koid == kParentJobKoid) {
EXPECT_TRUE(reply.status.ok());
} else {
// We should get an already exists error for the child job.
EXPECT_TRUE(reply.status.has_error());
EXPECT_EQ(reply.status.type(), debug::Status::Type::kAlreadyExists);
}
}
EXPECT_TRUE(harness.debug_agent()->GetDebuggedJob(kParentJobKoid));
EXPECT_FALSE(harness.debug_agent()->GetDebuggedJob(kChildJobKoid));
}
} // namespace debug_agent