diff options
| m--------- | externals/dynarmic | 0 | ||||
| -rw-r--r-- | src/common/page_table.h | 3 | ||||
| -rw-r--r-- | src/core/arm/arm_interface.cpp | 41 | ||||
| -rw-r--r-- | src/core/arm/arm_interface.h | 13 | ||||
| -rw-r--r-- | src/core/arm/dynarmic/arm_dynarmic_32.cpp | 69 | ||||
| -rw-r--r-- | src/core/arm/dynarmic/arm_dynarmic_32.h | 6 | ||||
| -rw-r--r-- | src/core/arm/dynarmic/arm_dynarmic_64.cpp | 79 | ||||
| -rw-r--r-- | src/core/arm/dynarmic/arm_dynarmic_64.h | 6 | ||||
| -rw-r--r-- | src/core/debugger/debugger.cpp | 19 | ||||
| -rw-r--r-- | src/core/debugger/debugger.h | 8 | ||||
| -rw-r--r-- | src/core/debugger/debugger_interface.h | 8 | ||||
| -rw-r--r-- | src/core/debugger/gdbstub.cpp | 139 | ||||
| -rw-r--r-- | src/core/debugger/gdbstub.h | 3 | ||||
| -rw-r--r-- | src/core/hardware_properties.h | 3 | ||||
| -rw-r--r-- | src/core/hle/kernel/k_process.cpp | 46 | ||||
| -rw-r--r-- | src/core/hle/kernel/k_process.h | 30 | ||||
| -rw-r--r-- | src/core/hle/kernel/k_scheduler.cpp | 1 | ||||
| -rw-r--r-- | src/core/memory.cpp | 79 | ||||
| -rw-r--r-- | src/core/memory.h | 11 |
19 files changed, 510 insertions, 54 deletions
diff --git a/externals/dynarmic b/externals/dynarmic | |||
| Subproject 57af72a567454b93c757e087b4510a24b81911b | Subproject 5ad1d02351bf4fee681a3d701d210b419f41a50 | ||
diff --git a/src/common/page_table.h b/src/common/page_table.h index fcbd12a43..1ad3a9f8b 100644 --- a/src/common/page_table.h +++ b/src/common/page_table.h | |||
| @@ -15,6 +15,9 @@ enum class PageType : u8 { | |||
| 15 | Unmapped, | 15 | Unmapped, |
| 16 | /// Page is mapped to regular memory. This is the only type you can get pointers to. | 16 | /// Page is mapped to regular memory. This is the only type you can get pointers to. |
| 17 | Memory, | 17 | Memory, |
| 18 | /// Page is mapped to regular memory, but inaccessible from CPU fastmem and must use | ||
| 19 | /// the callbacks. | ||
| 20 | DebugMemory, | ||
| 18 | /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and | 21 | /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and |
| 19 | /// invalidation | 22 | /// invalidation |
| 20 | RasterizerCachedMemory, | 23 | RasterizerCachedMemory, |
diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp index 9a285dfc6..6425e131f 100644 --- a/src/core/arm/arm_interface.cpp +++ b/src/core/arm/arm_interface.cpp | |||
| @@ -121,8 +121,15 @@ void ARM_Interface::Run() { | |||
| 121 | 121 | ||
| 122 | // Notify the debugger and go to sleep if a breakpoint was hit. | 122 | // Notify the debugger and go to sleep if a breakpoint was hit. |
| 123 | if (Has(hr, breakpoint)) { | 123 | if (Has(hr, breakpoint)) { |
| 124 | RewindBreakpointInstruction(); | ||
| 124 | system.GetDebugger().NotifyThreadStopped(current_thread); | 125 | system.GetDebugger().NotifyThreadStopped(current_thread); |
| 125 | current_thread->RequestSuspend(Kernel::SuspendType::Debug); | 126 | current_thread->RequestSuspend(SuspendType::Debug); |
| 127 | break; | ||
| 128 | } | ||
| 129 | if (Has(hr, watchpoint)) { | ||
| 130 | RewindBreakpointInstruction(); | ||
| 131 | system.GetDebugger().NotifyThreadWatchpoint(current_thread, *HaltedWatchpoint()); | ||
| 132 | current_thread->RequestSuspend(SuspendType::Debug); | ||
| 126 | break; | 133 | break; |
| 127 | } | 134 | } |
| 128 | 135 | ||
| @@ -136,4 +143,36 @@ void ARM_Interface::Run() { | |||
| 136 | } | 143 | } |
| 137 | } | 144 | } |
| 138 | 145 | ||
| 146 | void ARM_Interface::LoadWatchpointArray(const WatchpointArray& wp) { | ||
| 147 | watchpoints = ℘ | ||
| 148 | } | ||
| 149 | |||
| 150 | const Kernel::DebugWatchpoint* ARM_Interface::MatchingWatchpoint( | ||
| 151 | VAddr addr, u64 size, Kernel::DebugWatchpointType access_type) const { | ||
| 152 | if (!watchpoints) { | ||
| 153 | return nullptr; | ||
| 154 | } | ||
| 155 | |||
| 156 | const VAddr start_address{addr}; | ||
| 157 | const VAddr end_address{addr + size}; | ||
| 158 | |||
| 159 | for (size_t i = 0; i < Core::Hardware::NUM_WATCHPOINTS; i++) { | ||
| 160 | const auto& watch{(*watchpoints)[i]}; | ||
| 161 | |||
| 162 | if (end_address <= watch.start_address) { | ||
| 163 | continue; | ||
| 164 | } | ||
| 165 | if (start_address >= watch.end_address) { | ||
| 166 | continue; | ||
| 167 | } | ||
| 168 | if ((access_type & watch.type) == Kernel::DebugWatchpointType::None) { | ||
| 169 | continue; | ||
| 170 | } | ||
| 171 | |||
| 172 | return &watch; | ||
| 173 | } | ||
| 174 | |||
| 175 | return nullptr; | ||
| 176 | } | ||
| 177 | |||
| 139 | } // namespace Core | 178 | } // namespace Core |
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index 66f6107e9..4e431e27a 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <array> | 7 | #include <array> |
| 8 | #include <span> | ||
| 8 | #include <vector> | 9 | #include <vector> |
| 9 | 10 | ||
| 10 | #include <dynarmic/interface/halt_reason.h> | 11 | #include <dynarmic/interface/halt_reason.h> |
| @@ -19,13 +20,16 @@ struct PageTable; | |||
| 19 | 20 | ||
| 20 | namespace Kernel { | 21 | namespace Kernel { |
| 21 | enum class VMAPermission : u8; | 22 | enum class VMAPermission : u8; |
| 22 | } | 23 | enum class DebugWatchpointType : u8; |
| 24 | struct DebugWatchpoint; | ||
| 25 | } // namespace Kernel | ||
| 23 | 26 | ||
| 24 | namespace Core { | 27 | namespace Core { |
| 25 | class System; | 28 | class System; |
| 26 | class CPUInterruptHandler; | 29 | class CPUInterruptHandler; |
| 27 | 30 | ||
| 28 | using CPUInterrupts = std::array<CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>; | 31 | using CPUInterrupts = std::array<CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>; |
| 32 | using WatchpointArray = std::array<Kernel::DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>; | ||
| 29 | 33 | ||
| 30 | /// Generic ARMv8 CPU interface | 34 | /// Generic ARMv8 CPU interface |
| 31 | class ARM_Interface { | 35 | class ARM_Interface { |
| @@ -170,6 +174,7 @@ public: | |||
| 170 | virtual void SaveContext(ThreadContext64& ctx) = 0; | 174 | virtual void SaveContext(ThreadContext64& ctx) = 0; |
| 171 | virtual void LoadContext(const ThreadContext32& ctx) = 0; | 175 | virtual void LoadContext(const ThreadContext32& ctx) = 0; |
| 172 | virtual void LoadContext(const ThreadContext64& ctx) = 0; | 176 | virtual void LoadContext(const ThreadContext64& ctx) = 0; |
| 177 | void LoadWatchpointArray(const WatchpointArray& wp); | ||
| 173 | 178 | ||
| 174 | /// Clears the exclusive monitor's state. | 179 | /// Clears the exclusive monitor's state. |
| 175 | virtual void ClearExclusiveState() = 0; | 180 | virtual void ClearExclusiveState() = 0; |
| @@ -198,18 +203,24 @@ public: | |||
| 198 | static constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2; | 203 | static constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2; |
| 199 | static constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3; | 204 | static constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3; |
| 200 | static constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4; | 205 | static constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4; |
| 206 | static constexpr Dynarmic::HaltReason watchpoint = Dynarmic::HaltReason::UserDefined5; | ||
| 201 | 207 | ||
| 202 | protected: | 208 | protected: |
| 203 | /// System context that this ARM interface is running under. | 209 | /// System context that this ARM interface is running under. |
| 204 | System& system; | 210 | System& system; |
| 205 | CPUInterrupts& interrupt_handlers; | 211 | CPUInterrupts& interrupt_handlers; |
| 212 | const WatchpointArray* watchpoints; | ||
| 206 | bool uses_wall_clock; | 213 | bool uses_wall_clock; |
| 207 | 214 | ||
| 208 | static void SymbolicateBacktrace(Core::System& system, std::vector<BacktraceEntry>& out); | 215 | static void SymbolicateBacktrace(Core::System& system, std::vector<BacktraceEntry>& out); |
| 216 | const Kernel::DebugWatchpoint* MatchingWatchpoint( | ||
| 217 | VAddr addr, u64 size, Kernel::DebugWatchpointType access_type) const; | ||
| 209 | 218 | ||
| 210 | virtual Dynarmic::HaltReason RunJit() = 0; | 219 | virtual Dynarmic::HaltReason RunJit() = 0; |
| 211 | virtual Dynarmic::HaltReason StepJit() = 0; | 220 | virtual Dynarmic::HaltReason StepJit() = 0; |
| 212 | virtual u32 GetSvcNumber() const = 0; | 221 | virtual u32 GetSvcNumber() const = 0; |
| 222 | virtual const Kernel::DebugWatchpoint* HaltedWatchpoint() const = 0; | ||
| 223 | virtual void RewindBreakpointInstruction() = 0; | ||
| 213 | }; | 224 | }; |
| 214 | 225 | ||
| 215 | } // namespace Core | 226 | } // namespace Core |
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index 7c82d0b96..8c90c8be0 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp | |||
| @@ -29,45 +29,62 @@ using namespace Common::Literals; | |||
| 29 | class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks { | 29 | class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks { |
| 30 | public: | 30 | public: |
| 31 | explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_) | 31 | explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_) |
| 32 | : parent{parent_}, memory(parent.system.Memory()) {} | 32 | : parent{parent_}, |
| 33 | memory(parent.system.Memory()), debugger_enabled{parent.system.DebuggerEnabled()} {} | ||
| 33 | 34 | ||
| 34 | u8 MemoryRead8(u32 vaddr) override { | 35 | u8 MemoryRead8(u32 vaddr) override { |
| 36 | CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read); | ||
| 35 | return memory.Read8(vaddr); | 37 | return memory.Read8(vaddr); |
| 36 | } | 38 | } |
| 37 | u16 MemoryRead16(u32 vaddr) override { | 39 | u16 MemoryRead16(u32 vaddr) override { |
| 40 | CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read); | ||
| 38 | return memory.Read16(vaddr); | 41 | return memory.Read16(vaddr); |
| 39 | } | 42 | } |
| 40 | u32 MemoryRead32(u32 vaddr) override { | 43 | u32 MemoryRead32(u32 vaddr) override { |
| 44 | CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read); | ||
| 41 | return memory.Read32(vaddr); | 45 | return memory.Read32(vaddr); |
| 42 | } | 46 | } |
| 43 | u64 MemoryRead64(u32 vaddr) override { | 47 | u64 MemoryRead64(u32 vaddr) override { |
| 48 | CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read); | ||
| 44 | return memory.Read64(vaddr); | 49 | return memory.Read64(vaddr); |
| 45 | } | 50 | } |
| 46 | 51 | ||
| 47 | void MemoryWrite8(u32 vaddr, u8 value) override { | 52 | void MemoryWrite8(u32 vaddr, u8 value) override { |
| 48 | memory.Write8(vaddr, value); | 53 | if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) { |
| 54 | memory.Write8(vaddr, value); | ||
| 55 | } | ||
| 49 | } | 56 | } |
| 50 | void MemoryWrite16(u32 vaddr, u16 value) override { | 57 | void MemoryWrite16(u32 vaddr, u16 value) override { |
| 51 | memory.Write16(vaddr, value); | 58 | if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) { |
| 59 | memory.Write16(vaddr, value); | ||
| 60 | } | ||
| 52 | } | 61 | } |
| 53 | void MemoryWrite32(u32 vaddr, u32 value) override { | 62 | void MemoryWrite32(u32 vaddr, u32 value) override { |
| 54 | memory.Write32(vaddr, value); | 63 | if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) { |
| 64 | memory.Write32(vaddr, value); | ||
| 65 | } | ||
| 55 | } | 66 | } |
| 56 | void MemoryWrite64(u32 vaddr, u64 value) override { | 67 | void MemoryWrite64(u32 vaddr, u64 value) override { |
| 57 | memory.Write64(vaddr, value); | 68 | if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) { |
| 69 | memory.Write64(vaddr, value); | ||
| 70 | } | ||
| 58 | } | 71 | } |
| 59 | 72 | ||
| 60 | bool MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) override { | 73 | bool MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) override { |
| 61 | return memory.WriteExclusive8(vaddr, value, expected); | 74 | return CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write) && |
| 75 | memory.WriteExclusive8(vaddr, value, expected); | ||
| 62 | } | 76 | } |
| 63 | bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override { | 77 | bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override { |
| 64 | return memory.WriteExclusive16(vaddr, value, expected); | 78 | return CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write) && |
| 79 | memory.WriteExclusive16(vaddr, value, expected); | ||
| 65 | } | 80 | } |
| 66 | bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override { | 81 | bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override { |
| 67 | return memory.WriteExclusive32(vaddr, value, expected); | 82 | return CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write) && |
| 83 | memory.WriteExclusive32(vaddr, value, expected); | ||
| 68 | } | 84 | } |
| 69 | bool MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) override { | 85 | bool MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) override { |
| 70 | return memory.WriteExclusive64(vaddr, value, expected); | 86 | return CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write) && |
| 87 | memory.WriteExclusive64(vaddr, value, expected); | ||
| 71 | } | 88 | } |
| 72 | 89 | ||
| 73 | void InterpreterFallback(u32 pc, std::size_t num_instructions) override { | 90 | void InterpreterFallback(u32 pc, std::size_t num_instructions) override { |
| @@ -77,8 +94,8 @@ public: | |||
| 77 | } | 94 | } |
| 78 | 95 | ||
| 79 | void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override { | 96 | void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override { |
| 80 | if (parent.system.DebuggerEnabled()) { | 97 | if (debugger_enabled) { |
| 81 | parent.jit.load()->Regs()[15] = pc; | 98 | parent.SaveContext(parent.breakpoint_context); |
| 82 | parent.jit.load()->HaltExecution(ARM_Interface::breakpoint); | 99 | parent.jit.load()->HaltExecution(ARM_Interface::breakpoint); |
| 83 | return; | 100 | return; |
| 84 | } | 101 | } |
| @@ -117,9 +134,26 @@ public: | |||
| 117 | return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0); | 134 | return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0); |
| 118 | } | 135 | } |
| 119 | 136 | ||
| 137 | bool CheckMemoryAccess(VAddr addr, u64 size, Kernel::DebugWatchpointType type) { | ||
| 138 | if (!debugger_enabled) { | ||
| 139 | return true; | ||
| 140 | } | ||
| 141 | |||
| 142 | const auto match{parent.MatchingWatchpoint(addr, size, type)}; | ||
| 143 | if (match) { | ||
| 144 | parent.SaveContext(parent.breakpoint_context); | ||
| 145 | parent.jit.load()->HaltExecution(ARM_Interface::watchpoint); | ||
| 146 | parent.halted_watchpoint = match; | ||
| 147 | return false; | ||
| 148 | } | ||
| 149 | |||
| 150 | return true; | ||
| 151 | } | ||
| 152 | |||
| 120 | ARM_Dynarmic_32& parent; | 153 | ARM_Dynarmic_32& parent; |
| 121 | Core::Memory::Memory& memory; | 154 | Core::Memory::Memory& memory; |
| 122 | std::size_t num_interpreted_instructions{}; | 155 | std::size_t num_interpreted_instructions{}; |
| 156 | bool debugger_enabled{}; | ||
| 123 | static constexpr u64 minimum_run_cycles = 1000U; | 157 | static constexpr u64 minimum_run_cycles = 1000U; |
| 124 | }; | 158 | }; |
| 125 | 159 | ||
| @@ -154,6 +188,11 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* | |||
| 154 | config.code_cache_size = 512_MiB; | 188 | config.code_cache_size = 512_MiB; |
| 155 | config.far_code_offset = 400_MiB; | 189 | config.far_code_offset = 400_MiB; |
| 156 | 190 | ||
| 191 | // Allow memory fault handling to work | ||
| 192 | if (system.DebuggerEnabled()) { | ||
| 193 | config.check_halt_on_memory_access = true; | ||
| 194 | } | ||
| 195 | |||
| 157 | // null_jit | 196 | // null_jit |
| 158 | if (!page_table) { | 197 | if (!page_table) { |
| 159 | // Don't waste too much memory on null_jit | 198 | // Don't waste too much memory on null_jit |
| @@ -248,6 +287,14 @@ u32 ARM_Dynarmic_32::GetSvcNumber() const { | |||
| 248 | return svc_swi; | 287 | return svc_swi; |
| 249 | } | 288 | } |
| 250 | 289 | ||
| 290 | const Kernel::DebugWatchpoint* ARM_Dynarmic_32::HaltedWatchpoint() const { | ||
| 291 | return halted_watchpoint; | ||
| 292 | } | ||
| 293 | |||
| 294 | void ARM_Dynarmic_32::RewindBreakpointInstruction() { | ||
| 295 | LoadContext(breakpoint_context); | ||
| 296 | } | ||
| 297 | |||
| 251 | ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_, | 298 | ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_, |
| 252 | bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_, | 299 | bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_, |
| 253 | std::size_t core_index_) | 300 | std::size_t core_index_) |
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h index 5b1d60005..fcbe24f0c 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.h +++ b/src/core/arm/dynarmic/arm_dynarmic_32.h | |||
| @@ -72,6 +72,8 @@ protected: | |||
| 72 | Dynarmic::HaltReason RunJit() override; | 72 | Dynarmic::HaltReason RunJit() override; |
| 73 | Dynarmic::HaltReason StepJit() override; | 73 | Dynarmic::HaltReason StepJit() override; |
| 74 | u32 GetSvcNumber() const override; | 74 | u32 GetSvcNumber() const override; |
| 75 | const Kernel::DebugWatchpoint* HaltedWatchpoint() const override; | ||
| 76 | void RewindBreakpointInstruction() override; | ||
| 75 | 77 | ||
| 76 | private: | 78 | private: |
| 77 | std::shared_ptr<Dynarmic::A32::Jit> MakeJit(Common::PageTable* page_table) const; | 79 | std::shared_ptr<Dynarmic::A32::Jit> MakeJit(Common::PageTable* page_table) const; |
| @@ -98,6 +100,10 @@ private: | |||
| 98 | 100 | ||
| 99 | // SVC callback | 101 | // SVC callback |
| 100 | u32 svc_swi{}; | 102 | u32 svc_swi{}; |
| 103 | |||
| 104 | // Watchpoint info | ||
| 105 | const Kernel::DebugWatchpoint* halted_watchpoint; | ||
| 106 | ThreadContext32 breakpoint_context; | ||
| 101 | }; | 107 | }; |
| 102 | 108 | ||
| 103 | } // namespace Core | 109 | } // namespace Core |
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index d4c67eafd..4370ca294 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp | |||
| @@ -29,55 +29,76 @@ using namespace Common::Literals; | |||
| 29 | class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks { | 29 | class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks { |
| 30 | public: | 30 | public: |
| 31 | explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent_) | 31 | explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent_) |
| 32 | : parent{parent_}, memory(parent.system.Memory()) {} | 32 | : parent{parent_}, |
| 33 | memory(parent.system.Memory()), debugger_enabled{parent.system.DebuggerEnabled()} {} | ||
| 33 | 34 | ||
| 34 | u8 MemoryRead8(u64 vaddr) override { | 35 | u8 MemoryRead8(u64 vaddr) override { |
| 36 | CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read); | ||
| 35 | return memory.Read8(vaddr); | 37 | return memory.Read8(vaddr); |
| 36 | } | 38 | } |
| 37 | u16 MemoryRead16(u64 vaddr) override { | 39 | u16 MemoryRead16(u64 vaddr) override { |
| 40 | CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read); | ||
| 38 | return memory.Read16(vaddr); | 41 | return memory.Read16(vaddr); |
| 39 | } | 42 | } |
| 40 | u32 MemoryRead32(u64 vaddr) override { | 43 | u32 MemoryRead32(u64 vaddr) override { |
| 44 | CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read); | ||
| 41 | return memory.Read32(vaddr); | 45 | return memory.Read32(vaddr); |
| 42 | } | 46 | } |
| 43 | u64 MemoryRead64(u64 vaddr) override { | 47 | u64 MemoryRead64(u64 vaddr) override { |
| 48 | CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read); | ||
| 44 | return memory.Read64(vaddr); | 49 | return memory.Read64(vaddr); |
| 45 | } | 50 | } |
| 46 | Vector MemoryRead128(u64 vaddr) override { | 51 | Vector MemoryRead128(u64 vaddr) override { |
| 52 | CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Read); | ||
| 47 | return {memory.Read64(vaddr), memory.Read64(vaddr + 8)}; | 53 | return {memory.Read64(vaddr), memory.Read64(vaddr + 8)}; |
| 48 | } | 54 | } |
| 49 | 55 | ||
| 50 | void MemoryWrite8(u64 vaddr, u8 value) override { | 56 | void MemoryWrite8(u64 vaddr, u8 value) override { |
| 51 | memory.Write8(vaddr, value); | 57 | if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) { |
| 58 | memory.Write8(vaddr, value); | ||
| 59 | } | ||
| 52 | } | 60 | } |
| 53 | void MemoryWrite16(u64 vaddr, u16 value) override { | 61 | void MemoryWrite16(u64 vaddr, u16 value) override { |
| 54 | memory.Write16(vaddr, value); | 62 | if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) { |
| 63 | memory.Write16(vaddr, value); | ||
| 64 | } | ||
| 55 | } | 65 | } |
| 56 | void MemoryWrite32(u64 vaddr, u32 value) override { | 66 | void MemoryWrite32(u64 vaddr, u32 value) override { |
| 57 | memory.Write32(vaddr, value); | 67 | if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) { |
| 68 | memory.Write32(vaddr, value); | ||
| 69 | } | ||
| 58 | } | 70 | } |
| 59 | void MemoryWrite64(u64 vaddr, u64 value) override { | 71 | void MemoryWrite64(u64 vaddr, u64 value) override { |
| 60 | memory.Write64(vaddr, value); | 72 | if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) { |
| 73 | memory.Write64(vaddr, value); | ||
| 74 | } | ||
| 61 | } | 75 | } |
| 62 | void MemoryWrite128(u64 vaddr, Vector value) override { | 76 | void MemoryWrite128(u64 vaddr, Vector value) override { |
| 63 | memory.Write64(vaddr, value[0]); | 77 | if (CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Write)) { |
| 64 | memory.Write64(vaddr + 8, value[1]); | 78 | memory.Write64(vaddr, value[0]); |
| 79 | memory.Write64(vaddr + 8, value[1]); | ||
| 80 | } | ||
| 65 | } | 81 | } |
| 66 | 82 | ||
| 67 | bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, std::uint8_t expected) override { | 83 | bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, std::uint8_t expected) override { |
| 68 | return memory.WriteExclusive8(vaddr, value, expected); | 84 | return CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write) && |
| 85 | memory.WriteExclusive8(vaddr, value, expected); | ||
| 69 | } | 86 | } |
| 70 | bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, std::uint16_t expected) override { | 87 | bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, std::uint16_t expected) override { |
| 71 | return memory.WriteExclusive16(vaddr, value, expected); | 88 | return CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write) && |
| 89 | memory.WriteExclusive16(vaddr, value, expected); | ||
| 72 | } | 90 | } |
| 73 | bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, std::uint32_t expected) override { | 91 | bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, std::uint32_t expected) override { |
| 74 | return memory.WriteExclusive32(vaddr, value, expected); | 92 | return CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write) && |
| 93 | memory.WriteExclusive32(vaddr, value, expected); | ||
| 75 | } | 94 | } |
| 76 | bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, std::uint64_t expected) override { | 95 | bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, std::uint64_t expected) override { |
| 77 | return memory.WriteExclusive64(vaddr, value, expected); | 96 | return CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write) && |
| 97 | memory.WriteExclusive64(vaddr, value, expected); | ||
| 78 | } | 98 | } |
| 79 | bool MemoryWriteExclusive128(u64 vaddr, Vector value, Vector expected) override { | 99 | bool MemoryWriteExclusive128(u64 vaddr, Vector value, Vector expected) override { |
| 80 | return memory.WriteExclusive128(vaddr, value, expected); | 100 | return CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Write) && |
| 101 | memory.WriteExclusive128(vaddr, value, expected); | ||
| 81 | } | 102 | } |
| 82 | 103 | ||
| 83 | void InterpreterFallback(u64 pc, std::size_t num_instructions) override { | 104 | void InterpreterFallback(u64 pc, std::size_t num_instructions) override { |
| @@ -118,8 +139,8 @@ public: | |||
| 118 | case Dynarmic::A64::Exception::Yield: | 139 | case Dynarmic::A64::Exception::Yield: |
| 119 | return; | 140 | return; |
| 120 | default: | 141 | default: |
| 121 | if (parent.system.DebuggerEnabled()) { | 142 | if (debugger_enabled) { |
| 122 | parent.jit.load()->SetPC(pc); | 143 | parent.SaveContext(parent.breakpoint_context); |
| 123 | parent.jit.load()->HaltExecution(ARM_Interface::breakpoint); | 144 | parent.jit.load()->HaltExecution(ARM_Interface::breakpoint); |
| 124 | return; | 145 | return; |
| 125 | } | 146 | } |
| @@ -160,10 +181,27 @@ public: | |||
| 160 | return parent.system.CoreTiming().GetClockTicks(); | 181 | return parent.system.CoreTiming().GetClockTicks(); |
| 161 | } | 182 | } |
| 162 | 183 | ||
| 184 | bool CheckMemoryAccess(VAddr addr, u64 size, Kernel::DebugWatchpointType type) { | ||
| 185 | if (!debugger_enabled) { | ||
| 186 | return true; | ||
| 187 | } | ||
| 188 | |||
| 189 | const auto match{parent.MatchingWatchpoint(addr, size, type)}; | ||
| 190 | if (match) { | ||
| 191 | parent.SaveContext(parent.breakpoint_context); | ||
| 192 | parent.jit.load()->HaltExecution(ARM_Interface::watchpoint); | ||
| 193 | parent.halted_watchpoint = match; | ||
| 194 | return false; | ||
| 195 | } | ||
| 196 | |||
| 197 | return true; | ||
| 198 | } | ||
| 199 | |||
| 163 | ARM_Dynarmic_64& parent; | 200 | ARM_Dynarmic_64& parent; |
| 164 | Core::Memory::Memory& memory; | 201 | Core::Memory::Memory& memory; |
| 165 | u64 tpidrro_el0 = 0; | 202 | u64 tpidrro_el0 = 0; |
| 166 | u64 tpidr_el0 = 0; | 203 | u64 tpidr_el0 = 0; |
| 204 | bool debugger_enabled{}; | ||
| 167 | static constexpr u64 minimum_run_cycles = 1000U; | 205 | static constexpr u64 minimum_run_cycles = 1000U; |
| 168 | }; | 206 | }; |
| 169 | 207 | ||
| @@ -214,6 +252,11 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* | |||
| 214 | config.code_cache_size = 512_MiB; | 252 | config.code_cache_size = 512_MiB; |
| 215 | config.far_code_offset = 400_MiB; | 253 | config.far_code_offset = 400_MiB; |
| 216 | 254 | ||
| 255 | // Allow memory fault handling to work | ||
| 256 | if (system.DebuggerEnabled()) { | ||
| 257 | config.check_halt_on_memory_access = true; | ||
| 258 | } | ||
| 259 | |||
| 217 | // null_jit | 260 | // null_jit |
| 218 | if (!page_table) { | 261 | if (!page_table) { |
| 219 | // Don't waste too much memory on null_jit | 262 | // Don't waste too much memory on null_jit |
| @@ -308,6 +351,14 @@ u32 ARM_Dynarmic_64::GetSvcNumber() const { | |||
| 308 | return svc_swi; | 351 | return svc_swi; |
| 309 | } | 352 | } |
| 310 | 353 | ||
| 354 | const Kernel::DebugWatchpoint* ARM_Dynarmic_64::HaltedWatchpoint() const { | ||
| 355 | return halted_watchpoint; | ||
| 356 | } | ||
| 357 | |||
| 358 | void ARM_Dynarmic_64::RewindBreakpointInstruction() { | ||
| 359 | LoadContext(breakpoint_context); | ||
| 360 | } | ||
| 361 | |||
| 311 | ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_, | 362 | ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_, |
| 312 | bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_, | 363 | bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_, |
| 313 | std::size_t core_index_) | 364 | std::size_t core_index_) |
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h index abfbc3c3f..71dbaac5e 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.h +++ b/src/core/arm/dynarmic/arm_dynarmic_64.h | |||
| @@ -66,6 +66,8 @@ protected: | |||
| 66 | Dynarmic::HaltReason RunJit() override; | 66 | Dynarmic::HaltReason RunJit() override; |
| 67 | Dynarmic::HaltReason StepJit() override; | 67 | Dynarmic::HaltReason StepJit() override; |
| 68 | u32 GetSvcNumber() const override; | 68 | u32 GetSvcNumber() const override; |
| 69 | const Kernel::DebugWatchpoint* HaltedWatchpoint() const override; | ||
| 70 | void RewindBreakpointInstruction() override; | ||
| 69 | 71 | ||
| 70 | private: | 72 | private: |
| 71 | std::shared_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable* page_table, | 73 | std::shared_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable* page_table, |
| @@ -91,6 +93,10 @@ private: | |||
| 91 | 93 | ||
| 92 | // SVC callback | 94 | // SVC callback |
| 93 | u32 svc_swi{}; | 95 | u32 svc_swi{}; |
| 96 | |||
| 97 | // Breakpoint info | ||
| 98 | const Kernel::DebugWatchpoint* halted_watchpoint; | ||
| 99 | ThreadContext64 breakpoint_context; | ||
| 94 | }; | 100 | }; |
| 95 | 101 | ||
| 96 | } // namespace Core | 102 | } // namespace Core |
diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp index ab3940922..ac64d2f9d 100644 --- a/src/core/debugger/debugger.cpp +++ b/src/core/debugger/debugger.cpp | |||
| @@ -44,12 +44,14 @@ static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) { | |||
| 44 | 44 | ||
| 45 | enum class SignalType { | 45 | enum class SignalType { |
| 46 | Stopped, | 46 | Stopped, |
| 47 | Watchpoint, | ||
| 47 | ShuttingDown, | 48 | ShuttingDown, |
| 48 | }; | 49 | }; |
| 49 | 50 | ||
| 50 | struct SignalInfo { | 51 | struct SignalInfo { |
| 51 | SignalType type; | 52 | SignalType type; |
| 52 | Kernel::KThread* thread; | 53 | Kernel::KThread* thread; |
| 54 | const Kernel::DebugWatchpoint* watchpoint; | ||
| 53 | }; | 55 | }; |
| 54 | 56 | ||
| 55 | namespace Core { | 57 | namespace Core { |
| @@ -157,13 +159,19 @@ private: | |||
| 157 | void PipeData(std::span<const u8> data) { | 159 | void PipeData(std::span<const u8> data) { |
| 158 | switch (info.type) { | 160 | switch (info.type) { |
| 159 | case SignalType::Stopped: | 161 | case SignalType::Stopped: |
| 162 | case SignalType::Watchpoint: | ||
| 160 | // Stop emulation. | 163 | // Stop emulation. |
| 161 | PauseEmulation(); | 164 | PauseEmulation(); |
| 162 | 165 | ||
| 163 | // Notify the client. | 166 | // Notify the client. |
| 164 | active_thread = info.thread; | 167 | active_thread = info.thread; |
| 165 | UpdateActiveThread(); | 168 | UpdateActiveThread(); |
| 166 | frontend->Stopped(active_thread); | 169 | |
| 170 | if (info.type == SignalType::Watchpoint) { | ||
| 171 | frontend->Watchpoint(active_thread, *info.watchpoint); | ||
| 172 | } else { | ||
| 173 | frontend->Stopped(active_thread); | ||
| 174 | } | ||
| 167 | 175 | ||
| 168 | break; | 176 | break; |
| 169 | case SignalType::ShuttingDown: | 177 | case SignalType::ShuttingDown: |
| @@ -290,12 +298,17 @@ Debugger::Debugger(Core::System& system, u16 port) { | |||
| 290 | Debugger::~Debugger() = default; | 298 | Debugger::~Debugger() = default; |
| 291 | 299 | ||
| 292 | bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) { | 300 | bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) { |
| 293 | return impl && impl->SignalDebugger(SignalInfo{SignalType::Stopped, thread}); | 301 | return impl && impl->SignalDebugger(SignalInfo{SignalType::Stopped, thread, nullptr}); |
| 302 | } | ||
| 303 | |||
| 304 | bool Debugger::NotifyThreadWatchpoint(Kernel::KThread* thread, | ||
| 305 | const Kernel::DebugWatchpoint& watch) { | ||
| 306 | return impl && impl->SignalDebugger(SignalInfo{SignalType::Watchpoint, thread, &watch}); | ||
| 294 | } | 307 | } |
| 295 | 308 | ||
| 296 | void Debugger::NotifyShutdown() { | 309 | void Debugger::NotifyShutdown() { |
| 297 | if (impl) { | 310 | if (impl) { |
| 298 | impl->SignalDebugger(SignalInfo{SignalType::ShuttingDown, nullptr}); | 311 | impl->SignalDebugger(SignalInfo{SignalType::ShuttingDown, nullptr, nullptr}); |
| 299 | } | 312 | } |
| 300 | } | 313 | } |
| 301 | 314 | ||
diff --git a/src/core/debugger/debugger.h b/src/core/debugger/debugger.h index f9738ca3d..b2f503376 100644 --- a/src/core/debugger/debugger.h +++ b/src/core/debugger/debugger.h | |||
| @@ -9,7 +9,8 @@ | |||
| 9 | 9 | ||
| 10 | namespace Kernel { | 10 | namespace Kernel { |
| 11 | class KThread; | 11 | class KThread; |
| 12 | } | 12 | struct DebugWatchpoint; |
| 13 | } // namespace Kernel | ||
| 13 | 14 | ||
| 14 | namespace Core { | 15 | namespace Core { |
| 15 | class System; | 16 | class System; |
| @@ -40,6 +41,11 @@ public: | |||
| 40 | */ | 41 | */ |
| 41 | void NotifyShutdown(); | 42 | void NotifyShutdown(); |
| 42 | 43 | ||
| 44 | /* | ||
| 45 | * Notify the debugger that the given thread has stopped due to hitting a watchpoint. | ||
| 46 | */ | ||
| 47 | bool NotifyThreadWatchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch); | ||
| 48 | |||
| 43 | private: | 49 | private: |
| 44 | std::unique_ptr<DebuggerImpl> impl; | 50 | std::unique_ptr<DebuggerImpl> impl; |
| 45 | }; | 51 | }; |
diff --git a/src/core/debugger/debugger_interface.h b/src/core/debugger/debugger_interface.h index c0bb4ecaf..5b31edc43 100644 --- a/src/core/debugger/debugger_interface.h +++ b/src/core/debugger/debugger_interface.h | |||
| @@ -11,7 +11,8 @@ | |||
| 11 | 11 | ||
| 12 | namespace Kernel { | 12 | namespace Kernel { |
| 13 | class KThread; | 13 | class KThread; |
| 14 | } | 14 | struct DebugWatchpoint; |
| 15 | } // namespace Kernel | ||
| 15 | 16 | ||
| 16 | namespace Core { | 17 | namespace Core { |
| 17 | 18 | ||
| @@ -71,6 +72,11 @@ public: | |||
| 71 | */ | 72 | */ |
| 72 | virtual void ShuttingDown() = 0; | 73 | virtual void ShuttingDown() = 0; |
| 73 | 74 | ||
| 75 | /* | ||
| 76 | * Called when emulation has stopped on a watchpoint. | ||
| 77 | */ | ||
| 78 | virtual void Watchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch) = 0; | ||
| 79 | |||
| 74 | /** | 80 | /** |
| 75 | * Called when new data is asynchronously received on the client socket. | 81 | * Called when new data is asynchronously received on the client socket. |
| 76 | * A list of actions to perform is returned. | 82 | * A list of actions to perform is returned. |
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index 52e76f659..f5e9a303d 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp | |||
| @@ -112,6 +112,23 @@ void GDBStub::Stopped(Kernel::KThread* thread) { | |||
| 112 | SendReply(arch->ThreadStatus(thread, GDB_STUB_SIGTRAP)); | 112 | SendReply(arch->ThreadStatus(thread, GDB_STUB_SIGTRAP)); |
| 113 | } | 113 | } |
| 114 | 114 | ||
| 115 | void GDBStub::Watchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch) { | ||
| 116 | const auto status{arch->ThreadStatus(thread, GDB_STUB_SIGTRAP)}; | ||
| 117 | |||
| 118 | switch (watch.type) { | ||
| 119 | case Kernel::DebugWatchpointType::Read: | ||
| 120 | SendReply(fmt::format("{}rwatch:{:x};", status, watch.start_address)); | ||
| 121 | break; | ||
| 122 | case Kernel::DebugWatchpointType::Write: | ||
| 123 | SendReply(fmt::format("{}watch:{:x};", status, watch.start_address)); | ||
| 124 | break; | ||
| 125 | case Kernel::DebugWatchpointType::ReadOrWrite: | ||
| 126 | default: | ||
| 127 | SendReply(fmt::format("{}awatch:{:x};", status, watch.start_address)); | ||
| 128 | break; | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 115 | std::vector<DebuggerAction> GDBStub::ClientData(std::span<const u8> data) { | 132 | std::vector<DebuggerAction> GDBStub::ClientData(std::span<const u8> data) { |
| 116 | std::vector<DebuggerAction> actions; | 133 | std::vector<DebuggerAction> actions; |
| 117 | current_command.insert(current_command.end(), data.begin(), data.end()); | 134 | current_command.insert(current_command.end(), data.begin(), data.end()); |
| @@ -278,41 +295,121 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction | |||
| 278 | case 'c': | 295 | case 'c': |
| 279 | actions.push_back(DebuggerAction::Continue); | 296 | actions.push_back(DebuggerAction::Continue); |
| 280 | break; | 297 | break; |
| 281 | case 'Z': { | 298 | case 'Z': |
| 282 | const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; | 299 | HandleBreakpointInsert(command); |
| 283 | const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))}; | 300 | break; |
| 301 | case 'z': | ||
| 302 | HandleBreakpointRemove(command); | ||
| 303 | break; | ||
| 304 | default: | ||
| 305 | SendReply(GDB_STUB_REPLY_EMPTY); | ||
| 306 | break; | ||
| 307 | } | ||
| 308 | } | ||
| 284 | 309 | ||
| 285 | if (system.Memory().IsValidVirtualAddress(addr)) { | 310 | enum class BreakpointType { |
| 286 | replaced_instructions[addr] = system.Memory().Read32(addr); | 311 | Software = 0, |
| 287 | system.Memory().Write32(addr, arch->BreakpointInstruction()); | 312 | Hardware = 1, |
| 288 | system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32)); | 313 | WriteWatch = 2, |
| 314 | ReadWatch = 3, | ||
| 315 | AccessWatch = 4, | ||
| 316 | }; | ||
| 317 | |||
| 318 | void GDBStub::HandleBreakpointInsert(std::string_view command) { | ||
| 319 | const auto type{static_cast<BreakpointType>(strtoll(command.data(), nullptr, 16))}; | ||
| 320 | const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; | ||
| 321 | const auto size_sep{std::find(command.begin() + addr_sep, command.end(), ',') - | ||
| 322 | command.begin() + 1}; | ||
| 323 | const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))}; | ||
| 324 | const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))}; | ||
| 325 | |||
| 326 | if (!system.Memory().IsValidVirtualAddressRange(addr, size)) { | ||
| 327 | SendReply(GDB_STUB_REPLY_ERR); | ||
| 328 | return; | ||
| 329 | } | ||
| 289 | 330 | ||
| 290 | SendReply(GDB_STUB_REPLY_OK); | 331 | bool success{}; |
| 291 | } else { | 332 | |
| 292 | SendReply(GDB_STUB_REPLY_ERR); | 333 | switch (type) { |
| 293 | } | 334 | case BreakpointType::Software: |
| 335 | replaced_instructions[addr] = system.Memory().Read32(addr); | ||
| 336 | system.Memory().Write32(addr, arch->BreakpointInstruction()); | ||
| 337 | system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32)); | ||
| 338 | success = true; | ||
| 339 | break; | ||
| 340 | case BreakpointType::WriteWatch: | ||
| 341 | success = system.CurrentProcess()->InsertWatchpoint(system, addr, size, | ||
| 342 | Kernel::DebugWatchpointType::Write); | ||
| 294 | break; | 343 | break; |
| 344 | case BreakpointType::ReadWatch: | ||
| 345 | success = system.CurrentProcess()->InsertWatchpoint(system, addr, size, | ||
| 346 | Kernel::DebugWatchpointType::Read); | ||
| 347 | break; | ||
| 348 | case BreakpointType::AccessWatch: | ||
| 349 | success = system.CurrentProcess()->InsertWatchpoint( | ||
| 350 | system, addr, size, Kernel::DebugWatchpointType::ReadOrWrite); | ||
| 351 | break; | ||
| 352 | case BreakpointType::Hardware: | ||
| 353 | default: | ||
| 354 | SendReply(GDB_STUB_REPLY_EMPTY); | ||
| 355 | return; | ||
| 356 | } | ||
| 357 | |||
| 358 | if (success) { | ||
| 359 | SendReply(GDB_STUB_REPLY_OK); | ||
| 360 | } else { | ||
| 361 | SendReply(GDB_STUB_REPLY_ERR); | ||
| 295 | } | 362 | } |
| 296 | case 'z': { | 363 | } |
| 297 | const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; | 364 | |
| 298 | const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))}; | 365 | void GDBStub::HandleBreakpointRemove(std::string_view command) { |
| 366 | const auto type{static_cast<BreakpointType>(strtoll(command.data(), nullptr, 16))}; | ||
| 367 | const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; | ||
| 368 | const auto size_sep{std::find(command.begin() + addr_sep, command.end(), ',') - | ||
| 369 | command.begin() + 1}; | ||
| 370 | const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))}; | ||
| 371 | const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))}; | ||
| 372 | |||
| 373 | if (!system.Memory().IsValidVirtualAddressRange(addr, size)) { | ||
| 374 | SendReply(GDB_STUB_REPLY_ERR); | ||
| 375 | return; | ||
| 376 | } | ||
| 377 | |||
| 378 | bool success{}; | ||
| 299 | 379 | ||
| 380 | switch (type) { | ||
| 381 | case BreakpointType::Software: { | ||
| 300 | const auto orig_insn{replaced_instructions.find(addr)}; | 382 | const auto orig_insn{replaced_instructions.find(addr)}; |
| 301 | if (system.Memory().IsValidVirtualAddress(addr) && | 383 | if (orig_insn != replaced_instructions.end()) { |
| 302 | orig_insn != replaced_instructions.end()) { | ||
| 303 | system.Memory().Write32(addr, orig_insn->second); | 384 | system.Memory().Write32(addr, orig_insn->second); |
| 304 | system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32)); | 385 | system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32)); |
| 305 | replaced_instructions.erase(addr); | 386 | replaced_instructions.erase(addr); |
| 306 | 387 | success = true; | |
| 307 | SendReply(GDB_STUB_REPLY_OK); | ||
| 308 | } else { | ||
| 309 | SendReply(GDB_STUB_REPLY_ERR); | ||
| 310 | } | 388 | } |
| 311 | break; | 389 | break; |
| 312 | } | 390 | } |
| 391 | case BreakpointType::WriteWatch: | ||
| 392 | success = system.CurrentProcess()->RemoveWatchpoint(system, addr, size, | ||
| 393 | Kernel::DebugWatchpointType::Write); | ||
| 394 | break; | ||
| 395 | case BreakpointType::ReadWatch: | ||
| 396 | success = system.CurrentProcess()->RemoveWatchpoint(system, addr, size, | ||
| 397 | Kernel::DebugWatchpointType::Read); | ||
| 398 | break; | ||
| 399 | case BreakpointType::AccessWatch: | ||
| 400 | success = system.CurrentProcess()->RemoveWatchpoint( | ||
| 401 | system, addr, size, Kernel::DebugWatchpointType::ReadOrWrite); | ||
| 402 | break; | ||
| 403 | case BreakpointType::Hardware: | ||
| 313 | default: | 404 | default: |
| 314 | SendReply(GDB_STUB_REPLY_EMPTY); | 405 | SendReply(GDB_STUB_REPLY_EMPTY); |
| 315 | break; | 406 | return; |
| 407 | } | ||
| 408 | |||
| 409 | if (success) { | ||
| 410 | SendReply(GDB_STUB_REPLY_OK); | ||
| 411 | } else { | ||
| 412 | SendReply(GDB_STUB_REPLY_ERR); | ||
| 316 | } | 413 | } |
| 317 | } | 414 | } |
| 318 | 415 | ||
diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h index ec934c77e..0b0f56e4b 100644 --- a/src/core/debugger/gdbstub.h +++ b/src/core/debugger/gdbstub.h | |||
| @@ -24,6 +24,7 @@ public: | |||
| 24 | void Connected() override; | 24 | void Connected() override; |
| 25 | void Stopped(Kernel::KThread* thread) override; | 25 | void Stopped(Kernel::KThread* thread) override; |
| 26 | void ShuttingDown() override; | 26 | void ShuttingDown() override; |
| 27 | void Watchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch) override; | ||
| 27 | std::vector<DebuggerAction> ClientData(std::span<const u8> data) override; | 28 | std::vector<DebuggerAction> ClientData(std::span<const u8> data) override; |
| 28 | 29 | ||
| 29 | private: | 30 | private: |
| @@ -31,6 +32,8 @@ private: | |||
| 31 | void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions); | 32 | void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions); |
| 32 | void HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions); | 33 | void HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions); |
| 33 | void HandleQuery(std::string_view command); | 34 | void HandleQuery(std::string_view command); |
| 35 | void HandleBreakpointInsert(std::string_view command); | ||
| 36 | void HandleBreakpointRemove(std::string_view command); | ||
| 34 | std::vector<char>::const_iterator CommandEnd() const; | 37 | std::vector<char>::const_iterator CommandEnd() const; |
| 35 | std::optional<std::string> DetachCommand(); | 38 | std::optional<std::string> DetachCommand(); |
| 36 | Kernel::KThread* GetThreadByID(u64 thread_id); | 39 | Kernel::KThread* GetThreadByID(u64 thread_id); |
diff --git a/src/core/hardware_properties.h b/src/core/hardware_properties.h index aac362c51..13cbdb734 100644 --- a/src/core/hardware_properties.h +++ b/src/core/hardware_properties.h | |||
| @@ -25,6 +25,9 @@ constexpr std::array<s32, Common::BitSize<u64>()> VirtualToPhysicalCoreMap{ | |||
| 25 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, | 25 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, |
| 26 | }; | 26 | }; |
| 27 | 27 | ||
| 28 | // Cortex-A57 supports 4 memory watchpoints | ||
| 29 | constexpr u64 NUM_WATCHPOINTS = 4; | ||
| 30 | |||
| 28 | } // namespace Hardware | 31 | } // namespace Hardware |
| 29 | 32 | ||
| 30 | } // namespace Core | 33 | } // namespace Core |
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index cd863e715..6a73f6783 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp | |||
| @@ -584,6 +584,52 @@ ResultCode KProcess::DeleteThreadLocalRegion(VAddr addr) { | |||
| 584 | return ResultSuccess; | 584 | return ResultSuccess; |
| 585 | } | 585 | } |
| 586 | 586 | ||
| 587 | bool KProcess::InsertWatchpoint(Core::System& system, VAddr addr, u64 size, | ||
| 588 | DebugWatchpointType type) { | ||
| 589 | const auto watch{std::find_if(watchpoints.begin(), watchpoints.end(), [&](const auto& wp) { | ||
| 590 | return wp.type == DebugWatchpointType::None; | ||
| 591 | })}; | ||
| 592 | |||
| 593 | if (watch == watchpoints.end()) { | ||
| 594 | return false; | ||
| 595 | } | ||
| 596 | |||
| 597 | watch->start_address = addr; | ||
| 598 | watch->end_address = addr + size; | ||
| 599 | watch->type = type; | ||
| 600 | |||
| 601 | for (VAddr page = Common::AlignDown(addr, PageSize); page < addr + size; page += PageSize) { | ||
| 602 | debug_page_refcounts[page]++; | ||
| 603 | system.Memory().MarkRegionDebug(page, PageSize, true); | ||
| 604 | } | ||
| 605 | |||
| 606 | return true; | ||
| 607 | } | ||
| 608 | |||
| 609 | bool KProcess::RemoveWatchpoint(Core::System& system, VAddr addr, u64 size, | ||
| 610 | DebugWatchpointType type) { | ||
| 611 | const auto watch{std::find_if(watchpoints.begin(), watchpoints.end(), [&](const auto& wp) { | ||
| 612 | return wp.start_address == addr && wp.end_address == addr + size && wp.type == type; | ||
| 613 | })}; | ||
| 614 | |||
| 615 | if (watch == watchpoints.end()) { | ||
| 616 | return false; | ||
| 617 | } | ||
| 618 | |||
| 619 | watch->start_address = 0; | ||
| 620 | watch->end_address = 0; | ||
| 621 | watch->type = DebugWatchpointType::None; | ||
| 622 | |||
| 623 | for (VAddr page = Common::AlignDown(addr, PageSize); page < addr + size; page += PageSize) { | ||
| 624 | debug_page_refcounts[page]--; | ||
| 625 | if (!debug_page_refcounts[page]) { | ||
| 626 | system.Memory().MarkRegionDebug(page, PageSize, false); | ||
| 627 | } | ||
| 628 | } | ||
| 629 | |||
| 630 | return true; | ||
| 631 | } | ||
| 632 | |||
| 587 | void KProcess::LoadModule(CodeSet code_set, VAddr base_addr) { | 633 | void KProcess::LoadModule(CodeSet code_set, VAddr base_addr) { |
| 588 | const auto ReprotectSegment = [&](const CodeSet::Segment& segment, | 634 | const auto ReprotectSegment = [&](const CodeSet::Segment& segment, |
| 589 | Svc::MemoryPermission permission) { | 635 | Svc::MemoryPermission permission) { |
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index e562a79b8..c2086e5ba 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | #include <array> | 7 | #include <array> |
| 8 | #include <cstddef> | 8 | #include <cstddef> |
| 9 | #include <list> | 9 | #include <list> |
| 10 | #include <map> | ||
| 10 | #include <string> | 11 | #include <string> |
| 11 | #include "common/common_types.h" | 12 | #include "common/common_types.h" |
| 12 | #include "core/hle/kernel/k_address_arbiter.h" | 13 | #include "core/hle/kernel/k_address_arbiter.h" |
| @@ -68,6 +69,20 @@ enum class ProcessActivity : u32 { | |||
| 68 | Paused, | 69 | Paused, |
| 69 | }; | 70 | }; |
| 70 | 71 | ||
| 72 | enum class DebugWatchpointType : u8 { | ||
| 73 | None = 0, | ||
| 74 | Read = 1 << 0, | ||
| 75 | Write = 1 << 1, | ||
| 76 | ReadOrWrite = Read | Write, | ||
| 77 | }; | ||
| 78 | DECLARE_ENUM_FLAG_OPERATORS(DebugWatchpointType); | ||
| 79 | |||
| 80 | struct DebugWatchpoint { | ||
| 81 | VAddr start_address; | ||
| 82 | VAddr end_address; | ||
| 83 | DebugWatchpointType type; | ||
| 84 | }; | ||
| 85 | |||
| 71 | class KProcess final : public KAutoObjectWithSlabHeapAndContainer<KProcess, KWorkerTask> { | 86 | class KProcess final : public KAutoObjectWithSlabHeapAndContainer<KProcess, KWorkerTask> { |
| 72 | KERNEL_AUTOOBJECT_TRAITS(KProcess, KSynchronizationObject); | 87 | KERNEL_AUTOOBJECT_TRAITS(KProcess, KSynchronizationObject); |
| 73 | 88 | ||
| @@ -374,6 +389,19 @@ public: | |||
| 374 | // Frees a used TLS slot identified by the given address | 389 | // Frees a used TLS slot identified by the given address |
| 375 | ResultCode DeleteThreadLocalRegion(VAddr addr); | 390 | ResultCode DeleteThreadLocalRegion(VAddr addr); |
| 376 | 391 | ||
| 392 | /////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 393 | // Debug watchpoint management | ||
| 394 | |||
| 395 | // Attempts to insert a watchpoint into a free slot. Returns false if none are available. | ||
| 396 | bool InsertWatchpoint(Core::System& system, VAddr addr, u64 size, DebugWatchpointType type); | ||
| 397 | |||
| 398 | // Attempts to remove the watchpoint specified by the given parameters. | ||
| 399 | bool RemoveWatchpoint(Core::System& system, VAddr addr, u64 size, DebugWatchpointType type); | ||
| 400 | |||
| 401 | const std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>& GetWatchpoints() const { | ||
| 402 | return watchpoints; | ||
| 403 | } | ||
| 404 | |||
| 377 | private: | 405 | private: |
| 378 | void PinThread(s32 core_id, KThread* thread) { | 406 | void PinThread(s32 core_id, KThread* thread) { |
| 379 | ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES)); | 407 | ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES)); |
| @@ -478,6 +506,8 @@ private: | |||
| 478 | std::array<KThread*, Core::Hardware::NUM_CPU_CORES> running_threads{}; | 506 | std::array<KThread*, Core::Hardware::NUM_CPU_CORES> running_threads{}; |
| 479 | std::array<u64, Core::Hardware::NUM_CPU_CORES> running_thread_idle_counts{}; | 507 | std::array<u64, Core::Hardware::NUM_CPU_CORES> running_thread_idle_counts{}; |
| 480 | std::array<KThread*, Core::Hardware::NUM_CPU_CORES> pinned_threads{}; | 508 | std::array<KThread*, Core::Hardware::NUM_CPU_CORES> pinned_threads{}; |
| 509 | std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS> watchpoints{}; | ||
| 510 | std::map<VAddr, u64> debug_page_refcounts; | ||
| 481 | 511 | ||
| 482 | KThread* exception_thread{}; | 512 | KThread* exception_thread{}; |
| 483 | 513 | ||
diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp index 2d4e8637b..edd0e4eae 100644 --- a/src/core/hle/kernel/k_scheduler.cpp +++ b/src/core/hle/kernel/k_scheduler.cpp | |||
| @@ -710,6 +710,7 @@ void KScheduler::Reload(KThread* thread) { | |||
| 710 | Core::ARM_Interface& cpu_core = system.ArmInterface(core_id); | 710 | Core::ARM_Interface& cpu_core = system.ArmInterface(core_id); |
| 711 | cpu_core.LoadContext(thread->GetContext32()); | 711 | cpu_core.LoadContext(thread->GetContext32()); |
| 712 | cpu_core.LoadContext(thread->GetContext64()); | 712 | cpu_core.LoadContext(thread->GetContext64()); |
| 713 | cpu_core.LoadWatchpointArray(thread->GetOwnerProcess()->GetWatchpoints()); | ||
| 713 | cpu_core.SetTlsAddress(thread->GetTLSAddress()); | 714 | cpu_core.SetTlsAddress(thread->GetTLSAddress()); |
| 714 | cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0()); | 715 | cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0()); |
| 715 | cpu_core.ClearExclusiveState(); | 716 | cpu_core.ClearExclusiveState(); |
diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 7534de01e..584808d50 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp | |||
| @@ -67,6 +67,16 @@ struct Memory::Impl { | |||
| 67 | return system.DeviceMemory().GetPointer(paddr) + vaddr; | 67 | return system.DeviceMemory().GetPointer(paddr) + vaddr; |
| 68 | } | 68 | } |
| 69 | 69 | ||
| 70 | [[nodiscard]] u8* GetPointerFromDebugMemory(VAddr vaddr) const { | ||
| 71 | const PAddr paddr{current_page_table->backing_addr[vaddr >> PAGE_BITS]}; | ||
| 72 | |||
| 73 | if (paddr == 0) { | ||
| 74 | return {}; | ||
| 75 | } | ||
| 76 | |||
| 77 | return system.DeviceMemory().GetPointer(paddr) + vaddr; | ||
| 78 | } | ||
| 79 | |||
| 70 | u8 Read8(const VAddr addr) { | 80 | u8 Read8(const VAddr addr) { |
| 71 | return Read<u8>(addr); | 81 | return Read<u8>(addr); |
| 72 | } | 82 | } |
| @@ -187,6 +197,12 @@ struct Memory::Impl { | |||
| 187 | on_memory(copy_amount, mem_ptr); | 197 | on_memory(copy_amount, mem_ptr); |
| 188 | break; | 198 | break; |
| 189 | } | 199 | } |
| 200 | case Common::PageType::DebugMemory: { | ||
| 201 | DEBUG_ASSERT(pointer); | ||
| 202 | u8* const mem_ptr{GetPointerFromDebugMemory(current_vaddr)}; | ||
| 203 | on_memory(copy_amount, mem_ptr); | ||
| 204 | break; | ||
| 205 | } | ||
| 190 | case Common::PageType::RasterizerCachedMemory: { | 206 | case Common::PageType::RasterizerCachedMemory: { |
| 191 | u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)}; | 207 | u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)}; |
| 192 | on_rasterizer(current_vaddr, copy_amount, host_ptr); | 208 | on_rasterizer(current_vaddr, copy_amount, host_ptr); |
| @@ -316,6 +332,58 @@ struct Memory::Impl { | |||
| 316 | }); | 332 | }); |
| 317 | } | 333 | } |
| 318 | 334 | ||
| 335 | void MarkRegionDebug(VAddr vaddr, u64 size, bool debug) { | ||
| 336 | if (vaddr == 0) { | ||
| 337 | return; | ||
| 338 | } | ||
| 339 | |||
| 340 | // Iterate over a contiguous CPU address space, marking/unmarking the region. | ||
| 341 | // The region is at a granularity of CPU pages. | ||
| 342 | |||
| 343 | const u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1; | ||
| 344 | for (u64 i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) { | ||
| 345 | const Common::PageType page_type{ | ||
| 346 | current_page_table->pointers[vaddr >> PAGE_BITS].Type()}; | ||
| 347 | if (debug) { | ||
| 348 | // Switch page type to debug if now debug | ||
| 349 | switch (page_type) { | ||
| 350 | case Common::PageType::Unmapped: | ||
| 351 | ASSERT_MSG(false, "Attempted to mark unmapped pages as debug"); | ||
| 352 | break; | ||
| 353 | case Common::PageType::RasterizerCachedMemory: | ||
| 354 | case Common::PageType::DebugMemory: | ||
| 355 | // Page is already marked. | ||
| 356 | break; | ||
| 357 | case Common::PageType::Memory: | ||
| 358 | current_page_table->pointers[vaddr >> PAGE_BITS].Store( | ||
| 359 | nullptr, Common::PageType::DebugMemory); | ||
| 360 | break; | ||
| 361 | default: | ||
| 362 | UNREACHABLE(); | ||
| 363 | } | ||
| 364 | } else { | ||
| 365 | // Switch page type to non-debug if now non-debug | ||
| 366 | switch (page_type) { | ||
| 367 | case Common::PageType::Unmapped: | ||
| 368 | ASSERT_MSG(false, "Attempted to mark unmapped pages as non-debug"); | ||
| 369 | break; | ||
| 370 | case Common::PageType::RasterizerCachedMemory: | ||
| 371 | case Common::PageType::Memory: | ||
| 372 | // Don't mess with already non-debug or rasterizer memory. | ||
| 373 | break; | ||
| 374 | case Common::PageType::DebugMemory: { | ||
| 375 | u8* const pointer{GetPointerFromDebugMemory(vaddr & ~PAGE_MASK)}; | ||
| 376 | current_page_table->pointers[vaddr >> PAGE_BITS].Store( | ||
| 377 | pointer - (vaddr & ~PAGE_MASK), Common::PageType::Memory); | ||
| 378 | break; | ||
| 379 | } | ||
| 380 | default: | ||
| 381 | UNREACHABLE(); | ||
| 382 | } | ||
| 383 | } | ||
| 384 | } | ||
| 385 | } | ||
| 386 | |||
| 319 | void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) { | 387 | void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) { |
| 320 | if (vaddr == 0) { | 388 | if (vaddr == 0) { |
| 321 | return; | 389 | return; |
| @@ -342,6 +410,7 @@ struct Memory::Impl { | |||
| 342 | // It is not necessary for a process to have this region mapped into its address | 410 | // It is not necessary for a process to have this region mapped into its address |
| 343 | // space, for example, a system module need not have a VRAM mapping. | 411 | // space, for example, a system module need not have a VRAM mapping. |
| 344 | break; | 412 | break; |
| 413 | case Common::PageType::DebugMemory: | ||
| 345 | case Common::PageType::Memory: | 414 | case Common::PageType::Memory: |
| 346 | current_page_table->pointers[vaddr >> PAGE_BITS].Store( | 415 | current_page_table->pointers[vaddr >> PAGE_BITS].Store( |
| 347 | nullptr, Common::PageType::RasterizerCachedMemory); | 416 | nullptr, Common::PageType::RasterizerCachedMemory); |
| @@ -360,6 +429,7 @@ struct Memory::Impl { | |||
| 360 | // It is not necessary for a process to have this region mapped into its address | 429 | // It is not necessary for a process to have this region mapped into its address |
| 361 | // space, for example, a system module need not have a VRAM mapping. | 430 | // space, for example, a system module need not have a VRAM mapping. |
| 362 | break; | 431 | break; |
| 432 | case Common::PageType::DebugMemory: | ||
| 363 | case Common::PageType::Memory: | 433 | case Common::PageType::Memory: |
| 364 | // There can be more than one GPU region mapped per CPU region, so it's common | 434 | // There can be more than one GPU region mapped per CPU region, so it's common |
| 365 | // that this area is already unmarked as cached. | 435 | // that this area is already unmarked as cached. |
| @@ -460,6 +530,8 @@ struct Memory::Impl { | |||
| 460 | case Common::PageType::Memory: | 530 | case Common::PageType::Memory: |
| 461 | ASSERT_MSG(false, "Mapped memory page without a pointer @ 0x{:016X}", vaddr); | 531 | ASSERT_MSG(false, "Mapped memory page without a pointer @ 0x{:016X}", vaddr); |
| 462 | return nullptr; | 532 | return nullptr; |
| 533 | case Common::PageType::DebugMemory: | ||
| 534 | return GetPointerFromDebugMemory(vaddr); | ||
| 463 | case Common::PageType::RasterizerCachedMemory: { | 535 | case Common::PageType::RasterizerCachedMemory: { |
| 464 | u8* const host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)}; | 536 | u8* const host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)}; |
| 465 | on_rasterizer(); | 537 | on_rasterizer(); |
| @@ -591,7 +663,8 @@ bool Memory::IsValidVirtualAddress(const VAddr vaddr) const { | |||
| 591 | return false; | 663 | return false; |
| 592 | } | 664 | } |
| 593 | const auto [pointer, type] = page_table.pointers[page].PointerType(); | 665 | const auto [pointer, type] = page_table.pointers[page].PointerType(); |
| 594 | return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory; | 666 | return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory || |
| 667 | type == Common::PageType::DebugMemory; | ||
| 595 | } | 668 | } |
| 596 | 669 | ||
| 597 | bool Memory::IsValidVirtualAddressRange(VAddr base, u64 size) const { | 670 | bool Memory::IsValidVirtualAddressRange(VAddr base, u64 size) const { |
| @@ -707,4 +780,8 @@ void Memory::RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) { | |||
| 707 | impl->RasterizerMarkRegionCached(vaddr, size, cached); | 780 | impl->RasterizerMarkRegionCached(vaddr, size, cached); |
| 708 | } | 781 | } |
| 709 | 782 | ||
| 783 | void Memory::MarkRegionDebug(VAddr vaddr, u64 size, bool debug) { | ||
| 784 | impl->MarkRegionDebug(vaddr, size, debug); | ||
| 785 | } | ||
| 786 | |||
| 710 | } // namespace Core::Memory | 787 | } // namespace Core::Memory |
diff --git a/src/core/memory.h b/src/core/memory.h index 58cc27b29..f22c0a2d8 100644 --- a/src/core/memory.h +++ b/src/core/memory.h | |||
| @@ -446,6 +446,17 @@ public: | |||
| 446 | */ | 446 | */ |
| 447 | void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached); | 447 | void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached); |
| 448 | 448 | ||
| 449 | /** | ||
| 450 | * Marks each page within the specified address range as debug or non-debug. | ||
| 451 | * Debug addresses are not accessible from fastmem pointers. | ||
| 452 | * | ||
| 453 | * @param vaddr The virtual address indicating the start of the address range. | ||
| 454 | * @param size The size of the address range in bytes. | ||
| 455 | * @param debug Whether or not any pages within the address range should be | ||
| 456 | * marked as debug or non-debug. | ||
| 457 | */ | ||
| 458 | void MarkRegionDebug(VAddr vaddr, u64 size, bool debug); | ||
| 459 | |||
| 449 | private: | 460 | private: |
| 450 | Core::System& system; | 461 | Core::System& system; |
| 451 | 462 | ||