thermosphere: rewrite debug pause & fix single step state machine

This commit is contained in:
TuxSH 2020-01-16 01:18:34 +00:00
parent 6b8a843ffb
commit 906d6a4f20
13 changed files with 92 additions and 46 deletions

View file

@ -16,7 +16,7 @@
#define EXCEP_STACK_FRAME_SIZE 0x140 #define EXCEP_STACK_FRAME_SIZE 0x140
#define CORECTX_USER_FRAME_OFFSET 0x000 #define CORECTX_GUEST_FRAME_OFFSET 0x000
#define CORECTX_SCRATCH_OFFSET 0x008 #define CORECTX_SCRATCH_OFFSET 0x008
#define CORECTX_CRASH_STACK_OFFSET 0x010 #define CORECTX_CRASH_STACK_OFFSET 0x010

View file

@ -14,7 +14,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <stdatomic.h>
#include "barrier.h" #include "barrier.h"
#include "core_ctx.h" #include "core_ctx.h"
#include "utils.h" #include "utils.h"

View file

@ -15,6 +15,7 @@
*/ */
#pragma once #pragma once
#include <stdatomic.h>
#include <assert.h> #include <assert.h>
#include "utils.h" #include "utils.h"
#include "barrier.h" #include "barrier.h"
@ -22,7 +23,7 @@
struct ExceptionStackFrame; struct ExceptionStackFrame;
typedef struct CoreCtx { typedef struct CoreCtx {
struct ExceptionStackFrame *userFrame; // @0x00 struct ExceptionStackFrame *guestFrame; // @0x00
u64 scratch; // @0x08 u64 scratch; // @0x08
u8 *crashStack; // @0x10 u8 *crashStack; // @0x10
u64 kernelArgument; // @0x18 u64 kernelArgument; // @0x18
@ -42,6 +43,9 @@ typedef struct CoreCtx {
Barrier executedFunctionBarrier; // @0x50 Barrier executedFunctionBarrier; // @0x50
bool executedFunctionSync; // @0x54 bool executedFunctionSync; // @0x54
// Debug features
bool wasPaused; // @0x55
// Cache stuff // Cache stuff
u32 setWayCounter; // @0x58 u32 setWayCounter; // @0x58
} CoreCtx; } CoreCtx;
@ -49,6 +53,7 @@ typedef struct CoreCtx {
static_assert(offsetof(CoreCtx, warmboot) == 0x2E, "Wrong definition for CoreCtx"); static_assert(offsetof(CoreCtx, warmboot) == 0x2E, "Wrong definition for CoreCtx");
static_assert(offsetof(CoreCtx, emulPtimerCval) == 0x38, "Wrong definition for CoreCtx"); static_assert(offsetof(CoreCtx, emulPtimerCval) == 0x38, "Wrong definition for CoreCtx");
static_assert(offsetof(CoreCtx, executedFunctionSync) == 0x54, "Wrong definition for CoreCtx"); static_assert(offsetof(CoreCtx, executedFunctionSync) == 0x54, "Wrong definition for CoreCtx");
static_assert(offsetof(CoreCtx, setWayCounter) == 0x58, "Wrong definition for CoreCtx");
extern CoreCtx g_coreCtxs[4]; extern CoreCtx g_coreCtxs[4];
register CoreCtx *currentCoreCtx asm("x18"); register CoreCtx *currentCoreCtx asm("x18");

View file

@ -14,46 +14,74 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <stdatomic.h>
#include "debug_pause.h" #include "debug_pause.h"
#include "core_ctx.h" #include "core_ctx.h"
#include "irq.h" #include "irq.h"
#include "spinlock.h" #include "spinlock.h"
#include "single_step.h"
// Reminder: use these functions behind a lock // Reminder: use these functions behind a lock
static Barrier g_debugPauseBarrier; static Barrier g_debugPauseBarrier;
static RecursiveSpinlock g_debugPauseContinueLocks[4]; static atomic_uint g_debugPausePausedCoreList;
static atomic_uint g_debugPauseSingleStepCoreList;
void debugPauseSgiTopHalf(void) void debugPauseSgiHandler(void)
{ {
currentCoreCtx->wasPaused = true;
barrierWait(&g_debugPauseBarrier); barrierWait(&g_debugPauseBarrier);
} }
void debugPauseSgiBottomHalf(void) void debugPauseWaitAndUpdateSingleStep(void)
{ {
recursiveSpinlockLock(&g_debugPauseContinueLocks[currentCoreCtx->coreId]); u32 coreId = currentCoreCtx->coreId;
maskIrq(); // <- unlikely race condition here? If it happens, it shouldn't happen more than once/should be fine anyway if (atomic_load(&g_debugPausePausedCoreList) & BIT(coreId)) {
recursiveSpinlockUnlock(&g_debugPauseContinueLocks[currentCoreCtx->coreId]); unmaskIrq();
do {
__wfe();
} while (atomic_load(&g_debugPausePausedCoreList) & BIT(coreId));
maskIrq();
}
currentCoreCtx->wasPaused = false;
// Single-step: if inactive and requested, start single step; cancel if active and not requested
u32 ssReqd = (atomic_load(&g_debugPauseSingleStepCoreList) & ~BIT(currentCoreCtx->coreId)) != 0;
SingleStepState singleStepState = singleStepGetNextState(currentCoreCtx->guestFrame);
if (ssReqd && singleStepState == SingleStepState_Inactive) {
singleStepSetNextState(currentCoreCtx->guestFrame, SingleStepState_ActiveNotPending);
} else if (!ssReqd && singleStepState != SingleStepState_Inactive) {
singleStepSetNextState(currentCoreCtx->guestFrame, SingleStepState_Inactive);
}
} }
void debugPauseCores(u32 coreList) void debugPauseCores(u32 coreList)
{ {
coreList &= ~BIT(currentCoreCtx->coreId); // Since we're using a debugger lock, a simple stlr should be fine...
atomic_store(&g_debugPausePausedCoreList, coreList);
barrierInit(&g_debugPauseBarrier, coreList | BIT(currentCoreCtx->coreId)); if (coreList != BIT(currentCoreCtx->coreId)) {
FOREACH_BIT (tmp, core, coreList) { // We need to notify other cores...
recursiveSpinlockLock(&g_debugPauseContinueLocks[core]); u32 otherCores = coreList & ~BIT(currentCoreCtx->coreId);
} barrierInit(&g_debugPauseBarrier, otherCores | BIT(currentCoreCtx->coreId));
generateSgiForList(ThermosphereSgi_DebugPause, otherCores);
generateSgiForList(ThermosphereSgi_DebugPause, coreList);
barrierWait(&g_debugPauseBarrier); barrierWait(&g_debugPauseBarrier);
} }
void debugUnpauseCores(u32 coreList) if (coreList & BIT(currentCoreCtx->coreId)) {
{ currentCoreCtx->wasPaused = true;
coreList &= ~BIT(currentCoreCtx->coreId); }
}
FOREACH_BIT (tmp, core, coreList) { void debugUnpauseCores(u32 coreList, u32 singleStepList)
recursiveSpinlockUnlock(&g_debugPauseContinueLocks[core]); {
} singleStepList &= coreList;
// Since we're using a debugger lock, a simple stlr should be fine...
atomic_store(&g_debugPauseSingleStepCoreList, singleStepList);
atomic_store(&g_debugPausePausedCoreList, 0);
__sev();
} }

View file

@ -18,14 +18,13 @@
#include "utils.h" #include "utils.h"
void debugPauseSgiTopHalf(void); void debugPauseSgiHandler(void);
void debugPauseSgiBottomHalf(void);
// Hypervisor interrupts will be serviced during the pause-wait
void debugPauseWaitAndUpdateSingleStep(void);
// Note: these functions are not reentrant! (need a global debug lock...) // Note: these functions are not reentrant! (need a global debug lock...)
// These functions also run with interrupts unmasked (but we know we're in our code -- should be safe if we take care)
// while the core is paused.
// "Pause" makes sure all cores reaches the pause function before proceeding. // "Pause" makes sure all cores reaches the pause function before proceeding.
// "Unpause" doesn't synchronize, it just makes sure the core resumes & that "pause" can be called again. // "Unpause" doesn't synchronize, it just makes sure the core resumes & that "pause" can be called again.
void debugPauseCores(u32 coreList); void debugPauseCores(u32 coreList);
void debugUnpauseCores(u32 coreList); void debugUnpauseCores(u32 coreList, u32 singleStepList);

View file

@ -104,7 +104,7 @@ vector_entry \name
.if \type == EXCEPTION_TYPE_GUEST .if \type == EXCEPTION_TYPE_GUEST
ldp x18, xzr, [sp, #EXCEP_STACK_FRAME_SIZE] ldp x18, xzr, [sp, #EXCEP_STACK_FRAME_SIZE]
str x0, [x18, #CORECTX_USER_FRAME_OFFSET] str x0, [x18, #CORECTX_GUEST_FRAME_OFFSET]
mov w1, #1 mov w1, #1
.else .else
mov w1, #0 mov w1, #0

View file

@ -23,7 +23,8 @@
#include "core_ctx.h" #include "core_ctx.h"
#include "single_step.h" #include "single_step.h"
#include "data_abort.h" #include "data_abort.h"
#include "spinlock.h"
#include "debug_pause.h"
#include "timer.h" #include "timer.h"
bool spsrEvaluateConditionCode(u64 spsr, u32 conditionCode) bool spsrEvaluateConditionCode(u64 spsr, u32 conditionCode)
@ -113,10 +114,15 @@ void exceptionEntryPostprocess(ExceptionStackFrame *frame, bool isLowerEl)
// Called on exception return (avoids overflowing a vector section) // Called on exception return (avoids overflowing a vector section)
void exceptionReturnPreprocess(ExceptionStackFrame *frame) void exceptionReturnPreprocess(ExceptionStackFrame *frame)
{ {
if (currentCoreCtx->wasPaused && frame == currentCoreCtx->guestFrame) {
// Were we paused & are we about to return to the guest?
exceptionEnterInterruptibleHypervisorCode(frame);
debugPauseWaitAndUpdateSingleStep();
}
// Update virtual counter // Update virtual counter
currentCoreCtx->totalTimeInHypervisor += timerGetSystemTick() - frame->cntpct_el0; currentCoreCtx->totalTimeInHypervisor += timerGetSystemTick() - frame->cntpct_el0;
SET_SYSREG(cntvoff_el2, currentCoreCtx->totalTimeInHypervisor); SET_SYSREG(cntvoff_el2, currentCoreCtx->totalTimeInHypervisor);
//DEBUG("pct %lu - vct %lu = voff %lu\n", timerGetSystemTick() - GET_SYSREG(cntvct_el0), GET_SYSREG(cntvoff_el2));
// Restore interrupt mask // Restore interrupt mask
SET_SYSREG(cntp_ctl_el0, frame->cntp_ctl_el0); SET_SYSREG(cntp_ctl_el0, frame->cntp_ctl_el0);

View file

@ -237,8 +237,7 @@ void handleIrqException(ExceptionStackFrame *frame, bool isLowerEl, bool isA32)
// Nothing in particular to do here // Nothing in particular to do here
break; break;
case ThermosphereSgi_DebugPause: case ThermosphereSgi_DebugPause:
debugPauseSgiTopHalf(); debugPauseSgiHandler();
hasBottomHalf = true;
break; break;
case GIC_IRQID_MAINTENANCE: case GIC_IRQID_MAINTENANCE:
isMaintenanceInterrupt = true; isMaintenanceInterrupt = true;
@ -280,10 +279,7 @@ void handleIrqException(ExceptionStackFrame *frame, bool isLowerEl, bool isA32)
if (hasBottomHalf) { if (hasBottomHalf) {
exceptionEnterInterruptibleHypervisorCode(frame); exceptionEnterInterruptibleHypervisorCode(frame);
unmaskIrq(); unmaskIrq();
if (transportIface != NULL) {
if (irqId == ThermosphereSgi_DebugPause) {
debugPauseSgiBottomHalf();
} else if (transportIface != NULL) {
transportInterfaceIrqHandlerBottomHalf(transportIface); transportInterfaceIrqHandlerBottomHalf(transportIface);
} }
} }

View file

@ -59,7 +59,7 @@ void testProcessDataCallback(TransportInterface *iface, void *p, size_t sz)
{ {
(void)iface; (void)iface;
(void)sz; (void)sz;
debugUnpauseCores(BIT(0)); debugUnpauseCores(BIT(0), BIT(0));
TestCtx *ctx = (TestCtx *)p; TestCtx *ctx = (TestCtx *)p;
DEBUG("EL2 [core %u]: you typed: %s\n", currentCoreCtx->coreId, ctx->buf); DEBUG("EL2 [core %u]: you typed: %s\n", currentCoreCtx->coreId, ctx->buf);
} }

View file

@ -28,7 +28,7 @@ SingleStepState singleStepGetNextState(ExceptionStackFrame *frame)
if (!mdscrSS) { if (!mdscrSS) {
return SingleStepState_Inactive; return SingleStepState_Inactive;
} else { } else {
return pstateSS ? SingleStepState_ActivePending : SingleStepState_ActiveNotPending; return pstateSS ? SingleStepState_ActiveNotPending : SingleStepState_ActivePending;
} }
} }
@ -41,15 +41,16 @@ void singleStepSetNextState(ExceptionStackFrame *frame, SingleStepState state)
// Unset mdscr_el1.ss // Unset mdscr_el1.ss
mdscr &= ~MDSCR_SS; mdscr &= ~MDSCR_SS;
break; break;
case SingleStepState_ActivePending: case SingleStepState_ActiveNotPending:
// Set mdscr_el1.ss and pstate.ss // Set mdscr_el1.ss and pstate.ss
mdscr |= MDSCR_SS; mdscr |= MDSCR_SS;
frame->spsr_el2 |= PSTATE_SS; frame->spsr_el2 |= PSTATE_SS;
break; break;
case SingleStepState_ActiveNotPending: case SingleStepState_ActivePending:
// We never use this because pstate.ss is 0 by default...
// Set mdscr_el1.ss and unset pstate.ss // Set mdscr_el1.ss and unset pstate.ss
mdscr |= MDSCR_SS; mdscr |= MDSCR_SS;
frame->spsr_el2 |= PSTATE_SS; frame->spsr_el2 &= ~PSTATE_SS;
break; break;
default: default:
break; break;
@ -65,7 +66,4 @@ void handleSingleStep(ExceptionStackFrame *frame, ExceptionSyndromeRegister esr)
singleStepSetNextState(NULL, SingleStepState_Inactive); singleStepSetNextState(NULL, SingleStepState_Inactive);
DEBUG("Single-step exeception ELR = 0x%016llx, ISV = %u, EX = %u\n", frame->elr_el2, (esr.iss >> 24) & 1, (esr.iss >> 6) & 1); DEBUG("Single-step exeception ELR = 0x%016llx, ISV = %u, EX = %u\n", frame->elr_el2, (esr.iss >> 24) & 1, (esr.iss >> 6) & 1);
// Hehe boi
//singleStepSetNextState(frame, SingleStepState_ActivePending);
} }

View file

@ -21,8 +21,8 @@
typedef enum SingleStepState { typedef enum SingleStepState {
SingleStepState_Inactive = 0, // Single step disabled OR in the debugger SingleStepState_Inactive = 0, // Single step disabled OR in the debugger
SingleStepState_ActivePending = 1, // Instruction not yet executed SingleStepState_ActiveNotPending = 1, // Instruction not yet executed
SingleStepState_ActiveNotPending = 2, // Instruction executed, single-step exception is going to be generated soon SingleStepState_ActivePending = 2, // Instruction executed or return-from-trap, single-step exception is going to be generated soon
} SingleStepState; } SingleStepState;
/// Get the single-step state machine state (state after eret) /// Get the single-step state machine state (state after eret)

View file

@ -62,6 +62,21 @@ typedef enum ReadWriteDirection {
DIRECTION_READWRITE = DIRECTION_READ | DIRECTION_WRITE, DIRECTION_READWRITE = DIRECTION_READ | DIRECTION_WRITE,
} ReadWriteDirection; } ReadWriteDirection;
static inline void __wfe(void)
{
__asm__ __volatile__ ("wfe" ::: "memory");
}
static inline void __sev(void)
{
__asm__ __volatile__ ("sev" ::: "memory");
}
static inline void __sevl(void)
{
__asm__ __volatile__ ("sevl" ::: "memory");
}
/* /*
Domains: Domains:
- Inner shareable: typically cores within a cluster (maybe more) with L1+L2 caches - Inner shareable: typically cores within a cluster (maybe more) with L1+L2 caches