From cbf3b305ca21415cdbfd46a94e3c1f44de4b615e Mon Sep 17 00:00:00 2001 From: TuxSH <1922548+TuxSH@users.noreply.github.com> Date: Wed, 29 Jan 2020 01:19:38 +0000 Subject: [PATCH] thermosphere: gdb add break & vCont handling --- thermosphere/src/debug_manager.c | 16 +++ thermosphere/src/debug_manager.h | 5 + thermosphere/src/gdb/context.h | 4 +- thermosphere/src/gdb/debug.c | 214 +++++++++++++++++++++---------- thermosphere/src/gdb/debug.h | 4 +- thermosphere/src/gdb/thread.c | 18 ++- thermosphere/src/gdb/verbose.c | 14 +- thermosphere/src/irq.c | 13 +- thermosphere/src/irq.h | 1 + thermosphere/src/start.s | 1 + 10 files changed, 204 insertions(+), 86 deletions(-) diff --git a/thermosphere/src/debug_manager.c b/thermosphere/src/debug_manager.c index dc4a091fb..262cd6fd7 100644 --- a/thermosphere/src/debug_manager.c +++ b/thermosphere/src/debug_manager.c @@ -151,6 +151,11 @@ const DebugEventInfo *debugManagerMarkAndGetCoreDebugEvent(u32 coreId) return &g_debugManager.debugEventInfos[coreId]; } +const DebugEventInfo *debugManagerGetCoreDebugEvent(u32 coreId) +{ + return &g_debugManager.debugEventInfos[coreId]; +} + void debugManagerReportEvent(DebugEventType type, ...) { u64 flags = maskIrq(); @@ -186,3 +191,14 @@ void debugManagerReportEvent(DebugEventType type, ...) restoreInterruptFlags(flags); } + +void debugManagerBreakCores(u32 coreList) +{ + u32 coreId = currentCoreCtx->coreId; + if (coreList & ~BIT(coreId)) { + generateSgiForList(ThermosphereSgi_ReportDebuggerBreak, coreList & ~BIT(coreId)); + } + if (coreList & BIT(coreId)) { + debugManagerReportEvent(DBGEVENT_DEBUGGER_BREAK); + } +} diff --git a/thermosphere/src/debug_manager.h b/thermosphere/src/debug_manager.h index dab24e9de..7c300c13e 100644 --- a/thermosphere/src/debug_manager.h +++ b/thermosphere/src/debug_manager.h @@ -62,6 +62,11 @@ void debugManagerSetSteppingRange(u32 coreId, uintptr_t startAddr, uintptr_t end u32 debugManagerGetPausedCoreList(void); const DebugEventInfo *debugManagerMarkAndGetCoreDebugEvent(u32 coreId); +const DebugEventInfo *debugManagerGetCoreDebugEvent(u32 coreId); + +void debugManagerReportEvent(DebugEventType type, ...); + +void debugManagerBreakCores(u32 coreList); static inline bool debugManagerIsCorePaused(u32 coreId) { diff --git a/thermosphere/src/gdb/context.h b/thermosphere/src/gdb/context.h index ba05cddcd..d0140ad83 100644 --- a/thermosphere/src/gdb/context.h +++ b/thermosphere/src/gdb/context.h @@ -74,8 +74,8 @@ typedef struct GDBContext { u32 attachedCoreList; u32 currentThreadId; - u32 selectedThreadId; - u32 selectedThreadIdForContinuing; + int selectedThreadId; + int selectedThreadIdForContinuing; u32 sentDebugEventCoreList; diff --git a/thermosphere/src/gdb/debug.c b/thermosphere/src/gdb/debug.c index 10d55f0f7..f35155b0a 100644 --- a/thermosphere/src/gdb/debug.c +++ b/thermosphere/src/gdb/debug.c @@ -235,6 +235,15 @@ int GDB_TrySignalDebugEvent(GDBContext *ctx, DebugEventInfo *info) return ret; } +void GDB_BreakAllCores(GDBContext *ctx) +{ + if (ctx->flags & GDB_FLAG_NONSTOP) { + debugManagerBreakCores(ctx->attachedCoreList); + } else { + debugManagerBreakCores(BIT(currentCoreCtx->coreId)); + } +} + GDB_DECLARE_VERBOSE_HANDLER(Stopped) { u32 coreList = debugManagerGetPausedCoreList() & ctx->attachedCoreList; @@ -282,102 +291,175 @@ GDB_DECLARE_HANDLER(Kill) return 0; } -GDB_DECLARE_HANDLER(Break) +GDB_DECLARE_VERBOSE_HANDLER(CtrlC) { - // TODO - if(!(ctx->flags & GDB_FLAG_CONTINUING)) // Is this ever reached? - return GDB_SendPacket(ctx, "S02", 3); - else - { - ctx->flags &= ~GDB_FLAG_CONTINUING; - return 0; - } + int ret = GDB_ReplyOk(ctx); + GDB_BreakAllCores(ctx); } -void GDB_ContinueExecution(GDBContext *ctx) +GDB_DECLARE_HANDLER(ContinueOrStepDeprecated) { - ctx->selectedThreadId = ctx->selectedThreadIdForContinuing = 0; - svcContinueDebugEvent(ctx->debug, ctx->continueFlags); - ctx->flags |= GDB_FLAG_CONTINUING; -} - -GDB_DECLARE_HANDLER(Continue) -{ - // TODO char *addrStart = NULL; - u32 addr = 0; + uintptr_t addr = 0; - if(ctx->selectedThreadIdForContinuing != 0 && ctx->selectedThreadIdForContinuing != ctx->currentThreadId) - return 0; + char cmd = ctx->commandData[-1]; - if(ctx->commandData[-1] == 'C') - { - if(ctx->commandData[0] == 0 || ctx->commandData[1] == 0 || (ctx->commandData[2] != 0 && ctx->commandData[2] == ';')) + // This deprecated command should not be permitted in non-stop mode + if (ctx->flags & GDB_FLAG_NONSTOP) { + return GDB_ReplyErrno(ctx, EPERM); + } + + if(cmd == 'C' || cmd == 'S') { + // Check the presence of the two-digit signature, even if we ignore it. + u8 sg; + if (GDB_DecodeHex(&sg, ctx->commandData, 1) != 1) { return GDB_ReplyErrno(ctx, EILSEQ); + } - // Signal ignored... + // Check: [;addr] or [nothing] + if (ctx->commandData[2] != 0 && ctx->commandData[2] != ';') { + return GDB_ReplyErrno(ctx, EILSEQ); + } - if(ctx->commandData[2] == ';') + if(ctx->commandData[2] == ';') { addrStart = ctx->commandData + 3; + } } - else - { - if(ctx->commandData[0] != 0) + else { + // 'c', 's' + if (ctx->commandData[0] != 0) { addrStart = ctx->commandData; - } - - if(addrStart != NULL && ctx->currentThreadId != 0) - { - ThreadContext regs; - if(GDB_ParseHexIntegerList(&addr, ctx->commandData + 3, 1, 0) == NULL) - return GDB_ReplyErrno(ctx, EILSEQ); - - Result r = svcGetDebugThreadContext(®s, ctx->debug, ctx->currentThreadId, THREADCONTEXT_CONTROL_CPU_SPRS); - if(R_SUCCEEDED(r)) - { - regs.cpu_registers.pc = addr; - r = svcSetDebugThreadContext(ctx->debug, ctx->currentThreadId, ®s, THREADCONTEXT_CONTROL_CPU_SPRS); } } - GDB_ContinueExecution(ctx); + // Only support the simplest form, with no address + // Only degenerate clients will use ;addr, anyway (and the packets are deprecated in favor + // of vCont anyway) + + if (addrStart != NULL) { + return GDB_ReplyErrno(ctx, ENOSYS); + } + + u32 coreList = ctx->selectedThreadIdForContinuing == -1 ? ctx->attachedCoreList : BIT(ctx->selectedThreadIdForContinuing); + u32 ssMask = (cmd == 's' || cmd == 'S') ? coreList : 0; + + FOREACH_BIT (tmp, coreId, ssMask) { + debugManagerSetSteppingRange(coreId, 0, 0); + } + + debugManagerUnpauseCores(coreList, ssMask); return 0; } GDB_DECLARE_VERBOSE_HANDLER(Continue) { - // TODO - char *pos = ctx->commandData; - bool currentThreadFound = false; - while(pos != NULL && *pos != 0 && !currentThreadFound) - { - if(*pos != 'c' && *pos != 'C') - return GDB_ReplyErrno(ctx, EPERM); + u32 parsedCoreList = 0; + u32 continueCoreList = 0; + u32 stepCoreList = 0; + u32 stopCoreList = 0; - pos += *pos == 'C' ? 3 : 1; + char *cmd = ctx->commandData; - if(*pos++ != ':') // default action found - { - currentThreadFound = true; - break; + while (cmd != NULL) { + char *nextCmd; + char *threadIdPart; + int threadId; + u32 curMask = 0; + char *cmdEnd; + + // It it always fine if we set the single-stepping range to 0,0 by default + // Because the fields we set are the shadow fields copied to the real fields after debug unpause + uintptr_t ssStartAddr = 0; + uintptr_t ssEndAddr = 0; + + // Locate next command, replace delimiter by NUL + nextCmd = strchr(cmd, ';'); + if (nextCmd != NULL && *nextCmd == ';') { + *nextCmd++ = 0; } - char *nextpos = (char *)strchr(pos, ';'); - if(strncmp(pos, "-1", 2) == 0) - currentThreadFound = true; - else - { - u32 threadId; - if(GDB_ParseHexIntegerList(&threadId, pos, 1, ';') == NULL) + // Locate thread-id part, parse thread id + threadIdPart = strchr(cmd, ':'); + if (threadIdPart == NULL || strcmp(threadIdPart, "-1") == 0) { + // Default action... + threadId = -1; + curMask = ctx->attachedCoreList; + } else { + unsigned long id; + if(GDB_ParseHexIntegerList(&id, ctx->commandData + 1, 1, 0) == NULL) { return GDB_ReplyErrno(ctx, EILSEQ); - currentThreadFound = currentThreadFound || threadId == ctx->currentThreadId; + } else if (id >= MAX_CORE + 1) { + return GDB_ReplyErrno(ctx, EINVAL); + } + + threadId = id == 0 ? (int)currentCoreCtx->coreId : (int)id; + curMask = BIT(threadId - 1) & ctx->attachedCoreList; } - pos = nextpos; + // Parse the command itself + // Note that we may already have handled that thread in a previous command + curMask &= ~parsedCoreList; + switch (cmd[0]) { + case 'S': + case 'C': { + // Check the presence of the two-digit signature, even if we ignore it. + u8 sg; + if (GDB_DecodeHex(&sg, ctx->commandData, 1) != 1) { + return GDB_ReplyErrno(ctx, EILSEQ); + } + stepCoreList |= cmd[0] == 'S' ? curMask : 0; + continueCoreList |= curMask; + cmdEnd = cmd + 3; + break; + } + case 's': + stepCoreList |= curMask; + continueCoreList |= curMask; + cmdEnd = cmd + 1; + break; + case 'c': + continueCoreList |= curMask; + cmdEnd = cmd + 1; + break; + case 't': + stopCoreList |= curMask; + cmdEnd = cmd + 1; + break; + case 'r': { + // Range step + unsigned long tmp[2]; + cmdEnd = GDB_ParseHexIntegerList(tmp, cmd, 2, 0); + if (cmdEnd == NULL) { + return GDB_ReplyErrno(ctx, EILSEQ); + } + + ssStartAddr = tmp[0]; + ssEndAddr = tmp[1]; + stepCoreList |= curMask; + continueCoreList |= curMask; + break; + } + + default: + return GDB_ReplyErrno(ctx, EILSEQ); + } + + if (*cmdEnd != 0) { + // We've got garbage data... + return GDB_ReplyErrno(ctx, EILSEQ); + } + + FOREACH_BIT (tmp, t, curMask) { + // Set/unset stepping range for all threads affected by this command + debugManagerSetSteppingRange(t - 1, ssStartAddr, ssEndAddr); + } + + parsedCoreList |= curMask; + cmd = nextCmd; } - if(ctx->currentThreadId == 0 || currentThreadFound) - GDB_ContinueExecution(ctx); + debugManagerBreakCores(stopCoreList); + debugManagerUnpauseCores(continueCoreList, stepCoreList); return 0; } diff --git a/thermosphere/src/gdb/debug.h b/thermosphere/src/gdb/debug.h index cc99f5ac0..801a6d9b4 100644 --- a/thermosphere/src/gdb/debug.h +++ b/thermosphere/src/gdb/debug.h @@ -19,8 +19,8 @@ GDB_DECLARE_VERBOSE_HANDLER(Stopped); GDB_DECLARE_HANDLER(Detach); GDB_DECLARE_HANDLER(Kill); -GDB_DECLARE_HANDLER(Break); -GDB_DECLARE_HANDLER(Continue); +GDB_DECLARE_VERBOSE_HANDLER(CtrlC); +GDB_DECLARE_HANDLER(ContinueOrStepDeprecated); GDB_DECLARE_VERBOSE_HANDLER(Continue); GDB_DECLARE_HANDLER(GetStopReason); diff --git a/thermosphere/src/gdb/thread.c b/thermosphere/src/gdb/thread.c index f80221cab..20062bfe1 100644 --- a/thermosphere/src/gdb/thread.c +++ b/thermosphere/src/gdb/thread.c @@ -13,27 +13,33 @@ GDB_DECLARE_HANDLER(SetThreadId) { + // Id = 0 means any thread if (ctx->commandData[0] == 'g') { - if(strncmp(ctx->commandData + 1, "-1", 2) == 0) { + if(strcmp(ctx->commandData + 1, "-1") == 0) { return GDB_ReplyErrno(ctx, EINVAL); } unsigned long id; if (GDB_ParseHexIntegerList(&id, ctx->commandData + 1, 1, 0) == NULL) { return GDB_ReplyErrno(ctx, EILSEQ); + } else if (id >= MAX_CORE + 1) { + return GDB_ReplyErrno(ctx, EINVAL); } - ctx->selectedThreadId = id; + ctx->selectedThreadId = id == 0 ? (int)currentCoreCtx->coreId + 1 : (int)id; // TODO: change irq affinity (and remove selectedThreadId?) return GDB_ReplyOk(ctx); } else if (ctx->commandData[0] == 'c') { - if(strncmp(ctx->commandData + 1, "-1", 2) == 0) { - ctx->selectedThreadIdForContinuing = 0; + if(strcmp(ctx->commandData + 1, "-1") == 0) { + ctx->selectedThreadIdForContinuing = -1; } else { unsigned long id; - if(GDB_ParseHexIntegerList(&id, ctx->commandData + 1, 1, 0) == NULL) + if (GDB_ParseHexIntegerList(&id, ctx->commandData + 1, 1, 0) == NULL) { return GDB_ReplyErrno(ctx, EILSEQ); - ctx->selectedThreadIdForContinuing = id; + } else if (id >= MAX_CORE + 1) { + return GDB_ReplyErrno(ctx, EINVAL); + } + ctx->selectedThreadIdForContinuing = id == 0 ? (int)currentCoreCtx->coreId + 1 : (int)id; } return GDB_ReplyOk(ctx); diff --git a/thermosphere/src/gdb/verbose.c b/thermosphere/src/gdb/verbose.c index 6d5c8e57d..56e3408d0 100644 --- a/thermosphere/src/gdb/verbose.c +++ b/thermosphere/src/gdb/verbose.c @@ -13,11 +13,12 @@ static const struct { const char *name; + char trailingCharacter; GDBCommandHandler handler; } gdbVerboseCommandHandlers[] = { - { "Cont?", GDB_VERBOSE_HANDLER(ContinueSupported) }, - { "Cont", GDB_VERBOSE_HANDLER(Continue) }, - { "MustReplyEmpty", GDB_HANDLER(Unsupported) }, + { "Cont?", '\0', GDB_VERBOSE_HANDLER(ContinueSupported) }, + { "Cont", ';', GDB_VERBOSE_HANDLER(Continue) }, + { "MustReplyEmpty", '\0', GDB_HANDLER(Unsupported) }, }; GDB_DECLARE_HANDLER(VerboseCommand) @@ -31,6 +32,7 @@ GDB_DECLARE_HANDLER(VerboseCommand) char *vData = NULL; for (nameEnd = nameBegin; *nameEnd != 0 && *nameEnd != ';' && *nameEnd != ':'; nameEnd++); + char oldNameEnd = *nameEnd; if (*nameEnd != 0) { *nameEnd = 0; vData = nameEnd + 1; @@ -39,7 +41,11 @@ GDB_DECLARE_HANDLER(VerboseCommand) for (size_t i = 0; i < sizeof(gdbVerboseCommandHandlers) / sizeof(gdbVerboseCommandHandlers[0]); i++) { if (strcmp(gdbVerboseCommandHandlers[i].name, nameBegin) == 0) { ctx->commandData = vData; - return gdbVerboseCommandHandlers[i].handler(ctx); + if (oldNameEnd == gdbVerboseCommandHandlers[i].trailingCharacter) { + return gdbVerboseCommandHandlers[i].handler(ctx); + } else { + return GDB_ReplyErrno(ctx, EILSEQ); + } } } diff --git a/thermosphere/src/irq.c b/thermosphere/src/irq.c index 557e9136a..872d2ed5f 100644 --- a/thermosphere/src/irq.c +++ b/thermosphere/src/irq.c @@ -220,7 +220,6 @@ void handleIrqException(ExceptionStackFrame *frame, bool isLowerEl, bool isA32) bool isGuestInterrupt = false; bool isMaintenanceInterrupt = false; - bool hasBottomHalf = false; switch (irqId) { case ThermosphereSgi_ExecuteFunction: @@ -232,6 +231,9 @@ void handleIrqException(ExceptionStackFrame *frame, bool isLowerEl, bool isA32) case ThermosphereSgi_DebugPause: debugManagerPauseSgiHandler(); break; + case ThermosphereSgi_ReportDebuggerBreak: + // See bottom half + break; case GIC_IRQID_MAINTENANCE: isMaintenanceInterrupt = true; break; @@ -244,7 +246,6 @@ void handleIrqException(ExceptionStackFrame *frame, bool isLowerEl, bool isA32) } TransportInterface *transportIface = irqId >= 32 ? transportInterfaceIrqHandlerTopHalf(irqId) : NULL; - hasBottomHalf = hasBottomHalf || transportIface != NULL; // Priority drop gicc->eoir = iar; @@ -269,12 +270,12 @@ void handleIrqException(ExceptionStackFrame *frame, bool isLowerEl, bool isA32) recursiveSpinlockUnlock(&g_irqManager.lock); // Bottom half part - if (hasBottomHalf) { + if (transportIface != NULL) { exceptionEnterInterruptibleHypervisorCode(); unmaskIrq(); - if (transportIface != NULL) { - transportInterfaceIrqHandlerBottomHalf(transportIface); - } + transportInterfaceIrqHandlerBottomHalf(transportIface); + } else if (irqId == ThermosphereSgi_ReportDebuggerBreak) { + debugManagerReportEvent(DBGEVENT_DEBUGGER_BREAK); } } diff --git a/thermosphere/src/irq.h b/thermosphere/src/irq.h index 63cfa1c6e..21a510796 100644 --- a/thermosphere/src/irq.h +++ b/thermosphere/src/irq.h @@ -41,6 +41,7 @@ typedef enum ThermosphereSgi { ThermosphereSgi_ExecuteFunction = 0, ThermosphereSgi_VgicUpdate = 1, ThermosphereSgi_DebugPause = 2, + ThermosphereSgi_ReportDebuggerBreak = 3, ThermosphereSgi_Max, } ThermosphereSgi; diff --git a/thermosphere/src/start.s b/thermosphere/src/start.s index ec33bfc51..93e93f723 100644 --- a/thermosphere/src/start.s +++ b/thermosphere/src/start.s @@ -95,6 +95,7 @@ _postMmuEnableReturnAddr: mov x0, sp mov x1, x20 + str x0, [x18, #CORECTX_GUEST_FRAME_OFFSET] bl thermosphereMain prfm pldl1keep, [x18]