/* * 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 #include "dmnt2_hardware_watchpoint.hpp" #include "dmnt2_hardware_breakpoint.hpp" #include "dmnt2_debug_process.hpp" #include "dmnt2_debug_log.hpp" namespace ams::dmnt { namespace { Result SetDataBreakPoint(svc::HardwareBreakPointRegisterName reg, svc::HardwareBreakPointRegisterName ctx, u64 address, u64 size, bool read, bool write) { /* Determine lsc. */ const u8 lsc = (read ? 1 : 0) | (write ? 2 : 0); /* Check that the watchpoint is valid. */ if (lsc != 0) { R_UNLESS(HardwareWatchPointManager::IsValidWatchPoint(address, size), svc::ResultInvalidArgument()); } /* Determine bas/mask. */ u8 bas = 0, mask = 0; if (size <= 8) { bas = ((1 << size) - 1) << (address & 7); address = util::AlignDown(address, 8); } else { bas = 0xFF; mask = util::PopCount(size - 1); } /* Build dbgbcr value. */ const u64 dbgbcr = (mask << 24) | (ctx << 16) | (bas << 5) | (lsc << 3) | ((lsc != 0) ? 1 : 0); /* Set the breakpoint. */ const Result result = HardwareBreakPointManager::SetHardwareBreakPoint(reg, dbgbcr, address); if (R_FAILED(result)) { AMS_DMNT2_GDB_LOG_ERROR("SetDataBreakPoint FAIL 0x%08x, reg=%d, address=0x%lx\n", result.GetValue(), reg, address); } return result; } } bool HardwareWatchPointManager::IsValidWatchPoint(u64 address, u64 size) { /* Check size. */ if (size == 0) { AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::IsValidWatchPoint(%lx, %lx) FAIL size == 0\n", address, size); return false; } /* Validate. */ if (size <= 8) { /* Check that address is aligned. */ if (util::AlignDown(address, 8) != util::AlignDown(address + size - 1, 8)) { AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::IsValidWatchPoint(%lx, %lx) FAIL range crosses qword boundary\n", address, size); return false; } } else { /* Check size is small enough. */ if (size > 0x80000000) { AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::IsValidWatchPoint(%lx, %lx) FAIL size too big\n", address, size); return false; } /* Check size is power of two. */ if (!util::IsPowerOfTwo(size)) { AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::IsValidWatchPoint(%lx, %lx) FAIL size not power of two\n", address, size); return false; } /* Check alignment. */ if (!util::IsAligned(address, size)) { AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::IsValidWatchPoint(%lx, %lx) FAIL address not size-aligned\n", address, size); return false; } } return true; } Result WatchPoint::Clear(DebugProcess *debug_process) { Result result = svc::ResultInvalidArgument(); if (m_in_use) { AMS_DMNT2_GDB_LOG_DEBUG("WatchPoint::Clear %p 0x%lx\n", this, m_address); result = SetDataBreakPoint(m_reg, m_ctx, 0, 0, false, false); this->Reset(); } return result; } Result WatchPoint::Set(DebugProcess *debug_process, uintptr_t address, size_t size, bool read, bool write) { /* Set fields. */ m_address = address; m_size = size; m_read = read; m_write = write; /* Set context breakpoint. */ R_TRY(HardwareBreakPointManager::SetContextBreakPoint(m_ctx, debug_process)); /* Set watchpoint. */ R_TRY(SetDataBreakPoint(m_reg, m_ctx, address, size, read, write)); /* Set as in-use. */ m_in_use = true; return ResultSuccess(); } HardwareWatchPointManager::HardwareWatchPointManager(DebugProcess *debug_process) : BreakPointManagerBase(debug_process) { const svc::HardwareBreakPointRegisterName ctx = HardwareBreakPointManager::GetWatchPointContextRegister(); for (size_t i = 0; i < util::size(m_breakpoints); ++i) { m_breakpoints[i].Initialize(static_cast(svc::HardwareBreakPointRegisterName_D0 + i), ctx); } } BreakPointBase *HardwareWatchPointManager::GetBreakPoint(size_t index) { if (index < util::size(m_breakpoints)) { return m_breakpoints + index; } else { return nullptr; } } Result HardwareWatchPointManager::SetWatchPoint(u64 address, u64 size, bool read, bool write) { /* Get a free watchpoint. */ auto *bp = static_cast(this->GetFreeBreakPoint()); R_UNLESS(bp != nullptr, svc::ResultOutOfHandles()); /* Set the watchpoint. */ return bp->Set(m_debug_process, address, size, read, write); } Result HardwareWatchPointManager::GetWatchPointInfo(u64 address, bool &read, bool &write) { /* Find a matching watchpoint. */ for (const auto &bp : m_breakpoints) { if (bp.m_in_use) { if (bp.m_address <= address && address < bp.m_address + bp.m_size) { read = bp.m_read; write = bp.m_write; return ResultSuccess(); } } } /* Otherwise, we failed. */ AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::GetWatchPointInfo FAIL 0x%lx\n", address); read = false; write = false; return svc::ResultInvalidArgument(); } }