/* * 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 . */ #include #include #include #include "debug_manager.h" #include "core_ctx.h" #include "irq.h" #include "spinlock.h" #include "single_step.h" #include "gdb/debug.h" GDBContext g_gdbContext = { 0 }; typedef struct DebugManager { DebugEventInfo debugEventInfos[MAX_CORE]; uintptr_t steppingRangeStartAddrs[MAX_CORE]; uintptr_t steppingRangeEndAddrs[MAX_CORE]; ALIGN(64) atomic_uint pausedCoreList; atomic_uint singleStepCoreList; atomic_uint eventsSentList; Barrier pauseBarrier; atomic_bool reportingEnabled; } DebugManager; static DebugManager g_debugManager = { 0 }; static void debugManagerDoPauseCores(u32 coreList) { __builtin_prefetch(&g_debugManager.pausedCoreList, 1, 0); u32 desiredList = coreList; u32 remainingList = coreList; u32 readList = atomic_load(&g_debugManager.pausedCoreList); do { desiredList |= readList; remainingList &= ~readList; } while (!atomic_compare_exchange_weak(&g_debugManager.pausedCoreList, &readList, desiredList)); if (remainingList & ~BIT(currentCoreCtx->coreId)) { // We need to notify other cores... u32 otherCores = remainingList & ~BIT(currentCoreCtx->coreId); barrierInit(&g_debugManager.pauseBarrier, otherCores | BIT(currentCoreCtx->coreId)); generateSgiForList(ThermosphereSgi_DebugPause, otherCores); barrierWait(&g_debugManager.pauseBarrier); } if (remainingList & BIT(currentCoreCtx->coreId)) { currentCoreCtx->wasPaused = true; } __sev(); } void debugManagerPauseSgiHandler(void) { currentCoreCtx->wasPaused = true; barrierWait(&g_debugManager.pauseBarrier); } void debugManagerInit(TransportInterfaceType gdbIfaceType, u32 gdbIfaceId, u32 gdbIfaceFlags) { memset(&g_debugManager, 0, sizeof(DebugManager)); GDB_InitializeContext(&g_gdbContext, gdbIfaceType, gdbIfaceId, gdbIfaceFlags); GDB_MigrateRxIrq(&g_gdbContext, currentCoreCtx->coreId); } bool debugManagerHandlePause(void) { u32 coreId = currentCoreCtx->coreId; __builtin_prefetch(&g_debugManager.pausedCoreList, 0, 3); if (atomic_load(&g_debugManager.pausedCoreList) & BIT(coreId)) { unmaskIrq(); do { __wfe(); } while (atomic_load(&g_debugManager.pausedCoreList) & BIT(coreId)); maskIrq(); if (!g_debugManager.debugEventInfos[coreId].handled) { // Do we still have an unhandled debug event? GDB_TrySignalDebugEvent(&g_gdbContext, &g_debugManager.debugEventInfos[coreId]); return false; } } currentCoreCtx->wasPaused = false; // Single-step: if inactive and requested, start single step; cancel if active and not requested u32 ssReqd = (atomic_load(&g_debugManager.singleStepCoreList) & BIT(currentCoreCtx->coreId)) != 0; SingleStepState singleStepState = singleStepGetNextState(currentCoreCtx->guestFrame); if (ssReqd) { currentCoreCtx->steppingRangeStartAddr = g_debugManager.steppingRangeStartAddrs[coreId]; currentCoreCtx->steppingRangeEndAddr = g_debugManager.steppingRangeEndAddrs[coreId]; if(singleStepState == SingleStepState_Inactive) { singleStepSetNextState(currentCoreCtx->guestFrame, SingleStepState_ActiveNotPending); } } else if (!ssReqd && singleStepState != SingleStepState_Inactive) { singleStepSetNextState(currentCoreCtx->guestFrame, SingleStepState_Inactive); } return true; } void debugManagerSetReportingEnabled(bool enabled) { atomic_store(&g_debugManager.reportingEnabled, enabled); } bool debugManagerHasDebugEvent(u32 coreId) { bool isPaused = debugManagerIsCorePaused(coreId); return isPaused && g_debugManager.debugEventInfos[coreId].type != DBGEVENT_NONE; } void debugManagerPauseCores(u32 coreList) { u64 flags = maskIrq(); debugManagerDoPauseCores(coreList); restoreInterruptFlags(flags); } void debugManagerSetSingleStepCoreList(u32 coreList) { atomic_store(&g_debugManager.singleStepCoreList, coreList); } void debugManagerUnpauseCores(u32 coreList) { FOREACH_BIT (tmp, coreId, coreList) { if (&g_debugManager.debugEventInfos[coreId].handled) { // Discard already handled debug events g_debugManager.debugEventInfos[coreId].type = DBGEVENT_NONE; } } atomic_fetch_and(&g_debugManager.pausedCoreList, ~coreList); __sev(); } void debugManagerSetSteppingRange(u32 coreId, uintptr_t startAddr, uintptr_t endAddr) { g_debugManager.steppingRangeStartAddrs[coreId] = startAddr; g_debugManager.steppingRangeEndAddrs[coreId] = endAddr; } u32 debugManagerGetPausedCoreList(void) { return atomic_load(&g_debugManager.pausedCoreList); } DebugEventInfo *debugManagerGetDebugEvent(u32 coreId) { return &g_debugManager.debugEventInfos[coreId]; } void debugManagerReportEvent(DebugEventType type, ...) { u64 flags = maskIrq(); bool reportingEnabled = atomic_load(&g_debugManager.reportingEnabled); if (!reportingEnabled && type != DBGEVENT_DEBUGGER_BREAK) { restoreInterruptFlags(flags); return; } u32 coreId = currentCoreCtx->coreId; DebugEventInfo *info = &g_debugManager.debugEventInfos[coreId]; memset(info, 0 , sizeof(DebugEventInfo)); info->type = type; info->coreId = coreId; info->frame = currentCoreCtx->guestFrame; va_list args; va_start(args, type); switch (type) { case DBGEVENT_OUTPUT_STRING: info->outputString.address = va_arg(args, uintptr_t); info->outputString.size = va_arg(args, size_t); break; default: break; } va_end(args); // Now, pause ourselves and try to signal we have a debug event debugManagerDoPauseCores(BIT(coreId)); if (reportingEnabled) { exceptionEnterInterruptibleHypervisorCode(); unmaskIrq(); GDB_TrySignalDebugEvent(&g_gdbContext, info); } restoreInterruptFlags(flags); } void debugManagerBreakCores(u32 coreList) { u32 coreId = currentCoreCtx->coreId; if (coreList & ~BIT(coreId)) { generateSgiForList(ThermosphereSgi_ReportDebuggerBreak, coreList & ~BIT(coreId)); } if ((coreList & BIT(coreId)) && !debugManagerHasDebugEvent(coreId)) { debugManagerReportEvent(DBGEVENT_DEBUGGER_BREAK); } // Wait for all cores __sevl(); do { __wfe(); } while ((atomic_load(&g_debugManager.pausedCoreList) & coreList) != coreList); } void debugManagerContinueCores(u32 coreList) { u32 coreId = currentCoreCtx->coreId; if (coreList & ~BIT(coreId)) { generateSgiForList(ThermosphereSgi_DebuggerContinue, coreList & ~BIT(coreId)); } if (coreList & BIT(coreId) && debugManagerIsCorePaused(coreId)) { debugManagerUnpauseCores(BIT(coreId)); } // Wait for all cores __sevl(); do { __wfe(); } while ((atomic_load(&g_debugManager.pausedCoreList) & coreList) != 0); }