2019-08-08 19:38:13 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2019 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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include "software_breakpoints.h"
|
|
|
|
#include "utils.h"
|
2020-01-21 01:27:47 +00:00
|
|
|
#include "guest_memory.h"
|
|
|
|
#include "core_ctx.h"
|
2019-08-08 19:38:13 +00:00
|
|
|
|
|
|
|
SoftwareBreakpointManager g_softwareBreakpointManager = {0};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Consider the following:
|
|
|
|
- Breakpoints are based on VA
|
|
|
|
- Translation tables may change
|
|
|
|
- Translation tables may differ from core to core
|
|
|
|
|
|
|
|
We also define sw breakpoints on invalid addresses (for one or more cores) UNPREDICTABLE.
|
|
|
|
*/
|
|
|
|
|
2020-01-23 22:00:39 +00:00
|
|
|
static size_t findClosestSoftwareBreakpointSlot(uintptr_t address)
|
2019-08-08 19:38:13 +00:00
|
|
|
{
|
|
|
|
if(g_softwareBreakpointManager.numBreakpoints == 0 || address <= g_softwareBreakpointManager.breakpoints[0].address) {
|
|
|
|
return 0;
|
|
|
|
} else if(address > g_softwareBreakpointManager.breakpoints[g_softwareBreakpointManager.numBreakpoints - 1].address) {
|
|
|
|
return g_softwareBreakpointManager.numBreakpoints;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t a = 0, b = g_softwareBreakpointManager.numBreakpoints - 1, m;
|
|
|
|
|
|
|
|
do {
|
|
|
|
m = (a + b) / 2;
|
|
|
|
if(g_softwareBreakpointManager.breakpoints[m].address < address) {
|
|
|
|
a = m;
|
|
|
|
} else if(g_softwareBreakpointManager.breakpoints[m].address > address) {
|
|
|
|
b = m;
|
|
|
|
} else {
|
|
|
|
return m;
|
|
|
|
}
|
|
|
|
} while(b - a > 1);
|
|
|
|
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
2019-08-12 22:14:56 +00:00
|
|
|
static inline bool doApplySoftwareBreakpoint(size_t id)
|
2019-08-08 19:38:13 +00:00
|
|
|
{
|
|
|
|
SoftwareBreakpoint *bp = &g_softwareBreakpointManager.breakpoints[id];
|
2020-02-02 22:55:01 +00:00
|
|
|
u32 brkInst = 0xD4200000 | (bp->uid << 5);
|
2019-08-08 19:38:13 +00:00
|
|
|
|
2020-01-21 01:27:47 +00:00
|
|
|
size_t sz = guestReadWriteMemory(bp->address, 4, &bp->savedInstruction, &brkInst);
|
|
|
|
bp->applied = sz == 4;
|
|
|
|
atomic_store(&bp->triedToApplyOrRevert, true);
|
|
|
|
return sz == 4;
|
2019-08-08 19:38:13 +00:00
|
|
|
}
|
|
|
|
|
2019-08-12 22:14:56 +00:00
|
|
|
static void applySoftwareBreakpointHandler(void *p)
|
|
|
|
{
|
2020-01-21 01:27:47 +00:00
|
|
|
size_t id = *(size_t *)p;
|
|
|
|
if (currentCoreCtx->coreId == currentCoreCtx->executedFunctionSrcCore) {
|
|
|
|
doApplySoftwareBreakpoint(id);
|
|
|
|
__sev();
|
|
|
|
} else {
|
|
|
|
SoftwareBreakpoint *bp = &g_softwareBreakpointManager.breakpoints[id];
|
|
|
|
while (!atomic_load(&bp->triedToApplyOrRevert)) {
|
|
|
|
__wfe();
|
|
|
|
}
|
|
|
|
}
|
2019-08-12 22:14:56 +00:00
|
|
|
}
|
|
|
|
|
2020-01-21 01:27:47 +00:00
|
|
|
static bool applySoftwareBreakpoint(size_t id)
|
2019-08-12 22:14:56 +00:00
|
|
|
{
|
2020-01-21 01:27:47 +00:00
|
|
|
if (g_softwareBreakpointManager.breakpoints[id].applied) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-01-21 22:24:15 +00:00
|
|
|
// This is okay for non-stop mode if sync isn't perfect here
|
2020-01-21 01:27:47 +00:00
|
|
|
atomic_store(&g_softwareBreakpointManager.breakpoints[id].triedToApplyOrRevert, false);
|
2019-08-12 22:14:56 +00:00
|
|
|
executeFunctionOnAllCores(applySoftwareBreakpointHandler, &id, true);
|
2020-01-21 01:27:47 +00:00
|
|
|
atomic_signal_fence(memory_order_seq_cst);
|
|
|
|
return g_softwareBreakpointManager.breakpoints[id].applied;
|
2019-08-12 22:14:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool doRevertSoftwareBreakpoint(size_t id)
|
2019-08-08 19:38:13 +00:00
|
|
|
{
|
|
|
|
SoftwareBreakpoint *bp = &g_softwareBreakpointManager.breakpoints[id];
|
|
|
|
|
2020-01-21 01:27:47 +00:00
|
|
|
size_t sz = guestWriteMemory(bp->address, 4, &bp->savedInstruction);
|
|
|
|
bp->applied = sz != 4;
|
|
|
|
atomic_store(&bp->triedToApplyOrRevert, true);
|
|
|
|
return sz == 4;
|
2019-08-08 19:38:13 +00:00
|
|
|
}
|
|
|
|
|
2019-08-12 22:14:56 +00:00
|
|
|
static void revertSoftwareBreakpointHandler(void *p)
|
|
|
|
{
|
2020-01-21 01:27:47 +00:00
|
|
|
size_t id = *(size_t *)p;
|
|
|
|
if (currentCoreCtx->coreId == currentCoreCtx->executedFunctionSrcCore) {
|
|
|
|
doRevertSoftwareBreakpoint(id);
|
|
|
|
__sev();
|
|
|
|
} else {
|
|
|
|
SoftwareBreakpoint *bp = &g_softwareBreakpointManager.breakpoints[id];
|
|
|
|
while (!atomic_load(&bp->triedToApplyOrRevert)) {
|
|
|
|
__wfe();
|
|
|
|
}
|
|
|
|
}
|
2019-08-12 22:14:56 +00:00
|
|
|
}
|
|
|
|
|
2020-01-21 01:27:47 +00:00
|
|
|
static bool revertSoftwareBreakpoint(size_t id)
|
2019-08-12 22:14:56 +00:00
|
|
|
{
|
2020-01-21 01:27:47 +00:00
|
|
|
if (!g_softwareBreakpointManager.breakpoints[id].applied) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
atomic_store(&g_softwareBreakpointManager.breakpoints[id].triedToApplyOrRevert, false);
|
2019-08-12 22:14:56 +00:00
|
|
|
executeFunctionOnAllCores(revertSoftwareBreakpointHandler, &id, true);
|
2020-01-21 01:27:47 +00:00
|
|
|
atomic_signal_fence(memory_order_seq_cst);
|
|
|
|
return !g_softwareBreakpointManager.breakpoints[id].applied;
|
2019-08-12 22:14:56 +00:00
|
|
|
}
|
2019-08-08 19:38:13 +00:00
|
|
|
|
|
|
|
bool applyAllSoftwareBreakpoints(void)
|
|
|
|
{
|
2019-08-12 22:14:56 +00:00
|
|
|
u64 flags = recursiveSpinlockLockMaskIrq(&g_softwareBreakpointManager.lock);
|
2019-08-08 19:38:13 +00:00
|
|
|
bool ret = true;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < g_softwareBreakpointManager.numBreakpoints; i++) {
|
2019-08-12 22:14:56 +00:00
|
|
|
ret = ret && doApplySoftwareBreakpoint(i); // note: no broadcast intentional
|
2019-08-08 19:38:13 +00:00
|
|
|
}
|
|
|
|
|
2019-08-12 22:14:56 +00:00
|
|
|
recursiveSpinlockUnlockRestoreIrq(&g_softwareBreakpointManager.lock, flags);
|
2019-08-08 19:38:13 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool revertAllSoftwareBreakpoints(void)
|
|
|
|
{
|
2019-08-12 22:14:56 +00:00
|
|
|
u64 flags = recursiveSpinlockLockMaskIrq(&g_softwareBreakpointManager.lock);
|
2019-08-08 19:38:13 +00:00
|
|
|
bool ret = true;
|
|
|
|
for (size_t i = 0; i < g_softwareBreakpointManager.numBreakpoints; i++) {
|
2019-08-12 22:14:56 +00:00
|
|
|
ret = ret && doRevertSoftwareBreakpoint(i); // note: no broadcast intentional
|
2019-08-08 19:38:13 +00:00
|
|
|
}
|
|
|
|
|
2019-08-12 22:14:56 +00:00
|
|
|
recursiveSpinlockUnlockRestoreIrq(&g_softwareBreakpointManager.lock, flags);
|
2019-08-08 19:38:13 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-01-23 22:00:39 +00:00
|
|
|
int addSoftwareBreakpoint(uintptr_t addr, bool persistent)
|
2019-08-08 19:38:13 +00:00
|
|
|
{
|
|
|
|
if ((addr & 3) != 0) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
recursiveSpinlockLock(&g_softwareBreakpointManager.lock);
|
|
|
|
|
|
|
|
size_t id = findClosestSoftwareBreakpointSlot(addr);
|
|
|
|
|
|
|
|
if(id != g_softwareBreakpointManager.numBreakpoints && g_softwareBreakpointManager.breakpoints[id].address == addr) {
|
|
|
|
recursiveSpinlockUnlock(&g_softwareBreakpointManager.lock);
|
|
|
|
return -EEXIST;
|
|
|
|
} else if(g_softwareBreakpointManager.numBreakpoints == MAX_SW_BREAKPOINTS) {
|
|
|
|
recursiveSpinlockUnlock(&g_softwareBreakpointManager.lock);
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(size_t i = g_softwareBreakpointManager.numBreakpoints; i > id && i != 0; i--) {
|
|
|
|
g_softwareBreakpointManager.breakpoints[i] = g_softwareBreakpointManager.breakpoints[i - 1];
|
|
|
|
}
|
|
|
|
++g_softwareBreakpointManager.numBreakpoints;
|
|
|
|
|
|
|
|
SoftwareBreakpoint *bp = &g_softwareBreakpointManager.breakpoints[id];
|
|
|
|
bp->address = addr;
|
|
|
|
bp->persistent = persistent;
|
|
|
|
bp->applied = false;
|
2020-02-02 22:55:01 +00:00
|
|
|
bp->uid = (u16)(0x2000 + g_softwareBreakpointManager.bpUniqueCounter++);
|
2019-08-08 19:38:13 +00:00
|
|
|
|
2020-01-21 01:27:47 +00:00
|
|
|
int rc = applySoftwareBreakpoint(id) ? 0 : -EFAULT;
|
2019-08-08 19:38:13 +00:00
|
|
|
recursiveSpinlockUnlock(&g_softwareBreakpointManager.lock);
|
|
|
|
|
2020-01-21 01:27:47 +00:00
|
|
|
return rc;
|
2019-08-08 19:38:13 +00:00
|
|
|
}
|
|
|
|
|
2020-01-23 22:00:39 +00:00
|
|
|
int removeSoftwareBreakpoint(uintptr_t addr, bool keepPersistent)
|
2019-08-08 19:38:13 +00:00
|
|
|
{
|
|
|
|
if ((addr & 3) != 0) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
recursiveSpinlockLock(&g_softwareBreakpointManager.lock);
|
|
|
|
|
|
|
|
size_t id = findClosestSoftwareBreakpointSlot(addr);
|
2020-01-21 01:27:47 +00:00
|
|
|
bool ok = true;
|
2019-08-08 19:38:13 +00:00
|
|
|
|
|
|
|
if(id == g_softwareBreakpointManager.numBreakpoints || g_softwareBreakpointManager.breakpoints[id].address != addr) {
|
|
|
|
recursiveSpinlockUnlock(&g_softwareBreakpointManager.lock);
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
|
|
|
|
SoftwareBreakpoint *bp = &g_softwareBreakpointManager.breakpoints[id];
|
|
|
|
if (!keepPersistent || !bp->persistent) {
|
2020-01-21 01:27:47 +00:00
|
|
|
ok = revertSoftwareBreakpoint(id);
|
2019-08-08 19:38:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for(size_t i = id; i < g_softwareBreakpointManager.numBreakpoints - 1; i++) {
|
|
|
|
g_softwareBreakpointManager.breakpoints[i] = g_softwareBreakpointManager.breakpoints[i + 1];
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(&g_softwareBreakpointManager.breakpoints[--g_softwareBreakpointManager.numBreakpoints], 0, sizeof(SoftwareBreakpoint));
|
|
|
|
|
|
|
|
recursiveSpinlockUnlock(&g_softwareBreakpointManager.lock);
|
|
|
|
|
2020-01-21 01:27:47 +00:00
|
|
|
return ok ? 0 : -EFAULT;
|
2019-08-08 19:38:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int removeAllSoftwareBreakpoints(bool keepPersistent)
|
|
|
|
{
|
2020-01-21 01:27:47 +00:00
|
|
|
bool ok = true;
|
2019-08-08 19:38:13 +00:00
|
|
|
recursiveSpinlockLock(&g_softwareBreakpointManager.lock);
|
|
|
|
|
|
|
|
for (size_t id = 0; id < g_softwareBreakpointManager.numBreakpoints; id++) {
|
|
|
|
SoftwareBreakpoint *bp = &g_softwareBreakpointManager.breakpoints[id];
|
|
|
|
if (!keepPersistent || !bp->persistent) {
|
2020-01-21 01:27:47 +00:00
|
|
|
ok = ok && revertSoftwareBreakpoint(id);
|
2019-08-08 19:38:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g_softwareBreakpointManager.numBreakpoints = 0;
|
|
|
|
g_softwareBreakpointManager.bpUniqueCounter = 0;
|
|
|
|
memset(g_softwareBreakpointManager.breakpoints, 0, sizeof(g_softwareBreakpointManager.breakpoints));
|
|
|
|
|
|
|
|
recursiveSpinlockUnlock(&g_softwareBreakpointManager.lock);
|
|
|
|
|
2020-01-21 01:27:47 +00:00
|
|
|
return ok ? 0 : -EFAULT;
|
2020-01-09 22:47:22 +00:00
|
|
|
}
|