blob: b20d2e23d7539b4c4a57542b23b5900cebc45b7d [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 <zircon/status.h>
#include <gtest/gtest.h>
#include "src/developer/debug/debug_agent/arch_provider_impl.h"
#include "src/developer/debug/debug_agent/local_stream_backend.h"
#include "src/developer/debug/debug_agent/mock_object_provider.h"
#include "src/developer/debug/debug_agent/mock_process.h"
#include "src/developer/debug/debug_agent/system_info.h"
#include "src/developer/debug/debug_agent/test_utils.h"
#include "src/developer/debug/ipc/agent_protocol.h"
#include "src/developer/debug/ipc/message_writer.h"
#include "src/developer/debug/shared/platform_message_loop.h"
#include "src/lib/fxl/logging.h"
#include "src/lib/fxl/strings/string_printf.h"
using namespace fuchsia::exception;
namespace debug_agent {
namespace {
// Setup -------------------------------------------------------------------------------------------
class DebugAgentMessageLoop : public debug_ipc::PlatformMessageLoop {
public:
DebugAgentMessageLoop() {
std::string error_message;
bool success = Init(&error_message);
FXL_CHECK(success) << error_message;
}
~DebugAgentMessageLoop() { Cleanup(); }
void StopWatching(int id) override {}
zx_status_t WatchProcessExceptions(WatchProcessConfig config, WatchHandle* out) override {
watches_.push_back(std::move(config));
*out = WatchHandle(this, next_watch_id_++);
return ZX_OK;
}
const std::vector<WatchProcessConfig> watches() const { return watches_; }
private:
int next_watch_id_ = 1;
std::vector<WatchProcessConfig> watches_;
};
class DebugAgentStreamBackend : public LocalStreamBackend {
public:
void HandleAttach(debug_ipc::AttachReply attach_reply) override {
attach_replies_.push_back(std::move(attach_reply));
}
void HandleNotifyProcessStarting(debug_ipc::NotifyProcessStarting notification) override {
process_starts_.push_back(std::move(notification));
}
void HandleNotifyModules(debug_ipc::NotifyModules modules) override {
modules_.push_back(std::move(modules));
}
const std::vector<debug_ipc::AttachReply>& attach_replies() const { return attach_replies_; }
const std::vector<debug_ipc::NotifyProcessStarting>& process_starts() const {
return process_starts_;
}
const std::vector<debug_ipc::NotifyModules> modules() const { return modules_; }
private:
std::vector<debug_ipc::AttachReply> attach_replies_;
std::vector<debug_ipc::NotifyProcessStarting> process_starts_;
std::vector<debug_ipc::NotifyModules> modules_;
};
class DebugAgentMockProcess : public MockProcess {
public:
DebugAgentMockProcess(DebugAgent* debug_agent, zx_koid_t koid, std::string name,
std::shared_ptr<ObjectProvider> object_provider,
std::shared_ptr<arch::ArchProvider> arch_provider)
: MockProcess(debug_agent, koid, std::move(name), std::move(arch_provider),
std::move(object_provider)) {}
~DebugAgentMockProcess() = default;
void SuspendAndSendModulesIfKnown() override {
// Send the modules over to the ipc.
debug_ipc::MessageWriter writer;
debug_ipc::WriteNotifyModules(modules_to_send_, &writer);
debug_agent()->stream()->Write(writer.MessageComplete());
};
void set_modules_to_send(debug_ipc::NotifyModules m) { modules_to_send_ = std::move(m); }
private:
debug_ipc::NotifyModules modules_to_send_;
};
class MockLimboProvider : public LimboProvider {
public:
MockLimboProvider() : LimboProvider(nullptr) {}
const std::map<zx_koid_t, ProcessExceptionMetadata>& Limbo() const override { return limbo_; }
const std::vector<zx_koid_t> release_calls() const { return release_calls_; }
bool Valid() const override { return true; }
zx_status_t RetrieveException(zx_koid_t process_koid,
fuchsia::exception::ProcessException* out) override {
auto it = limbo_.find(process_koid);
if (it == limbo_.end())
return ZX_ERR_NOT_FOUND;
*out = ToProcessException(it->second);
limbo_.erase(it);
return ZX_OK;
}
void AppendException(const MockProcessObject* process, const MockThreadObject* thread,
ExceptionType exception_type) {
ExceptionInfo info = {};
info.process_koid = process->koid;
info.thread_koid = thread->koid;
info.type = exception_type;
ProcessExceptionMetadata metadata = {};
metadata.set_info(std::move(info));
metadata.set_process(process->GetHandle());
metadata.set_thread(thread->GetHandle());
limbo_[process->koid] = std::move(metadata);
}
void CallOnEnterLimbo() {
FXL_DCHECK(on_enter_limbo_);
std::vector<ProcessExceptionMetadata> processes;
processes.reserve(limbo_.size());
for (const auto& [process_koid, metadata] : limbo_) {
processes.push_back(CopyMetadata(metadata));
}
on_enter_limbo_(std::move(processes));
}
zx_status_t ReleaseProcess(zx_koid_t process_koid) override {
release_calls_.push_back(process_koid);
auto it = limbo_.find(process_koid);
if (it == limbo_.end())
return ZX_ERR_NOT_FOUND;
limbo_.erase(it);
return ZX_OK;
}
private:
ProcessExceptionMetadata CopyMetadata(const ProcessExceptionMetadata& metadata) {
ProcessExceptionMetadata exception = {};
exception.set_info(metadata.info());
exception.set_process(zx::process(metadata.info().process_koid));
exception.set_thread(zx::thread(metadata.info().thread_koid));
return exception;
}
ProcessException ToProcessException(const ProcessExceptionMetadata& metadata) {
ProcessException exception = {};
exception.set_info(metadata.info());
exception.set_exception(zx::exception(metadata.info().thread_koid));
exception.set_process(zx::process(metadata.info().process_koid));
exception.set_thread(zx::thread(metadata.info().thread_koid));
return exception;
}
std::map<zx_koid_t, ProcessExceptionMetadata> limbo_;
std::vector<zx_koid_t> release_calls_;
};
class TestObjectProvider : public MockObjectProvider {
public:
zx_status_t Kill(zx_handle_t) override { return next_kill_status_; }
void set_next_kill_status(zx_status_t status) { next_kill_status_ = status; }
private:
zx_status_t next_kill_status_ = ZX_OK;
};
std::pair<const MockProcessObject*, const MockThreadObject*> GetProcessThread(
const MockObjectProvider& object_provider, const std::string& process_name,
const std::string& thread_name) {
const MockProcessObject* process = object_provider.ProcessByName(process_name);
FXL_DCHECK(process);
const MockThreadObject* thread = process->GetThread(thread_name);
FXL_DCHECK(thread);
return {process, thread};
}
struct TestContext {
DebugAgentMessageLoop loop;
DebugAgentStreamBackend stream_backend;
std::shared_ptr<TestObjectProvider> object_provider;
std::shared_ptr<MockLimboProvider> limbo_provider;
std::shared_ptr<arch::ArchProvider> arch_provider;
};
SystemProviders ToSystemProviders(const TestContext& context) {
SystemProviders providers;
providers.arch_provider = context.arch_provider;
providers.limbo_provider = context.limbo_provider;
providers.object_provider = context.object_provider;
return providers;
}
std::unique_ptr<TestContext> CreateTestContext() {
auto context = std::make_unique<TestContext>();
context->arch_provider = std::make_shared<ArchProviderImpl>();
context->limbo_provider = std::make_shared<MockLimboProvider>();
context->object_provider = std::make_unique<TestObjectProvider>();
FillInMockObjectProvider(context->object_provider.get());
return context;
}
} // namespace
// Tests -------------------------------------------------------------------------------------------
class DebugAgentTests : public ::testing::Test {};
TEST_F(DebugAgentTests, OnGlobalStatus) {
auto test_context = CreateTestContext();
DebugAgent debug_agent(nullptr, ToSystemProviders(*test_context));
debug_agent.Connect(&test_context->stream_backend.stream());
RemoteAPI* remote_api = &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,
test_context->arch_provider, test_context->object_provider);
process1->AddThread(kProcess1ThreadKoid1);
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].process_koid, kProcessKoid1);
EXPECT_EQ(reply.processes[0].threads[0].thread_koid, 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,
test_context->arch_provider, test_context->object_provider);
process2->AddThread(kProcess2ThreadKoid1);
process2->AddThread(kProcess2ThreadKoid2);
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].process_koid, kProcessKoid1);
EXPECT_EQ(reply.processes[0].threads[0].thread_koid, 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].process_koid, kProcessKoid2);
EXPECT_EQ(reply.processes[1].threads[0].thread_koid, kProcess2ThreadKoid1);
EXPECT_EQ(reply.processes[1].threads[1].process_koid, kProcessKoid2);
EXPECT_EQ(reply.processes[1].threads[1].thread_koid, kProcess2ThreadKoid2);
// Set a limbo provider.
const MockObjectProvider& object_provider = *test_context->object_provider;
const std::string kLimboProcess1 = "job1-p1";
const std::string kLimboProcess1Thread = "initial-thread";
constexpr ExceptionType kLimboException1 = ExceptionType::FATAL_PAGE_FAULT;
auto [limbo_proc1, limbo_thread1] =
GetProcessThread(object_provider, kLimboProcess1, kLimboProcess1Thread);
const std::string kLimboProcess2 = "job121-p2";
const std::string kLimboProcess2Thread = "second-thread";
constexpr ExceptionType kLimboException2 = ExceptionType::UNALIGNED_ACCESS;
auto [limbo_proc2, limbo_thread2] =
GetProcessThread(object_provider, kLimboProcess2, kLimboProcess2Thread);
test_context->limbo_provider->AppendException(limbo_proc1, limbo_thread1, kLimboException1);
test_context->limbo_provider->AppendException(limbo_proc2, limbo_thread2, kLimboException2);
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, limbo_proc1->koid);
EXPECT_EQ(reply.limbo[0].process_name, limbo_proc1->name);
ASSERT_EQ(reply.limbo[0].threads.size(), 1u);
EXPECT_EQ(reply.limbo[0].threads[0].process_koid, limbo_proc1->koid);
EXPECT_EQ(reply.limbo[0].threads[0].thread_koid, limbo_thread1->koid);
EXPECT_EQ(reply.limbo[0].threads[0].name, limbo_thread1->name);
EXPECT_EQ(reply.limbo[0].threads[0].state, debug_ipc::ThreadRecord::State::kBlocked);
EXPECT_EQ(reply.limbo[0].threads[0].blocked_reason,
debug_ipc::ThreadRecord::BlockedReason::kException);
// TODO(donosoc): Add exception type.
}
TEST_F(DebugAgentTests, OnProcessStatus) {
auto test_context = CreateTestContext();
DebugAgent debug_agent(nullptr, ToSystemProviders(*test_context));
debug_agent.Connect(&test_context->stream_backend.stream());
RemoteAPI* remote_api = &debug_agent;
constexpr uint64_t kProcessKoid1 = 0x1234;
std::string kProcessName1 = "process-1";
auto process1 = std::make_unique<DebugAgentMockProcess>(
&debug_agent, kProcessKoid1, kProcessName1, test_context->object_provider,
test_context->arch_provider);
debug_agent.InjectProcessForTest(std::move(process1));
constexpr uint64_t kProcessKoid2 = 0x5678;
std::string kProcessName2 = "process-2";
auto process2 = std::make_unique<DebugAgentMockProcess>(
&debug_agent, kProcessKoid2, kProcessName2, test_context->object_provider,
test_context->arch_provider);
auto* process2_ptr = process2.get();
debug_agent.InjectProcessForTest(std::move(process2));
// Asking for a un-existent process should fail.
debug_ipc::ProcessStatusRequest request = {};
request.process_koid = 0xdeadbeef;
debug_ipc::ProcessStatusReply reply = {};
remote_api->OnProcessStatus(request, &reply);
EXPECT_EQ(reply.status, (uint32_t)ZX_ERR_NOT_FOUND) << zx_status_get_string(reply.status);
debug_ipc::NotifyModules modules_to_send = {};
modules_to_send.process_koid = kProcessKoid2;
modules_to_send.modules.push_back({"module-1", 0x1, 0x5, "build-1"});
modules_to_send.modules.push_back({"module-2", 0x2, 0x7, "build-2"});
process2_ptr->set_modules_to_send(modules_to_send);
// Asking for an existent one should send the process and modules notification.
request.process_koid = kProcessKoid2;
remote_api->OnProcessStatus(request, &reply);
EXPECT_EQ(reply.status, (uint32_t)ZX_OK) << zx_status_get_string(reply.status);
test_context->loop.RunUntilNoTasks();
auto& process_starts = test_context->stream_backend.process_starts();
ASSERT_EQ(process_starts.size(), 1u);
EXPECT_EQ(process_starts[0].koid, kProcessKoid2);
EXPECT_EQ(process_starts[0].name, kProcessName2);
auto& modules = test_context->stream_backend.modules();
ASSERT_EQ(modules.size(), 1u);
EXPECT_EQ(modules[0].process_koid, kProcessKoid2);
ASSERT_EQ(modules[0].modules.size(), modules_to_send.modules.size());
ASSERT_EQ(modules[0].modules[0].name, modules_to_send.modules[0].name);
ASSERT_EQ(modules[0].modules[0].base, modules_to_send.modules[0].base);
ASSERT_EQ(modules[0].modules[0].debug_address, modules_to_send.modules[0].debug_address);
ASSERT_EQ(modules[0].modules[0].build_id, modules_to_send.modules[0].build_id);
ASSERT_EQ(modules[0].modules[1].name, modules_to_send.modules[1].name);
ASSERT_EQ(modules[0].modules[1].base, modules_to_send.modules[1].base);
ASSERT_EQ(modules[0].modules[1].debug_address, modules_to_send.modules[1].debug_address);
ASSERT_EQ(modules[0].modules[1].build_id, modules_to_send.modules[1].build_id);
}
TEST_F(DebugAgentTests, OnAttachNotFound) {
uint32_t transaction_id = 1u;
auto test_context = CreateTestContext();
DebugAgent debug_agent(nullptr, ToSystemProviders(*test_context));
debug_agent.Connect(&test_context->stream_backend.stream());
RemoteAPI* remote_api = &debug_agent;
debug_ipc::AttachRequest attach_request;
attach_request.type = debug_ipc::TaskType::kProcess;
attach_request.koid = -1;
remote_api->OnAttach(transaction_id++, attach_request);
{
// Should've gotten an attach reply.
auto& attach_replies = test_context->stream_backend.attach_replies();
ASSERT_EQ(attach_replies.size(), 1u);
EXPECT_ZX_EQ(attach_replies[0].status, ZX_ERR_NOT_FOUND);
// There should be no attach watch.
auto& watches = test_context->loop.watches();
ASSERT_EQ(watches.size(), 0u);
}
auto [proc_object, thread_object] =
GetProcessThread(*test_context->object_provider, "job11-p1", "second-thread");
test_context->limbo_provider->AppendException(proc_object, thread_object,
ExceptionType::FATAL_PAGE_FAULT);
// Even with limbo it should fail.
remote_api->OnAttach(transaction_id++, attach_request);
{
// Should've gotten an attach reply.
auto& attach_replies = test_context->stream_backend.attach_replies();
ASSERT_EQ(attach_replies.size(), 2u);
EXPECT_ZX_EQ(attach_replies[1].status, ZX_ERR_NOT_FOUND);
// There should be no attach watch.
auto& watches = test_context->loop.watches();
ASSERT_EQ(watches.size(), 0u);
}
}
TEST_F(DebugAgentTests, OnAttach) {
uint32_t transaction_id = 1u;
auto test_context = CreateTestContext();
DebugAgent debug_agent(nullptr, ToSystemProviders(*test_context));
debug_agent.Connect(&test_context->stream_backend.stream());
RemoteAPI* remote_api = &debug_agent;
debug_ipc::AttachRequest attach_request;
attach_request.type = debug_ipc::TaskType::kProcess;
attach_request.koid = 11;
remote_api->OnAttach(transaction_id++, attach_request);
// We should've received a watch command (which does the low level exception watching).
auto& watches = test_context->loop.watches();
ASSERT_EQ(watches.size(), 1u);
EXPECT_EQ(watches[0].process_name, "job1-p2");
EXPECT_EQ(watches[0].process_handle, 11u);
EXPECT_EQ(watches[0].process_koid, 11u);
// We should've gotten an attach reply.
auto& attach_replies = test_context->stream_backend.attach_replies();
auto reply = attach_replies.back();
ASSERT_EQ(attach_replies.size(), 1u);
EXPECT_ZX_EQ(reply.status, ZX_OK);
EXPECT_EQ(reply.koid, 11u);
EXPECT_EQ(reply.name, "job1-p2");
// Asking for some invalid process should fail.
attach_request.koid = 0x231315; // Some invalid value.
remote_api->OnAttach(transaction_id++, attach_request);
// We should've gotten an error reply.
ASSERT_EQ(attach_replies.size(), 2u);
reply = attach_replies.back();
EXPECT_ZX_EQ(reply.status, ZX_ERR_NOT_FOUND);
// Attaching to a third process should work.
attach_request.koid = 21u;
remote_api->OnAttach(transaction_id++, attach_request);
ASSERT_EQ(attach_replies.size(), 3u);
reply = attach_replies.back();
EXPECT_ZX_EQ(reply.status, ZX_OK);
EXPECT_EQ(reply.koid, 21u);
EXPECT_EQ(reply.name, "job121-p2");
// Attaching again to a process should fail.
remote_api->OnAttach(transaction_id++, attach_request);
ASSERT_EQ(attach_replies.size(), 4u);
reply = attach_replies.back();
EXPECT_ZX_EQ(reply.status, ZX_ERR_ALREADY_BOUND);
}
TEST_F(DebugAgentTests, AttachToLimbo) {
uint32_t transaction_id = 1u;
auto test_context = CreateTestContext();
DebugAgent debug_agent(nullptr, ToSystemProviders(*test_context));
debug_agent.Connect(&test_context->stream_backend.stream());
RemoteAPI* remote_api = &debug_agent;
auto [proc_object, thread_object] =
GetProcessThread(*test_context->object_provider, "job11-p1", "second-thread");
test_context->limbo_provider->AppendException(proc_object, thread_object,
ExceptionType::FATAL_PAGE_FAULT);
debug_ipc::AttachRequest attach_request = {};
attach_request.type = debug_ipc::TaskType::kProcess;
attach_request.koid = proc_object->koid;
remote_api->OnAttach(transaction_id++, attach_request);
// We should've received a watch command (which does the low level exception watching).
auto& watches = test_context->loop.watches();
ASSERT_EQ(watches.size(), 1u);
EXPECT_EQ(watches[0].process_name, proc_object->name);
EXPECT_EQ(watches[0].process_handle, proc_object->koid);
EXPECT_EQ(watches[0].process_koid, proc_object->koid);
// We should've gotten an attach reply.
auto& attach_replies = test_context->stream_backend.attach_replies();
auto reply = attach_replies.back();
ASSERT_EQ(attach_replies.size(), 1u);
EXPECT_ZX_EQ(reply.status, ZX_OK);
EXPECT_EQ(reply.koid, proc_object->koid);
EXPECT_EQ(reply.name, proc_object->name);
{
DebuggedProcess* process = debug_agent.GetDebuggedProcess(proc_object->koid);
ASSERT_TRUE(process);
auto threads = process->GetThreads();
ASSERT_EQ(threads.size(), 2u);
// Search for the exception thread.
DebuggedThread* exception_thread = nullptr;
for (DebuggedThread* thread : threads) {
if (thread->koid() == thread_object->koid) {
exception_thread = thread;
break;
}
}
ASSERT_TRUE(exception_thread);
ASSERT_TRUE(exception_thread->IsInException());
EXPECT_EQ(exception_thread->exception_handle().get(), thread_object->koid);
}
}
TEST_F(DebugAgentTests, OnEnterLimbo) {
auto test_context = CreateTestContext();
DebugAgent debug_agent(nullptr, ToSystemProviders(*test_context));
debug_agent.Connect(&test_context->stream_backend.stream());
auto [proc_object, thread_object] =
GetProcessThread(*test_context->object_provider, "job11-p1", "second-thread");
test_context->limbo_provider->AppendException(proc_object, thread_object,
ExceptionType::FATAL_PAGE_FAULT);
// Call the limbo.
test_context->limbo_provider->CallOnEnterLimbo();
// Should've sent a notification.
{
ASSERT_EQ(test_context->stream_backend.process_starts().size(), 1u);
auto& process_start = test_context->stream_backend.process_starts()[0];
EXPECT_EQ(process_start.type, debug_ipc::NotifyProcessStarting::Type::kLimbo);
EXPECT_EQ(process_start.koid, proc_object->koid);
EXPECT_EQ(process_start.component_id, 0u);
EXPECT_EQ(process_start.name, proc_object->name);
}
}
TEST_F(DebugAgentTests, DetachFromLimbo) {
auto test_context = CreateTestContext();
DebugAgent debug_agent(nullptr, ToSystemProviders(*test_context));
debug_agent.Connect(&test_context->stream_backend.stream());
RemoteAPI* remote_api = &debug_agent;
auto [proc_object, thread_object] =
GetProcessThread(*test_context->object_provider, "job11-p1", "second-thread");
// Attempting to detach to a process that doesn't exist should fail.
{
debug_ipc::DetachRequest request = {};
request.type = debug_ipc::TaskType::kProcess;
request.koid = proc_object->koid;
debug_ipc::DetachReply reply = {};
remote_api->OnDetach(request, &reply);
ASSERT_ZX_EQ(reply.status, ZX_ERR_NOT_FOUND);
ASSERT_EQ(test_context->limbo_provider->release_calls().size(), 0u);
}
// Adding it should now find it and remove it.
test_context->limbo_provider->AppendException(proc_object, thread_object,
ExceptionType::FATAL_PAGE_FAULT);
{
debug_ipc::DetachRequest request = {};
request.type = debug_ipc::TaskType::kProcess;
request.koid = proc_object->koid;
debug_ipc::DetachReply reply = {};
remote_api->OnDetach(request, &reply);
ASSERT_ZX_EQ(reply.status, ZX_OK);
ASSERT_EQ(test_context->limbo_provider->release_calls().size(), 1u);
EXPECT_EQ(test_context->limbo_provider->release_calls()[0], proc_object->koid);
}
// This should've remove it from limbo, trying it again should fail.
{
debug_ipc::DetachRequest request = {};
request.type = debug_ipc::TaskType::kProcess;
request.koid = proc_object->koid;
debug_ipc::DetachReply reply = {};
remote_api->OnDetach(request, &reply);
ASSERT_ZX_EQ(reply.status, ZX_ERR_NOT_FOUND);
ASSERT_EQ(test_context->limbo_provider->release_calls().size(), 1u);
EXPECT_EQ(test_context->limbo_provider->release_calls()[0], proc_object->koid);
}
}
TEST_F(DebugAgentTests, Kill) {
uint32_t transaction_id = 1u;
auto test_context = CreateTestContext();
DebugAgent debug_agent(nullptr, ToSystemProviders(*test_context));
debug_agent.Connect(&test_context->stream_backend.stream());
RemoteAPI* remote_api = &debug_agent;
auto [proc_object, thread_object] =
GetProcessThread(*test_context->object_provider, "job11-p1", "second-thread");
// Attempt to kill a process that's not there should fail.
{
debug_ipc::KillRequest kill_request = {};
kill_request.process_koid = proc_object->koid;
debug_ipc::KillReply kill_reply = {};
remote_api->OnKill(kill_request, &kill_reply);
ASSERT_ZX_EQ(kill_reply.status, ZX_ERR_NOT_FOUND);
}
// Attach to a process so that the debugger knows about it.
{
debug_ipc::AttachRequest attach_request = {};
attach_request.type = debug_ipc::TaskType::kProcess;
attach_request.koid = proc_object->koid;
remote_api->OnAttach(transaction_id++, attach_request);
// There should be a process.
ASSERT_EQ(debug_agent.procs_.size(), 1u);
// Should not come from limbo.
EXPECT_FALSE(debug_agent.procs_.begin()->second->from_limbo());
}
// Killing now should work.
{
debug_ipc::KillRequest kill_request = {};
kill_request.process_koid = proc_object->koid;
debug_ipc::KillReply kill_reply = {};
remote_api->OnKill(kill_request, &kill_reply);
// There should be no more processes.
ASSERT_EQ(debug_agent.procs_.size(), 0u);
// Killing again should fail.
remote_api->OnKill(kill_request, &kill_reply);
ASSERT_ZX_EQ(kill_reply.status, ZX_ERR_NOT_FOUND);
}
// We now add the process to the limbo.
test_context->limbo_provider->AppendException(proc_object, thread_object,
ExceptionType::FATAL_PAGE_FAULT);
// There should be no more processes.
ASSERT_EQ(debug_agent.procs_.size(), 0u);
// Killing now should release it.
{
debug_ipc::KillRequest kill_request = {};
kill_request.process_koid = proc_object->koid;
debug_ipc::KillReply kill_reply = {};
remote_api->OnKill(kill_request, &kill_reply);
ASSERT_ZX_EQ(kill_reply.status, ZX_OK);
ASSERT_EQ(test_context->limbo_provider->release_calls().size(), 1u);
EXPECT_EQ(test_context->limbo_provider->release_calls()[0], proc_object->koid);
// Killing again should not find it.
remote_api->OnKill(kill_request, &kill_reply);
ASSERT_ZX_EQ(kill_reply.status, ZX_ERR_NOT_FOUND);
}
test_context->limbo_provider->AppendException(proc_object, thread_object,
ExceptionType::FATAL_PAGE_FAULT);
// This is a limbo process, so we cannot kill it.
test_context->object_provider->set_next_kill_status(ZX_ERR_ACCESS_DENIED);
debug_ipc::AttachRequest attach_request = {};
attach_request.type = debug_ipc::TaskType::kProcess;
attach_request.koid = proc_object->koid;
remote_api->OnAttach(transaction_id++, attach_request);
// There should be a process.
ASSERT_EQ(debug_agent.procs_.size(), 1u);
{
auto it = debug_agent.procs_.find(proc_object->koid);
ASSERT_NE(it, debug_agent.procs_.end());
EXPECT_TRUE(debug_agent.procs_.begin()->second->from_limbo());
// Killing it should free the process.
debug_ipc::KillRequest kill_request = {};
kill_request.process_koid = proc_object->koid;
debug_ipc::KillReply kill_reply = {};
remote_api->OnKill(kill_request, &kill_reply);
ASSERT_ZX_EQ(kill_reply.status, ZX_OK);
ASSERT_EQ(debug_agent.procs_.size(), 0u);
// There should be a limbo process to be killed.
ASSERT_EQ(debug_agent.killed_limbo_procs_.size(), 1u);
EXPECT_EQ(debug_agent.killed_limbo_procs_.count(proc_object->koid), 1u);
// There should've have been more release calls (yet).
ASSERT_EQ(test_context->limbo_provider->release_calls().size(), 1u);
// When the process "re-enters" the limbo, it should be removed.
test_context->limbo_provider->AppendException(proc_object, thread_object,
ExceptionType::FATAL_PAGE_FAULT);
test_context->limbo_provider->CallOnEnterLimbo();
// There should not be an additional proc in the agent.
ASSERT_EQ(debug_agent.procs_.size(), 0u);
// There should've been a release call.
ASSERT_EQ(test_context->limbo_provider->release_calls().size(), 2u);
EXPECT_EQ(test_context->limbo_provider->release_calls()[1], proc_object->koid);
}
}
} // namespace debug_agent