| // 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/debugged_process.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/mock_process.h" |
| #include "src/developer/debug/debug_agent/mock_thread.h" |
| #include "src/developer/debug/debug_agent/test_utils.h" |
| |
| namespace debug_agent { |
| namespace { |
| |
| class MockProcessDelegate : public Breakpoint::ProcessDelegate { |
| public: |
| debug::Status RegisterBreakpoint(Breakpoint*, zx_koid_t, uint64_t) override { |
| return debug::Status(); |
| } |
| void UnregisterBreakpoint(Breakpoint* bp, zx_koid_t process_koid, uint64_t address) override {} |
| |
| debug::Status RegisterWatchpoint(Breakpoint*, zx_koid_t, const debug::AddressRange&) override { |
| return debug::Status(); |
| } |
| void UnregisterWatchpoint(Breakpoint*, zx_koid_t, const debug::AddressRange&) override {} |
| }; |
| |
| // Fills a vector with the breakpoint instruction for the current architecture. The size will vary |
| // by architecture. |
| std::vector<uint8_t> GetBreakpointMemory() { |
| std::vector<uint8_t> result; |
| result.resize(arch::kBreakInstructionSize); |
| memcpy(result.data(), &arch::kBreakInstruction, arch::kBreakInstructionSize); |
| return result; |
| } |
| |
| // If |thread| is null, it means a process-wide breakpoint. |
| debug_ipc::ProcessBreakpointSettings CreateLocation(zx_koid_t process_koid, zx_koid_t thread_koid, |
| uint64_t address) { |
| debug_ipc::ProcessBreakpointSettings location = {}; |
| location.id = {.process = process_koid, .thread = thread_koid}; |
| location.address = address; |
| |
| return location; |
| } |
| |
| debug_ipc::ProcessBreakpointSettings CreateLocation(zx_koid_t process_koid, zx_koid_t thread_koid, |
| const debug::AddressRange& range) { |
| debug_ipc::ProcessBreakpointSettings location = {}; |
| location.id = {.process = process_koid, .thread = thread_koid}; |
| location.address_range = range; |
| |
| return location; |
| } |
| |
| debug::AddressRange SetLocation(Breakpoint* breakpoint, zx_koid_t koid, |
| const debug::AddressRange& range) { |
| debug_ipc::BreakpointSettings settings; |
| settings.type = debug_ipc::BreakpointType::kWrite; |
| settings.locations.push_back(CreateLocation(koid, 0, range)); |
| breakpoint->SetSettings(settings); |
| |
| return range; |
| } |
| |
| // Tests ------------------------------------------------------------------------------------------- |
| |
| constexpr zx_koid_t kProcessKoid = 0x1; |
| const std::string kProcessName = "process-name"; |
| constexpr uint64_t kAddress1 = 0x1234; |
| constexpr uint64_t kAddress2 = 0x5678; |
| constexpr uint64_t kAddress3 = 0x9abc; |
| constexpr uint64_t kAddress4 = 0xdef0; |
| |
| constexpr debug::AddressRange kAddressRange1 = {0x1, 0x2}; |
| |
| TEST(DebuggedProcess, RegisterBreakpoints) { |
| MockProcessDelegate process_delegate; |
| MockProcess process(nullptr, kProcessKoid, kProcessName); |
| |
| // There needs to be memory backing the breakpoints so set some memory here. When the breakpoint |
| // is uninstalled, the breakpoint expects that it will be the debug break instruction. So we |
| // handle both cases and always report a breakpoint at these addresses. |
| process.mock_process_handle().mock_memory().AddMemory(kAddress1, GetBreakpointMemory()); |
| process.mock_process_handle().mock_memory().AddMemory(kAddress2, GetBreakpointMemory()); |
| process.mock_process_handle().mock_memory().AddMemory(kAddress3, GetBreakpointMemory()); |
| process.mock_process_handle().mock_memory().AddMemory(kAddress4, GetBreakpointMemory()); |
| |
| debug_ipc::BreakpointSettings settings; |
| settings.type = debug_ipc::BreakpointType::kSoftware; |
| settings.locations.push_back(CreateLocation(kProcessKoid, 0, kAddress1)); |
| settings.locations.push_back(CreateLocation(kProcessKoid, 0, kAddress2)); |
| settings.locations.push_back(CreateLocation(kProcessKoid, 0, kAddress3)); |
| Breakpoint breakpoint(&process_delegate); |
| breakpoint.SetSettings(settings); |
| |
| ASSERT_TRUE(process.RegisterBreakpoint(&breakpoint, kAddress1).ok()); |
| |
| ASSERT_EQ(process.software_breakpoints().size(), 1u); |
| auto it = process.software_breakpoints().begin(); |
| EXPECT_EQ(it++->first, kAddress1); |
| EXPECT_EQ(it, process.software_breakpoints().end()); |
| |
| // Add 2 other breakpoints. |
| ASSERT_TRUE(process.RegisterBreakpoint(&breakpoint, kAddress2).ok()); |
| ASSERT_TRUE(process.RegisterBreakpoint(&breakpoint, kAddress3).ok()); |
| ASSERT_EQ(process.software_breakpoints().size(), 3u); |
| it = process.software_breakpoints().begin(); |
| EXPECT_EQ(it++->first, kAddress1); |
| EXPECT_EQ(it++->first, kAddress2); |
| EXPECT_EQ(it++->first, kAddress3); |
| EXPECT_EQ(it, process.software_breakpoints().end()); |
| |
| // Unregister a breakpoint. |
| process.UnregisterBreakpoint(&breakpoint, kAddress1); |
| ASSERT_EQ(process.software_breakpoints().size(), 2u); |
| it = process.software_breakpoints().begin(); |
| EXPECT_EQ(it++->first, kAddress2); |
| EXPECT_EQ(it++->first, kAddress3); |
| EXPECT_EQ(it, process.software_breakpoints().end()); |
| |
| // Register a hardware breakpoint. |
| Breakpoint hw_breakpoint(&process_delegate); |
| debug_ipc::BreakpointSettings hw_settings; |
| hw_settings.type = debug_ipc::BreakpointType::kHardware; |
| hw_settings.locations.push_back(CreateLocation(kProcessKoid, 0, kAddress4)); |
| hw_breakpoint.SetSettings(hw_settings); |
| |
| ASSERT_TRUE(process.RegisterBreakpoint(&hw_breakpoint, kAddress3).ok()); |
| ASSERT_TRUE(process.RegisterBreakpoint(&hw_breakpoint, kAddress4).ok()); |
| |
| // Should've inserted 2 HW breakpoint. |
| ASSERT_EQ(process.software_breakpoints().size(), 2u); |
| ASSERT_EQ(process.hardware_breakpoints().size(), 2u); |
| auto hw_it = process.hardware_breakpoints().begin(); |
| EXPECT_EQ(hw_it++->first, kAddress3); |
| EXPECT_EQ(hw_it++->first, kAddress4); |
| EXPECT_EQ(hw_it, process.hardware_breakpoints().end()); |
| |
| // Remove a hardware breakpoint. |
| process.UnregisterBreakpoint(&hw_breakpoint, kAddress3); |
| ASSERT_EQ(process.software_breakpoints().size(), 2u); |
| ASSERT_EQ(process.hardware_breakpoints().size(), 1u); |
| hw_it = process.hardware_breakpoints().begin(); |
| EXPECT_EQ(hw_it++->first, kAddress4); |
| EXPECT_EQ(hw_it, process.hardware_breakpoints().end()); |
| |
| // Add a watchpoint. |
| Breakpoint wp_breakpoint(&process_delegate); |
| debug_ipc::BreakpointSettings wp_settings; |
| wp_settings.type = debug_ipc::BreakpointType::kWrite; |
| wp_settings.locations.push_back(CreateLocation(kProcessKoid, 0, kAddressRange1)); |
| wp_breakpoint.SetSettings(wp_settings); |
| |
| ASSERT_TRUE(process.RegisterWatchpoint(&wp_breakpoint, kAddressRange1).ok()); |
| ASSERT_EQ(process.software_breakpoints().size(), 2u); |
| ASSERT_EQ(process.hardware_breakpoints().size(), 1u); |
| ASSERT_EQ(process.watchpoints().size(), 1u); |
| auto wp_it = process.watchpoints().begin(); |
| EXPECT_EQ(wp_it++->first, kAddressRange1); |
| EXPECT_EQ(wp_it, process.watchpoints().end()); |
| } |
| |
| TEST(DebuggedProcess, WatchpointRegistration) { |
| MockProcessDelegate process_delegate; |
| Breakpoint breakpoint(&process_delegate); |
| |
| MockProcess process(nullptr, kProcessKoid, kProcessName); |
| |
| // 1 byte. |
| for (uint32_t i = 0; i < 16; i++) { |
| debug::AddressRange range = {0x10 + i, 0x10 + i + 1}; |
| SetLocation(&breakpoint, process.koid(), range); |
| ASSERT_TRUE(process.RegisterWatchpoint(&breakpoint, range).ok()); |
| } |
| |
| // 2 bytes. |
| for (uint32_t i = 0; i < 16; i++) { |
| debug::AddressRange range = {0x10 + i, 0x10 + i + 2}; |
| SetLocation(&breakpoint, process.koid(), range); |
| |
| // Only aligned values should work. |
| bool expected_ok = (range.begin() & 0b1) == 0; |
| SCOPED_TRACE(range.ToString()); |
| ASSERT_EQ(process.RegisterWatchpoint(&breakpoint, range).ok(), expected_ok); |
| } |
| |
| // 3 bytes. |
| for (uint32_t i = 0; i < 16; i++) { |
| debug::AddressRange range = {0x10 + i, 0x10 + i + 3}; |
| SetLocation(&breakpoint, process.koid(), range); |
| ASSERT_TRUE(process.RegisterWatchpoint(&breakpoint, range).has_error()); |
| } |
| |
| // 4 bytes. |
| for (uint32_t i = 0; i < 16; i++) { |
| debug::AddressRange range = {0x10 + i, 0x10 + i + 4}; |
| SetLocation(&breakpoint, process.koid(), range); |
| |
| // Only aligned values should work. |
| bool expected_ok = (range.begin() & 0b11) == 0; |
| SCOPED_TRACE(range.ToString()); |
| ASSERT_EQ(process.RegisterWatchpoint(&breakpoint, range).ok(), expected_ok); |
| } |
| |
| // 5 bytes. |
| for (uint32_t i = 0; i < 16; i++) { |
| debug::AddressRange range = {0x10 + i, 0x10 + i + 5}; |
| SetLocation(&breakpoint, process.koid(), range); |
| ASSERT_TRUE(process.RegisterWatchpoint(&breakpoint, range).has_error()); |
| } |
| |
| // 6 bytes. |
| for (uint32_t i = 0; i < 16; i++) { |
| debug::AddressRange range = {0x10 + i, 0x10 + i + 6}; |
| SetLocation(&breakpoint, process.koid(), range); |
| ASSERT_TRUE(process.RegisterWatchpoint(&breakpoint, range).has_error()); |
| } |
| |
| // 6 bytes. |
| for (uint32_t i = 0; i < 16; i++) { |
| debug::AddressRange range = {0x10 + i, 0x10 + i + 6}; |
| SetLocation(&breakpoint, process.koid(), range); |
| ASSERT_TRUE(process.RegisterWatchpoint(&breakpoint, range).has_error()); |
| } |
| |
| // 7 bytes. |
| for (uint32_t i = 0; i < 16; i++) { |
| debug::AddressRange range = {0x10 + i, 0x10 + i + 7}; |
| SetLocation(&breakpoint, process.koid(), range); |
| ASSERT_TRUE(process.RegisterWatchpoint(&breakpoint, range).has_error()); |
| } |
| |
| // 8 bytes. |
| for (uint32_t i = 0; i < 16; i++) { |
| debug::AddressRange range = {0x10 + i, 0x10 + i + 8}; |
| SetLocation(&breakpoint, process.koid(), range); |
| |
| // Only aligned values should work. |
| bool expected_ok = (range.begin() & 0b111) == 0; |
| SCOPED_TRACE(range.ToString()); |
| ASSERT_EQ(process.RegisterWatchpoint(&breakpoint, range).ok(), expected_ok); |
| } |
| } |
| |
| TEST(DebuggedProcess, DetachFromProcess) { |
| MockProcess process(nullptr, kProcessKoid, kProcessName); |
| MockThread* thread = process.AddThread(2); |
| |
| ASSERT_TRUE(thread->running()); |
| process.ClientSuspendAllThreads(); |
| ASSERT_FALSE(thread->running()); |
| process.DetachFromProcess(); |
| ASSERT_TRUE(thread->running()); |
| } |
| |
| } // namespace |
| } // namespace debug_agent |