blob: 9af6146f636fbf035333bbb97beb97bac365c9eb [file] [log] [blame]
// 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 "garnet/bin/debug_agent/debugged_job.h"
#include <thread>
#include <lib/fdio/spawn.h>
#include <lib/fit/function.h>
#include <zircon/processargs.h>
#include "garnet/bin/debug_agent/object_util.h"
#include "garnet/lib/debug_ipc/helper/message_loop_zircon.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "lib/fxl/arraysize.h"
namespace debug_agent {
namespace {
class JobDebuggerTest : public ::testing::Test, public ProcessStartHandler {
public:
void OnProcessStart(zx::process process) override {
processes_.push_back(std::move(process));
}
protected:
void SetUp() override {
message_loop_.Init();
zx::unowned_job current_job(zx_job_default());
ASSERT_EQ(ZX_OK, zx::job::create(*current_job, 0u, &job_));
}
~JobDebuggerTest() { message_loop_.Cleanup(); }
static void LaunchProcess(const zx::job& job,
const std::vector<const char*>& argv,
const char* name, int outfd, zx_handle_t* proc) {
std::vector<const char*> normalized_argv = argv;
normalized_argv.push_back(nullptr);
// Redirect process's stdout to file.
fdio_spawn_action_t actions[] = {
{.action = FDIO_SPAWN_ACTION_CLONE_FD,
.fd = {.local_fd = outfd, .target_fd = STDOUT_FILENO}},
{.action = FDIO_SPAWN_ACTION_CLONE_FD,
.fd = {.local_fd = STDIN_FILENO, .target_fd = STDIN_FILENO}},
{.action = FDIO_SPAWN_ACTION_CLONE_FD,
.fd = {.local_fd = STDERR_FILENO, .target_fd = STDERR_FILENO}},
{.action = FDIO_SPAWN_ACTION_SET_NAME, .name = {.data = name}}};
char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
zx_status_t status = fdio_spawn_etc(
job.get(), FDIO_SPAWN_CLONE_ALL, argv[0], normalized_argv.data(),
nullptr, arraysize(actions), actions, proc, err_msg);
ASSERT_EQ(status, ZX_OK) << "Failed to spawn command: " << err_msg;
}
// Returns true if condition is true before timeout.
bool RunLoopWithTimeoutOrUntil(fit::function<bool()> condition,
zx::duration timeout = zx::sec(10),
zx::duration step = zx::msec(10)) {
const zx::time deadline = (timeout == zx::sec(0))
? zx::time::infinite()
: zx::deadline_after(timeout);
while (zx::clock::get_monotonic() < deadline) {
if (condition()) {
return true;
}
message_loop_.RunUntilTimeout(step);
}
return condition();
}
void WaitForProcToExit(zx_handle_t proc, int exit_code) {
zx_info_process_t info;
ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&] {
zx_object_get_info(proc, ZX_INFO_PROCESS, &info, sizeof(info), nullptr,
nullptr);
return info.exited;
}));
ASSERT_EQ(exit_code, info.return_code);
}
std::vector<zx::process> processes_;
zx::job job_;
debug_ipc::MessageLoopZircon message_loop_;
};
TEST_F(JobDebuggerTest, OneProcess) {
zx::job duplicate_job;
ASSERT_EQ(ZX_OK, job_.duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate_job));
DebuggedJob debugged_job(this, KoidForObject(duplicate_job),
std::move(duplicate_job));
ASSERT_TRUE(debugged_job.Init());
debugged_job.SetFilters({"t"});
ASSERT_EQ(0u, processes_.size());
zx_handle_t proc = ZX_HANDLE_INVALID;
int pipefd[2];
ASSERT_EQ(0, pipe(pipefd));
const std::vector<const char*> args = {"/system/bin/true"};
LaunchProcess(job_, args, "true", pipefd[0], &proc);
ASSERT_TRUE(RunLoopWithTimeoutOrUntil([this] {
return processes_.size() == 1;
})) << "Expected processes size 1, got: "
<< processes_.size();
zx::process process = zx::process(proc);
ASSERT_EQ(KoidForObject(processes_[0]), KoidForObject(process));
WaitForProcToExit(proc, 0);
}
// Tests that job debug exception is removed when debugged job is killed.
TEST_F(JobDebuggerTest, DebuggedJobKilled) {
// make sure that job debugger works
{
zx::job duplicate_job;
ASSERT_EQ(ZX_OK, job_.duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate_job));
DebuggedJob debugged_job(this, KoidForObject(duplicate_job),
std::move(duplicate_job));
ASSERT_TRUE(debugged_job.Init());
debugged_job.SetFilters({"t"});
ASSERT_EQ(0u, processes_.size());
zx_handle_t proc = ZX_HANDLE_INVALID;
int pipefd[2];
ASSERT_EQ(0, pipe(pipefd));
const std::vector<const char*> args = {"/system/bin/true"};
LaunchProcess(job_, args, "true", pipefd[0], &proc);
ASSERT_TRUE(RunLoopWithTimeoutOrUntil([this] {
return processes_.size() == 1;
})) << "Expected processes size 1, got: "
<< processes_.size();
zx::process process = zx::process(proc);
ASSERT_EQ(KoidForObject(processes_[0]), KoidForObject(process));
WaitForProcToExit(proc, 0);
}
// test that new processes are not put into stasis.
processes_.clear();
zx_handle_t proc = ZX_HANDLE_INVALID;
int pipefd[2];
ASSERT_EQ(0, pipe(pipefd));
const std::vector<const char*> args = {"/system/bin/true"};
LaunchProcess(job_, args, "true", pipefd[0], &proc);
WaitForProcToExit(proc, 0);
ASSERT_EQ(0u, processes_.size());
}
TEST_F(JobDebuggerTest, MultipleProcesses) {
zx::job duplicate_job;
ASSERT_EQ(ZX_OK, job_.duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate_job));
DebuggedJob debugged_job(this, KoidForObject(duplicate_job),
std::move(duplicate_job));
ASSERT_TRUE(debugged_job.Init());
debugged_job.SetFilters({"t"});
ASSERT_EQ(0u, processes_.size());
int pipefd[2];
ASSERT_EQ(0, pipe(pipefd));
const std::vector<const char*> args = {"/system/bin/true"};
zx_handle_t proc1 = ZX_HANDLE_INVALID;
zx_handle_t proc2 = ZX_HANDLE_INVALID;
LaunchProcess(job_, args, "true", pipefd[0], &proc1);
zx::process process1 = zx::process(proc1);
auto pid1 = KoidForObject(process1);
LaunchProcess(job_, args, "true", pipefd[0], &proc2);
zx::process process2 = zx::process(proc2);
auto pid2 = KoidForObject(process2);
ASSERT_TRUE(RunLoopWithTimeoutOrUntil([this] {
return processes_.size() == 2;
})) << "Expected processes size 2, got: "
<< processes_.size();
std::vector<uint64_t> pids = {KoidForObject(processes_[0]),
KoidForObject(processes_[1])};
EXPECT_THAT(pids, ::testing::UnorderedElementsAre(pid1, pid2));
WaitForProcToExit(proc1, 0);
WaitForProcToExit(proc2, 0);
}
TEST_F(JobDebuggerTest, ProcessInNestedJob) {
zx::job child_job;
ASSERT_EQ(ZX_OK, zx::job::create(job_, 0u, &child_job));
zx::job duplicate_job;
ASSERT_EQ(ZX_OK, job_.duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate_job));
DebuggedJob debugged_job(this, KoidForObject(duplicate_job),
std::move(duplicate_job));
ASSERT_TRUE(debugged_job.Init());
debugged_job.SetFilters({"t"});
ASSERT_EQ(0u, processes_.size());
zx_handle_t proc = ZX_HANDLE_INVALID;
int pipefd[2];
ASSERT_EQ(0, pipe(pipefd));
const std::vector<const char*> args = {"/system/bin/true"};
LaunchProcess(child_job, args, "true", pipefd[0], &proc);
ASSERT_TRUE(RunLoopWithTimeoutOrUntil([this] {
return processes_.size() == 1;
})) << "Expected processes size 1, got: "
<< processes_.size();
zx::process process = zx::process(proc);
ASSERT_EQ(KoidForObject(processes_[0]), KoidForObject(process));
WaitForProcToExit(proc, 0);
}
TEST_F(JobDebuggerTest, FilterFullName) {
zx::job duplicate_job;
ASSERT_EQ(ZX_OK, job_.duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate_job));
DebuggedJob debugged_job(this, KoidForObject(duplicate_job),
std::move(duplicate_job));
ASSERT_TRUE(debugged_job.Init());
constexpr char name[] = "true";
debugged_job.SetFilters({name});
ASSERT_EQ(0u, processes_.size());
zx_handle_t proc = ZX_HANDLE_INVALID;
int pipefd[2];
ASSERT_EQ(0, pipe(pipefd));
const std::vector<const char*> args = {"/system/bin/true"};
LaunchProcess(job_, args, name, pipefd[0], &proc);
ASSERT_TRUE(RunLoopWithTimeoutOrUntil([this] {
return processes_.size() == 1;
})) << "Expected processes size 1, got: "
<< processes_.size();
zx::process process = zx::process(proc);
ASSERT_EQ(KoidForObject(processes_[0]), KoidForObject(process));
WaitForProcToExit(proc, 0);
}
TEST_F(JobDebuggerTest, FilterMultipleProcess) {
zx::job duplicate_job;
ASSERT_EQ(ZX_OK, job_.duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate_job));
DebuggedJob debugged_job(this, KoidForObject(duplicate_job),
std::move(duplicate_job));
ASSERT_TRUE(debugged_job.Init());
debugged_job.SetFilters({"t"});
ASSERT_EQ(0u, processes_.size());
int pipefd[2];
ASSERT_EQ(0, pipe(pipefd));
const std::vector<const char*> args = {"/system/bin/true"};
zx_handle_t proc1 = ZX_HANDLE_INVALID;
zx_handle_t proc2 = ZX_HANDLE_INVALID;
LaunchProcess(job_, args, "false", pipefd[0], &proc1);
LaunchProcess(job_, args, "true", pipefd[0], &proc2);
zx::process process = zx::process(proc2);
auto pid = KoidForObject(process);
ASSERT_TRUE(RunLoopWithTimeoutOrUntil([this] {
return processes_.size() == 1;
})) << "Expected processes size 1, got: "
<< processes_.size();
ASSERT_EQ(KoidForObject(processes_[0]), pid);
WaitForProcToExit(proc1, 0);
WaitForProcToExit(proc2, 0);
}
// Launch two separate processes and check that multiple filters can attach to
// them.
TEST_F(JobDebuggerTest, MultipleFilters) {
zx::job duplicate_job;
ASSERT_EQ(ZX_OK, job_.duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate_job));
DebuggedJob debugged_job(this, KoidForObject(duplicate_job),
std::move(duplicate_job));
ASSERT_TRUE(debugged_job.Init());
debugged_job.SetFilters({"t", "f"});
ASSERT_EQ(0u, processes_.size());
int pipefd[2];
ASSERT_EQ(0, pipe(pipefd));
const std::vector<const char*> args = {"/system/bin/true"};
zx_handle_t proc1 = ZX_HANDLE_INVALID;
zx_handle_t proc2 = ZX_HANDLE_INVALID;
LaunchProcess(job_, args, "false", pipefd[0], &proc1);
zx::process process1 = zx::process(proc1);
auto pid1 = KoidForObject(process1);
LaunchProcess(job_, args, "true", pipefd[0], &proc2);
zx::process process2 = zx::process(proc2);
auto pid2 = KoidForObject(process2);
ASSERT_TRUE(RunLoopWithTimeoutOrUntil([this] {
return processes_.size() == 2;
})) << "Expected processes size 2, got: "
<< processes_.size();
// debug_agent can get processes in any order because LaunchProcess is async
// so create an array and check that it contains both pids.
std::vector<uint64_t> pids = {KoidForObject(processes_[1]),
KoidForObject(processes_[0])};
EXPECT_THAT(pids, ::testing::UnorderedElementsAre(pid2, pid1));
WaitForProcToExit(proc1, 0);
WaitForProcToExit(proc2, 0);
}
} // namespace
} // namespace debug_agent