blob: 8be4940eae2e4f9389c862b1184a775960309ace [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/zxdb/client/system.h"
#include <gtest/gtest.h>
#include "src/developer/debug/shared/zx_status.h"
#include "src/developer/debug/zxdb/client/frame.h"
#include "src/developer/debug/zxdb/client/job.h"
#include "src/developer/debug/zxdb/client/mock_remote_api.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/process_observer.h"
#include "src/developer/debug/zxdb/client/remote_api_test.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/setting_schema_definition.h"
#include "src/developer/debug/zxdb/client/system_observer.h"
#include "src/developer/debug/zxdb/client/target_observer.h"
#include "src/developer/debug/zxdb/client/thread.h"
namespace zxdb {
namespace {
struct ProcessInfo {
uint64_t koid;
std::string name;
};
class APISink : public MockRemoteAPI {
public:
void Attach(const debug_ipc::AttachRequest& request,
fit::callback<void(const Err&, debug_ipc::AttachReply)> cb) override {
attach_requests_.push_back(request);
FX_DCHECK(info_count_ < next_infos_.size());
auto info = next_infos_[info_count_++];
debug_ipc::AttachReply reply;
reply.status = debug::Status();
reply.koid = info.koid;
reply.name = info.name;
cb(Err(), std::move(reply));
}
const std::vector<debug_ipc::AttachRequest>& attach_requests() const { return attach_requests_; }
// Sets the list of replys the sink will return.
void SetNextInfos(std::vector<ProcessInfo> infos) {
next_infos_ = std::move(infos);
info_count_ = 0;
}
void UpdateGlobalSettings(
const debug_ipc::UpdateGlobalSettingsRequest& request,
fit::callback<void(const Err&, debug_ipc::UpdateGlobalSettingsReply)> cb) override {
global_setting_requests_.push_back(request);
debug_ipc::UpdateGlobalSettingsReply reply;
cb(Err(), std::move(reply));
}
const std::vector<debug_ipc::UpdateGlobalSettingsRequest>& global_setting_requests() const {
return global_setting_requests_;
}
private:
size_t info_count_ = 0;
std::vector<ProcessInfo> next_infos_;
std::vector<debug_ipc::AttachRequest> attach_requests_;
std::vector<debug_ipc::UpdateGlobalSettingsRequest> global_setting_requests_;
};
class SystemTest : public RemoteAPITest {
public:
SystemTest() = default;
~SystemTest() override = default;
APISink* sink() { return sink_; }
private:
std::unique_ptr<RemoteAPI> GetRemoteAPIImpl() override {
auto sink = std::make_unique<APISink>();
// The session will own the sink.
sink_ = sink.get();
return sink;
}
APISink* sink_;
};
// We need a RAII-esque wrapper because observers are supposed to outlive the system.
class MockSystemObserver : public TargetObserver, public ProcessObserver {
public:
explicit MockSystemObserver(Session* session) : session_(session) {
session->target_observers().AddObserver(this);
session->process_observers().AddObserver(this);
}
~MockSystemObserver() {
session_->process_observers().RemoveObserver(this);
session_->target_observers().RemoveObserver(this);
}
// TargetObserver.
void DidCreateTarget(Target*) override { target_create_count_++; }
// ProcessObserver.
void DidCreateProcess(Process*, bool, uint64_t) override { process_create_count_++; }
int target_create_count() const { return target_create_count_; }
int process_create_count() const { return process_create_count_; }
private:
Session* session_ = nullptr;
int target_create_count_ = 0;
int process_create_count_ = 0;
};
} // namespace
// Tests that thread state is updated when doing a system-wide continue.
TEST_F(SystemTest, GlobalContinue) {
// Make a process and thread for notifying about.
constexpr uint64_t kProcessKoid = 1234;
InjectProcess(kProcessKoid);
constexpr uint64_t kThread1Koid = 5678;
Thread* thread1 = InjectThread(kProcessKoid, kThread1Koid);
constexpr uint64_t kThread2Koid = 9012;
Thread* thread2 = InjectThread(kProcessKoid, kThread2Koid);
sink()->GetAndResetResumeCount(); // Clear from thread init.
constexpr uint64_t kAddress = 0x12345678;
constexpr uint64_t kStack = 0x7890;
// Notify of thread stop on thread 1.
debug_ipc::NotifyException break_notification;
break_notification.type = debug_ipc::ExceptionType::kSoftwareBreakpoint;
break_notification.thread.id = {.process = kProcessKoid, .thread = kThread1Koid};
break_notification.thread.state = debug_ipc::ThreadRecord::State::kBlocked;
break_notification.thread.frames.emplace_back(kAddress, kStack, kStack);
InjectException(break_notification);
EXPECT_EQ(0, sink()->GetAndResetResumeCount());
// Same on thread 2.
break_notification.thread.id = {.process = kProcessKoid, .thread = kThread2Koid};
InjectException(break_notification);
// Continue globally. This should in turn update the thread.
session().system().Continue(false);
// Both threads should have been resumed in the backend.
EXPECT_EQ(2, sink()->GetAndResetResumeCount());
// The threads should have no stack.
EXPECT_FALSE(thread1->GetStack().has_all_frames());
ASSERT_EQ(0u, thread1->GetStack().size());
EXPECT_FALSE(thread2->GetStack().has_all_frames());
ASSERT_EQ(0u, thread2->GetStack().size());
}
TEST_F(SystemTest, FilterMatchesAndRematching) {
System& system = session().system();
MockSystemObserver system_observer(&session());
constexpr uint64_t kJobKoid = 0x1234;
Job job(&session(), false);
job.AttachForTesting(kJobKoid, "job-name");
ASSERT_TRUE(sink()->attach_requests().empty());
// There should be only one empty target.
auto targets = system.GetTargets();
ASSERT_EQ(targets.size(), 1u);
EXPECT_FALSE(targets[0]->GetProcess());
// We match on a new process.
constexpr uint64_t kProcessKoid = 0x5678;
std::string kProcessName = "some-process";
ProcessInfo info = {kProcessKoid, kProcessName};
sink()->SetNextInfos({info});
session().system().OnFilterMatches(&job, {kProcessKoid});
// There should be an attach request.
auto& requests = sink()->attach_requests();
ASSERT_EQ(requests.size(), 1u);
EXPECT_EQ(requests[0].type, debug_ipc::TaskType::kProcess);
EXPECT_EQ(requests[0].koid, kProcessKoid);
// The system should've reused the empty target.
ASSERT_EQ(system_observer.target_create_count(), 0);
targets = system.GetTargets();
ASSERT_EQ(targets.size(), 1u);
// Should've created the process.
ASSERT_EQ(system_observer.process_create_count(), 1);
Process* process = targets[0]->GetProcess();
ASSERT_TRUE(process);
EXPECT_EQ(process->GetKoid(), kProcessKoid);
EXPECT_EQ(process->GetName(), kProcessName);
// Rematching should not create a new target.
sink()->SetNextInfos({info});
session().system().OnFilterMatches(&job, {kProcessKoid});
// The system should've reused the empty target.
ASSERT_EQ(system_observer.target_create_count(), 0);
targets = system.GetTargets();
ASSERT_EQ(targets.size(), 1u);
// Should've created the process.
ASSERT_EQ(system_observer.process_create_count(), 1);
process = targets[0]->GetProcess();
ASSERT_TRUE(process);
EXPECT_EQ(process->GetKoid(), kProcessKoid);
EXPECT_EQ(process->GetName(), kProcessName);
}
TEST_F(SystemTest, ExistenProcessShouldCreateTarget) {
System& system = session().system();
MockSystemObserver system_observer(&session());
constexpr uint64_t kJobKoid = 0x1234;
Job job(&session(), false);
job.AttachForTesting(kJobKoid, "job-name");
ASSERT_TRUE(sink()->attach_requests().empty());
// Before injecting the process there should not be an event, after it there should be one.
ASSERT_EQ(system_observer.process_create_count(), 0);
constexpr uint64_t kProcessKoid1 = 0x1;
InjectProcess(kProcessKoid1);
ASSERT_EQ(system_observer.process_create_count(), 1);
// There should be a target with a process.
auto targets = system.GetTargets();
ASSERT_EQ(targets.size(), 1u);
Process* process = targets[0]->GetProcess();
ASSERT_TRUE(process);
ASSERT_EQ(process->GetKoid(), kProcessKoid1);
// We match on a new process.
constexpr uint64_t kProcessKoid2 = 0x2;
std::string kProcessName = "some-process";
ProcessInfo info = {kProcessKoid2, kProcessName};
sink()->SetNextInfos({info});
session().system().OnFilterMatches(&job, {kProcessKoid2});
// There should be an attach request.
auto& requests = sink()->attach_requests();
ASSERT_EQ(requests.size(), 1u);
EXPECT_EQ(requests[0].type, debug_ipc::TaskType::kProcess);
EXPECT_EQ(requests[0].koid, kProcessKoid2);
// The system should've created a new target.
ASSERT_EQ(system_observer.target_create_count(), 1);
targets = system.GetTargets();
ASSERT_EQ(targets.size(), 2u);
// Should've created the process.
ASSERT_EQ(system_observer.process_create_count(), 2);
process = targets[1]->GetProcess();
ASSERT_TRUE(process);
EXPECT_EQ(process->GetKoid(), kProcessKoid2);
EXPECT_EQ(process->GetName(), kProcessName);
}
TEST_F(SystemTest, UpdateSecondChanceExceptions) {
System& system = session().system();
{
system.settings().SetList(ClientSettings::System::kSecondChanceExceptions, {"pf"});
ASSERT_EQ(1u, sink()->global_setting_requests().size());
auto& request = sink()->global_setting_requests()[0];
std::set<debug_ipc::ExceptionType> second_chance_excps;
for (const auto& update : request.exception_strategies) {
if (update.value == debug_ipc::ExceptionStrategy::kSecondChance) {
second_chance_excps.insert(update.type);
}
}
EXPECT_EQ(std::set({debug_ipc::ExceptionType::kPageFault}), second_chance_excps);
}
{
system.settings().SetList(ClientSettings::System::kSecondChanceExceptions, {"gen", "ua"});
ASSERT_EQ(2u, sink()->global_setting_requests().size());
auto& request = sink()->global_setting_requests()[1];
std::set<debug_ipc::ExceptionType> second_chance_excps;
for (const auto& update : request.exception_strategies) {
if (update.value == debug_ipc::ExceptionStrategy::kSecondChance) {
second_chance_excps.insert(update.type);
}
}
EXPECT_EQ(
std::set({debug_ipc::ExceptionType::kGeneral, debug_ipc::ExceptionType::kUnalignedAccess}),
second_chance_excps);
}
}
} // namespace zxdb