| // 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/software_breakpoint.h" |
| |
| #include <string.h> |
| #include <zircon/syscalls/exception.h> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/developer/debug/debug_agent/arch_provider_impl.h" |
| #include "src/developer/debug/debug_agent/breakpoint.h" |
| #include "src/developer/debug/debug_agent/debugged_thread.h" |
| #include "src/developer/debug/debug_agent/mock_arch_provider.h" |
| #include "src/developer/debug/debug_agent/mock_process.h" |
| #include "src/developer/debug/debug_agent/mock_thread.h" |
| #include "src/developer/debug/debug_agent/process_memory_accessor.h" |
| #include "src/developer/debug/shared/logging/debug.h" |
| |
| namespace debug_agent { |
| |
| namespace { |
| |
| // Provides a fake view of memory with the given initial contents. |
| class FakeMemory : public ProcessMemoryAccessor { |
| public: |
| FakeMemory(intptr_t address, const char* data, size_t data_len) : address_(address) { |
| data_.assign(data, data + data_len); |
| } |
| |
| const char* data() const { return &data_[0]; } |
| |
| zx_status_t ReadProcessMemory(uintptr_t address, void* buffer, size_t len, |
| size_t* actual) override { |
| *actual = 0; |
| if (address < address_ || address + len > address_ + data_.size()) |
| return ZX_ERR_NO_MEMORY; // We require everything to be mapped. |
| |
| memcpy(buffer, &data_[address - address_], len); |
| *actual = len; |
| return ZX_OK; |
| } |
| |
| zx_status_t WriteProcessMemory(uintptr_t address, const void* buffer, size_t len, |
| size_t* actual) override { |
| *actual = 0; |
| if (address < address_ || address + len > address_ + data_.size()) |
| return ZX_ERR_NO_MEMORY; // We require everything to be mapped. |
| |
| memcpy(&data_[address - address_], buffer, len); |
| *actual = len; |
| return ZX_OK; |
| } |
| |
| private: |
| uintptr_t address_; |
| std::vector<char> data_; |
| }; |
| |
| // Provides a buffer of known memory for tests below. |
| class BreakpointFakeMemory { |
| public: |
| // Make a fake memory buffer with enough room to hold a break instruction. |
| static constexpr uintptr_t kAddress = 0x1234; |
| static constexpr size_t kDataSize = 4; |
| static_assert(kDataSize >= sizeof(arch::BreakInstructionType), |
| "Make data bigger for this platform."); |
| static const char kOriginalData[kDataSize]; |
| |
| BreakpointFakeMemory() : memory_(kAddress, kOriginalData, kDataSize) {} |
| ~BreakpointFakeMemory() {} |
| |
| FakeMemory* memory() { return &memory_; } |
| |
| // Returns the memory pointer read out as the type required for the |
| // breakpoint instruction. |
| arch::BreakInstructionType AsInstructionType() const { |
| return *reinterpret_cast<const arch::BreakInstructionType*>(memory_.data()); |
| } |
| |
| // Returns true if the buffer starts with a breakpoint instruction for the |
| // current platform. |
| bool StartsWithBreak() const { return AsInstructionType() == arch::kBreakInstruction; } |
| |
| // Returns true if the buffer is in its original state. |
| bool IsOriginal() const { return memcmp(memory_.data(), kOriginalData, kDataSize) == 0; } |
| |
| private: |
| FakeMemory memory_; |
| }; |
| |
| // A no-op process delegate. |
| class TestProcessDelegate : public Breakpoint::ProcessDelegate { |
| public: |
| TestProcessDelegate() = default; |
| |
| BreakpointFakeMemory& mem() { return mem_; } |
| std::map<uint64_t, std::unique_ptr<ProcessBreakpoint>>& bps() { return bps_; } |
| |
| void InjectMockProcess(std::unique_ptr<MockProcess> proc) { |
| procs_[proc->koid()] = std::move(proc); |
| } |
| |
| // This only gets called if Breakpoint.SetSettings() is called. |
| zx_status_t RegisterBreakpoint(Breakpoint* bp, zx_koid_t koid, uint64_t address) override { |
| auto found = bps_.find(address); |
| if (found == bps_.end()) { |
| auto pbp = |
| std::make_unique<SoftwareBreakpoint>(bp, procs_[koid].get(), mem_.memory(), address); |
| |
| zx_status_t status = pbp->Init(); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Failure initializing %d\n", (int)status); |
| return status; |
| } |
| |
| bps_[address] = std::move(pbp); |
| } else { |
| found->second->RegisterBreakpoint(bp); |
| } |
| return ZX_OK; |
| } |
| void UnregisterBreakpoint(Breakpoint* bp, zx_koid_t, uint64_t address) override { |
| auto found = bps_.find(address); |
| if (found == bps_.end()) |
| GTEST_FAIL(); |
| |
| bool still_used = found->second->UnregisterBreakpoint(bp); |
| if (!still_used) |
| bps_.erase(found); |
| } |
| |
| private: |
| BreakpointFakeMemory mem_; |
| |
| std::map<uint64_t, std::unique_ptr<ProcessBreakpoint>> bps_; |
| std::map<zx_koid_t, std::unique_ptr<MockProcess>> procs_; |
| }; |
| |
| constexpr uintptr_t BreakpointFakeMemory::kAddress; |
| constexpr size_t BreakpointFakeMemory::kDataSize; |
| const char BreakpointFakeMemory::kOriginalData[BreakpointFakeMemory::kDataSize] = {0x01, 0x02, 0x03, |
| 0x04}; |
| |
| template <typename T> |
| std::string ToString(const std::vector<T>& v) { |
| std::stringstream ss; |
| for (size_t i = 0; i < v.size(); i++) { |
| if (i > 0) |
| ss << ", "; |
| ss << v[i]; |
| } |
| |
| return ss.str(); |
| } |
| |
| template <typename T> |
| void CheckVectorContainsElements(const debug_ipc::FileLineFunction& location, |
| const std::vector<T>& got, const std::vector<T>& expected) { |
| ASSERT_EQ(expected.size(), got.size()) |
| << location.ToString() << ": " |
| << "Expected (" << ToString(expected) << "), Got (" << ToString(got) << ")."; |
| |
| for (size_t i = 0; i < expected.size(); i++) { |
| ASSERT_TRUE(expected[i] == got[i]) |
| << location.ToString() << ": " |
| << "Expected (" << ToString(expected) << "), Got (" << ToString(got) << ")."; |
| } |
| } |
| |
| } // namespace |
| |
| TEST(ProcessBreakpoint, InstallAndFixup) { |
| auto arch_provider = std::make_shared<ArchProviderImpl>(); |
| auto object_provider = std::make_shared<ObjectProvider>(); |
| |
| TestProcessDelegate process_delegate; |
| |
| Breakpoint main_breakpoint(&process_delegate); |
| debug_ipc::BreakpointSettings settings; |
| settings.type = debug_ipc::BreakpointType::kSoftware; |
| main_breakpoint.SetSettings(settings); |
| |
| zx_koid_t process_koid = 0x1234; |
| const std::string process_name = "process"; |
| MockProcess process(nullptr, process_koid, process_name, arch_provider, object_provider); |
| |
| SoftwareBreakpoint bp(&main_breakpoint, &process, process_delegate.mem().memory(), |
| BreakpointFakeMemory::kAddress); |
| ASSERT_EQ(ZX_OK, bp.Init()); |
| |
| // Should have written the breakpoint instruction to the buffer. |
| EXPECT_TRUE(process_delegate.mem().StartsWithBreak()); |
| |
| // Make a memory block that contains the address set as the breakpoint. |
| // Offset it by kBlockOffset to make sure non-aligned cases are handled. |
| debug_ipc::MemoryBlock block; |
| constexpr size_t kBlockOffset = 4; |
| block.address = BreakpointFakeMemory::kAddress - kBlockOffset; |
| block.valid = true; |
| block.size = 16; |
| block.data.resize(block.size); |
| |
| // Fill with current memory contents (including breakpoint instruction). |
| memcpy(&block.data[kBlockOffset], process_delegate.mem().memory()->data(), |
| BreakpointFakeMemory::kDataSize); |
| |
| // FixupMemoryBlock should give back the original data. |
| bp.FixupMemoryBlock(&block); |
| EXPECT_EQ(0, memcmp(&block.data[kBlockOffset], BreakpointFakeMemory::kOriginalData, |
| BreakpointFakeMemory::kDataSize)); |
| } |
| |
| // clang-format off |
| TEST(ProcessBreakpoint, StepSingle) { |
| auto arch_provider = std::make_shared<ArchProviderImpl>(); |
| auto object_provider = std::make_shared<ObjectProvider>(); |
| |
| TestProcessDelegate process_delegate; |
| |
| Breakpoint main_breakpoint(&process_delegate); |
| debug_ipc::BreakpointSettings settings; |
| settings.type = debug_ipc::BreakpointType::kSoftware; |
| main_breakpoint.SetSettings(settings); |
| |
| constexpr zx_koid_t process_koid = 0x1234; |
| const std::string process_name = "process"; |
| MockProcess process(nullptr, process_koid, process_name, arch_provider, object_provider); |
| |
| // The step over strategy is as follows: |
| // Thread 1, 2, 3 will hit the breakpoint and attempt a step over. |
| // Thread 4 will remain oblivious to the breakpoint, as will 5. |
| // Thread 5 is IsSuspended from the client, so it should not be resumed by the |
| // agent during step over. |
| |
| constexpr zx_koid_t kThread1Koid = 1; |
| constexpr zx_koid_t kThread2Koid = 2; |
| constexpr zx_koid_t kThread3Koid = 3; |
| constexpr zx_koid_t kThread4Koid = 4; |
| constexpr zx_koid_t kThread5Koid = 5; |
| MockThread* mock_thread1 = process.AddThread(kThread1Koid); |
| MockThread* mock_thread2 = process.AddThread(kThread2Koid); |
| MockThread* mock_thread3 = process.AddThread(kThread3Koid); |
| MockThread* mock_thread4 = process.AddThread(kThread4Koid); |
| MockThread* mock_thread5 = process.AddThread(kThread5Koid); |
| |
| mock_thread5->set_client_state(DebuggedThread::ClientState::kPaused); |
| mock_thread5->Suspend(); |
| |
| SoftwareBreakpoint bp(&main_breakpoint, &process, process_delegate.mem().memory(), |
| BreakpointFakeMemory::kAddress); |
| ASSERT_EQ(ZX_OK, bp.Init()); |
| |
| // The breakpoint should be installed. |
| EXPECT_TRUE(process_delegate.mem().StartsWithBreak()); |
| |
| // Thread 1 hits breakpoint ---------------------------------------------------------------------- |
| |
| bp.BeginStepOver(mock_thread1); |
| |
| // Breakpoint should be removed. |
| EXPECT_TRUE(process_delegate.mem().IsOriginal()); |
| |
| // There should be one enqueued step over. |
| ASSERT_EQ(process.step_over_queue().size(), 1u); |
| ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &bp); |
| ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread1); |
| |
| // Only thread 1 should be still running. The rest should be suspended. |
| EXPECT_TRUE(mock_thread1->running()); |
| EXPECT_TRUE(mock_thread2->IsSuspended()); |
| EXPECT_TRUE(mock_thread3->IsSuspended()); |
| EXPECT_TRUE(mock_thread4->IsSuspended()); |
| EXPECT_TRUE(mock_thread5->IsSuspended()); |
| |
| // Only thread 1 should be stepping over. |
| EXPECT_TRUE(mock_thread1->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread2->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread3->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread4->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread5->stepping_over_breakpoint()); |
| |
| CheckVectorContainsElements(FROM_HERE, bp.CurrentlySuspendedThreads(), {kThread2Koid, |
| kThread3Koid, |
| kThread4Koid, |
| kThread5Koid}); |
| |
| // Thread 2 hits breakpoint ---------------------------------------------------------------------- |
| |
| // Now the second thread gets the exception. |
| bp.BeginStepOver(mock_thread2); |
| |
| // There should be 2 enqueued step overs. |
| ASSERT_EQ(process.step_over_queue().size(), 2u); |
| ASSERT_EQ(process.step_over_queue()[1].process_breakpoint.get(), &bp); |
| ASSERT_EQ(process.step_over_queue()[1].thread.get(), mock_thread2); |
| |
| // Only thread 1 should be running. |
| EXPECT_TRUE(mock_thread1->running()); |
| EXPECT_TRUE(mock_thread2->IsSuspended()); |
| EXPECT_TRUE(mock_thread3->IsSuspended()); |
| EXPECT_TRUE(mock_thread4->IsSuspended()); |
| EXPECT_TRUE(mock_thread5->IsSuspended()); |
| |
| // Only thread 1 should be stepping over. |
| EXPECT_TRUE(mock_thread1->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread2->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread3->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread4->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread5->stepping_over_breakpoint()); |
| |
| CheckVectorContainsElements(FROM_HERE, bp.CurrentlySuspendedThreads(), {kThread2Koid, |
| kThread3Koid, |
| kThread4Koid, |
| kThread5Koid}); |
| |
| // Thread 3 hits breakpoint ---------------------------------------------------------------------- |
| |
| bp.BeginStepOver(mock_thread3); |
| |
| // There should be 2 enqueued step overs. |
| ASSERT_EQ(process.step_over_queue().size(), 3u); |
| ASSERT_EQ(process.step_over_queue()[2].process_breakpoint.get(), &bp); |
| ASSERT_EQ(process.step_over_queue()[2].thread.get(), mock_thread3); |
| |
| // Only thread 1 should be running. |
| EXPECT_TRUE(mock_thread1->running()); |
| EXPECT_TRUE(mock_thread2->IsSuspended()); |
| EXPECT_TRUE(mock_thread3->IsSuspended()); |
| EXPECT_TRUE(mock_thread4->IsSuspended()); |
| EXPECT_TRUE(mock_thread5->IsSuspended()); |
| |
| // Only thread 1 should be stepping over right now. |
| EXPECT_TRUE(mock_thread1->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread2->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread3->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread4->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread5->stepping_over_breakpoint()); |
| |
| CheckVectorContainsElements(FROM_HERE, bp.CurrentlySuspendedThreads(), {kThread2Koid, |
| kThread3Koid, |
| kThread4Koid, |
| kThread5Koid}); |
| |
| // Breakpoint should still remain removed. |
| EXPECT_TRUE(process_delegate.mem().IsOriginal()); |
| |
| // Thread 1 steps over --------------------------------------------------------------------------- |
| |
| bp.EndStepOver(mock_thread1); |
| |
| // There should be 2 enqueued step overs. |
| ASSERT_EQ(process.step_over_queue().size(), 2u); |
| ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &bp); |
| ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread2); |
| ASSERT_EQ(process.step_over_queue()[1].process_breakpoint.get(), &bp); |
| ASSERT_EQ(process.step_over_queue()[1].thread.get(), mock_thread3); |
| |
| // Only thread 2 should be running. |
| EXPECT_TRUE(mock_thread1->IsSuspended()); |
| EXPECT_TRUE(mock_thread2->running()); |
| EXPECT_TRUE(mock_thread3->IsSuspended()); |
| EXPECT_TRUE(mock_thread4->IsSuspended()); |
| EXPECT_TRUE(mock_thread5->IsSuspended()); |
| |
| // Only thread 2 should be stepping over right now. |
| EXPECT_FALSE(mock_thread1->stepping_over_breakpoint()); |
| EXPECT_TRUE(mock_thread2->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread3->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread4->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread5->stepping_over_breakpoint()); |
| |
| CheckVectorContainsElements(FROM_HERE, bp.CurrentlySuspendedThreads(), {kThread1Koid, |
| kThread3Koid, |
| kThread4Koid, |
| kThread5Koid}); |
| |
| // The breakpoint should remain removed. |
| EXPECT_TRUE(process_delegate.mem().IsOriginal()); |
| |
| // Thread 2 steps over --------------------------------------------------------------------------- |
| |
| bp.EndStepOver(mock_thread2); |
| |
| // There should be 1 enqueued step overs. |
| ASSERT_EQ(process.step_over_queue().size(), 1u); |
| ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &bp); |
| ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread3); |
| |
| // Only thread 3 should be running, as it's the only one stepping over. |
| EXPECT_TRUE(mock_thread1->IsSuspended()); |
| EXPECT_TRUE(mock_thread2->IsSuspended()); |
| EXPECT_TRUE(mock_thread3->running()); |
| EXPECT_TRUE(mock_thread4->IsSuspended()); |
| EXPECT_TRUE(mock_thread5->IsSuspended()); |
| |
| // Only thread 3 should be stepping over right now. |
| EXPECT_FALSE(mock_thread1->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread2->stepping_over_breakpoint()); |
| EXPECT_TRUE(mock_thread3->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread4->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread5->stepping_over_breakpoint()); |
| |
| CheckVectorContainsElements(FROM_HERE, bp.CurrentlySuspendedThreads(), {kThread1Koid, |
| kThread2Koid, |
| kThread4Koid, |
| kThread5Koid}); |
| |
| // Breakpoint remains removed. |
| EXPECT_TRUE(process_delegate.mem().IsOriginal()); |
| |
| // Thread 3 steps over --------------------------------------------------------------------------- |
| |
| bp.EndStepOver(mock_thread3); |
| |
| // No more enqueued elements. |
| ASSERT_EQ(process.step_over_queue().size(), 0u); |
| |
| // All threads should be resumed except 5, which was paused by the client. |
| EXPECT_TRUE(mock_thread1->running()); |
| EXPECT_TRUE(mock_thread2->running()); |
| EXPECT_TRUE(mock_thread3->running()); |
| EXPECT_TRUE(mock_thread4->running()); |
| EXPECT_TRUE(mock_thread5->IsSuspended()); |
| |
| // No thread should be stepping over. |
| EXPECT_FALSE(mock_thread1->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread2->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread3->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread4->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread5->stepping_over_breakpoint()); |
| |
| EXPECT_TRUE(bp.CurrentlySuspendedThreads().empty()); |
| |
| // Breakpoint is set again. |
| EXPECT_TRUE(process_delegate.mem().StartsWithBreak()); |
| } |
| // clang-format on |
| |
| // clang-format off |
| TEST(ProcessBreakpoint, MultipleBreakpoints) { |
| auto arch_provider = std::make_shared<ArchProviderImpl>(); |
| auto object_provider = std::make_shared<ObjectProvider>(); |
| |
| TestProcessDelegate process_delegate1; |
| TestProcessDelegate process_delegate2; |
| TestProcessDelegate process_delegate3; |
| |
| Breakpoint main_breakpoint1(&process_delegate1); |
| Breakpoint main_breakpoint2(&process_delegate2); |
| Breakpoint main_breakpoint3(&process_delegate3); |
| |
| debug_ipc::BreakpointSettings settings; |
| settings.type = debug_ipc::BreakpointType::kSoftware; |
| main_breakpoint1.SetSettings(settings); |
| main_breakpoint2.SetSettings(settings); |
| main_breakpoint3.SetSettings(settings); |
| |
| constexpr zx_koid_t process_koid = 0x1234; |
| const std::string process_name = "process"; |
| MockProcess process(nullptr, process_koid, process_name, arch_provider, object_provider); |
| |
| // The step over strategy is as follows: |
| // 1. Thread 1 hits breakpoint 1. |
| // 2. Thread 2 hits breakpoint 2. |
| // 3. Thread 3 hits breakpoint 3. |
| // 4. Thread 1 finishes step over. |
| // 5. Thread 4 hits breakpoint 2 (somehow). |
| // 6. Thread 2 finishes step over. |
| // 7. Thread 3 finishes step over. |
| // 8. Thread 4 finishes step over. |
| |
| constexpr zx_koid_t kThread1Koid = 1; |
| constexpr zx_koid_t kThread2Koid = 2; |
| constexpr zx_koid_t kThread3Koid = 3; |
| constexpr zx_koid_t kThread4Koid = 4; |
| constexpr zx_koid_t kThread5Koid = 5; |
| DebuggedThread* mock_thread1 = process.AddThread(kThread1Koid); |
| DebuggedThread* mock_thread2 = process.AddThread(kThread2Koid); |
| DebuggedThread* mock_thread3 = process.AddThread(kThread3Koid); |
| DebuggedThread* mock_thread4 = process.AddThread(kThread4Koid); |
| DebuggedThread* mock_thread5 = process.AddThread(kThread5Koid); |
| |
| SoftwareBreakpoint breakpoint1(&main_breakpoint1, &process, process_delegate1.mem().memory(), |
| BreakpointFakeMemory::kAddress); |
| |
| SoftwareBreakpoint breakpoint2(&main_breakpoint2, &process, process_delegate2.mem().memory(), |
| BreakpointFakeMemory::kAddress); |
| SoftwareBreakpoint breakpoint3(&main_breakpoint3, &process, process_delegate3.mem().memory(), |
| BreakpointFakeMemory::kAddress); |
| |
| ASSERT_EQ(ZX_OK, breakpoint1.Init()); |
| ASSERT_EQ(ZX_OK, breakpoint2.Init()); |
| ASSERT_EQ(ZX_OK, breakpoint3.Init()); |
| |
| // The breakpoint should be installed |
| EXPECT_TRUE(process_delegate1.mem().StartsWithBreak()); |
| EXPECT_TRUE(process_delegate2.mem().StartsWithBreak()); |
| EXPECT_TRUE(process_delegate3.mem().StartsWithBreak()); |
| |
| // Thread 1 hits breakpoint 1 -------------------------------------------------------------------- |
| |
| breakpoint1.BeginStepOver(mock_thread1); |
| |
| // There should be one enqueued step over. |
| ASSERT_EQ(process.step_over_queue().size(), 1u); |
| ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &breakpoint1); |
| ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread1); |
| |
| // Only thread 1 should be still running. The rest should be suspended. |
| EXPECT_TRUE(mock_thread1->running()); |
| EXPECT_TRUE(mock_thread2->IsSuspended()); |
| EXPECT_TRUE(mock_thread3->IsSuspended()); |
| EXPECT_TRUE(mock_thread4->IsSuspended()); |
| EXPECT_TRUE(mock_thread5->IsSuspended()); |
| |
| // Only thread 1 should be stepping over. |
| EXPECT_TRUE(mock_thread1->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread2->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread3->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread4->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread5->stepping_over_breakpoint()); |
| |
| CheckVectorContainsElements(FROM_HERE, breakpoint1.CurrentlySuspendedThreads(), {kThread2Koid, |
| kThread3Koid, |
| kThread4Koid, |
| kThread5Koid}); |
| CheckVectorContainsElements(FROM_HERE, breakpoint2.CurrentlySuspendedThreads(), {}); |
| CheckVectorContainsElements(FROM_HERE, breakpoint3.CurrentlySuspendedThreads(), {}); |
| |
| // Breakpoint should be removed. Others breakpoints should remain set. |
| EXPECT_TRUE(process_delegate1.mem().IsOriginal()); |
| EXPECT_TRUE(process_delegate2.mem().StartsWithBreak()); |
| EXPECT_TRUE(process_delegate3.mem().StartsWithBreak()); |
| |
| // Thread 2 hits breakpoint 2 -------------------------------------------------------------------- |
| |
| // Now the second thread gets the exception. |
| breakpoint2.BeginStepOver(mock_thread2); |
| |
| // There should be 2 enqueued step overs. |
| ASSERT_EQ(process.step_over_queue().size(), 2u); |
| ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &breakpoint1); |
| ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread1); |
| ASSERT_EQ(process.step_over_queue()[1].process_breakpoint.get(), &breakpoint2); |
| ASSERT_EQ(process.step_over_queue()[1].thread.get(), mock_thread2); |
| |
| // Only thread 1 should be running. |
| EXPECT_TRUE(mock_thread1->running()); |
| EXPECT_TRUE(mock_thread2->IsSuspended()); |
| EXPECT_TRUE(mock_thread3->IsSuspended()); |
| EXPECT_TRUE(mock_thread4->IsSuspended()); |
| EXPECT_TRUE(mock_thread5->IsSuspended()); |
| |
| // Only thread 1 should be stepping over. |
| EXPECT_TRUE(mock_thread1->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread2->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread3->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread4->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread5->stepping_over_breakpoint()); |
| |
| CheckVectorContainsElements(FROM_HERE, breakpoint1.CurrentlySuspendedThreads(), {kThread2Koid, |
| kThread3Koid, |
| kThread4Koid, |
| kThread5Koid}); |
| CheckVectorContainsElements(FROM_HERE, breakpoint2.CurrentlySuspendedThreads(), {}); |
| CheckVectorContainsElements(FROM_HERE, breakpoint3.CurrentlySuspendedThreads(), {}); |
| |
| // Breakpoint should be removed. Others breakpoints should remain set. |
| EXPECT_TRUE(process_delegate1.mem().IsOriginal()); |
| EXPECT_TRUE(process_delegate2.mem().StartsWithBreak()); |
| EXPECT_TRUE(process_delegate3.mem().StartsWithBreak()); |
| |
| // Thread 3 hits breakpoint ---------------------------------------------------------------------- |
| |
| breakpoint3.BeginStepOver(mock_thread3); |
| |
| // There should be 2 enqueued step overs. |
| ASSERT_EQ(process.step_over_queue().size(), 3u); |
| ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &breakpoint1); |
| ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread1); |
| ASSERT_EQ(process.step_over_queue()[1].process_breakpoint.get(), &breakpoint2); |
| ASSERT_EQ(process.step_over_queue()[1].thread.get(), mock_thread2); |
| ASSERT_EQ(process.step_over_queue()[2].process_breakpoint.get(), &breakpoint3); |
| ASSERT_EQ(process.step_over_queue()[2].thread.get(), mock_thread3); |
| |
| // Only thread 1 should be running. |
| EXPECT_TRUE(mock_thread1->running()); |
| EXPECT_TRUE(mock_thread2->IsSuspended()); |
| EXPECT_TRUE(mock_thread3->IsSuspended()); |
| EXPECT_TRUE(mock_thread4->IsSuspended()); |
| EXPECT_TRUE(mock_thread5->IsSuspended()); |
| |
| // Only thread 1 should be stepping over right now. |
| EXPECT_TRUE(mock_thread1->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread2->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread3->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread4->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread5->stepping_over_breakpoint()); |
| |
| CheckVectorContainsElements(FROM_HERE, breakpoint1.CurrentlySuspendedThreads(), {kThread2Koid, |
| kThread3Koid, |
| kThread4Koid, |
| kThread5Koid}); |
| CheckVectorContainsElements(FROM_HERE, breakpoint2.CurrentlySuspendedThreads(), {}); |
| CheckVectorContainsElements(FROM_HERE, breakpoint3.CurrentlySuspendedThreads(), {}); |
| |
| // Breakpoint should be removed. Others breakpoints should remain set. |
| EXPECT_TRUE(process_delegate1.mem().IsOriginal()); |
| EXPECT_TRUE(process_delegate2.mem().StartsWithBreak()); |
| EXPECT_TRUE(process_delegate3.mem().StartsWithBreak()); |
| |
| // Breakpoint 1 should be removed. Others breakpoints should remain set. |
| EXPECT_TRUE(process_delegate1.mem().IsOriginal()); |
| EXPECT_TRUE(process_delegate2.mem().StartsWithBreak()); |
| EXPECT_TRUE(process_delegate3.mem().StartsWithBreak()); |
| |
| // Thread 1 steps over --------------------------------------------------------------------------- |
| |
| breakpoint1.EndStepOver(mock_thread1); |
| |
| // There should be 2 enqueued step overs. |
| ASSERT_EQ(process.step_over_queue().size(), 2u); |
| ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &breakpoint2); |
| ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread2); |
| ASSERT_EQ(process.step_over_queue()[1].process_breakpoint.get(), &breakpoint3); |
| ASSERT_EQ(process.step_over_queue()[1].thread.get(), mock_thread3); |
| |
| // Only thread 2 should be running. |
| EXPECT_TRUE(mock_thread1->IsSuspended()); |
| EXPECT_TRUE(mock_thread2->running()); |
| EXPECT_TRUE(mock_thread3->IsSuspended()); |
| EXPECT_TRUE(mock_thread4->IsSuspended()); |
| EXPECT_TRUE(mock_thread5->IsSuspended()); |
| |
| // Only thread 2 should be stepping over right now. |
| EXPECT_FALSE(mock_thread1->stepping_over_breakpoint()); |
| EXPECT_TRUE(mock_thread2->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread3->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread4->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread5->stepping_over_breakpoint()); |
| |
| |
| CheckVectorContainsElements(FROM_HERE, breakpoint1.CurrentlySuspendedThreads(), {}); |
| CheckVectorContainsElements(FROM_HERE, breakpoint2.CurrentlySuspendedThreads(), {kThread1Koid, |
| kThread3Koid, |
| kThread4Koid, |
| kThread5Koid}); |
| CheckVectorContainsElements(FROM_HERE, breakpoint3.CurrentlySuspendedThreads(), {}); |
| |
| // Breakpoint 2 should be the only one removed. |
| EXPECT_TRUE(process_delegate1.mem().StartsWithBreak()); |
| EXPECT_TRUE(process_delegate2.mem().IsOriginal()); |
| EXPECT_TRUE(process_delegate3.mem().StartsWithBreak()); |
| |
| // Thread 4 hits breakpoint 2 -------------------------------------------------------------------- |
| |
| breakpoint2.BeginStepOver(mock_thread4); |
| |
| // There should be 3 enqueued step overs. |
| ASSERT_EQ(process.step_over_queue().size(), 3u); |
| ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &breakpoint2); |
| ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread2); |
| ASSERT_EQ(process.step_over_queue()[1].process_breakpoint.get(), &breakpoint3); |
| ASSERT_EQ(process.step_over_queue()[1].thread.get(), mock_thread3); |
| ASSERT_EQ(process.step_over_queue()[2].process_breakpoint.get(), &breakpoint2); |
| ASSERT_EQ(process.step_over_queue()[2].thread.get(), mock_thread4); |
| |
| // Only thread 2 should be running. |
| EXPECT_TRUE(mock_thread1->IsSuspended()); |
| EXPECT_TRUE(mock_thread2->running()); |
| EXPECT_TRUE(mock_thread3->IsSuspended()); |
| EXPECT_TRUE(mock_thread4->IsSuspended()); |
| EXPECT_TRUE(mock_thread5->IsSuspended()); |
| |
| // Only thread 2 should be stepping over right now. |
| EXPECT_FALSE(mock_thread1->stepping_over_breakpoint()); |
| EXPECT_TRUE(mock_thread2->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread3->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread4->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread5->stepping_over_breakpoint()); |
| |
| CheckVectorContainsElements(FROM_HERE, breakpoint1.CurrentlySuspendedThreads(), {}); |
| CheckVectorContainsElements(FROM_HERE, breakpoint2.CurrentlySuspendedThreads(), {kThread1Koid, |
| kThread3Koid, |
| kThread4Koid, |
| kThread5Koid}); |
| CheckVectorContainsElements(FROM_HERE, breakpoint3.CurrentlySuspendedThreads(), {}); |
| |
| // Breakpoint 2 should be the only one removed. |
| EXPECT_TRUE(process_delegate1.mem().StartsWithBreak()); |
| EXPECT_TRUE(process_delegate2.mem().IsOriginal()); |
| EXPECT_TRUE(process_delegate3.mem().StartsWithBreak()); |
| |
| // Thread 2 steps over --------------------------------------------------------------------------- |
| |
| breakpoint2.EndStepOver(mock_thread2); |
| |
| // There should be 2 enqueued step overs. |
| ASSERT_EQ(process.step_over_queue().size(), 2u); |
| ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &breakpoint3); |
| ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread3); |
| ASSERT_EQ(process.step_over_queue()[1].process_breakpoint.get(), &breakpoint2); |
| ASSERT_EQ(process.step_over_queue()[1].thread.get(), mock_thread4); |
| |
| // Only thread 3 should be running, as it's the only one stepping over. |
| EXPECT_TRUE(mock_thread1->IsSuspended()); |
| EXPECT_TRUE(mock_thread2->IsSuspended()); |
| EXPECT_TRUE(mock_thread3->running()); |
| EXPECT_TRUE(mock_thread4->IsSuspended()); |
| EXPECT_TRUE(mock_thread5->IsSuspended()); |
| |
| // Only thread 3 should be stepping over right now. |
| EXPECT_FALSE(mock_thread1->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread2->stepping_over_breakpoint()); |
| EXPECT_TRUE(mock_thread3->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread4->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread5->stepping_over_breakpoint()); |
| |
| CheckVectorContainsElements(FROM_HERE, breakpoint1.CurrentlySuspendedThreads(), {}); |
| CheckVectorContainsElements(FROM_HERE, breakpoint2.CurrentlySuspendedThreads(), {}); |
| CheckVectorContainsElements(FROM_HERE, breakpoint3.CurrentlySuspendedThreads(), {kThread1Koid, |
| kThread2Koid, |
| kThread4Koid, |
| kThread5Koid}); |
| // Breakpoint 3 should be the only one removed. |
| EXPECT_TRUE(process_delegate1.mem().StartsWithBreak()); |
| EXPECT_TRUE(process_delegate2.mem().StartsWithBreak()); |
| EXPECT_TRUE(process_delegate3.mem().IsOriginal()); |
| |
| // Thread 3 steps over --------------------------------------------------------------------------- |
| |
| breakpoint3.EndStepOver(mock_thread3); |
| |
| // No more enqueued elements. |
| ASSERT_EQ(process.step_over_queue().size(), 1u); |
| ASSERT_EQ(process.step_over_queue()[0].process_breakpoint.get(), &breakpoint2); |
| ASSERT_EQ(process.step_over_queue()[0].thread.get(), mock_thread4); |
| |
| // Only thread 4 should be running, as it's the only one stepping over. |
| EXPECT_TRUE(mock_thread1->IsSuspended()); |
| EXPECT_TRUE(mock_thread2->IsSuspended()); |
| EXPECT_TRUE(mock_thread3->IsSuspended()); |
| EXPECT_TRUE(mock_thread4->running()); |
| EXPECT_TRUE(mock_thread5->IsSuspended()); |
| |
| // Only thread 3 should be stepping over right now. |
| EXPECT_FALSE(mock_thread1->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread2->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread3->stepping_over_breakpoint()); |
| EXPECT_TRUE(mock_thread4->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread5->stepping_over_breakpoint()); |
| |
| CheckVectorContainsElements(FROM_HERE, breakpoint1.CurrentlySuspendedThreads(), {}); |
| CheckVectorContainsElements(FROM_HERE, breakpoint2.CurrentlySuspendedThreads(), {kThread1Koid, |
| kThread2Koid, |
| kThread3Koid, |
| kThread5Koid}); |
| CheckVectorContainsElements(FROM_HERE, breakpoint3.CurrentlySuspendedThreads(), {}); |
| |
| // Breakpoint 2 should be the only one removed. |
| EXPECT_TRUE(process_delegate1.mem().StartsWithBreak()); |
| EXPECT_TRUE(process_delegate2.mem().IsOriginal()); |
| EXPECT_TRUE(process_delegate3.mem().StartsWithBreak()); |
| |
| // Thread 4 steps over --------------------------------------------------------------------------- |
| |
| breakpoint2.EndStepOver(mock_thread4); |
| |
| // All threads should be resumed. |
| EXPECT_TRUE(mock_thread1->running()); |
| EXPECT_TRUE(mock_thread2->running()); |
| EXPECT_TRUE(mock_thread3->running()); |
| EXPECT_TRUE(mock_thread4->running()); |
| EXPECT_TRUE(mock_thread5->running()); |
| |
| // No thread should be stepping over. |
| EXPECT_FALSE(mock_thread1->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread2->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread3->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread4->stepping_over_breakpoint()); |
| EXPECT_FALSE(mock_thread5->stepping_over_breakpoint()); |
| |
| EXPECT_TRUE(breakpoint1.CurrentlySuspendedThreads().empty()); |
| EXPECT_TRUE(breakpoint2.CurrentlySuspendedThreads().empty()); |
| EXPECT_TRUE(breakpoint3.CurrentlySuspendedThreads().empty()); |
| |
| // All breakpoints are set again. |
| EXPECT_TRUE(process_delegate1.mem().StartsWithBreak()); |
| EXPECT_TRUE(process_delegate2.mem().StartsWithBreak()); |
| EXPECT_TRUE(process_delegate3.mem().StartsWithBreak()); |
| } |
| // clang-format on |
| |
| // This also tests registration and unregistration of ProcessBreakpoints via |
| // the Breakpoint object. |
| TEST(ProcessBreakpoint, HitCount) { |
| TestProcessDelegate process_delegate; |
| |
| constexpr uint32_t kBreakpointId1 = 12; |
| debug_ipc::BreakpointSettings settings; |
| settings.id = kBreakpointId1; |
| settings.type = debug_ipc::BreakpointType::kSoftware; |
| settings.locations.resize(1); |
| |
| constexpr zx_koid_t kProcess1 = 1; |
| |
| debug_ipc::ProcessBreakpointSettings& pr_settings = settings.locations.back(); |
| pr_settings.process_koid = kProcess1; |
| pr_settings.thread_koid = 0; |
| pr_settings.address = BreakpointFakeMemory::kAddress; |
| |
| // Create a ProcessBreakpoint referencing the two Breakpoint objects |
| // (corresponds to two logical breakpoints at the same address). |
| std::unique_ptr<Breakpoint> main_breakpoint1 = std::make_unique<Breakpoint>(&process_delegate); |
| zx_status_t status = main_breakpoint1->SetSettings(settings); |
| ASSERT_EQ(ZX_OK, status); |
| |
| std::unique_ptr<Breakpoint> main_breakpoint2 = std::make_unique<Breakpoint>(&process_delegate); |
| constexpr uint32_t kBreakpointId2 = 13; |
| settings.id = kBreakpointId2; |
| status = main_breakpoint2->SetSettings(settings); |
| ASSERT_EQ(ZX_OK, status); |
| |
| // There should only be one address with a breakpoint. |
| ASSERT_EQ(1u, process_delegate.bps().size()); |
| EXPECT_EQ(BreakpointFakeMemory::kAddress, process_delegate.bps().begin()->first); |
| |
| // Hitting the ProcessBreakpoint should update both Breakpoints. |
| std::vector<debug_ipc::BreakpointStats> stats; |
| process_delegate.bps().begin()->second->OnHit(debug_ipc::BreakpointType::kSoftware, &stats); |
| ASSERT_EQ(2u, stats.size()); |
| |
| // Order of the vector is not defined so allow either. |
| EXPECT_TRUE((stats[0].id == kBreakpointId1 && stats[1].id == kBreakpointId2) || |
| (stats[0].id == kBreakpointId2 && stats[1].id == kBreakpointId1)); |
| |
| // The hit count of both should be 1 (order doesn't matter). |
| EXPECT_EQ(1u, stats[0].hit_count); |
| EXPECT_EQ(1u, stats[1].hit_count); |
| |
| // Unregistering one Breakpoint should keep the ProcessBreakpoint. |
| main_breakpoint2.reset(); |
| ASSERT_EQ(1u, process_delegate.bps().size()); |
| |
| // Unregistering the other should delete it. |
| main_breakpoint1.reset(); |
| ASSERT_EQ(0u, process_delegate.bps().size()); |
| } |
| |
| #ifdef PROCESS_BREAKPOINT_TRANSITION |
| |
| TEST(ProcessBreakpoint, HWBreakpointForAllThreads) { |
| constexpr zx_koid_t kProcessId = 0x1234; |
| constexpr zx_koid_t kThreadId1 = 0x1; |
| constexpr zx_koid_t kThreadId2 = 0x2; |
| constexpr zx_koid_t kThreadId3 = 0x3; |
| constexpr uint32_t kBreakpointId1 = 0x1; |
| constexpr uint64_t kAddress = 0x80000000; |
| |
| auto process = std::make_unique<MockProcess>(nullptr, kProcessId, ObjectProvider::Get()); |
| process->AddThread(kThreadId1); |
| process->AddThread(kThreadId2); |
| process->AddThread(kThreadId3); |
| TestProcessDelegate process_delegate; |
| process_delegate.InjectMockProcess(std::move(process)); |
| |
| // Any calls to the architecture will be routed to this instance. |
| ScopedMockArchProvider scoped_arch_provider; |
| MockArchProvider* arch_provider = scoped_arch_provider.get_provider(); |
| |
| auto breakpoint = std::make_unique<Breakpoint>(&process_delegate); |
| debug_ipc::BreakpointSettings settings1 = {}; |
| settings1.id = kBreakpointId1; |
| // This location is for all threads. |
| settings1.locations.push_back({kProcessId, 0, kAddress}); |
| zx_status_t status = breakpoint->SetSettings(debug_ipc::BreakpointType::kHardware, settings1); |
| ASSERT_EQ(status, ZX_OK); |
| |
| // Should have installed the breakpoint. |
| ASSERT_EQ(process_delegate.bps().size(), 1u); |
| auto& process_bp = process_delegate.bps().begin()->second; |
| ASSERT_EQ(process_bp->address(), kAddress); |
| |
| // It should have installed a HW breakpoint for each thread. |
| EXPECT_FALSE(process_bp->SoftwareBreakpointInstalled()); |
| EXPECT_TRUE(process_bp->HardwareBreakpointInstalled()); |
| EXPECT_EQ(arch_provider->BreakpointInstallCount(kAddress), 3u); |
| |
| // Deleting the breakpoint should remove the process breakpoint. |
| breakpoint.reset(); |
| EXPECT_EQ(arch_provider->BreakpointUninstallCount(kAddress), 3u); |
| EXPECT_EQ(process_delegate.bps().size(), 0u); |
| } |
| |
| TEST(ProcessBreakpoint, HWBreakpointWithThreadId) { |
| constexpr zx_koid_t kProcessId = 0x1234; |
| constexpr zx_koid_t kThreadId1 = 0x1; |
| constexpr zx_koid_t kThreadId2 = 0x2; |
| constexpr zx_koid_t kThreadId3 = 0x3; |
| constexpr uint32_t kBreakpointId1 = 0x1; |
| constexpr uint32_t kBreakpointId2 = 0x2; |
| constexpr uint32_t kSwBreakpointId = 0x3; |
| constexpr uint64_t kAddress = BreakpointFakeMemory::kAddress; |
| constexpr uint64_t kOtherAddress = 0x8fffffff; |
| |
| auto process = std::make_unique<MockProcess>(nullptr, kProcessId, ObjectProvider::Get()); |
| process->AddThread(kThreadId1); |
| process->AddThread(kThreadId2); |
| process->AddThread(kThreadId3); |
| TestProcessDelegate process_delegate; |
| process_delegate.InjectMockProcess(std::move(process)); |
| |
| // Any calls to the architecture will be routed to this instance. |
| ScopedMockArchProvider scoped_arch_provider; |
| MockArchProvider* arch_provider = scoped_arch_provider.get_provider(); |
| |
| auto breakpoint1 = std::make_unique<Breakpoint>(&process_delegate); |
| debug_ipc::BreakpointSettings settings1 = {}; |
| settings1.id = kBreakpointId1; |
| settings1.type = debug_ipc::BreakpointType::kHardware; |
| settings1.locations.push_back({kProcessId, kThreadId1, kAddress}); |
| zx_status_t status = breakpoint1->SetSettings(settings1); |
| ASSERT_EQ(status, ZX_OK); |
| // Should have installed the process breakpoint. |
| ASSERT_EQ(process_delegate.bps().size(), 1u); |
| auto& process_bp = process_delegate.bps().begin()->second; |
| ASSERT_EQ(process_bp->address(), kAddress); |
| // This should have installed HW breakpoint for only one thread. |
| // This should have installed only a HW breakpoint. |
| ASSERT_EQ(arch_provider->TotalBreakpointInstallCalls(), 1u); |
| ASSERT_EQ(arch_provider->BreakpointInstallCount(kAddress), 1u); |
| ASSERT_EQ(arch_provider->TotalBreakpointUninstallCalls(), 0u); |
| EXPECT_FALSE(process_bp->SoftwareBreakpointInstalled()); |
| EXPECT_TRUE(process_bp->HardwareBreakpointInstalled()); |
| |
| // Register another breakpoint. |
| auto breakpoint2 = std::make_unique<Breakpoint>(&process_delegate); |
| debug_ipc::BreakpointSettings settings2 = {}; |
| settings2.id = kBreakpointId2; |
| settings2.type = debug_ipc::BreakpointType::kHardware; |
| settings2.locations.push_back({kProcessId, kThreadId2, kAddress}); |
| // This breakpoint has another location for another thread. |
| // In practice, this should not happen, but it's important that no HW |
| // breakpoint get installed if for the wrong location. |
| settings2.locations.push_back({kProcessId, kThreadId3, kOtherAddress}); |
| breakpoint2->SetSettings(settings2); |
| // Registering this breakpoint should create a new ProcessBreakpoint. |
| ASSERT_EQ(process_delegate.bps().size(), 2u); |
| auto& process_bp2 = (process_delegate.bps().begin()++)->second; |
| ASSERT_EQ(process_bp2->address(), kOtherAddress); |
| // Registering the second breakpoint should install for the new thread in |
| // the old location and one in the new location. |
| ASSERT_EQ(arch_provider->TotalBreakpointInstallCalls(), 3u); |
| ASSERT_EQ(arch_provider->BreakpointInstallCount(kAddress), 2u); |
| ASSERT_EQ(arch_provider->BreakpointInstallCount(kOtherAddress), 1u); |
| ASSERT_EQ(arch_provider->TotalBreakpointUninstallCalls(), 0u); |
| EXPECT_FALSE(process_bp->SoftwareBreakpointInstalled()); |
| |
| // Unregistering a breakpoint should only uninstall the HW breakpoint for |
| // one thread. |
| breakpoint1.reset(); |
| ASSERT_EQ(arch_provider->TotalBreakpointInstallCalls(), 3u); |
| ASSERT_EQ(arch_provider->TotalBreakpointUninstallCalls(), 1u); |
| ASSERT_EQ(arch_provider->BreakpointUninstallCount(kAddress), 1u); |
| ASSERT_EQ(arch_provider->BreakpointUninstallCount(kOtherAddress), 0u); |
| EXPECT_FALSE(process_bp->SoftwareBreakpointInstalled()); |
| EXPECT_FALSE(process_bp->SoftwareBreakpointInstalled()); |
| EXPECT_TRUE(process_bp->HardwareBreakpointInstalled()); |
| EXPECT_TRUE(process_bp2->HardwareBreakpointInstalled()); |
| |
| // Adding a SW breakpoint should not install HW locations. |
| auto sw_breakpoint = std::make_unique<Breakpoint>(&process_delegate); |
| debug_ipc::BreakpointSettings sw_settings = {}; |
| sw_settings.id = kSwBreakpointId; |
| sw_settings.type = debug_ipc::BreakpointType::kSoftware; |
| sw_settings.locations.push_back({kProcessId, 0, kAddress}); |
| sw_breakpoint->SetSettings(sw_settings); |
| // Should have installed only a SW breakpoint. |
| ASSERT_EQ(arch_provider->TotalBreakpointInstallCalls(), 3u); |
| ASSERT_EQ(arch_provider->TotalBreakpointUninstallCalls(), 1u); |
| EXPECT_TRUE(process_bp->SoftwareBreakpointInstalled()); |
| |
| // Unregistering should remove the other hw breakpoint. |
| // And also the second process breakpoint. |
| breakpoint2.reset(); |
| ASSERT_EQ(arch_provider->TotalBreakpointInstallCalls(), 3u); |
| ASSERT_EQ(arch_provider->TotalBreakpointUninstallCalls(), 3u); |
| ASSERT_EQ(arch_provider->BreakpointUninstallCount(kAddress), 2u); |
| ASSERT_EQ(arch_provider->BreakpointUninstallCount(kOtherAddress), 1u); |
| EXPECT_FALSE(process_bp->HardwareBreakpointInstalled()); |
| EXPECT_TRUE(process_bp->SoftwareBreakpointInstalled()); |
| ASSERT_EQ(process_delegate.bps().size(), 1u); |
| EXPECT_EQ(process_delegate.bps().begin()->second->address(), kAddress); |
| |
| // Removing the SW breakpoint should work and would delete the final process |
| // breakpoint. |
| sw_breakpoint.reset(); |
| ASSERT_EQ(arch_provider->TotalBreakpointInstallCalls(), 3u); |
| ASSERT_EQ(arch_provider->TotalBreakpointUninstallCalls(), 3u); |
| EXPECT_EQ(process_delegate.bps().size(), 0u); |
| } |
| |
| #endif |
| |
| } // namespace debug_agent |