Merge pull request #8432 from liamwhite/watchpoint
core/debugger: memory breakpoint support
This commit is contained in:
commit
737c446fc1
|
@ -1 +1 @@
|
||||||
Subproject commit 57af72a567454b93c757e087b4510a24b81911b1
|
Subproject commit 5ad1d02351bf4fee681a3d701d210b419f41a505
|
|
@ -15,6 +15,9 @@ enum class PageType : u8 {
|
||||||
Unmapped,
|
Unmapped,
|
||||||
/// Page is mapped to regular memory. This is the only type you can get pointers to.
|
/// Page is mapped to regular memory. This is the only type you can get pointers to.
|
||||||
Memory,
|
Memory,
|
||||||
|
/// Page is mapped to regular memory, but inaccessible from CPU fastmem and must use
|
||||||
|
/// the callbacks.
|
||||||
|
DebugMemory,
|
||||||
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
|
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
|
||||||
/// invalidation
|
/// invalidation
|
||||||
RasterizerCachedMemory,
|
RasterizerCachedMemory,
|
||||||
|
|
|
@ -121,8 +121,15 @@ void ARM_Interface::Run() {
|
||||||
|
|
||||||
// Notify the debugger and go to sleep if a breakpoint was hit.
|
// Notify the debugger and go to sleep if a breakpoint was hit.
|
||||||
if (Has(hr, breakpoint)) {
|
if (Has(hr, breakpoint)) {
|
||||||
|
RewindBreakpointInstruction();
|
||||||
system.GetDebugger().NotifyThreadStopped(current_thread);
|
system.GetDebugger().NotifyThreadStopped(current_thread);
|
||||||
current_thread->RequestSuspend(Kernel::SuspendType::Debug);
|
current_thread->RequestSuspend(SuspendType::Debug);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (Has(hr, watchpoint)) {
|
||||||
|
RewindBreakpointInstruction();
|
||||||
|
system.GetDebugger().NotifyThreadWatchpoint(current_thread, *HaltedWatchpoint());
|
||||||
|
current_thread->RequestSuspend(SuspendType::Debug);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,4 +143,36 @@ void ARM_Interface::Run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ARM_Interface::LoadWatchpointArray(const WatchpointArray& wp) {
|
||||||
|
watchpoints = ℘
|
||||||
|
}
|
||||||
|
|
||||||
|
const Kernel::DebugWatchpoint* ARM_Interface::MatchingWatchpoint(
|
||||||
|
VAddr addr, u64 size, Kernel::DebugWatchpointType access_type) const {
|
||||||
|
if (!watchpoints) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VAddr start_address{addr};
|
||||||
|
const VAddr end_address{addr + size};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < Core::Hardware::NUM_WATCHPOINTS; i++) {
|
||||||
|
const auto& watch{(*watchpoints)[i]};
|
||||||
|
|
||||||
|
if (end_address <= watch.start_address) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (start_address >= watch.end_address) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((access_type & watch.type) == Kernel::DebugWatchpointType::None) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &watch;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <span>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <dynarmic/interface/halt_reason.h>
|
#include <dynarmic/interface/halt_reason.h>
|
||||||
|
@ -19,13 +20,16 @@ struct PageTable;
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
enum class VMAPermission : u8;
|
enum class VMAPermission : u8;
|
||||||
}
|
enum class DebugWatchpointType : u8;
|
||||||
|
struct DebugWatchpoint;
|
||||||
|
} // namespace Kernel
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
class CPUInterruptHandler;
|
class CPUInterruptHandler;
|
||||||
|
|
||||||
using CPUInterrupts = std::array<CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>;
|
using CPUInterrupts = std::array<CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>;
|
||||||
|
using WatchpointArray = std::array<Kernel::DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>;
|
||||||
|
|
||||||
/// Generic ARMv8 CPU interface
|
/// Generic ARMv8 CPU interface
|
||||||
class ARM_Interface {
|
class ARM_Interface {
|
||||||
|
@ -170,6 +174,7 @@ public:
|
||||||
virtual void SaveContext(ThreadContext64& ctx) = 0;
|
virtual void SaveContext(ThreadContext64& ctx) = 0;
|
||||||
virtual void LoadContext(const ThreadContext32& ctx) = 0;
|
virtual void LoadContext(const ThreadContext32& ctx) = 0;
|
||||||
virtual void LoadContext(const ThreadContext64& ctx) = 0;
|
virtual void LoadContext(const ThreadContext64& ctx) = 0;
|
||||||
|
void LoadWatchpointArray(const WatchpointArray& wp);
|
||||||
|
|
||||||
/// Clears the exclusive monitor's state.
|
/// Clears the exclusive monitor's state.
|
||||||
virtual void ClearExclusiveState() = 0;
|
virtual void ClearExclusiveState() = 0;
|
||||||
|
@ -198,18 +203,24 @@ public:
|
||||||
static constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
|
static constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
|
||||||
static constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
|
static constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
|
||||||
static constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4;
|
static constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4;
|
||||||
|
static constexpr Dynarmic::HaltReason watchpoint = Dynarmic::HaltReason::UserDefined5;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// System context that this ARM interface is running under.
|
/// System context that this ARM interface is running under.
|
||||||
System& system;
|
System& system;
|
||||||
CPUInterrupts& interrupt_handlers;
|
CPUInterrupts& interrupt_handlers;
|
||||||
|
const WatchpointArray* watchpoints;
|
||||||
bool uses_wall_clock;
|
bool uses_wall_clock;
|
||||||
|
|
||||||
static void SymbolicateBacktrace(Core::System& system, std::vector<BacktraceEntry>& out);
|
static void SymbolicateBacktrace(Core::System& system, std::vector<BacktraceEntry>& out);
|
||||||
|
const Kernel::DebugWatchpoint* MatchingWatchpoint(
|
||||||
|
VAddr addr, u64 size, Kernel::DebugWatchpointType access_type) const;
|
||||||
|
|
||||||
virtual Dynarmic::HaltReason RunJit() = 0;
|
virtual Dynarmic::HaltReason RunJit() = 0;
|
||||||
virtual Dynarmic::HaltReason StepJit() = 0;
|
virtual Dynarmic::HaltReason StepJit() = 0;
|
||||||
virtual u32 GetSvcNumber() const = 0;
|
virtual u32 GetSvcNumber() const = 0;
|
||||||
|
virtual const Kernel::DebugWatchpoint* HaltedWatchpoint() const = 0;
|
||||||
|
virtual void RewindBreakpointInstruction() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -29,45 +29,62 @@ using namespace Common::Literals;
|
||||||
class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
|
class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
|
||||||
public:
|
public:
|
||||||
explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_)
|
explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_)
|
||||||
: parent{parent_}, memory(parent.system.Memory()) {}
|
: parent{parent_},
|
||||||
|
memory(parent.system.Memory()), debugger_enabled{parent.system.DebuggerEnabled()} {}
|
||||||
|
|
||||||
u8 MemoryRead8(u32 vaddr) override {
|
u8 MemoryRead8(u32 vaddr) override {
|
||||||
|
CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read);
|
||||||
return memory.Read8(vaddr);
|
return memory.Read8(vaddr);
|
||||||
}
|
}
|
||||||
u16 MemoryRead16(u32 vaddr) override {
|
u16 MemoryRead16(u32 vaddr) override {
|
||||||
|
CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read);
|
||||||
return memory.Read16(vaddr);
|
return memory.Read16(vaddr);
|
||||||
}
|
}
|
||||||
u32 MemoryRead32(u32 vaddr) override {
|
u32 MemoryRead32(u32 vaddr) override {
|
||||||
|
CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read);
|
||||||
return memory.Read32(vaddr);
|
return memory.Read32(vaddr);
|
||||||
}
|
}
|
||||||
u64 MemoryRead64(u32 vaddr) override {
|
u64 MemoryRead64(u32 vaddr) override {
|
||||||
|
CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read);
|
||||||
return memory.Read64(vaddr);
|
return memory.Read64(vaddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemoryWrite8(u32 vaddr, u8 value) override {
|
void MemoryWrite8(u32 vaddr, u8 value) override {
|
||||||
|
if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) {
|
||||||
memory.Write8(vaddr, value);
|
memory.Write8(vaddr, value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void MemoryWrite16(u32 vaddr, u16 value) override {
|
void MemoryWrite16(u32 vaddr, u16 value) override {
|
||||||
|
if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) {
|
||||||
memory.Write16(vaddr, value);
|
memory.Write16(vaddr, value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void MemoryWrite32(u32 vaddr, u32 value) override {
|
void MemoryWrite32(u32 vaddr, u32 value) override {
|
||||||
|
if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) {
|
||||||
memory.Write32(vaddr, value);
|
memory.Write32(vaddr, value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void MemoryWrite64(u32 vaddr, u64 value) override {
|
void MemoryWrite64(u32 vaddr, u64 value) override {
|
||||||
|
if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) {
|
||||||
memory.Write64(vaddr, value);
|
memory.Write64(vaddr, value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) override {
|
bool MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) override {
|
||||||
return memory.WriteExclusive8(vaddr, value, expected);
|
return CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write) &&
|
||||||
|
memory.WriteExclusive8(vaddr, value, expected);
|
||||||
}
|
}
|
||||||
bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override {
|
bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override {
|
||||||
return memory.WriteExclusive16(vaddr, value, expected);
|
return CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write) &&
|
||||||
|
memory.WriteExclusive16(vaddr, value, expected);
|
||||||
}
|
}
|
||||||
bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override {
|
bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override {
|
||||||
return memory.WriteExclusive32(vaddr, value, expected);
|
return CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write) &&
|
||||||
|
memory.WriteExclusive32(vaddr, value, expected);
|
||||||
}
|
}
|
||||||
bool MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) override {
|
bool MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) override {
|
||||||
return memory.WriteExclusive64(vaddr, value, expected);
|
return CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write) &&
|
||||||
|
memory.WriteExclusive64(vaddr, value, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InterpreterFallback(u32 pc, std::size_t num_instructions) override {
|
void InterpreterFallback(u32 pc, std::size_t num_instructions) override {
|
||||||
|
@ -77,8 +94,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
|
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
|
||||||
if (parent.system.DebuggerEnabled()) {
|
if (debugger_enabled) {
|
||||||
parent.jit.load()->Regs()[15] = pc;
|
parent.SaveContext(parent.breakpoint_context);
|
||||||
parent.jit.load()->HaltExecution(ARM_Interface::breakpoint);
|
parent.jit.load()->HaltExecution(ARM_Interface::breakpoint);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -117,9 +134,26 @@ public:
|
||||||
return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0);
|
return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CheckMemoryAccess(VAddr addr, u64 size, Kernel::DebugWatchpointType type) {
|
||||||
|
if (!debugger_enabled) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto match{parent.MatchingWatchpoint(addr, size, type)};
|
||||||
|
if (match) {
|
||||||
|
parent.SaveContext(parent.breakpoint_context);
|
||||||
|
parent.jit.load()->HaltExecution(ARM_Interface::watchpoint);
|
||||||
|
parent.halted_watchpoint = match;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
ARM_Dynarmic_32& parent;
|
ARM_Dynarmic_32& parent;
|
||||||
Core::Memory::Memory& memory;
|
Core::Memory::Memory& memory;
|
||||||
std::size_t num_interpreted_instructions{};
|
std::size_t num_interpreted_instructions{};
|
||||||
|
bool debugger_enabled{};
|
||||||
static constexpr u64 minimum_run_cycles = 1000U;
|
static constexpr u64 minimum_run_cycles = 1000U;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -154,6 +188,11 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
|
||||||
config.code_cache_size = 512_MiB;
|
config.code_cache_size = 512_MiB;
|
||||||
config.far_code_offset = 400_MiB;
|
config.far_code_offset = 400_MiB;
|
||||||
|
|
||||||
|
// Allow memory fault handling to work
|
||||||
|
if (system.DebuggerEnabled()) {
|
||||||
|
config.check_halt_on_memory_access = true;
|
||||||
|
}
|
||||||
|
|
||||||
// null_jit
|
// null_jit
|
||||||
if (!page_table) {
|
if (!page_table) {
|
||||||
// Don't waste too much memory on null_jit
|
// Don't waste too much memory on null_jit
|
||||||
|
@ -248,6 +287,14 @@ u32 ARM_Dynarmic_32::GetSvcNumber() const {
|
||||||
return svc_swi;
|
return svc_swi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Kernel::DebugWatchpoint* ARM_Dynarmic_32::HaltedWatchpoint() const {
|
||||||
|
return halted_watchpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARM_Dynarmic_32::RewindBreakpointInstruction() {
|
||||||
|
LoadContext(breakpoint_context);
|
||||||
|
}
|
||||||
|
|
||||||
ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_,
|
ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_,
|
||||||
bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_,
|
bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_,
|
||||||
std::size_t core_index_)
|
std::size_t core_index_)
|
||||||
|
|
|
@ -72,6 +72,8 @@ protected:
|
||||||
Dynarmic::HaltReason RunJit() override;
|
Dynarmic::HaltReason RunJit() override;
|
||||||
Dynarmic::HaltReason StepJit() override;
|
Dynarmic::HaltReason StepJit() override;
|
||||||
u32 GetSvcNumber() const override;
|
u32 GetSvcNumber() const override;
|
||||||
|
const Kernel::DebugWatchpoint* HaltedWatchpoint() const override;
|
||||||
|
void RewindBreakpointInstruction() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<Dynarmic::A32::Jit> MakeJit(Common::PageTable* page_table) const;
|
std::shared_ptr<Dynarmic::A32::Jit> MakeJit(Common::PageTable* page_table) const;
|
||||||
|
@ -98,6 +100,10 @@ private:
|
||||||
|
|
||||||
// SVC callback
|
// SVC callback
|
||||||
u32 svc_swi{};
|
u32 svc_swi{};
|
||||||
|
|
||||||
|
// Watchpoint info
|
||||||
|
const Kernel::DebugWatchpoint* halted_watchpoint;
|
||||||
|
ThreadContext32 breakpoint_context;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -29,55 +29,76 @@ using namespace Common::Literals;
|
||||||
class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
|
class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
|
||||||
public:
|
public:
|
||||||
explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent_)
|
explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent_)
|
||||||
: parent{parent_}, memory(parent.system.Memory()) {}
|
: parent{parent_},
|
||||||
|
memory(parent.system.Memory()), debugger_enabled{parent.system.DebuggerEnabled()} {}
|
||||||
|
|
||||||
u8 MemoryRead8(u64 vaddr) override {
|
u8 MemoryRead8(u64 vaddr) override {
|
||||||
|
CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read);
|
||||||
return memory.Read8(vaddr);
|
return memory.Read8(vaddr);
|
||||||
}
|
}
|
||||||
u16 MemoryRead16(u64 vaddr) override {
|
u16 MemoryRead16(u64 vaddr) override {
|
||||||
|
CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read);
|
||||||
return memory.Read16(vaddr);
|
return memory.Read16(vaddr);
|
||||||
}
|
}
|
||||||
u32 MemoryRead32(u64 vaddr) override {
|
u32 MemoryRead32(u64 vaddr) override {
|
||||||
|
CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read);
|
||||||
return memory.Read32(vaddr);
|
return memory.Read32(vaddr);
|
||||||
}
|
}
|
||||||
u64 MemoryRead64(u64 vaddr) override {
|
u64 MemoryRead64(u64 vaddr) override {
|
||||||
|
CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read);
|
||||||
return memory.Read64(vaddr);
|
return memory.Read64(vaddr);
|
||||||
}
|
}
|
||||||
Vector MemoryRead128(u64 vaddr) override {
|
Vector MemoryRead128(u64 vaddr) override {
|
||||||
|
CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Read);
|
||||||
return {memory.Read64(vaddr), memory.Read64(vaddr + 8)};
|
return {memory.Read64(vaddr), memory.Read64(vaddr + 8)};
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemoryWrite8(u64 vaddr, u8 value) override {
|
void MemoryWrite8(u64 vaddr, u8 value) override {
|
||||||
|
if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) {
|
||||||
memory.Write8(vaddr, value);
|
memory.Write8(vaddr, value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void MemoryWrite16(u64 vaddr, u16 value) override {
|
void MemoryWrite16(u64 vaddr, u16 value) override {
|
||||||
|
if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) {
|
||||||
memory.Write16(vaddr, value);
|
memory.Write16(vaddr, value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void MemoryWrite32(u64 vaddr, u32 value) override {
|
void MemoryWrite32(u64 vaddr, u32 value) override {
|
||||||
|
if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) {
|
||||||
memory.Write32(vaddr, value);
|
memory.Write32(vaddr, value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void MemoryWrite64(u64 vaddr, u64 value) override {
|
void MemoryWrite64(u64 vaddr, u64 value) override {
|
||||||
|
if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) {
|
||||||
memory.Write64(vaddr, value);
|
memory.Write64(vaddr, value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
void MemoryWrite128(u64 vaddr, Vector value) override {
|
void MemoryWrite128(u64 vaddr, Vector value) override {
|
||||||
|
if (CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Write)) {
|
||||||
memory.Write64(vaddr, value[0]);
|
memory.Write64(vaddr, value[0]);
|
||||||
memory.Write64(vaddr + 8, value[1]);
|
memory.Write64(vaddr + 8, value[1]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, std::uint8_t expected) override {
|
bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, std::uint8_t expected) override {
|
||||||
return memory.WriteExclusive8(vaddr, value, expected);
|
return CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write) &&
|
||||||
|
memory.WriteExclusive8(vaddr, value, expected);
|
||||||
}
|
}
|
||||||
bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, std::uint16_t expected) override {
|
bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, std::uint16_t expected) override {
|
||||||
return memory.WriteExclusive16(vaddr, value, expected);
|
return CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write) &&
|
||||||
|
memory.WriteExclusive16(vaddr, value, expected);
|
||||||
}
|
}
|
||||||
bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, std::uint32_t expected) override {
|
bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, std::uint32_t expected) override {
|
||||||
return memory.WriteExclusive32(vaddr, value, expected);
|
return CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write) &&
|
||||||
|
memory.WriteExclusive32(vaddr, value, expected);
|
||||||
}
|
}
|
||||||
bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, std::uint64_t expected) override {
|
bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, std::uint64_t expected) override {
|
||||||
return memory.WriteExclusive64(vaddr, value, expected);
|
return CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write) &&
|
||||||
|
memory.WriteExclusive64(vaddr, value, expected);
|
||||||
}
|
}
|
||||||
bool MemoryWriteExclusive128(u64 vaddr, Vector value, Vector expected) override {
|
bool MemoryWriteExclusive128(u64 vaddr, Vector value, Vector expected) override {
|
||||||
return memory.WriteExclusive128(vaddr, value, expected);
|
return CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Write) &&
|
||||||
|
memory.WriteExclusive128(vaddr, value, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InterpreterFallback(u64 pc, std::size_t num_instructions) override {
|
void InterpreterFallback(u64 pc, std::size_t num_instructions) override {
|
||||||
|
@ -118,8 +139,8 @@ public:
|
||||||
case Dynarmic::A64::Exception::Yield:
|
case Dynarmic::A64::Exception::Yield:
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
if (parent.system.DebuggerEnabled()) {
|
if (debugger_enabled) {
|
||||||
parent.jit.load()->SetPC(pc);
|
parent.SaveContext(parent.breakpoint_context);
|
||||||
parent.jit.load()->HaltExecution(ARM_Interface::breakpoint);
|
parent.jit.load()->HaltExecution(ARM_Interface::breakpoint);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -160,10 +181,27 @@ public:
|
||||||
return parent.system.CoreTiming().GetClockTicks();
|
return parent.system.CoreTiming().GetClockTicks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CheckMemoryAccess(VAddr addr, u64 size, Kernel::DebugWatchpointType type) {
|
||||||
|
if (!debugger_enabled) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto match{parent.MatchingWatchpoint(addr, size, type)};
|
||||||
|
if (match) {
|
||||||
|
parent.SaveContext(parent.breakpoint_context);
|
||||||
|
parent.jit.load()->HaltExecution(ARM_Interface::watchpoint);
|
||||||
|
parent.halted_watchpoint = match;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
ARM_Dynarmic_64& parent;
|
ARM_Dynarmic_64& parent;
|
||||||
Core::Memory::Memory& memory;
|
Core::Memory::Memory& memory;
|
||||||
u64 tpidrro_el0 = 0;
|
u64 tpidrro_el0 = 0;
|
||||||
u64 tpidr_el0 = 0;
|
u64 tpidr_el0 = 0;
|
||||||
|
bool debugger_enabled{};
|
||||||
static constexpr u64 minimum_run_cycles = 1000U;
|
static constexpr u64 minimum_run_cycles = 1000U;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -214,6 +252,11 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
|
||||||
config.code_cache_size = 512_MiB;
|
config.code_cache_size = 512_MiB;
|
||||||
config.far_code_offset = 400_MiB;
|
config.far_code_offset = 400_MiB;
|
||||||
|
|
||||||
|
// Allow memory fault handling to work
|
||||||
|
if (system.DebuggerEnabled()) {
|
||||||
|
config.check_halt_on_memory_access = true;
|
||||||
|
}
|
||||||
|
|
||||||
// null_jit
|
// null_jit
|
||||||
if (!page_table) {
|
if (!page_table) {
|
||||||
// Don't waste too much memory on null_jit
|
// Don't waste too much memory on null_jit
|
||||||
|
@ -308,6 +351,14 @@ u32 ARM_Dynarmic_64::GetSvcNumber() const {
|
||||||
return svc_swi;
|
return svc_swi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Kernel::DebugWatchpoint* ARM_Dynarmic_64::HaltedWatchpoint() const {
|
||||||
|
return halted_watchpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARM_Dynarmic_64::RewindBreakpointInstruction() {
|
||||||
|
LoadContext(breakpoint_context);
|
||||||
|
}
|
||||||
|
|
||||||
ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_,
|
ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_,
|
||||||
bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_,
|
bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_,
|
||||||
std::size_t core_index_)
|
std::size_t core_index_)
|
||||||
|
|
|
@ -66,6 +66,8 @@ protected:
|
||||||
Dynarmic::HaltReason RunJit() override;
|
Dynarmic::HaltReason RunJit() override;
|
||||||
Dynarmic::HaltReason StepJit() override;
|
Dynarmic::HaltReason StepJit() override;
|
||||||
u32 GetSvcNumber() const override;
|
u32 GetSvcNumber() const override;
|
||||||
|
const Kernel::DebugWatchpoint* HaltedWatchpoint() const override;
|
||||||
|
void RewindBreakpointInstruction() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable* page_table,
|
std::shared_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable* page_table,
|
||||||
|
@ -91,6 +93,10 @@ private:
|
||||||
|
|
||||||
// SVC callback
|
// SVC callback
|
||||||
u32 svc_swi{};
|
u32 svc_swi{};
|
||||||
|
|
||||||
|
// Breakpoint info
|
||||||
|
const Kernel::DebugWatchpoint* halted_watchpoint;
|
||||||
|
ThreadContext64 breakpoint_context;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -44,12 +44,14 @@ static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) {
|
||||||
|
|
||||||
enum class SignalType {
|
enum class SignalType {
|
||||||
Stopped,
|
Stopped,
|
||||||
|
Watchpoint,
|
||||||
ShuttingDown,
|
ShuttingDown,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SignalInfo {
|
struct SignalInfo {
|
||||||
SignalType type;
|
SignalType type;
|
||||||
Kernel::KThread* thread;
|
Kernel::KThread* thread;
|
||||||
|
const Kernel::DebugWatchpoint* watchpoint;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
@ -157,13 +159,19 @@ private:
|
||||||
void PipeData(std::span<const u8> data) {
|
void PipeData(std::span<const u8> data) {
|
||||||
switch (info.type) {
|
switch (info.type) {
|
||||||
case SignalType::Stopped:
|
case SignalType::Stopped:
|
||||||
|
case SignalType::Watchpoint:
|
||||||
// Stop emulation.
|
// Stop emulation.
|
||||||
PauseEmulation();
|
PauseEmulation();
|
||||||
|
|
||||||
// Notify the client.
|
// Notify the client.
|
||||||
active_thread = info.thread;
|
active_thread = info.thread;
|
||||||
UpdateActiveThread();
|
UpdateActiveThread();
|
||||||
|
|
||||||
|
if (info.type == SignalType::Watchpoint) {
|
||||||
|
frontend->Watchpoint(active_thread, *info.watchpoint);
|
||||||
|
} else {
|
||||||
frontend->Stopped(active_thread);
|
frontend->Stopped(active_thread);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case SignalType::ShuttingDown:
|
case SignalType::ShuttingDown:
|
||||||
|
@ -290,12 +298,17 @@ Debugger::Debugger(Core::System& system, u16 port) {
|
||||||
Debugger::~Debugger() = default;
|
Debugger::~Debugger() = default;
|
||||||
|
|
||||||
bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) {
|
bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) {
|
||||||
return impl && impl->SignalDebugger(SignalInfo{SignalType::Stopped, thread});
|
return impl && impl->SignalDebugger(SignalInfo{SignalType::Stopped, thread, nullptr});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Debugger::NotifyThreadWatchpoint(Kernel::KThread* thread,
|
||||||
|
const Kernel::DebugWatchpoint& watch) {
|
||||||
|
return impl && impl->SignalDebugger(SignalInfo{SignalType::Watchpoint, thread, &watch});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Debugger::NotifyShutdown() {
|
void Debugger::NotifyShutdown() {
|
||||||
if (impl) {
|
if (impl) {
|
||||||
impl->SignalDebugger(SignalInfo{SignalType::ShuttingDown, nullptr});
|
impl->SignalDebugger(SignalInfo{SignalType::ShuttingDown, nullptr, nullptr});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
class KThread;
|
class KThread;
|
||||||
}
|
struct DebugWatchpoint;
|
||||||
|
} // namespace Kernel
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
|
@ -40,6 +41,11 @@ public:
|
||||||
*/
|
*/
|
||||||
void NotifyShutdown();
|
void NotifyShutdown();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Notify the debugger that the given thread has stopped due to hitting a watchpoint.
|
||||||
|
*/
|
||||||
|
bool NotifyThreadWatchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<DebuggerImpl> impl;
|
std::unique_ptr<DebuggerImpl> impl;
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
class KThread;
|
class KThread;
|
||||||
}
|
struct DebugWatchpoint;
|
||||||
|
} // namespace Kernel
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
|
@ -71,6 +72,11 @@ public:
|
||||||
*/
|
*/
|
||||||
virtual void ShuttingDown() = 0;
|
virtual void ShuttingDown() = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called when emulation has stopped on a watchpoint.
|
||||||
|
*/
|
||||||
|
virtual void Watchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when new data is asynchronously received on the client socket.
|
* Called when new data is asynchronously received on the client socket.
|
||||||
* A list of actions to perform is returned.
|
* A list of actions to perform is returned.
|
||||||
|
|
|
@ -112,6 +112,23 @@ void GDBStub::Stopped(Kernel::KThread* thread) {
|
||||||
SendReply(arch->ThreadStatus(thread, GDB_STUB_SIGTRAP));
|
SendReply(arch->ThreadStatus(thread, GDB_STUB_SIGTRAP));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GDBStub::Watchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch) {
|
||||||
|
const auto status{arch->ThreadStatus(thread, GDB_STUB_SIGTRAP)};
|
||||||
|
|
||||||
|
switch (watch.type) {
|
||||||
|
case Kernel::DebugWatchpointType::Read:
|
||||||
|
SendReply(fmt::format("{}rwatch:{:x};", status, watch.start_address));
|
||||||
|
break;
|
||||||
|
case Kernel::DebugWatchpointType::Write:
|
||||||
|
SendReply(fmt::format("{}watch:{:x};", status, watch.start_address));
|
||||||
|
break;
|
||||||
|
case Kernel::DebugWatchpointType::ReadOrWrite:
|
||||||
|
default:
|
||||||
|
SendReply(fmt::format("{}awatch:{:x};", status, watch.start_address));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<DebuggerAction> GDBStub::ClientData(std::span<const u8> data) {
|
std::vector<DebuggerAction> GDBStub::ClientData(std::span<const u8> data) {
|
||||||
std::vector<DebuggerAction> actions;
|
std::vector<DebuggerAction> actions;
|
||||||
current_command.insert(current_command.end(), data.begin(), data.end());
|
current_command.insert(current_command.end(), data.begin(), data.end());
|
||||||
|
@ -278,44 +295,124 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
|
||||||
case 'c':
|
case 'c':
|
||||||
actions.push_back(DebuggerAction::Continue);
|
actions.push_back(DebuggerAction::Continue);
|
||||||
break;
|
break;
|
||||||
case 'Z': {
|
case 'Z':
|
||||||
const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
|
HandleBreakpointInsert(command);
|
||||||
const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
|
|
||||||
|
|
||||||
if (system.Memory().IsValidVirtualAddress(addr)) {
|
|
||||||
replaced_instructions[addr] = system.Memory().Read32(addr);
|
|
||||||
system.Memory().Write32(addr, arch->BreakpointInstruction());
|
|
||||||
system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
|
|
||||||
|
|
||||||
SendReply(GDB_STUB_REPLY_OK);
|
|
||||||
} else {
|
|
||||||
SendReply(GDB_STUB_REPLY_ERR);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
case 'z':
|
||||||
case 'z': {
|
HandleBreakpointRemove(command);
|
||||||
const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
|
|
||||||
const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
|
|
||||||
|
|
||||||
const auto orig_insn{replaced_instructions.find(addr)};
|
|
||||||
if (system.Memory().IsValidVirtualAddress(addr) &&
|
|
||||||
orig_insn != replaced_instructions.end()) {
|
|
||||||
system.Memory().Write32(addr, orig_insn->second);
|
|
||||||
system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
|
|
||||||
replaced_instructions.erase(addr);
|
|
||||||
|
|
||||||
SendReply(GDB_STUB_REPLY_OK);
|
|
||||||
} else {
|
|
||||||
SendReply(GDB_STUB_REPLY_ERR);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
SendReply(GDB_STUB_REPLY_EMPTY);
|
SendReply(GDB_STUB_REPLY_EMPTY);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class BreakpointType {
|
||||||
|
Software = 0,
|
||||||
|
Hardware = 1,
|
||||||
|
WriteWatch = 2,
|
||||||
|
ReadWatch = 3,
|
||||||
|
AccessWatch = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
void GDBStub::HandleBreakpointInsert(std::string_view command) {
|
||||||
|
const auto type{static_cast<BreakpointType>(strtoll(command.data(), nullptr, 16))};
|
||||||
|
const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
|
||||||
|
const auto size_sep{std::find(command.begin() + addr_sep, command.end(), ',') -
|
||||||
|
command.begin() + 1};
|
||||||
|
const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
|
||||||
|
const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
|
||||||
|
|
||||||
|
if (!system.Memory().IsValidVirtualAddressRange(addr, size)) {
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success{};
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case BreakpointType::Software:
|
||||||
|
replaced_instructions[addr] = system.Memory().Read32(addr);
|
||||||
|
system.Memory().Write32(addr, arch->BreakpointInstruction());
|
||||||
|
system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
case BreakpointType::WriteWatch:
|
||||||
|
success = system.CurrentProcess()->InsertWatchpoint(system, addr, size,
|
||||||
|
Kernel::DebugWatchpointType::Write);
|
||||||
|
break;
|
||||||
|
case BreakpointType::ReadWatch:
|
||||||
|
success = system.CurrentProcess()->InsertWatchpoint(system, addr, size,
|
||||||
|
Kernel::DebugWatchpointType::Read);
|
||||||
|
break;
|
||||||
|
case BreakpointType::AccessWatch:
|
||||||
|
success = system.CurrentProcess()->InsertWatchpoint(
|
||||||
|
system, addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
|
||||||
|
break;
|
||||||
|
case BreakpointType::Hardware:
|
||||||
|
default:
|
||||||
|
SendReply(GDB_STUB_REPLY_EMPTY);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
SendReply(GDB_STUB_REPLY_OK);
|
||||||
|
} else {
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDBStub::HandleBreakpointRemove(std::string_view command) {
|
||||||
|
const auto type{static_cast<BreakpointType>(strtoll(command.data(), nullptr, 16))};
|
||||||
|
const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
|
||||||
|
const auto size_sep{std::find(command.begin() + addr_sep, command.end(), ',') -
|
||||||
|
command.begin() + 1};
|
||||||
|
const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
|
||||||
|
const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
|
||||||
|
|
||||||
|
if (!system.Memory().IsValidVirtualAddressRange(addr, size)) {
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success{};
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case BreakpointType::Software: {
|
||||||
|
const auto orig_insn{replaced_instructions.find(addr)};
|
||||||
|
if (orig_insn != replaced_instructions.end()) {
|
||||||
|
system.Memory().Write32(addr, orig_insn->second);
|
||||||
|
system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
|
||||||
|
replaced_instructions.erase(addr);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BreakpointType::WriteWatch:
|
||||||
|
success = system.CurrentProcess()->RemoveWatchpoint(system, addr, size,
|
||||||
|
Kernel::DebugWatchpointType::Write);
|
||||||
|
break;
|
||||||
|
case BreakpointType::ReadWatch:
|
||||||
|
success = system.CurrentProcess()->RemoveWatchpoint(system, addr, size,
|
||||||
|
Kernel::DebugWatchpointType::Read);
|
||||||
|
break;
|
||||||
|
case BreakpointType::AccessWatch:
|
||||||
|
success = system.CurrentProcess()->RemoveWatchpoint(
|
||||||
|
system, addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
|
||||||
|
break;
|
||||||
|
case BreakpointType::Hardware:
|
||||||
|
default:
|
||||||
|
SendReply(GDB_STUB_REPLY_EMPTY);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
SendReply(GDB_STUB_REPLY_OK);
|
||||||
|
} else {
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Structure offsets are from Atmosphere
|
// Structure offsets are from Atmosphere
|
||||||
// See osdbg_thread_local_region.os.horizon.hpp and osdbg_thread_type.os.horizon.hpp
|
// See osdbg_thread_local_region.os.horizon.hpp and osdbg_thread_type.os.horizon.hpp
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ public:
|
||||||
void Connected() override;
|
void Connected() override;
|
||||||
void Stopped(Kernel::KThread* thread) override;
|
void Stopped(Kernel::KThread* thread) override;
|
||||||
void ShuttingDown() override;
|
void ShuttingDown() override;
|
||||||
|
void Watchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch) override;
|
||||||
std::vector<DebuggerAction> ClientData(std::span<const u8> data) override;
|
std::vector<DebuggerAction> ClientData(std::span<const u8> data) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -31,6 +32,8 @@ private:
|
||||||
void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions);
|
void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions);
|
||||||
void HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions);
|
void HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions);
|
||||||
void HandleQuery(std::string_view command);
|
void HandleQuery(std::string_view command);
|
||||||
|
void HandleBreakpointInsert(std::string_view command);
|
||||||
|
void HandleBreakpointRemove(std::string_view command);
|
||||||
std::vector<char>::const_iterator CommandEnd() const;
|
std::vector<char>::const_iterator CommandEnd() const;
|
||||||
std::optional<std::string> DetachCommand();
|
std::optional<std::string> DetachCommand();
|
||||||
Kernel::KThread* GetThreadByID(u64 thread_id);
|
Kernel::KThread* GetThreadByID(u64 thread_id);
|
||||||
|
|
|
@ -25,6 +25,9 @@ constexpr std::array<s32, Common::BitSize<u64>()> VirtualToPhysicalCoreMap{
|
||||||
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,
|
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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Cortex-A57 supports 4 memory watchpoints
|
||||||
|
constexpr u64 NUM_WATCHPOINTS = 4;
|
||||||
|
|
||||||
} // namespace Hardware
|
} // namespace Hardware
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -579,6 +579,52 @@ ResultCode KProcess::DeleteThreadLocalRegion(VAddr addr) {
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool KProcess::InsertWatchpoint(Core::System& system, VAddr addr, u64 size,
|
||||||
|
DebugWatchpointType type) {
|
||||||
|
const auto watch{std::find_if(watchpoints.begin(), watchpoints.end(), [&](const auto& wp) {
|
||||||
|
return wp.type == DebugWatchpointType::None;
|
||||||
|
})};
|
||||||
|
|
||||||
|
if (watch == watchpoints.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
watch->start_address = addr;
|
||||||
|
watch->end_address = addr + size;
|
||||||
|
watch->type = type;
|
||||||
|
|
||||||
|
for (VAddr page = Common::AlignDown(addr, PageSize); page < addr + size; page += PageSize) {
|
||||||
|
debug_page_refcounts[page]++;
|
||||||
|
system.Memory().MarkRegionDebug(page, PageSize, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KProcess::RemoveWatchpoint(Core::System& system, VAddr addr, u64 size,
|
||||||
|
DebugWatchpointType type) {
|
||||||
|
const auto watch{std::find_if(watchpoints.begin(), watchpoints.end(), [&](const auto& wp) {
|
||||||
|
return wp.start_address == addr && wp.end_address == addr + size && wp.type == type;
|
||||||
|
})};
|
||||||
|
|
||||||
|
if (watch == watchpoints.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
watch->start_address = 0;
|
||||||
|
watch->end_address = 0;
|
||||||
|
watch->type = DebugWatchpointType::None;
|
||||||
|
|
||||||
|
for (VAddr page = Common::AlignDown(addr, PageSize); page < addr + size; page += PageSize) {
|
||||||
|
debug_page_refcounts[page]--;
|
||||||
|
if (!debug_page_refcounts[page]) {
|
||||||
|
system.Memory().MarkRegionDebug(page, PageSize, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void KProcess::LoadModule(CodeSet code_set, VAddr base_addr) {
|
void KProcess::LoadModule(CodeSet code_set, VAddr base_addr) {
|
||||||
const auto ReprotectSegment = [&](const CodeSet::Segment& segment,
|
const auto ReprotectSegment = [&](const CodeSet::Segment& segment,
|
||||||
Svc::MemoryPermission permission) {
|
Svc::MemoryPermission permission) {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/hle/kernel/k_address_arbiter.h"
|
#include "core/hle/kernel/k_address_arbiter.h"
|
||||||
|
@ -68,6 +69,20 @@ enum class ProcessActivity : u32 {
|
||||||
Paused,
|
Paused,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class DebugWatchpointType : u8 {
|
||||||
|
None = 0,
|
||||||
|
Read = 1 << 0,
|
||||||
|
Write = 1 << 1,
|
||||||
|
ReadOrWrite = Read | Write,
|
||||||
|
};
|
||||||
|
DECLARE_ENUM_FLAG_OPERATORS(DebugWatchpointType);
|
||||||
|
|
||||||
|
struct DebugWatchpoint {
|
||||||
|
VAddr start_address;
|
||||||
|
VAddr end_address;
|
||||||
|
DebugWatchpointType type;
|
||||||
|
};
|
||||||
|
|
||||||
class KProcess final : public KAutoObjectWithSlabHeapAndContainer<KProcess, KWorkerTask> {
|
class KProcess final : public KAutoObjectWithSlabHeapAndContainer<KProcess, KWorkerTask> {
|
||||||
KERNEL_AUTOOBJECT_TRAITS(KProcess, KSynchronizationObject);
|
KERNEL_AUTOOBJECT_TRAITS(KProcess, KSynchronizationObject);
|
||||||
|
|
||||||
|
@ -374,6 +389,19 @@ public:
|
||||||
// Frees a used TLS slot identified by the given address
|
// Frees a used TLS slot identified by the given address
|
||||||
ResultCode DeleteThreadLocalRegion(VAddr addr);
|
ResultCode DeleteThreadLocalRegion(VAddr addr);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Debug watchpoint management
|
||||||
|
|
||||||
|
// Attempts to insert a watchpoint into a free slot. Returns false if none are available.
|
||||||
|
bool InsertWatchpoint(Core::System& system, VAddr addr, u64 size, DebugWatchpointType type);
|
||||||
|
|
||||||
|
// Attempts to remove the watchpoint specified by the given parameters.
|
||||||
|
bool RemoveWatchpoint(Core::System& system, VAddr addr, u64 size, DebugWatchpointType type);
|
||||||
|
|
||||||
|
const std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>& GetWatchpoints() const {
|
||||||
|
return watchpoints;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void PinThread(s32 core_id, KThread* thread) {
|
void PinThread(s32 core_id, KThread* thread) {
|
||||||
ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
|
ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
|
||||||
|
@ -478,6 +506,8 @@ private:
|
||||||
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> running_threads{};
|
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> running_threads{};
|
||||||
std::array<u64, Core::Hardware::NUM_CPU_CORES> running_thread_idle_counts{};
|
std::array<u64, Core::Hardware::NUM_CPU_CORES> running_thread_idle_counts{};
|
||||||
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> pinned_threads{};
|
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> pinned_threads{};
|
||||||
|
std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS> watchpoints{};
|
||||||
|
std::map<VAddr, u64> debug_page_refcounts;
|
||||||
|
|
||||||
KThread* exception_thread{};
|
KThread* exception_thread{};
|
||||||
|
|
||||||
|
|
|
@ -710,6 +710,7 @@ void KScheduler::Reload(KThread* thread) {
|
||||||
Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
|
Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
|
||||||
cpu_core.LoadContext(thread->GetContext32());
|
cpu_core.LoadContext(thread->GetContext32());
|
||||||
cpu_core.LoadContext(thread->GetContext64());
|
cpu_core.LoadContext(thread->GetContext64());
|
||||||
|
cpu_core.LoadWatchpointArray(thread->GetOwnerProcess()->GetWatchpoints());
|
||||||
cpu_core.SetTlsAddress(thread->GetTLSAddress());
|
cpu_core.SetTlsAddress(thread->GetTLSAddress());
|
||||||
cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0());
|
cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0());
|
||||||
cpu_core.ClearExclusiveState();
|
cpu_core.ClearExclusiveState();
|
||||||
|
|
|
@ -67,6 +67,16 @@ struct Memory::Impl {
|
||||||
return system.DeviceMemory().GetPointer(paddr) + vaddr;
|
return system.DeviceMemory().GetPointer(paddr) + vaddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] u8* GetPointerFromDebugMemory(VAddr vaddr) const {
|
||||||
|
const PAddr paddr{current_page_table->backing_addr[vaddr >> PAGE_BITS]};
|
||||||
|
|
||||||
|
if (paddr == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return system.DeviceMemory().GetPointer(paddr) + vaddr;
|
||||||
|
}
|
||||||
|
|
||||||
u8 Read8(const VAddr addr) {
|
u8 Read8(const VAddr addr) {
|
||||||
return Read<u8>(addr);
|
return Read<u8>(addr);
|
||||||
}
|
}
|
||||||
|
@ -187,6 +197,12 @@ struct Memory::Impl {
|
||||||
on_memory(copy_amount, mem_ptr);
|
on_memory(copy_amount, mem_ptr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Common::PageType::DebugMemory: {
|
||||||
|
DEBUG_ASSERT(pointer);
|
||||||
|
u8* const mem_ptr{GetPointerFromDebugMemory(current_vaddr)};
|
||||||
|
on_memory(copy_amount, mem_ptr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case Common::PageType::RasterizerCachedMemory: {
|
case Common::PageType::RasterizerCachedMemory: {
|
||||||
u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)};
|
u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)};
|
||||||
on_rasterizer(current_vaddr, copy_amount, host_ptr);
|
on_rasterizer(current_vaddr, copy_amount, host_ptr);
|
||||||
|
@ -316,6 +332,58 @@ struct Memory::Impl {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MarkRegionDebug(VAddr vaddr, u64 size, bool debug) {
|
||||||
|
if (vaddr == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over a contiguous CPU address space, marking/unmarking the region.
|
||||||
|
// The region is at a granularity of CPU pages.
|
||||||
|
|
||||||
|
const u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1;
|
||||||
|
for (u64 i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) {
|
||||||
|
const Common::PageType page_type{
|
||||||
|
current_page_table->pointers[vaddr >> PAGE_BITS].Type()};
|
||||||
|
if (debug) {
|
||||||
|
// Switch page type to debug if now debug
|
||||||
|
switch (page_type) {
|
||||||
|
case Common::PageType::Unmapped:
|
||||||
|
ASSERT_MSG(false, "Attempted to mark unmapped pages as debug");
|
||||||
|
break;
|
||||||
|
case Common::PageType::RasterizerCachedMemory:
|
||||||
|
case Common::PageType::DebugMemory:
|
||||||
|
// Page is already marked.
|
||||||
|
break;
|
||||||
|
case Common::PageType::Memory:
|
||||||
|
current_page_table->pointers[vaddr >> PAGE_BITS].Store(
|
||||||
|
nullptr, Common::PageType::DebugMemory);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Switch page type to non-debug if now non-debug
|
||||||
|
switch (page_type) {
|
||||||
|
case Common::PageType::Unmapped:
|
||||||
|
ASSERT_MSG(false, "Attempted to mark unmapped pages as non-debug");
|
||||||
|
break;
|
||||||
|
case Common::PageType::RasterizerCachedMemory:
|
||||||
|
case Common::PageType::Memory:
|
||||||
|
// Don't mess with already non-debug or rasterizer memory.
|
||||||
|
break;
|
||||||
|
case Common::PageType::DebugMemory: {
|
||||||
|
u8* const pointer{GetPointerFromDebugMemory(vaddr & ~PAGE_MASK)};
|
||||||
|
current_page_table->pointers[vaddr >> PAGE_BITS].Store(
|
||||||
|
pointer - (vaddr & ~PAGE_MASK), Common::PageType::Memory);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
|
void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
|
||||||
if (vaddr == 0) {
|
if (vaddr == 0) {
|
||||||
return;
|
return;
|
||||||
|
@ -342,6 +410,7 @@ struct Memory::Impl {
|
||||||
// It is not necessary for a process to have this region mapped into its address
|
// It is not necessary for a process to have this region mapped into its address
|
||||||
// space, for example, a system module need not have a VRAM mapping.
|
// space, for example, a system module need not have a VRAM mapping.
|
||||||
break;
|
break;
|
||||||
|
case Common::PageType::DebugMemory:
|
||||||
case Common::PageType::Memory:
|
case Common::PageType::Memory:
|
||||||
current_page_table->pointers[vaddr >> PAGE_BITS].Store(
|
current_page_table->pointers[vaddr >> PAGE_BITS].Store(
|
||||||
nullptr, Common::PageType::RasterizerCachedMemory);
|
nullptr, Common::PageType::RasterizerCachedMemory);
|
||||||
|
@ -360,6 +429,7 @@ struct Memory::Impl {
|
||||||
// It is not necessary for a process to have this region mapped into its address
|
// It is not necessary for a process to have this region mapped into its address
|
||||||
// space, for example, a system module need not have a VRAM mapping.
|
// space, for example, a system module need not have a VRAM mapping.
|
||||||
break;
|
break;
|
||||||
|
case Common::PageType::DebugMemory:
|
||||||
case Common::PageType::Memory:
|
case Common::PageType::Memory:
|
||||||
// There can be more than one GPU region mapped per CPU region, so it's common
|
// There can be more than one GPU region mapped per CPU region, so it's common
|
||||||
// that this area is already unmarked as cached.
|
// that this area is already unmarked as cached.
|
||||||
|
@ -460,6 +530,8 @@ struct Memory::Impl {
|
||||||
case Common::PageType::Memory:
|
case Common::PageType::Memory:
|
||||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ 0x{:016X}", vaddr);
|
ASSERT_MSG(false, "Mapped memory page without a pointer @ 0x{:016X}", vaddr);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
case Common::PageType::DebugMemory:
|
||||||
|
return GetPointerFromDebugMemory(vaddr);
|
||||||
case Common::PageType::RasterizerCachedMemory: {
|
case Common::PageType::RasterizerCachedMemory: {
|
||||||
u8* const host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)};
|
u8* const host_ptr{GetPointerFromRasterizerCachedMemory(vaddr)};
|
||||||
on_rasterizer();
|
on_rasterizer();
|
||||||
|
@ -591,7 +663,8 @@ bool Memory::IsValidVirtualAddress(const VAddr vaddr) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto [pointer, type] = page_table.pointers[page].PointerType();
|
const auto [pointer, type] = page_table.pointers[page].PointerType();
|
||||||
return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory;
|
return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory ||
|
||||||
|
type == Common::PageType::DebugMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Memory::IsValidVirtualAddressRange(VAddr base, u64 size) const {
|
bool Memory::IsValidVirtualAddressRange(VAddr base, u64 size) const {
|
||||||
|
@ -707,4 +780,8 @@ void Memory::RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
|
||||||
impl->RasterizerMarkRegionCached(vaddr, size, cached);
|
impl->RasterizerMarkRegionCached(vaddr, size, cached);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Memory::MarkRegionDebug(VAddr vaddr, u64 size, bool debug) {
|
||||||
|
impl->MarkRegionDebug(vaddr, size, debug);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Core::Memory
|
} // namespace Core::Memory
|
||||||
|
|
|
@ -446,6 +446,17 @@ public:
|
||||||
*/
|
*/
|
||||||
void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached);
|
void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks each page within the specified address range as debug or non-debug.
|
||||||
|
* Debug addresses are not accessible from fastmem pointers.
|
||||||
|
*
|
||||||
|
* @param vaddr The virtual address indicating the start of the address range.
|
||||||
|
* @param size The size of the address range in bytes.
|
||||||
|
* @param debug Whether or not any pages within the address range should be
|
||||||
|
* marked as debug or non-debug.
|
||||||
|
*/
|
||||||
|
void MarkRegionDebug(VAddr vaddr, u64 size, bool debug);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
|
|
||||||
|
|
Reference in New Issue