| // 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 |