blob: fa7c89273c0528ab92dedcb13105c421bab3c4a1 [file] [log] [blame]
// Copyright 2020 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/zxdb/console/commands/verb_attach.h"
#include <cstdint>
#include <string>
#include <gtest/gtest.h>
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/console/console_test.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
class AttachTestRemoteAPI : public RemoteAPI {
public:
struct AttachLog {
debug_ipc::AttachRequest request;
fit::callback<void(const Err&, debug_ipc::AttachReply)> cb;
};
void Attach(const debug_ipc::AttachRequest& request,
fit::callback<void(const Err&, debug_ipc::AttachReply)> cb) override {
last_attach = AttachLog{request, std::move(cb)};
}
void UpdateFilter(const debug_ipc::UpdateFilterRequest& request,
fit::callback<void(const Err&, debug_ipc::UpdateFilterReply)> cb) override {
update_filter_requests.push_back(request);
}
// Stores the last one.
std::optional<AttachLog> last_attach;
// Stores a log of all requests (since the tests needs all of them).
std::vector<debug_ipc::UpdateFilterRequest> update_filter_requests;
};
class VerbAttach : public ConsoleTest {
public:
AttachTestRemoteAPI* attach_remote_api() { return attach_remote_api_; }
// Returns the last filter in the last UpdateFilter request.
const debug_ipc::Filter& GetLastFilter() {
loop().RunUntilNoTasks(); // Filter sync is asynchronous.
FX_CHECK(!attach_remote_api_->update_filter_requests.empty());
FX_CHECK(!attach_remote_api_->update_filter_requests.back().filters.empty());
return attach_remote_api_->update_filter_requests.back().filters.back();
}
protected:
// RemoteAPITest overrides.
std::unique_ptr<RemoteAPI> GetRemoteAPIImpl() override {
auto remote_api = std::make_unique<AttachTestRemoteAPI>();
attach_remote_api_ = remote_api.get();
return remote_api;
}
AttachTestRemoteAPI* attach_remote_api_ = nullptr;
};
// a large but valid koid, as kernel-generated koids only use 63 bits.
constexpr uint64_t kLargeKoid = 0x1ull << 60;
} // namespace
TEST_F(VerbAttach, Bad) {
// Missing argument.
console().ProcessInputLine("attach");
auto event = console().GetOutputEvent();
EXPECT_EQ(MockConsole::OutputEvent::Type::kOutput, event.type);
EXPECT_EQ("Wrong number of arguments to attach.", event.output.AsString());
// Can't attach to a process by filter.
console().ProcessInputLine("process attach --exact 123");
event = console().GetOutputEvent();
EXPECT_EQ(MockConsole::OutputEvent::Type::kOutput, event.type);
EXPECT_EQ("Attaching by filters doesn't support \"process\" noun.", event.output.AsString());
console().ProcessInputLine("attach --exact");
event = console().GetOutputEvent();
EXPECT_EQ(MockConsole::OutputEvent::Type::kOutput, event.type);
EXPECT_EQ("Wrong number of arguments to attach.", event.output.AsString());
console().ProcessInputLine("attach --job 123 --exact");
event = console().GetOutputEvent();
EXPECT_EQ(MockConsole::OutputEvent::Type::kOutput, event.type);
EXPECT_EQ("Wrong number of arguments to attach.", event.output.AsString());
}
TEST_F(VerbAttach, Koid) {
const std::string kLargeKoidInString = std::to_string(kLargeKoid);
const std::string kCommand = "attach " + kLargeKoidInString;
console().ProcessInputLine(kCommand);
// This should create a new process context and give "process 2" because the default console test
// harness makes a mock running process #1 by default.
ASSERT_TRUE(attach_remote_api()->last_attach);
ASSERT_EQ(kLargeKoid, attach_remote_api()->last_attach->request.koid);
debug_ipc::AttachReply reply;
reply.status = debug::Status();
reply.koid = kLargeKoid;
reply.name = "some process";
attach_remote_api()->last_attach->cb(Err(), reply);
auto event = console().GetOutputEvent();
EXPECT_EQ(MockConsole::OutputEvent::Type::kOutput, event.type);
EXPECT_EQ(
"Attached Process 2 state=Running koid=" + kLargeKoidInString + " name=\"some process\"\n",
event.output.AsString());
// Attaching to the same process again should give an error.
console().ProcessInputLine(kCommand);
event = console().GetOutputEvent();
EXPECT_EQ(MockConsole::OutputEvent::Type::kOutput, event.type);
EXPECT_EQ("Process " + kLargeKoidInString + " is already being debugged.",
event.output.AsString());
}
TEST_F(VerbAttach, KoidWeak) {
const std::string kLargeKoidInString = std::to_string(kLargeKoid);
const std::string kCommand = "attach --weak " + kLargeKoidInString;
console().ProcessInputLine(kCommand);
// This should create a new process context and give "process 2" because the default console test
// harness makes a mock running process #1 by default.
ASSERT_TRUE(attach_remote_api()->last_attach);
ASSERT_EQ(kLargeKoid, attach_remote_api()->last_attach->request.koid);
debug_ipc::AttachReply reply;
reply.status = debug::Status();
reply.koid = kLargeKoid;
reply.name = "some process";
attach_remote_api()->last_attach->cb(Err(), reply);
auto event = console().GetOutputEvent();
EXPECT_EQ(MockConsole::OutputEvent::Type::kOutput, event.type);
EXPECT_EQ(
"Attached Process 2 state=Running koid=" + kLargeKoidInString + " name=\"some process\"\n",
event.output.AsString());
// Attaching to the same process again should give an error.
console().ProcessInputLine(kCommand);
event = console().GetOutputEvent();
EXPECT_EQ(MockConsole::OutputEvent::Type::kOutput, event.type);
EXPECT_EQ("Process " + kLargeKoidInString + " is already being debugged.",
event.output.AsString());
// We should not load any symbols because we are attached weakly.
const auto process = console().context().session()->system().ProcessFromKoid(kLargeKoid);
EXPECT_EQ(Process::SymbolStatus::kNoModules, process->GetSymbolStatus());
}
TEST_F(VerbAttach, Filter) {
// Normal filter case.
console().ProcessInputLine("attach foo");
auto event = console().GetOutputEvent();
EXPECT_EQ(MockConsole::OutputEvent::Type::kOutput, event.type);
ASSERT_EQ(
"Waiting for process matching \"foo\".\n"
"Type \"filter\" to see the current filters.",
event.output.AsString());
EXPECT_EQ(debug_ipc::Filter::Type::kProcessNameSubstr, GetLastFilter().type);
EXPECT_EQ("foo", GetLastFilter().pattern);
// Exact name.
console().ProcessInputLine("attach --exact 12345");
EXPECT_EQ(debug_ipc::Filter::Type::kProcessName, GetLastFilter().type);
EXPECT_EQ("12345", GetLastFilter().pattern);
console().ProcessInputLine("attach --exact /pkg/bin/true");
EXPECT_EQ(debug_ipc::Filter::Type::kProcessName, GetLastFilter().type);
EXPECT_EQ("/pkg/bin/true", GetLastFilter().pattern);
console().FlushOutputEvents();
// Extra long filter case with an exact name. Even with --exact, this will be trimmed since it
// will never exactly match if it is longer than the max name length.
const std::string kSuperLongName = "super_long_name_with_over_32_characters";
const std::string kTrimmedLongName = kSuperLongName.substr(0, kZirconMaxNameLength);
console().ProcessInputLine("attach --exact " + kSuperLongName);
EXPECT_EQ(debug_ipc::Filter::Type::kProcessName, GetLastFilter().type);
EXPECT_EQ(kTrimmedLongName, GetLastFilter().pattern);
event = console().GetOutputEvent();
EXPECT_EQ(MockConsole::OutputEvent::Type::kOutput, event.type);
ASSERT_EQ(
"The filter is trimmed to 31 characters because it's the maximum length for a process name in Zircon.",
event.output.AsString());
event = console().GetOutputEvent();
EXPECT_EQ(MockConsole::OutputEvent::Type::kOutput, event.type);
ASSERT_EQ(fxl::StringPrintf("Waiting for process matching \"%s\".\n"
"Type \"filter\" to see the current filters.",
kTrimmedLongName.c_str()),
event.output.AsString());
// Component URL.
const std::string kComponentUrl = "fuchsia-pkg://devhost/package#meta/component.cm";
console().ProcessInputLine("attach " + kComponentUrl);
EXPECT_EQ(debug_ipc::Filter::Type::kComponentUrl, GetLastFilter().type);
EXPECT_EQ(kComponentUrl, GetLastFilter().pattern);
// Component moniker.
const std::string kComponentMoniker = "/some_realm/" + kSuperLongName;
console().ProcessInputLine("attach " + kComponentMoniker);
EXPECT_EQ(debug_ipc::Filter::Type::kComponentMoniker, GetLastFilter().type);
EXPECT_EQ(kComponentMoniker, GetLastFilter().pattern);
// Component moniker substr.
const std::string kComponentMonikerSubstr = "some_ending_realm/" + kSuperLongName;
console().ProcessInputLine("attach " + kComponentMonikerSubstr);
EXPECT_EQ(debug_ipc::Filter::Type::kComponentMonikerSuffix, GetLastFilter().type);
EXPECT_EQ(kComponentMonikerSubstr, GetLastFilter().pattern);
// Component name.
const std::string kComponentName = kSuperLongName + ".cm";
console().ProcessInputLine("attach " + kComponentName);
EXPECT_EQ(debug_ipc::Filter::Type::kComponentName, GetLastFilter().type);
EXPECT_EQ(kComponentName, GetLastFilter().pattern);
// Job without a name.
console().ProcessInputLine("attach --job " + std::to_string(kLargeKoid));
EXPECT_EQ(debug_ipc::Filter::Type::kProcessNameSubstr, GetLastFilter().type);
EXPECT_EQ("", GetLastFilter().pattern);
EXPECT_EQ(kLargeKoid, GetLastFilter().job_koid);
// Job with an exact name.
console().ProcessInputLine("attach -j 1234 --exact " + kSuperLongName);
EXPECT_EQ(debug_ipc::Filter::Type::kProcessName, GetLastFilter().type);
EXPECT_EQ(kSuperLongName.substr(0, kZirconMaxNameLength), GetLastFilter().pattern);
EXPECT_EQ(1234ull, GetLastFilter().job_koid);
console().ProcessInputLine("attach --job-only " + kComponentName);
EXPECT_EQ(debug_ipc::Filter::Type::kComponentName, GetLastFilter().type);
EXPECT_TRUE(GetLastFilter().config.job_only);
}
} // namespace zxdb