diff --git a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_cpu_system_registers.hpp b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_cpu_system_registers.hpp index 6b0fb93f5..eb858242b 100644 --- a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_cpu_system_registers.hpp +++ b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_cpu_system_registers.hpp @@ -241,6 +241,10 @@ namespace ams::kern::arch::arm64::cpu { constexpr ALWAYS_INLINE size_t GetNumBreakpoints() const { return this->GetBits(12, 4); } + + constexpr ALWAYS_INLINE size_t GetNumContextAwareBreakpoints() const { + return this->GetBits(28, 4); + } }; MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(MonitorDebugSystemControl) { diff --git a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_debug.hpp b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_debug.hpp index 0a1aa42bc..58fa54964 100644 --- a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_debug.hpp +++ b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_debug.hpp @@ -34,6 +34,8 @@ namespace ams::kern::arch::arm64 { virtual ~KDebug() { /* ... */ } static void PostDestroy(uintptr_t arg) { /* ... */ } + public: + static Result SetHardwareBreakPoint(ams::svc::HardwareBreakPointRegisterName name, u64 flags, u64 value); /* TODO: This is a placeholder definition. */ }; diff --git a/libraries/libmesosphere/source/arch/arm64/kern_k_debug.cpp b/libraries/libmesosphere/source/arch/arm64/kern_k_debug.cpp new file mode 100644 index 000000000..034fdce86 --- /dev/null +++ b/libraries/libmesosphere/source/arch/arm64/kern_k_debug.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::kern::arch::arm64 { + + namespace { + + constexpr inline u64 ForbiddenBreakPointFlagsMask = (((1ul << 40) - 1) << 24) | /* Reserved upper bits. */ + (((1ul << 1) - 1) << 23) | /* Match VMID BreakPoint Type. */ + (((1ul << 2) - 1) << 14) | /* Security State Control. */ + (((1ul << 1) - 1) << 13) | /* Hyp Mode Control. */ + (((1ul << 4) - 1) << 9) | /* Reserved middle bits. */ + (((1ul << 2) - 1) << 3) | /* Reserved lower bits. */ + (((1ul << 2) - 1) << 1); /* Privileged Mode Control. */ + + static_assert(ForbiddenBreakPointFlagsMask == 0xFFFFFFFFFF80FE1Eul); + + constexpr inline u64 ForbiddenWatchPointFlagsMask = (((1ul << 32) - 1) << 32) | /* Reserved upper bits. */ + (((1ul << 4) - 1) << 20) | /* WatchPoint Type. */ + (((1ul << 2) - 1) << 14) | /* Security State Control. */ + (((1ul << 1) - 1) << 13) | /* Hyp Mode Control. */ + (((1ul << 2) - 1) << 1); /* Privileged Access Control. */ + + static_assert(ForbiddenWatchPointFlagsMask == 0xFFFFFFFF00F0E006ul); + } + + #define MESOSPHERE_SET_HW_BREAK_POINT(ID, FLAGS, VALUE) \ + ({ \ + cpu::SetDbgBcr##ID##El1(0); \ + cpu::EnsureInstructionConsistency(); \ + cpu::SetDbgBvr##ID##El1(VALUE); \ + cpu::EnsureInstructionConsistency(); \ + cpu::SetDbgBcr##ID##El1(FLAGS); \ + cpu::EnsureInstructionConsistency(); \ + }) + + #define MESOSPHERE_SET_HW_WATCH_POINT(ID, FLAGS, VALUE) \ + ({ \ + cpu::SetDbgWcr##ID##El1(0); \ + cpu::EnsureInstructionConsistency(); \ + cpu::SetDbgWvr##ID##El1(VALUE); \ + cpu::EnsureInstructionConsistency(); \ + cpu::SetDbgWcr##ID##El1(FLAGS); \ + cpu::EnsureInstructionConsistency(); \ + }) + + Result KDebug::SetHardwareBreakPoint(ams::svc::HardwareBreakPointRegisterName name, u64 flags, u64 value) { + /* Get the debug feature register. */ + cpu::DebugFeatureRegisterAccessor dfr0; + + /* Extract interesting info from the debug feature register. */ + const auto num_bp = dfr0.GetNumBreakpoints(); + const auto num_wp = dfr0.GetNumWatchpoints(); + const auto num_ctx = dfr0.GetNumContextAwareBreakpoints(); + + if (ams::svc::HardwareBreakPointRegisterName_I0 <= name && name <= ams::svc::HardwareBreakPointRegisterName_I15) { + /* Check that the name is a valid instruction breakpoint. */ + R_UNLESS((name - ams::svc::HardwareBreakPointRegisterName_I0) <= num_bp, svc::ResultNotSupported()); + + /* We may be getting the process, so prepare a scoped reference holder. */ + KScopedAutoObject process; + + /* Configure flags/value. */ + if ((flags & 1) != 0) { + /* We're enabling the breakpoint. Check that the flags are allowable. */ + R_UNLESS((flags & ForbiddenBreakPointFlagsMask) == 0, svc::ResultInvalidCombination()); + + /* Require that the breakpoint be linked or match context id. */ + R_UNLESS((flags & ((1ul << 21) | (1ul << 20))) != 0, svc::ResultInvalidCombination()); + + /* If the breakpoint matches context id, we need to get the context id. */ + if ((flags & (1ul << 21)) != 0) { + /* Ensure that the breakpoint is context-aware. */ + R_UNLESS((name - ams::svc::HardwareBreakPointRegisterName_I0) <= (num_bp - num_ctx), svc::ResultNotSupported()); + + /* Check that the breakpoint does not have the mismatch bit. */ + R_UNLESS((flags & (1ul << 22)) == 0, svc::ResultInvalidCombination()); + + /* Get the debug object from the current handle table. */ + KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject(static_cast(value)); + R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the process from the debug object. */ + process = debug->GetProcess(); + R_UNLESS(process.IsNotNull(), svc::ResultProcessTerminated()); + + /* Set the value to be the context id. */ + value = process->GetId() & 0xFFFFFFFF; + } + + /* Set the breakpoint as non-secure EL0-only. */ + flags |= (1ul << 14) | (2ul << 1); + } else { + /* We're disabling the breakpoint. */ + flags = 0; + value = 0; + } + + /* Set the breakpoint. */ + switch (name) { + case ams::svc::HardwareBreakPointRegisterName_I0: MESOSPHERE_SET_HW_BREAK_POINT( 0, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I1: MESOSPHERE_SET_HW_BREAK_POINT( 1, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I2: MESOSPHERE_SET_HW_BREAK_POINT( 2, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I3: MESOSPHERE_SET_HW_BREAK_POINT( 3, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I4: MESOSPHERE_SET_HW_BREAK_POINT( 4, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I5: MESOSPHERE_SET_HW_BREAK_POINT( 5, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I6: MESOSPHERE_SET_HW_BREAK_POINT( 6, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I7: MESOSPHERE_SET_HW_BREAK_POINT( 7, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I8: MESOSPHERE_SET_HW_BREAK_POINT( 8, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I9: MESOSPHERE_SET_HW_BREAK_POINT( 9, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I10: MESOSPHERE_SET_HW_BREAK_POINT(10, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I11: MESOSPHERE_SET_HW_BREAK_POINT(11, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I12: MESOSPHERE_SET_HW_BREAK_POINT(12, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I13: MESOSPHERE_SET_HW_BREAK_POINT(13, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I14: MESOSPHERE_SET_HW_BREAK_POINT(14, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I15: MESOSPHERE_SET_HW_BREAK_POINT(15, flags, value); break; + default: break; + } + } else if (ams::svc::HardwareBreakPointRegisterName_D0 <= name && name <= ams::svc::HardwareBreakPointRegisterName_D15) { + /* Check that the name is a valid data breakpoint. */ + R_UNLESS((name - ams::svc::HardwareBreakPointRegisterName_D0) <= num_wp, svc::ResultNotSupported()); + + /* Configure flags/value. */ + if ((flags & 1) != 0) { + /* We're enabling the watchpoint. Check that the flags are allowable. */ + R_UNLESS((flags & ForbiddenWatchPointFlagsMask) == 0, svc::ResultInvalidCombination()); + + /* Set the breakpoint as linked non-secure EL0-only. */ + flags |= (1ul << 20) | (1ul << 14) | (2ul << 1); + } else { + /* We're disabling the watchpoint. */ + flags = 0; + value = 0; + } + + /* Set the watchkpoint. */ + switch (name) { + case ams::svc::HardwareBreakPointRegisterName_D0: MESOSPHERE_SET_HW_WATCH_POINT( 0, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D1: MESOSPHERE_SET_HW_WATCH_POINT( 1, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D2: MESOSPHERE_SET_HW_WATCH_POINT( 2, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D3: MESOSPHERE_SET_HW_WATCH_POINT( 3, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D4: MESOSPHERE_SET_HW_WATCH_POINT( 4, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D5: MESOSPHERE_SET_HW_WATCH_POINT( 5, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D6: MESOSPHERE_SET_HW_WATCH_POINT( 6, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D7: MESOSPHERE_SET_HW_WATCH_POINT( 7, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D8: MESOSPHERE_SET_HW_WATCH_POINT( 8, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D9: MESOSPHERE_SET_HW_WATCH_POINT( 9, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D10: MESOSPHERE_SET_HW_WATCH_POINT(10, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D11: MESOSPHERE_SET_HW_WATCH_POINT(11, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D12: MESOSPHERE_SET_HW_WATCH_POINT(12, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D13: MESOSPHERE_SET_HW_WATCH_POINT(13, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D14: MESOSPHERE_SET_HW_WATCH_POINT(14, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D15: MESOSPHERE_SET_HW_WATCH_POINT(15, flags, value); break; + default: break; + } + } else { + /* Invalid name. */ + return svc::ResultInvalidEnumValue(); + } + + return ResultSuccess(); + } + + #undef MESOSPHERE_SET_HW_WATCH_POINT + #undef MESOSPHERE_SET_HW_BREAK_POINT + +} diff --git a/libraries/libmesosphere/source/svc/kern_svc_debug.cpp b/libraries/libmesosphere/source/svc/kern_svc_debug.cpp index d0235830a..708d2ab01 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_debug.cpp +++ b/libraries/libmesosphere/source/svc/kern_svc_debug.cpp @@ -208,6 +208,16 @@ namespace ams::kern::svc { return ResultSuccess(); } + Result SetHardwareBreakPoint(ams::svc::HardwareBreakPointRegisterName name, uint64_t flags, uint64_t value) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented()); + + /* Set the breakpoint. */ + R_TRY(KDebug::SetHardwareBreakPoint(name, flags, value)); + + return ResultSuccess(); + } + } /* ============================= 64 ABI ============================= */ @@ -257,7 +267,7 @@ namespace ams::kern::svc { } Result SetHardwareBreakPoint64(ams::svc::HardwareBreakPointRegisterName name, uint64_t flags, uint64_t value) { - MESOSPHERE_PANIC("Stubbed SvcSetHardwareBreakPoint64 was called."); + return SetHardwareBreakPoint(name, flags, value); } Result GetDebugThreadParam64(uint64_t *out_64, uint32_t *out_32, ams::svc::Handle debug_handle, uint64_t thread_id, ams::svc::DebugThreadParam param) { @@ -311,7 +321,7 @@ namespace ams::kern::svc { } Result SetHardwareBreakPoint64From32(ams::svc::HardwareBreakPointRegisterName name, uint64_t flags, uint64_t value) { - MESOSPHERE_PANIC("Stubbed SvcSetHardwareBreakPoint64From32 was called."); + return SetHardwareBreakPoint(name, flags, value); } Result GetDebugThreadParam64From32(uint64_t *out_64, uint32_t *out_32, ams::svc::Handle debug_handle, uint64_t thread_id, ams::svc::DebugThreadParam param) {