diff --git a/thermosphere/src/debug_manager.c b/thermosphere/src/debug_manager.c index 0888329e5..00b517b47 100644 --- a/thermosphere/src/debug_manager.c +++ b/thermosphere/src/debug_manager.c @@ -25,6 +25,8 @@ #include "gdb/debug.h" +GDBContext g_gdbContext = { 0 }; + typedef struct DebugManager { DebugEventInfo debugEventInfos[MAX_CORE]; @@ -32,8 +34,6 @@ typedef struct DebugManager { atomic_uint singleStepCoreList; atomic_uint eventsSentList; Barrier pauseBarrier; - - atomic_bool nonStop; } DebugManager; static DebugManager g_debugManager = { 0 }; @@ -114,6 +114,17 @@ void debugManagerUnpauseCores(u32 coreList, u32 singleStepList) __sev(); } +u32 debugManagerGetPausedCoreList(void) +{ + return atomic_load(&g_debugManager.pausedCoreList); +} + +const DebugEventInfo *debugManagerMarkAndGetCoreDebugEvent(u32 coreId) +{ + g_debugManager.debugEventInfos[coreId].handled = true; + return &g_debugManager.debugEventInfos[coreId]; +} + void debugManagerReportEvent(DebugEventType type, ...) { u64 flags = maskIrq(); @@ -141,6 +152,11 @@ void debugManagerReportEvent(DebugEventType type, ...) // Now, pause ourselves and try to signal we have a debug event debugManagerDoPauseCores(BIT(coreId)); - // TODO gdb enter leave functions + + exceptionEnterInterruptibleHypervisorCode(); + unmaskIrq(); + + GDB_TrySignalDebugEvent(&g_gdbContext, info); + restoreInterruptFlags(flags); } diff --git a/thermosphere/src/debug_manager.h b/thermosphere/src/debug_manager.h index 3c5bf501e..30b8ee83f 100644 --- a/thermosphere/src/debug_manager.h +++ b/thermosphere/src/debug_manager.h @@ -17,6 +17,9 @@ #pragma once #include "exceptions.h" +#include "gdb/context.h" + +extern GDBContext g_gdbContext; typedef enum DebugEventType { DBGEVENT_DEBUGGER_BREAK = 0, @@ -34,6 +37,7 @@ typedef struct OutputStringDebugEventInfo { typedef struct DebugEventInfo { DebugEventType type; + bool handled; u32 coreId; ExceptionStackFrame *frame; union { @@ -51,3 +55,12 @@ void debugManagerHandlePause(void); // "Unpause" doesn't synchronize, it just makes sure the core resumes & that "pause" can be called again. void debugManagerPauseCores(u32 coreList); void debugManagerUnpauseCores(u32 coreList, u32 singleStepList); + +u32 debugManagerGetPausedCoreList(void); + +const DebugEventInfo *debugManagerMarkAndGetCoreDebugEvent(u32 coreId); + +static inline bool debugManagerIsCorePaused(u32 coreId) +{ + return (debugManagerGetPausedCoreList() & BIT(coreId)) != 0; +} diff --git a/thermosphere/src/gdb/context.h b/thermosphere/src/gdb/context.h index 55d596983..ba05cddcd 100644 --- a/thermosphere/src/gdb/context.h +++ b/thermosphere/src/gdb/context.h @@ -61,6 +61,8 @@ typedef enum GDBState GDB_STATE_DETACHING, } GDBState; +struct DebugEventInfo; + typedef struct GDBContext { // No need for a lock, it's in the transport interface layer... @@ -69,13 +71,20 @@ typedef struct GDBContext { GDBState state; bool noAckSent; + u32 attachedCoreList; + u32 currentThreadId; u32 selectedThreadId; u32 selectedThreadIdForContinuing; + + u32 sentDebugEventCoreList; + + bool sendOwnDebugEventAllowed; + bool catchThreadEvents; bool processEnded, processExited; - //DebugEventInfo latestDebugEvent; FIXME + const struct DebugEventInfo *lastDebugEvent; uintptr_t currentHioRequestTargetAddr; PackedGdbHioRequest currentHioRequest; diff --git a/thermosphere/src/gdb/debug.c b/thermosphere/src/gdb/debug.c index 77d444479..55aafd0a8 100644 --- a/thermosphere/src/gdb/debug.c +++ b/thermosphere/src/gdb/debug.c @@ -8,7 +8,7 @@ #define _GNU_SOURCE // for strchrnul #include #include -#include "../exceptions.h" +#include "../debug_manager.h" #include "../watchpoints.h" #include "debug.h" @@ -19,17 +19,246 @@ #include "thread.h" #include "mem.h" #include "hio.h" -#include "watchpoints.h" #include #include +static int GDB_ParseExceptionFrame(char *out, const DebugEventInfo *info, int sig) +{ + u32 coreId = info->coreId; + ExceptionStackFrame *frame = info->frame; + + int n = sprintf(out, "T%02xthread:%lx;core:%lx;", sig, 1 + coreId, coreId); + + // Dump the GPRs & sp & pc & cpsr (cpsr is 32-bit in the xml desc) + // For performance reasons, we don't include the FPU registers here + for (u32 i = 0; i < 31; i++) { + n += sprintf(out + n, "%x:%016lx;", i, __builtin_bswap64(readFrameRegister(frame, i))); + } + + n += sprintf( + out + n, + "1f:%016lx;20:%016lx;21:%08x", + __builtin_bswap64(*exceptionGetSpPtr(frame)), + __builitin_bswap32((u32)frame->spsr_el2) + ); + + return n; +} + +int GDB_SendStopReply(GDBContext *ctx, const DebugEventInfo *info, bool asNotification) +{ + char *buf = ctx->buffer + 1; + int n; + bool invalid = false; + + buf[0] = 0; + + if (asNotification) { + strcpy(buf, "Stopped:"); + } + + n = strlen(buf); + + // Even if the info is invalid: + ctx->lastDebugEvent = info; + ctx->sentDebugEventCoreList |= BIT(info->coreId); + + switch(info->type) { + case DBGEVENT_DEBUGGER_BREAK: { + strcat(buf, "S02"); + } + + case DBGEVENT_CORE_ON: { + if (ctx->catchThreadEvents) { + ctx->currentThreadId = info->coreId + 1; // FIXME? + strcat(buf, "T05create:;"); + } else { + invalid = true; + } + break; + } + + case DBGEVENT_CORE_OFF: { + if(ctx->catchThreadEvents) { + sprintf(buf, "w0;%x", info->coreId + 1); + } else { + invalid = true; + } + break; + } + + case DBGEVENT_EXIT: { + // exited (no error / unhandled exception), SIGTERM (process terminated) * 2 + static const char *processExitReplies[] = { "W00", "X0f" }; + strcat(buf, processExitReplies[ctx->processExited ? 0 : 1]); + break; + } + + case DBGEVENT_EXCEPTION: { + ExceptionClass ec = info->frame->esr_el2.ec; + ctx->currentThreadId = info->coreId + 1; // FIXME? + + // Aside from stage 2 translation faults and other pre-handled exceptions, + // the only notable exceptions we get are stop point/single step events from the debugee (basically classes 0x3x) + switch(ec) { + case Exception_BreakpointLowerEl: { + n += GDB_ParseExceptionFrame(buf + n, ctx, SIGTRAP); + strcat(buf, "hwbreak:;"); + } + + case Exception_WatchpointLowerEl: { + static const char *kinds[] = { "", "r", "", "a" }; + // Note: exception info doesn't provide us with the access size. Use 1. + bool wnr = (info->frame->esr_el2.iss & BIT(6)) != 0; + WatchpointLoadStoreControl dr = wnr ? WatchpointLoadStoreControl_Store : WatchpointLoadStoreControl_Load; + DebugControlRegister cr = retrieveSplitWatchpointConfig(info->frame->far_el2, 1, dr, false); + if (!cr.enabled) { + DEBUG("GDB: oops, unhandled watchpoint for core id %u, far=%016lx\n", info->coreId, info->frame->far_el2); + } else { + n += GDB_ParseExceptionFrame(buf + n, ctx, SIGTRAP); + sprintf(buf + n, "%swatch:%016lx;", kinds[cr.lsc], info->frame->far_el2); + } + } + + // Note: we don't really support 32-bit sw breakpoints, we'll still report them + // if the guest has inserted some of them manually... + case Exception_SoftwareBreakpointA64: + case Exception_SoftwareBreakpointA32: { + n += GDB_ParseExceptionFrame(buf + n, ctx, SIGTRAP); + strcat(buf, "swbreak:;"); + } + + default: { + invalid = true; + DEBUG("GDB: oops, unhandled exception for core id %u\n", info->coreId); + break; + } + } + break; + } + + case DBGEVENT_OUTPUT_STRING: { + if (!(ctx->flags & GDB_FLAG_NONSTOP)) { + uintptr_t addr = info->outputString.address; + size_t remaining = info->outputString.size; + size_t sent = 0; + size_t total = 0; + while (remaining > 0) { + size_t pending = (GDB_BUF_LEN - 1) / 2; + pending = pending < remaining ? pending : remaining; + + int res = GDB_SendMemory(ctx, "O", 1, addr + sent, pending); + if(res < 0 || res != 5 + 2 * pending) + break; + + sent += pending; + remaining -= pending; + total += res; + } + + return (int)total; + } else { + invalid = true; + } + } + + // TODO: HIO + + default: { + invalid = true; + DEBUG("GDB: unknown exception type %u, core id %u\n", (u32)info->type, info->coreId); + break; + } + } + + if (invalid) { + return 0; + } else if (asNotification) { + return GDB_SendNotificationPacket(ctx, buf, strlen(buf)); + } else { + return GDB_SendPacket(ctx, buf, strlen(buf)); + } +} + /* - Since we can't select particular threads to continue (and that's uncompliant behavior): - - if we continue the current thread, continue all threads - - otherwise, leaves all threads stopped but make the client believe it's continuing + Non-stop mode: + -> %Stop: + <- $vStopped + -> $ + <- vStopped, etc. + -> $OK + If we're the first to try to send a notification, send it. + Otherwise don't, the core which will handle the GDB packets then will see the changes. + + GDB can also send the "?" packet. This aborts the current notfication/vStopped sequence, + and asks to resend the events for each stopped core, no matter if already sent before. + + Full-stop mode (default): + + If we lose the race, we have to wait until we're continued to send the remaining events... */ +int GDB_TrySignalDebugEvent(GDBContext *ctx, DebugEventInfo *info) +{ + int ret = 0; + + // Acquire the gdb lock/disable rx irq. We most likely block here. + GDB_AcquireContext(ctx); + + // Are we still paused & has the packet not been handled & are we allowed to send on our own? + + if (ctx->sendOwnDebugEventAllowed && !info->handled && debugManagerIsCorePaused(info->coreId)) { + bool nonStop = (ctx->flags & GDB_FLAG_NONSTOP) != 0; + info->handled = true; + + // Full-stop mode: stop other cores + if (!nonStop) { + debugManagerPauseCores(ctx->attachedCoreList & ~BIT(info->coreId)); + } + + ctx->sendOwnDebugEventAllowed = false; + ret = GDB_SendStopReply(ctx, info, nonStop); + } + + GDB_ReleaseContext(ctx); + + return ret; +} + +GDB_DECLARE_VERBOSE_HANDLER(Stopped) +{ + u32 coreList = debugManagerGetPausedCoreList() & ctx->attachedCoreList; + u32 remaining = coreList & ~ctx->sentDebugEventCoreList; + + if (remaining != 0) { + // Send one more debug event (marking it as handled) + u32 coreId = __builtin_ctz(remaining); + DebugEventInfo *info = debugManagerMarkAndGetCoreDebugEvent(coreId); + + ctx->sendOwnDebugEventAllowed = false; + return GDB_SendStopReply(ctx, info, false); + } else { + // vStopped sequenced finished + ctx->sendOwnDebugEventAllowed = true; + return GDB_ReplyOk(ctx); + } +} + +GDB_DECLARE_HANDLER(GetStopReason) +{ + bool nonStop = (ctx->flags & GDB_FLAG_NONSTOP) != 0; + if (!nonStop) { + // Full-stop: + return GDB_SendStopReply(ctx, &ctx->lastDebugEvent, true); + } else { + // Non-stop, start new vStopped sequence + ctx->sentDebugEventCoreList = 0; + ctx->sendOwnDebugEventAllowed = false; + return GDB_HandleVerboseStopped(ctx); + } +} + GDB_DECLARE_HANDLER(Detach) { ctx->state = GDB_STATE_DETACHING; @@ -143,151 +372,3 @@ GDB_DECLARE_VERBOSE_HANDLER(Continue) return 0; } - -GDB_DECLARE_HANDLER(GetStopReason) -{ - if (ctx->processEnded && ctx->processExited) { - return GDB_SendPacket(ctx, "W00", 3); - } else if (ctx->processEnded && !ctx->processExited) { - return GDB_SendPacket(ctx, "X0f", 3); - } else if (!GDB_IsAttached(ctx)) { - return GDB_SendPacket(ctx, "W00", 3); - } else { - // TODO //return GDB_SendStopReply(ctx, &ctx->latestDebugEvent); - } -} - -static int GDB_ParseExceptionFrame(char *out, const DebugEventInfo *info, int sig) -{ - u32 coreId = info->coreId; - ExceptionStackFrame *frame = info->frame; - - int n = sprintf(out, "T%02xthread:%lx;core:%lx;", sig, 1 + coreId, coreId); - - // Dump the GPRs & sp & pc & cpsr (cpsr is 32-bit in the xml desc) - // For performance reasons, we don't include the FPU registers here - for (u32 i = 0; i < 31; i++) { - n += sprintf(out + n, "%x:%016lx;", i, __builtin_bswap64(readFrameRegister(frame, i))); - } - - n += sprintf( - out + n, - "1f:%016lx;20:%016lx;21:%08x", - __builtin_bswap64(*exceptionGetSpPtr(frame)), - __builitin_bswap32((u32)frame->spsr_el2) - ); - - return n; -} - -int GDB_SendStopReply(GDBContext *ctx, const DebugEventInfo *info) -{ - char buffer[GDB_BUF_LEN + 1]; - int n; - - buffer[0] = 0; - - switch(info->type) { - case DBGEVENT_DEBUGGER_BREAK: { - strcpy(buffer, "S02"); - } - - case DBGEVENT_CORE_ON: { - if (ctx->catchThreadEvents) { - ctx->currentThreadId = info->coreId + 1; // FIXME? - strcpy(buffer, "T05create:;"); - } - break; - } - - case DBGEVENT_CORE_OFF: { - if(ctx->catchThreadEvents) { - sprintf(buffer, "w0;%x", info->coreId + 1); - } - break; - } - - case DBGEVENT_EXIT: { - // exited (no error / unhandled exception), SIGTERM (process terminated) * 2 - static const char *processExitReplies[] = { "W00", "X0f" }; - strcpy(buffer, processExitReplies[ctx->processExited ? 0 : 1]); - break; - } - - case DBGEVENT_EXCEPTION: { - ExceptionClass ec = info->frame->esr_el2.ec; - ctx->currentThreadId = info->coreId + 1; // FIXME? - - // Aside from stage 2 translation faults and other pre-handled exceptions, - // the only notable exceptions we get are stop point/single step events from the debugee (basically classes 0x3x) - switch(ec) { - case Exception_BreakpointLowerEl: { - n = GDB_ParseExceptionFrame(buffer, ctx, SIGTRAP); - strcat(buffer + n, "hwbreak:;"); - } - - case Exception_WatchpointLowerEl: { - static const char *kinds[] = { "", "r", "", "a" }; - // Note: exception info doesn't provide us with the access size. Use 1. - bool wnr = (info->frame->esr_el2.iss & BIT(6)) != 0; - WatchpointLoadStoreControl dr = wnr ? WatchpointLoadStoreControl_Store : WatchpointLoadStoreControl_Load; - DebugControlRegister cr = retrieveSplitWatchpointConfig(info->frame->far_el2, 1, dr, false); - if (!cr.enabled) { - DEBUG("GDB: oops, unhandled watchpoint for core id %u, far=%016lx\n", info->coreId, info->frame->far_el2); - } else { - n = GDB_ParseExceptionFrame(buffer, ctx, SIGTRAP); - sprintf(buffer + n, "%swatch:%016lx;", kinds[cr.lsc], info->frame->far_el2); - } - } - - // Note: we don't really support 32-bit sw breakpoints, we'll still report them - // if the guest has inserted some of them manually... - case Exception_SoftwareBreakpointA64: - case Exception_SoftwareBreakpointA32: { - n = GDB_ParseExceptionFrame(buffer, ctx, SIGTRAP); - strcat(buffer + n, "swbreak:;"); - } - - default: { - DEBUG("GDB: oops, unhandled exception for core id %u\n", info->coreId); - break; - } - } - break; - } - - case DBGEVENT_OUTPUT_STRING: { - uintptr_t addr = info->outputString.address; - size_t remaining = info->outputString.size; - size_t sent = 0; - size_t total = 0; - while (remaining > 0) { - size_t pending = (GDB_BUF_LEN - 1) / 2; - pending = pending < remaining ? pending : remaining; - - int res = GDB_SendMemory(ctx, "O", 1, addr + sent, pending); - if(res < 0 || res != 5 + 2 * pending) - break; - - sent += pending; - remaining -= pending; - total += res; - } - - return (int)total; - } - - // TODO: HIO - - default: { - DEBUG("GDB: unknown exception type %u, core id %u\n", (u32)info->type, info->coreId); - break; - } - } - - if (buffer[0] == 0) { - return 0; - } else { - return GDB_SendPacket(ctx, buffer, strlen(buffer)); - } -} diff --git a/thermosphere/src/gdb/debug.h b/thermosphere/src/gdb/debug.h index 87c09e5d2..cc99f5ac0 100644 --- a/thermosphere/src/gdb/debug.h +++ b/thermosphere/src/gdb/debug.h @@ -11,6 +11,12 @@ #include "../core_ctx.h" #include "../debug_manager.h" +void GDB_ContinueExecution(GDBContext *ctx); +int GDB_SendStopReply(GDBContext *ctx, const DebugEventInfo *info, bool asNotification); +int GDB_TrySignalDebugEvent(GDBContext *ctx, DebugEventInfo *info); + +GDB_DECLARE_VERBOSE_HANDLER(Stopped); + GDB_DECLARE_HANDLER(Detach); GDB_DECLARE_HANDLER(Kill); GDB_DECLARE_HANDLER(Break); @@ -18,7 +24,4 @@ GDB_DECLARE_HANDLER(Continue); GDB_DECLARE_VERBOSE_HANDLER(Continue); GDB_DECLARE_HANDLER(GetStopReason); -void GDB_ContinueExecution(GDBContext *ctx); -int GDB_SendStopReply(GDBContext *ctx, const DebugEventInfo *info); -int GDB_HandleDebugEvents(GDBContext *ctx); //void GDB_BreakProcessAndSinkDebugEvents(GDBContext *ctx, DebugFlags flags); diff --git a/thermosphere/src/gdb/net.c b/thermosphere/src/gdb/net.c index 026d2948f..2963df197 100644 --- a/thermosphere/src/gdb/net.c +++ b/thermosphere/src/gdb/net.c @@ -295,6 +295,21 @@ int GDB_SendHexPacket(GDBContext *ctx, const void *packetData, size_t len) return GDB_DoSendPacket(ctx, 4 + 2 * len); } +int GDB_SendNotificationPacket(GDBContext *ctx, const char *packetData, size_t len) +{ + if (packetData != ctx->buffer + 1) { + memmove(ctx->buffer + 1, packetData, len); + } + + ctx->buffer[0] = '%'; + + char *checksumLoc = ctx->buffer + len + 1; + *checksumLoc++ = '#'; + + hexItoa(GDB_ComputeChecksum(ctx->buffer + 1, len), checksumLoc, 2, false); + return GDB_DoSendPacket(ctx, 4 + len); +} + int GDB_SendStreamData(GDBContext *ctx, const char *streamData, size_t offset, size_t length, size_t totalSize, bool forceEmptyLast) { // GDB_BUF_LEN does not include the usual %#<1-byte checksum> diff --git a/thermosphere/src/gdb/net.h b/thermosphere/src/gdb/net.h index f74e268d7..c36a694ed 100644 --- a/thermosphere/src/gdb/net.h +++ b/thermosphere/src/gdb/net.h @@ -24,6 +24,7 @@ int GDB_ReceivePacket(GDBContext *ctx); int GDB_SendPacket(GDBContext *ctx, const char *packetData, size_t len); int GDB_SendFormattedPacket(GDBContext *ctx, const char *packetDataFmt, ...); int GDB_SendHexPacket(GDBContext *ctx, const void *packetData, size_t len); +int GDB_SendNotificationPacket(GDBContext *ctx, const char *packetData, size_t len); int GDB_SendStreamData(GDBContext *ctx, const char *streamData, size_t offset, size_t length, size_t totalSize, bool forceEmptyLast); int GDB_ReplyEmpty(GDBContext *ctx); int GDB_ReplyOk(GDBContext *ctx); diff --git a/thermosphere/src/gdb/thread.c b/thermosphere/src/gdb/thread.c index b17533c9d..f80221cab 100644 --- a/thermosphere/src/gdb/thread.c +++ b/thermosphere/src/gdb/thread.c @@ -50,7 +50,7 @@ GDB_DECLARE_HANDLER(IsThreadAlive) return GDB_ReplyErrno(ctx, EILSEQ); } - u32 coreMask = getActiveCoreMask(); + u32 coreMask = ctx->attachedCoreList; return (coreMask & BIT(threadId)) != 0 ? GDB_ReplyOk(ctx) : GDB_ReplyErrno(ctx, ESRCH); } @@ -71,7 +71,7 @@ GDB_DECLARE_QUERY_HANDLER(fThreadInfo) int n = 1; buf[0] = 'm'; - u32 coreMask = getActiveCoreMask(); + u32 coreMask = ctx->attachedCoreList; FOREACH_BIT (tmp, coreId, coreMask) { n += sprintf(buf + n, "%x,", 1 + coreId); @@ -86,6 +86,7 @@ GDB_DECLARE_QUERY_HANDLER(fThreadInfo) GDB_DECLARE_QUERY_HANDLER(sThreadInfo) { // We have made our GDB packet big enough to list all the thread ids (coreIds + 1 for each coreId) in fThreadInfo + // Note: we assume GDB doesn't accept notifications during the sequence transfer... return GDB_SendPacket(ctx, "m", 1); }