From 1eda049ada7d59c2b92de322c1719b2a5ff31908 Mon Sep 17 00:00:00 2001 From: TuxSH <1922548+TuxSH@users.noreply.github.com> Date: Mon, 10 Feb 2020 02:20:48 +0000 Subject: [PATCH] thermosphere: hw breakpoint/watchpoint managers --- .../cpu/hvisor_cpu_debug_register_pair.hpp | 7 +- .../src/hvisor_hw_breakpoint_manager.cpp | 69 ++++++++++ .../src/hvisor_hw_breakpoint_manager.hpp | 43 ++++++ .../src/hvisor_hw_stop_point_manager.cpp | 104 +++++++++++++++ .../src/hvisor_hw_stop_point_manager.hpp | 53 ++++++++ thermosphere/src/hvisor_synchronization.hpp | 6 +- .../src/hvisor_watchpoint_manager.cpp | 126 ++++++++++++++++++ .../src/hvisor_watchpoint_manager.hpp | 43 ++++++ 8 files changed, 445 insertions(+), 6 deletions(-) create mode 100644 thermosphere/src/hvisor_hw_breakpoint_manager.cpp create mode 100644 thermosphere/src/hvisor_hw_breakpoint_manager.hpp create mode 100644 thermosphere/src/hvisor_hw_stop_point_manager.cpp create mode 100644 thermosphere/src/hvisor_hw_stop_point_manager.hpp create mode 100644 thermosphere/src/hvisor_watchpoint_manager.cpp create mode 100644 thermosphere/src/hvisor_watchpoint_manager.hpp diff --git a/thermosphere/src/cpu/hvisor_cpu_debug_register_pair.hpp b/thermosphere/src/cpu/hvisor_cpu_debug_register_pair.hpp index 9c0d6dafc..1595d195d 100644 --- a/thermosphere/src/cpu/hvisor_cpu_debug_register_pair.hpp +++ b/thermosphere/src/cpu/hvisor_cpu_debug_register_pair.hpp @@ -63,9 +63,10 @@ namespace ams::hvisor::cpu { // Watchpoints only enum LoadStoreControl { - Load = 1, - Store = 2, - LoadStore = 3, + NotAWatchpoint = 0, + Load = 1, + Store = 2, + LoadStore = 3, }; // bas only 4 bits for breakpoints, other bits res0. diff --git a/thermosphere/src/hvisor_hw_breakpoint_manager.cpp b/thermosphere/src/hvisor_hw_breakpoint_manager.cpp new file mode 100644 index 000000000..f26acf5c5 --- /dev/null +++ b/thermosphere/src/hvisor_hw_breakpoint_manager.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019-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 "hvisor_hw_breakpoint_manager.hpp" +#include "cpu/hvisor_cpu_instructions.hpp" + +#define _REENT_ONLY +#include + +namespace ams::hvisor { + + HwBreakpointManager HwBreakpointManager::instance{}; + + void HwBreakpointManager::ReloadOnAllCoresSgiHandler() + { + // TODO + } + + void HwBreakpointManager::ReloadOnAllCores() const + { + cpu::dmb(); + // TODO + } + + bool HwBreakpointManager::FindPredicate(const cpu::DebugRegisterPair &pair, uintptr_t addr, size_t, cpu::DebugRegisterPair::LoadStoreControl) const + { + return pair.vr == addr; + } + + // Note: A32/T32/T16 support intentionnally left out + // Note: addresses are supposed to be well-formed regarding the sign extension bits + int HwBreakpointManager::Add(uintptr_t addr) + { + // Reject misaligned addresses + if (addr & 3) { + return -EINVAL; + } + + cpu::DebugRegisterPair bp{}; + bp.cr.bt = cpu::DebugRegisterPair::AddressMatch; + bp.cr.bas = 0xF; // mandated + bp.vr = addr; + + return AddImpl(addr, 0, bp); + } + + int HwBreakpointManager::Remove(uintptr_t addr) + { + // Reject misaligned addresses + if (addr & 3) { + return -EINVAL; + } + + return RemoveImpl(addr, 0, cpu::DebugRegisterPair::NotAWatchpoint); + } +} diff --git a/thermosphere/src/hvisor_hw_breakpoint_manager.hpp b/thermosphere/src/hvisor_hw_breakpoint_manager.hpp new file mode 100644 index 000000000..b24fcc464 --- /dev/null +++ b/thermosphere/src/hvisor_hw_breakpoint_manager.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019-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 . + */ + +#pragma once + +#include "hvisor_hw_stop_point_manager.hpp" + +namespace ams::hvisor { + + class HwBreakpointManager final : public HwStopPointManager { + protected: + virtual bool FindPredicate(const cpu::DebugRegisterPair &pair, uintptr_t addr, size_t, cpu::DebugRegisterPair::LoadStoreControl) const; + + private: + static HwBreakpointManager instance; + + public: + virtual void ReloadOnAllCores() const; + static void ReloadOnAllCoresSgiHandler(); + + cpu::DebugRegisterPair RetrieveWatchpointConfig(uintptr_t addr, cpu::DebugRegisterPair::LoadStoreControl direction) const; + int Add(uintptr_t addr); + int Remove(uintptr_t addr); + + HwBreakpointManager &GetInstance() { return instance; } + + public: + constexpr HwBreakpointManager() : HwStopPointManager(MAX_BCR) {} + }; +} diff --git a/thermosphere/src/hvisor_hw_stop_point_manager.cpp b/thermosphere/src/hvisor_hw_stop_point_manager.cpp new file mode 100644 index 000000000..7c501de88 --- /dev/null +++ b/thermosphere/src/hvisor_hw_stop_point_manager.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2019-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 "hvisor_hw_stop_point_manager.hpp" +#include + +#define _REENT_ONLY +#include + +namespace ams::hvisor { + + cpu::DebugRegisterPair *HwStopPointManager::Allocate() + { + size_t pos = __builtin_ffs(m_freeBitmap); + if (pos == 0) { + return nullptr; + } else { + m_freeBitmap &= ~BIT(pos - 1); + m_usedBitmap |= BIT(pos - 1); + return &m_stopPoints[pos - 1]; + } + } + + void HwStopPointManager::Free(size_t pos) + { + m_stopPoints[pos] = {}; + m_freeBitmap |= BIT(pos); + m_usedBitmap &= ~BIT(pos); + } + + const cpu::DebugRegisterPair *HwStopPointManager::Find(uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl dir) const + { + for (auto bit: util::BitsOf{m_usedBitmap}) { + auto *p = &m_stopPoints[bit]; + if (FindPredicate(*p, addr, size, dir)) { + return p; + } + } + + return nullptr; + } + + int HwStopPointManager::AddImpl(uintptr_t addr, size_t size, cpu::DebugRegisterPair preconfiguredPair) + { + std::scoped_lock lk{m_lock}; + auto lsc = preconfiguredPair.cr.lsc; + + if (m_freeBitmap == 0) { + // Oops + return -EBUSY; + } + + if (Find(addr, size, lsc) != nullptr) { + // Already exists + return -EEXIST; + } + + auto *regs = Allocate(); + regs->SetDefaults(); + + // Apply preconfig + regs->cr.raw |= preconfiguredPair.cr.raw; + regs->vr = preconfiguredPair.vr; + regs->cr.enabled = true; + + ReloadOnAllCores(); + return 0; + } + + int HwStopPointManager::RemoveImpl(uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl dir) + { + std::scoped_lock lk{m_lock}; + const auto *p = Find(addr, size, dir); + if (p == nullptr) { + return -ENOENT; + } + + Free(p - m_stopPoints.data()); + return 0; + } + + void HwStopPointManager::RemoveAll() + { + std::scoped_lock lk{m_lock}; + m_freeBitmap |= m_usedBitmap; + m_usedBitmap = 0; + std::fill(m_stopPoints.begin(), m_stopPoints.end(), cpu::DebugRegisterPair{}); + ReloadOnAllCores(); + } + +} diff --git a/thermosphere/src/hvisor_hw_stop_point_manager.hpp b/thermosphere/src/hvisor_hw_stop_point_manager.hpp new file mode 100644 index 000000000..350181298 --- /dev/null +++ b/thermosphere/src/hvisor_hw_stop_point_manager.hpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019-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 . + */ + +#pragma once + +#include "defines.hpp" +#include "hvisor_synchronization.hpp" +#include "cpu/hvisor_cpu_debug_register_pair.hpp" + +namespace ams::hvisor { + + class HwStopPointManager { + NON_COPYABLE(HwStopPointManager); + NON_MOVEABLE(HwStopPointManager); + protected: + static constexpr size_t maxStopPoints = std::max(MAX_BCR, MAX_WCR); + + mutable RecursiveSpinlock m_lock{}; + u16 m_freeBitmap; + u16 m_usedBitmap = 0; + std::array m_stopPoints{}; + + protected: + cpu::DebugRegisterPair *Allocate(); + void Free(size_t pos); + const cpu::DebugRegisterPair *Find(uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl dir) const; + + virtual bool FindPredicate(const cpu::DebugRegisterPair &pair, uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl direction) const = 0; + + int AddImpl(uintptr_t addr, size_t size, cpu::DebugRegisterPair preconfiguredPair); + int RemoveImpl(uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl direction); + + protected: + constexpr HwStopPointManager(size_t numStopPoints) : m_freeBitmap(MASK(numStopPoints)) {} + + public: + virtual void ReloadOnAllCores() const = 0; + void RemoveAll(); + }; +} diff --git a/thermosphere/src/hvisor_synchronization.hpp b/thermosphere/src/hvisor_synchronization.hpp index d87ee207d..8f3a91581 100644 --- a/thermosphere/src/hvisor_synchronization.hpp +++ b/thermosphere/src/hvisor_synchronization.hpp @@ -27,7 +27,7 @@ namespace ams::hvisor { private: u32 m_val = 0; public: - Spinlock() = default; + constexpr Spinlock() = default; void lock(); void unlock() noexcept; }; @@ -38,7 +38,7 @@ namespace ams::hvisor { private: u32 m_val = 0; public: - Barrier() = default; + constexpr Barrier() = default; void Join(); constexpr void Reset(u32 val) @@ -55,7 +55,7 @@ namespace ams::hvisor { u32 m_tag = 0; u32 m_count = 0; public: - RecursiveSpinlock() = default; + constexpr RecursiveSpinlock() = default; void lock(); void unlock() noexcept; }; diff --git a/thermosphere/src/hvisor_watchpoint_manager.cpp b/thermosphere/src/hvisor_watchpoint_manager.cpp new file mode 100644 index 000000000..7caa24d9b --- /dev/null +++ b/thermosphere/src/hvisor_watchpoint_manager.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2019-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 "hvisor_watchpoint_manager.hpp" +#include "cpu/hvisor_cpu_instructions.hpp" +#include + +#define _REENT_ONLY +#include + +namespace { + constexpr bool IsRangeMaskWatchpoint(uintptr_t addr, size_t size) + { + // size needs to be a power of 2, at least 8 (we'll only allow 16+ though), addr needs to be aligned. + bool ret = (size & (size - 1)) == 0 && size >= 16 && (addr & (size - 1)) == 0; + return ret; + } + + constexpr bool CheckWatchpointAddressAndSizeParams(uintptr_t addr, size_t size) + { + if (size == 0) { + return false; + } else if (size > 8) { + return IsRangeMaskWatchpoint(addr, size); + } else { + return ((addr + size) & ~7ul) == (addr & ~7ul); + } + } +} + +namespace ams::hvisor { + + WatchpointManager WatchpointManager::instance{}; + + void WatchpointManager::ReloadOnAllCoresSgiHandler() + { + // TODO + } + + void WatchpointManager::ReloadOnAllCores() const + { + cpu::dmb(); + // TODO + } + + bool WatchpointManager::FindPredicate(const cpu::DebugRegisterPair &pair, uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl direction) const + { + size_t off; + size_t sz; + size_t nmask; + + if (pair.cr.mask != 0) { + off = 0; + sz = MASK(pair.cr.mask); + nmask = ~sz; + } else { + off = __builtin_ffs(pair.cr.bas) - 1; + sz = __builtin_popcount(pair.cr.bas); + nmask = ~7ul; + } + + if (size != 0) { + // Strict watchpoint check + if (addr == pair.vr + off && direction == pair.cr.lsc && sz == size) { + return true; + } + } else { + // Return first wp that could have triggered the exception + if ((addr & nmask) == pair.vr && (direction & pair.cr.lsc) != 0) { + return true; + } + } + + return false; + } + + cpu::DebugRegisterPair WatchpointManager::RetrieveWatchpointConfig(uintptr_t addr, cpu::DebugRegisterPair::LoadStoreControl direction) const + { + std::scoped_lock lk{m_lock}; + const cpu::DebugRegisterPair *p = Find(addr, 0, direction); + return p != nullptr ? *p : cpu::DebugRegisterPair{}; + } + + int WatchpointManager::Add(uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl direction) + { + if (!CheckWatchpointAddressAndSizeParams(addr, size)) { + return -EINVAL; + } + + cpu::DebugRegisterPair wp{}; + wp.cr.lsc = direction; + if (IsRangeMaskWatchpoint(addr, size)) { + wp.vr = addr; + wp.cr.bas = 0xFF; // TRM-mandated + wp.cr.mask = static_cast(__builtin_ffsl(size) - 1); + } else { + size_t off = addr & 7ull; + wp.vr = addr & ~7ul; + wp.cr.bas = MASK2(off + size, off); + } + + return AddImpl(addr, size, wp); + } + + int WatchpointManager::Remove(uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl direction) + { + if (!CheckWatchpointAddressAndSizeParams(addr, size)) { + return -EINVAL; + } + + return RemoveImpl(addr, size, direction); + } +} diff --git a/thermosphere/src/hvisor_watchpoint_manager.hpp b/thermosphere/src/hvisor_watchpoint_manager.hpp new file mode 100644 index 000000000..4cbaac6d1 --- /dev/null +++ b/thermosphere/src/hvisor_watchpoint_manager.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019-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 . + */ + +#pragma once + +#include "hvisor_hw_stop_point_manager.hpp" + +namespace ams::hvisor { + + class WatchpointManager final : public HwStopPointManager { + protected: + virtual bool FindPredicate(const cpu::DebugRegisterPair &pair, uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl direction) const; + + private: + static WatchpointManager instance; + + public: + virtual void ReloadOnAllCores() const; + static void ReloadOnAllCoresSgiHandler(); + + cpu::DebugRegisterPair RetrieveWatchpointConfig(uintptr_t addr, cpu::DebugRegisterPair::LoadStoreControl direction) const; + int Add(uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl direction); + int Remove(uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl direction); + + WatchpointManager &GetInstance() { return instance; } + + public: + constexpr WatchpointManager() : HwStopPointManager(MAX_WCR) {} + }; +}