| // 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.h" |
| #include "src/developer/debug/debug_agent/breakpoint.h" |
| #include "src/developer/debug/debug_agent/debug_agent.h" |
| #include "src/developer/debug/debug_agent/debugged_thread.h" |
| #include "src/developer/debug/debug_agent/mock_job_handle.h" |
| #include "src/developer/debug/debug_agent/mock_process.h" |
| #include "src/developer/debug/debug_agent/mock_system_interface.h" |
| #include "src/developer/debug/debug_agent/mock_thread.h" |
| #include "src/developer/debug/shared/logging/debug.h" |
| |
| namespace debug_agent { |
| |
| namespace { |
| |
| // A no-op process delegate. |
| class TestProcessDelegate : public Breakpoint::ProcessDelegate { |
| public: |
| TestProcessDelegate() = default; |
| |
| 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. |
| debug::Status 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(), address); |
| |
| auto status = pbp->Init(); |
| if (status.has_error()) { |
| fprintf(stderr, "Failure initializing %s\n", status.message().c_str()); |
| return status; |
| } |
| |
| bps_[address] = std::move(pbp); |
| } else { |
| found->second->RegisterBreakpoint(bp); |
| } |
| return debug::Status(); |
| } |
| 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: |
| std::map<uint64_t, std::unique_ptr<ProcessBreakpoint>> bps_; |
| std::map<zx_koid_t, std::unique_ptr<MockProcess>> procs_; |
| }; |
| |
| constexpr uintptr_t kAddress = 0x1234; |
| constexpr size_t kDataSize = 4u; |
| const uint8_t kOriginalData[kDataSize] = {0x01, 0x02, 0x03, 0x04}; |
| |
| std::vector<uint8_t> GetOriginalData() { |
| return std::vector<uint8_t>(std::begin(kOriginalData), std::end(kOriginalData)); |
| } |
| |
| // Returns the memory data buffer with the beginning overwritten by a software breakpoint. |
| std::vector<uint8_t> GetOriginalDataWithBreakpoint() { |
| auto result = GetOriginalData(); |
| memcpy(result.data(), &arch::kBreakInstruction, arch::kBreakInstructionSize); |
| return result; |
| } |
| |
| // Writes the original data or breakpoint data at |kAddress| to the mock data backing the given |
| // process handle. |
| void LoadOriginalMemory(MockProcessHandle& handle) { |
| handle.mock_memory().AddMemory(kAddress, GetOriginalData()); |
| } |
| |
| bool MemoryContains(MockProcessHandle& handle, uint64_t address, const void* data, |
| size_t data_len) { |
| if (data_len == 0) |
| return true; |
| |
| std::unique_ptr<uint8_t[]> read(new uint8_t[data_len]); |
| size_t actual = 0; |
| if (handle.ReadMemory(address, read.get(), data_len, &actual).has_error() || actual != data_len) |
| return false; |
| |
| return memcmp(data, read.get(), data_len) == 0; |
| } |
| |
| bool MemoryContainsBreak(MockProcessHandle& handle, uint64_t address) { |
| return MemoryContains(handle, address, &arch::kBreakInstruction, arch::kBreakInstructionSize); |
| } |
| bool MemoryContainsOriginal(MockProcessHandle& handle, uint64_t address) { |
| return MemoryContains(handle, address, kOriginalData, arch::kBreakInstructionSize); |
| } |
| |
| 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::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) { |
| 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); |
| |
| LoadOriginalMemory(process.mock_process_handle()); |
| |
| SoftwareBreakpoint bp(&main_breakpoint, &process, kAddress); |
| ASSERT_TRUE(bp.Init().ok()); |
| |
| // Should have written the breakpoint instruction. |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress)); |
| |
| // 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 = kAddress - kBlockOffset; |
| block.valid = true; |
| block.size = 16; |
| block.data.resize(block.size); |
| |
| // Fill with memory contents reflecting the breakpoint instruction. |
| auto with_bp = GetOriginalDataWithBreakpoint(); |
| memcpy(&block.data[kBlockOffset], with_bp.data(), with_bp.size()); |
| |
| // FixupMemoryBlock should give back the original data. |
| bp.FixupMemoryBlock(&block); |
| EXPECT_TRUE( |
| std::equal(&block.data[kBlockOffset], &block.data[kBlockOffset + kDataSize], kOriginalData)); |
| } |
| |
| TEST(ProcessBreakpoint, StepSingle) { |
| 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); |
| |
| process.mock_process_handle().mock_memory().AddMemory(kAddress, GetOriginalData()); |
| |
| // 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 suspended 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->ClientSuspend(); |
| |
| SoftwareBreakpoint bp(&main_breakpoint, &process, kAddress); |
| ASSERT_TRUE(bp.Init().ok()); |
| |
| // The breakpoint should be installed. |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress)); |
| |
| // Thread 1 hits breakpoint ---------------------------------------------------------------------- |
| |
| bp.BeginStepOver(mock_thread1); |
| |
| // Breakpoint should be removed. |
| EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress)); |
| |
| // 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->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended()); |
| |
| // 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->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended()); |
| |
| // 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->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended()); |
| |
| // 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}); |
| |
| // The memory should be original. |
| EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress)); |
| |
| // 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->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread2->running()); |
| EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended()); |
| |
| // 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 memory should be original. |
| EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress)); |
| |
| // 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->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread2->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread3->running()); |
| EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended()); |
| |
| // 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(MemoryContainsOriginal(process.mock_process_handle(), kAddress)); |
| |
| // 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->mock_thread_handle().is_suspended()); |
| |
| // 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(MemoryContainsBreak(process.mock_process_handle(), kAddress)); |
| } |
| |
| TEST(ProcessBreakpoint, MultipleBreakpoints) { |
| 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); |
| |
| constexpr uint64_t kAddress1 = kAddress; |
| constexpr uint64_t kAddress2 = kAddress + 0x100; |
| constexpr uint64_t kAddress3 = kAddress + 0x200; |
| process.mock_process_handle().mock_memory().AddMemory(kAddress1, GetOriginalData()); |
| process.mock_process_handle().mock_memory().AddMemory(kAddress2, GetOriginalData()); |
| process.mock_process_handle().mock_memory().AddMemory(kAddress3, GetOriginalData()); |
| |
| // 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; |
| 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); |
| |
| SoftwareBreakpoint breakpoint1(&main_breakpoint1, &process, kAddress1); |
| SoftwareBreakpoint breakpoint2(&main_breakpoint2, &process, kAddress2); |
| SoftwareBreakpoint breakpoint3(&main_breakpoint3, &process, kAddress3); |
| |
| ASSERT_TRUE(breakpoint1.Init().ok()); |
| ASSERT_TRUE(breakpoint2.Init().ok()); |
| ASSERT_TRUE(breakpoint3.Init().ok()); |
| |
| // The breakpoint should be installed at all locations. |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress1)); |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress2)); |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress3)); |
| |
| // 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->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended()); |
| |
| // 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(MemoryContainsOriginal(process.mock_process_handle(), kAddress1)); |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress2)); |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress3)); |
| |
| // 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->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended()); |
| |
| // 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(MemoryContainsOriginal(process.mock_process_handle(), kAddress1)); |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress2)); |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress3)); |
| |
| // 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->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended()); |
| |
| // 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(MemoryContainsOriginal(process.mock_process_handle(), kAddress1)); |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress2)); |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress3)); |
| |
| // 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->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread2->running()); |
| EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended()); |
| |
| // 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(MemoryContainsBreak(process.mock_process_handle(), kAddress1)); |
| EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress2)); |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress3)); |
| |
| // 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->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread2->running()); |
| EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended()); |
| |
| // 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(MemoryContainsBreak(process.mock_process_handle(), kAddress1)); |
| EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress2)); |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress3)); |
| |
| // 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->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread2->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread3->running()); |
| EXPECT_TRUE(mock_thread4->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended()); |
| |
| // 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(MemoryContainsBreak(process.mock_process_handle(), kAddress1)); |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress2)); |
| EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress3)); |
| |
| // 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->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread2->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread3->mock_thread_handle().is_suspended()); |
| EXPECT_TRUE(mock_thread4->running()); |
| EXPECT_TRUE(mock_thread5->mock_thread_handle().is_suspended()); |
| |
| // 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(MemoryContainsBreak(process.mock_process_handle(), kAddress1)); |
| EXPECT_TRUE(MemoryContainsOriginal(process.mock_process_handle(), kAddress2)); |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress3)); |
| |
| // 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(MemoryContainsBreak(process.mock_process_handle(), kAddress1)); |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress2)); |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress3)); |
| } |
| |
| // It's possible for a thread to try to step over the same breakpoint twice. This normally indicates |
| // an error removing the breakpoint to step over it. We must not enqueue the same thread twice in |
| // this error case or it will be forever stuck waiting for itself. |
| TEST(ProcessBreakpoint, RecursiveStep) { |
| 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); |
| |
| process.mock_process_handle().mock_memory().AddMemory(kAddress, GetOriginalData()); |
| |
| constexpr zx_koid_t kThreadKoid = 1; |
| MockThread* mock_thread = process.AddThread(kThreadKoid); |
| |
| SoftwareBreakpoint bp(&main_breakpoint, &process, kAddress); |
| ASSERT_TRUE(bp.Init().ok()); |
| |
| // The breakpoint should be installed. |
| EXPECT_TRUE(MemoryContainsBreak(process.mock_process_handle(), kAddress)); |
| |
| // Hit the breakpoint the first time. |
| bp.BeginStepOver(mock_thread); |
| |
| // 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_thread); |
| |
| // Recursively hits the same breakpoint. |
| bp.BeginStepOver(mock_thread); |
| |
| // There should still just 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_thread); |
| } |
| |
| // This also tests registration and unregistration of ProcessBreakpoints via the Breakpoint object. |
| TEST(ProcessBreakpoint, HitCount) { |
| // This test requires a debug agent because it causes a breakpoint hit which will try to suspend |
| // all processes attached. |
| DebugAgent debug_agent(std::make_unique<MockSystemInterface>(MockJobHandle(99999))); |
| TestProcessDelegate process_delegate; |
| |
| constexpr zx_koid_t kProcess1 = 1; |
| auto owning_process = std::make_unique<MockProcess>(&debug_agent, kProcess1); |
| DebuggedProcess* process = owning_process.get(); |
| owning_process->mock_process_handle().mock_memory().AddMemory(kAddress, GetOriginalData()); |
| process_delegate.InjectMockProcess(std::move(owning_process)); |
| |
| auto owning_thread = std::make_unique<MockThread>(process, 28374); |
| MockThread* thread = owning_thread.get(); |
| process->InjectThreadForTest(std::move(owning_thread)); |
| |
| constexpr uint32_t kBreakpointId1 = 12; |
| debug_ipc::BreakpointSettings settings; |
| settings.id = kBreakpointId1; |
| settings.type = debug_ipc::BreakpointType::kSoftware; |
| settings.locations.resize(1); |
| |
| debug_ipc::ProcessBreakpointSettings& pr_settings = settings.locations.back(); |
| pr_settings.id = {.process = kProcess1, .thread = 0}; |
| pr_settings.address = 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); |
| auto status = main_breakpoint1->SetSettings(settings); |
| ASSERT_TRUE(status.ok()); |
| |
| 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_TRUE(status.ok()); |
| |
| // There should only be one address with a breakpoint. |
| ASSERT_EQ(1u, process_delegate.bps().size()); |
| EXPECT_EQ(kAddress, process_delegate.bps().begin()->first); |
| |
| // Hitting the ProcessBreakpoint should update both Breakpoints. |
| std::vector<debug_ipc::BreakpointStats> stats; |
| std::vector<debug_ipc::ThreadRecord> affected_threads; |
| process_delegate.bps().begin()->second->OnHit(thread, debug_ipc::BreakpointType::kSoftware, stats, |
| affected_threads); |
| 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()); |
| } |
| |
| } // namespace debug_agent |