diff --git a/thermosphere/src/gdb.c b/thermosphere/src/gdb.c new file mode 100644 index 000000000..c45ec17bc --- /dev/null +++ b/thermosphere/src/gdb.c @@ -0,0 +1,199 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#if 0 +#include "gdb.h" +#include "gdb/net.h" +#include "gdb/server.h" + +#include "gdb/debug.h" + +#include "gdb/watchpoints.h" +#include "gdb/breakpoints.h" +#include "gdb/stop_point.h" + +void GDB_InitializeContext(GDBContext *ctx) +{ + memset(ctx, 0, sizeof(GDBContext)); + RecursiveLock_Init(&ctx->lock); + + RecursiveLock_Lock(&ctx->lock); + + svcCreateEvent(&ctx->continuedEvent, RESET_ONESHOT); + svcCreateEvent(&ctx->processAttachedEvent, RESET_STICKY); + + ctx->eventToWaitFor = ctx->processAttachedEvent; + ctx->continueFlags = (DebugFlags)(DBG_SIGNAL_FAULT_EXCEPTION_EVENTS | DBG_INHIBIT_USER_CPU_EXCEPTION_HANDLERS); + + RecursiveLock_Unlock(&ctx->lock); +} + +void GDB_FinalizeContext(GDBContext *ctx) +{ + RecursiveLock_Lock(&ctx->lock); + + svcClearEvent(ctx->processAttachedEvent); + + svcCloseHandle(ctx->processAttachedEvent); + svcCloseHandle(ctx->continuedEvent); + + RecursiveLock_Unlock(&ctx->lock); +} + +Result GDB_AttachToProcess(GDBContext *ctx) +{ + Result r; + + // Two cases: attached during execution, or started attached + // The second case will have, after RunQueuedProcess: attach process, debugger break, attach thread (with creator = 0) + + if (!(ctx->flags & GDB_FLAG_ATTACHED_AT_START)) + r = svcDebugActiveProcess(&ctx->debug, ctx->pid); + else + { + r = 0; + } + if(R_SUCCEEDED(r)) + { + // Note: ctx->pid will be (re)set while processing 'attach process' + DebugEventInfo *info = &ctx->latestDebugEvent; + ctx->processExited = ctx->processEnded = false; + if (!(ctx->flags & GDB_FLAG_ATTACHED_AT_START)) + { + while(R_SUCCEEDED(svcGetProcessDebugEvent(info, ctx->debug)) && + info->type != DBGEVENT_EXCEPTION && + info->exception.type != EXCEVENT_ATTACH_BREAK) + { + GDB_PreprocessDebugEvent(ctx, info); + svcContinueDebugEvent(ctx->debug, ctx->continueFlags); + } + } + else + { + // Attach process, debugger break + for(u32 i = 0; i < 2; i++) + { + if (R_FAILED(r = svcGetProcessDebugEvent(info, ctx->debug))) + return r; + GDB_PreprocessDebugEvent(ctx, info); + if (R_FAILED(r = svcContinueDebugEvent(ctx->debug, ctx->continueFlags))) + return r; + } + + if(R_FAILED(r = svcWaitSynchronization(ctx->debug, -1LL))) + return r; + if (R_FAILED(r = svcGetProcessDebugEvent(info, ctx->debug))) + return r; + // Attach thread + GDB_PreprocessDebugEvent(ctx, info); + } + } + else + return r; + + r = svcSignalEvent(ctx->processAttachedEvent); + if (R_SUCCEEDED(r)) + ctx->state = GDB_STATE_ATTACHED; + return r; +} + +void GDB_DetachFromProcess(GDBContext *ctx) +{ + DebugEventInfo dummy; + for(u32 i = 0; i < ctx->nbBreakpoints; i++) + { + if(!ctx->breakpoints[i].persistent) + GDB_DisableBreakpointById(ctx, i); + } + memset(&ctx->breakpoints, 0, sizeof(ctx->breakpoints)); + ctx->nbBreakpoints = 0; + + for(u32 i = 0; i < ctx->nbWatchpoints; i++) + { + GDB_RemoveWatchpoint(ctx, ctx->watchpoints[i], WATCHPOINT_DISABLED); + ctx->watchpoints[i] = 0; + } + ctx->nbWatchpoints = 0; + + svcKernelSetState(0x10002, ctx->pid, false); + memset(ctx->svcMask, 0, 32); + + memset(ctx->threadListData, 0, sizeof(ctx->threadListData)); + ctx->threadListDataPos = 0; + + //svcSignalEvent(server->statusUpdated); + + /* + There's a possibility of a race condition with a possible user exception handler, but you shouldn't + use 'kill' on APPLICATION titles in the first place (reboot hanging because the debugger is still running, etc). + */ + + ctx->continueFlags = (DebugFlags)0; + + while(R_SUCCEEDED(svcGetProcessDebugEvent(&dummy, ctx->debug))); + while(R_SUCCEEDED(svcContinueDebugEvent(ctx->debug, ctx->continueFlags))); + if(ctx->flags & GDB_FLAG_TERMINATE_PROCESS) + { + svcTerminateDebugProcess(ctx->debug); + ctx->processEnded = true; + ctx->processExited = false; + } + + while(R_SUCCEEDED(svcGetProcessDebugEvent(&dummy, ctx->debug))); + while(R_SUCCEEDED(svcContinueDebugEvent(ctx->debug, ctx->continueFlags))); + + svcCloseHandle(ctx->debug); + ctx->debug = 0; + memset(&ctx->launchedProgramInfo, 0, sizeof(FS_ProgramInfo)); + ctx->launchedProgramLaunchFlags = 0; + + ctx->eventToWaitFor = ctx->processAttachedEvent; + ctx->continueFlags = (DebugFlags)(DBG_SIGNAL_FAULT_EXCEPTION_EVENTS | DBG_INHIBIT_USER_CPU_EXCEPTION_HANDLERS); + ctx->pid = 0; + ctx->currentThreadId = 0; + ctx->selectedThreadId = 0; + ctx->selectedThreadIdForContinuing = 0; + ctx->nbThreads = 0; + ctx->totalNbCreatedThreads = 0; + memset(ctx->threadInfos, 0, sizeof(ctx->threadInfos)); + + ctx->currentHioRequestTargetAddr = 0; + memset(&ctx->currentHioRequest, 0, sizeof(PackedGdbHioRequest)); +} + +Result GDB_CreateProcess(GDBContext *ctx, const FS_ProgramInfo *progInfo, u32 launchFlags) +{ + Handle debug = 0; + ctx->debug = 0; + Result r = PMDBG_LaunchTitleDebug(&debug, progInfo, launchFlags); + if(R_FAILED(r)) + return r; + + ctx->flags |= GDB_FLAG_CREATED | GDB_FLAG_ATTACHED_AT_START; + ctx->debug = debug; + ctx->launchedProgramInfo = *progInfo; + ctx->launchedProgramLaunchFlags = launchFlags; + r = GDB_AttachToProcess(ctx); + return r; +} + +GDB_DECLARE_HANDLER(Unsupported) +{ + return GDB_ReplyEmpty(ctx); +} + +GDB_DECLARE_HANDLER(EnableExtendedMode) +{ + if (ctx->localPort >= GDB_PORT_BASE && ctx->localPort < GDB_PORT_BASE + MAX_DEBUG) + { + ctx->flags |= GDB_FLAG_EXTENDED_REMOTE; + return GDB_ReplyOk(ctx); + } + else + return GDB_ReplyEmpty(ctx); +} +#endif diff --git a/thermosphere/src/gdb.h b/thermosphere/src/gdb.h new file mode 100644 index 000000000..aa1499f32 --- /dev/null +++ b/thermosphere/src/gdb.h @@ -0,0 +1,168 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#pragma once + +#include <3ds/types.h> +#include <3ds/svc.h> +#include <3ds/synchronization.h> +#include <3ds/result.h> +#include "pmdbgext.h" +#include "sock_util.h" +#include "memory.h" +#include "ifile.h" + +#define MAX_DEBUG 3 +#define MAX_DEBUG_THREAD 127 +#define MAX_BREAKPOINT 256 + +#define MAX_TIO_OPEN_FILE 32 + +// 512+24 is the ideal size as IDA will try to read exactly 0x100 bytes at a time. Add 4 to this, for $#, see below. +// IDA seems to want additional bytes as well. +// 1024 is fine enough to put all regs in the 'T' stop reply packets +#define GDB_BUF_LEN 2048 + +#define GDB_HANDLER(name) GDB_Handle##name +#define GDB_QUERY_HANDLER(name) GDB_HANDLER(Query##name) +#define GDB_VERBOSE_HANDLER(name) GDB_HANDLER(Verbose##name) + +#define GDB_DECLARE_HANDLER(name) int GDB_HANDLER(name)(GDBContext *ctx) +#define GDB_DECLARE_QUERY_HANDLER(name) GDB_DECLARE_HANDLER(Query##name) +#define GDB_DECLARE_VERBOSE_HANDLER(name) GDB_DECLARE_HANDLER(Verbose##name) + +typedef struct Breakpoint +{ + u32 address; + u32 savedInstruction; + u8 instructionSize; + bool persistent; +} Breakpoint; + +typedef struct PackedGdbHioRequest +{ + char magic[4]; // "GDB\x00" + u32 version; + + // Request + char functionName[16+1]; + char paramFormat[8+1]; + + u64 parameters[8]; + size_t stringLengths[8]; + + // Return + s64 retval; + int gdbErrno; + bool ctrlC; +} PackedGdbHioRequest; + +typedef struct GdbTioFileInfo +{ + IFile f; + int flags; +} GdbTioFileInfo; + +enum +{ + GDB_FLAG_SELECTED = 1, + GDB_FLAG_USED = 2, + GDB_FLAG_ALLOCATED_MASK = GDB_FLAG_SELECTED | GDB_FLAG_USED, + GDB_FLAG_EXTENDED_REMOTE = 4, + GDB_FLAG_NOACK = 8, + GDB_FLAG_PROC_RESTART_MASK = GDB_FLAG_NOACK | GDB_FLAG_EXTENDED_REMOTE | GDB_FLAG_USED, + GDB_FLAG_PROCESS_CONTINUING = 16, + GDB_FLAG_TERMINATE_PROCESS = 32, + GDB_FLAG_ATTACHED_AT_START = 64, + GDB_FLAG_CREATED = 128, +}; + +typedef enum GDBState +{ + GDB_STATE_DISCONNECTED, + GDB_STATE_CONNECTED, + GDB_STATE_ATTACHED, + GDB_STATE_DETACHING, +} GDBState; + +typedef struct ThreadInfo +{ + u32 id; + u32 tls; +} ThreadInfo; + +struct GDBServer; + +typedef struct GDBContext +{ + sock_ctx super; + struct GDBServer *parent; + + RecursiveLock lock; + u16 localPort; + + u32 flags; + GDBState state; + bool noAckSent; + + u32 pid; + Handle debug; + + // vRun and R (restart) info: + FS_ProgramInfo launchedProgramInfo; + u32 launchedProgramLaunchFlags; + + ThreadInfo threadInfos[MAX_DEBUG_THREAD]; + u32 nbThreads; + u32 currentThreadId, selectedThreadId, selectedThreadIdForContinuing; + u32 totalNbCreatedThreads; + + Handle processAttachedEvent, continuedEvent; + Handle eventToWaitFor; + + bool catchThreadEvents; + bool processEnded, processExited; + + DebugEventInfo latestDebugEvent; + DebugFlags continueFlags; + u32 svcMask[8]; + + u32 nbBreakpoints; + Breakpoint breakpoints[MAX_BREAKPOINT]; + + u32 nbWatchpoints; + u32 watchpoints[2]; + + u32 currentHioRequestTargetAddr; + PackedGdbHioRequest currentHioRequest; + + GdbTioFileInfo openTioFileInfos[MAX_TIO_OPEN_FILE]; + u32 numOpenTioFiles; + + bool enableExternalMemoryAccess; + char *commandData, *commandEnd; + int latestSentPacketSize; + char buffer[GDB_BUF_LEN + 4]; + + char threadListData[0x800]; + u32 threadListDataPos; + + char memoryOsInfoXmlData[0x800]; + char processesOsInfoXmlData[0x1800]; +} GDBContext; + +typedef int (*GDBCommandHandler)(GDBContext *ctx); + +void GDB_InitializeContext(GDBContext *ctx); +void GDB_FinalizeContext(GDBContext *ctx); + +Result GDB_AttachToProcess(GDBContext *ctx); +void GDB_DetachFromProcess(GDBContext *ctx); +Result GDB_CreateProcess(GDBContext *ctx, const FS_ProgramInfo *progInfo, u32 launchFlags); + +GDB_DECLARE_HANDLER(Unsupported); +GDB_DECLARE_HANDLER(EnableExtendedMode); diff --git a/thermosphere/src/gdb/breakpoints.c b/thermosphere/src/gdb/breakpoints.c new file mode 100644 index 000000000..75d24b676 --- /dev/null +++ b/thermosphere/src/gdb/breakpoints.c @@ -0,0 +1,116 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#include "gdb/breakpoints.h" + +#define _REENT_ONLY +#include + +u32 GDB_FindClosestBreakpointSlot(GDBContext *ctx, u32 address) +{ + if(ctx->nbBreakpoints == 0 || address <= ctx->breakpoints[0].address) + return 0; + else if(address > ctx->breakpoints[ctx->nbBreakpoints - 1].address) + return ctx->nbBreakpoints; + + u32 a = 0, b = ctx->nbBreakpoints - 1, m; + + do + { + m = (a + b) / 2; + if(ctx->breakpoints[m].address < address) + a = m; + else if(ctx->breakpoints[m].address > address) + b = m; + else + return m; + } + while(b - a > 1); + + return b; +} + +int GDB_GetBreakpointInstruction(u32 *instruction, GDBContext *ctx, u32 address) +{ + u32 id = GDB_FindClosestBreakpointSlot(ctx, address); + + if(id == ctx->nbBreakpoints || ctx->breakpoints[id].address != address) + return -EINVAL; + + if(instruction != NULL) + *instruction = ctx->breakpoints[id].savedInstruction; + + return 0; +} + +int GDB_AddBreakpoint(GDBContext *ctx, u32 address, bool thumb, bool persist) +{ + if(!thumb && (address & 3) != 0) + return -EINVAL; + + address &= ~1; + + u32 id = GDB_FindClosestBreakpointSlot(ctx, address); + + if(id != ctx->nbBreakpoints && ctx->breakpoints[id].instructionSize != 0 && ctx->breakpoints[id].address == address) + return 0; + else if(ctx->nbBreakpoints == MAX_BREAKPOINT) + return -EBUSY; + + for(u32 i = ctx->nbBreakpoints; i > id && i != 0; i--) + ctx->breakpoints[i] = ctx->breakpoints[i - 1]; + + ctx->nbBreakpoints++; + + Breakpoint *bkpt = &ctx->breakpoints[id]; + u32 instr = thumb ? BREAKPOINT_INSTRUCTION_THUMB : BREAKPOINT_INSTRUCTION_ARM; + if(R_FAILED(svcReadProcessMemory(&bkpt->savedInstruction, ctx->debug, address, thumb ? 2 : 4)) || + R_FAILED(svcWriteProcessMemory(ctx->debug, &instr, address, thumb ? 2 : 4))) + { + for(u32 i = id; i < ctx->nbBreakpoints - 1; i++) + ctx->breakpoints[i] = ctx->breakpoints[i + 1]; + + memset(&ctx->breakpoints[--ctx->nbBreakpoints], 0, sizeof(Breakpoint)); + return -EFAULT; + } + + bkpt->instructionSize = thumb ? 2 : 4; + bkpt->address = address; + bkpt->persistent = persist; + + return 0; +} + +int GDB_DisableBreakpointById(GDBContext *ctx, u32 id) +{ + Breakpoint *bkpt = &ctx->breakpoints[id]; + if(R_FAILED(svcWriteProcessMemory(ctx->debug, &bkpt->savedInstruction, bkpt->address, bkpt->instructionSize))) + return -EFAULT; + else return 0; +} + +int GDB_RemoveBreakpoint(GDBContext *ctx, u32 address) +{ + address &= ~1; + + u32 id = GDB_FindClosestBreakpointSlot(ctx, address); + if(id == ctx->nbBreakpoints || ctx->breakpoints[id].address != address) + return -EINVAL; + + int r = GDB_DisableBreakpointById(ctx, id); + if(r != 0) + return r; + else + { + for(u32 i = id; i < ctx->nbBreakpoints - 1; i++) + ctx->breakpoints[i] = ctx->breakpoints[i + 1]; + + memset(&ctx->breakpoints[--ctx->nbBreakpoints], 0, sizeof(Breakpoint)); + + return 0; + } +} diff --git a/thermosphere/src/gdb/breakpoints.h b/thermosphere/src/gdb/breakpoints.h new file mode 100644 index 000000000..c625511aa --- /dev/null +++ b/thermosphere/src/gdb/breakpoints.h @@ -0,0 +1,20 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#pragma once + +#include "gdb.h" + +// We'll actually use SVC 0xFF for breakpoints :P +#define BREAKPOINT_INSTRUCTION_ARM 0xEF0000FF +#define BREAKPOINT_INSTRUCTION_THUMB 0xDFFF + +u32 GDB_FindClosestBreakpointSlot(GDBContext *ctx, u32 address); +int GDB_GetBreakpointInstruction(u32 *instr, GDBContext *ctx, u32 address); +int GDB_AddBreakpoint(GDBContext *ctx, u32 address, bool thumb, bool persist); +int GDB_DisableBreakpointById(GDBContext *ctx, u32 id); +int GDB_RemoveBreakpoint(GDBContext *ctx, u32 address); diff --git a/thermosphere/src/gdb/debug.c b/thermosphere/src/gdb/debug.c new file mode 100644 index 000000000..47d8477d9 --- /dev/null +++ b/thermosphere/src/gdb/debug.c @@ -0,0 +1,660 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#define _GNU_SOURCE // for strchrnul +#include "gdb/debug.h" +#include "gdb/server.h" +#include "gdb/verbose.h" +#include "gdb/net.h" +#include "gdb/thread.h" +#include "gdb/mem.h" +#include "gdb/hio.h" +#include "gdb/watchpoints.h" +#include "fmt.h" + +#include +#include +#include "pmdbgext.h" + +static void GDB_DetachImmediatelyExtended(GDBContext *ctx) +{ + // detach immediately + RecursiveLock_Lock(&ctx->lock); + ctx->state = GDB_STATE_DETACHING; + + svcClearEvent(ctx->processAttachedEvent); + ctx->eventToWaitFor = ctx->processAttachedEvent; + + svcClearEvent(ctx->parent->statusUpdateReceived); + svcSignalEvent(ctx->parent->statusUpdated); + RecursiveLock_Unlock(&ctx->lock); + + svcWaitSynchronization(ctx->parent->statusUpdateReceived, -1LL); + + RecursiveLock_Lock(&ctx->lock); + GDB_DetachFromProcess(ctx); + ctx->flags &= GDB_FLAG_PROC_RESTART_MASK; + RecursiveLock_Unlock(&ctx->lock); +} + +GDB_DECLARE_VERBOSE_HANDLER(Run) +{ + // Note: only titleId [mediaType [launchFlags]] is supported, and the launched title shouldn't rely on APT + // all 3 parameters should be hex-encoded. + + // Extended remote only + if (!(ctx->flags & GDB_FLAG_EXTENDED_REMOTE)) + return GDB_ReplyErrno(ctx, EPERM); + + u64 titleId; + u32 mediaType = MEDIATYPE_NAND; + u32 launchFlags = PMLAUNCHFLAG_LOAD_DEPENDENCIES; + + char args[3][32] = {{0}}; + char *pos = ctx->commandData; + for (u32 i = 0; i < 3 && *pos != 0; i++) + { + char *pos2 = strchrnul(pos, ';'); + u32 dist = pos2 - pos; + if (dist < 2) + return GDB_ReplyErrno(ctx, EILSEQ); + if (dist % 2 == 1) + return GDB_ReplyErrno(ctx, EILSEQ); + + if (dist / 2 > 16) // buffer overflow check + return GDB_ReplyErrno(ctx, EINVAL); + + u32 n = GDB_DecodeHex(args[i], pos, dist / 2); + if (n == 0) + return GDB_ReplyErrno(ctx, EILSEQ); + pos = *pos2 == 0 ? pos2 : pos2 + 1; + } + + if (args[0][0] == 0) + return GDB_ReplyErrno(ctx, EINVAL); // first arg mandatory + + if (GDB_ParseIntegerList64(&titleId, args[0], 1, 0, 0, 16, false) == NULL) + return GDB_ReplyErrno(ctx, EINVAL); + + if (args[1][0] != 0 && (GDB_ParseIntegerList(&mediaType, args[1], 1, 0, 0, 16, true) == NULL || mediaType >= 0x100)) + return GDB_ReplyErrno(ctx, EINVAL); + + if (args[2][0] != 0 && GDB_ParseIntegerList(&launchFlags, args[2], 1, 0, 0, 16, true) == NULL) + return GDB_ReplyErrno(ctx, EINVAL); + + FS_ProgramInfo progInfo; + progInfo.mediaType = (FS_MediaType)mediaType; + progInfo.programId = titleId; + + RecursiveLock_Lock(&ctx->lock); + Result r = GDB_CreateProcess(ctx, &progInfo, launchFlags); + + if (R_FAILED(r)) + { + if(ctx->debug != 0) + GDB_DetachImmediatelyExtended(ctx); + RecursiveLock_Unlock(&ctx->lock); + return GDB_ReplyErrno(ctx, EPERM); + } + + RecursiveLock_Unlock(&ctx->lock); + return R_SUCCEEDED(r) ? GDB_SendStopReply(ctx, &ctx->latestDebugEvent) : GDB_ReplyErrno(ctx, EPERM); +} + +GDB_DECLARE_HANDLER(Restart) +{ + // Note: removed from gdb + // Extended remote only & process must have been created + if (!(ctx->flags & GDB_FLAG_EXTENDED_REMOTE) || !(ctx->flags & GDB_FLAG_CREATED)) + return GDB_ReplyErrno(ctx, EPERM); + + FS_ProgramInfo progInfo = ctx->launchedProgramInfo; + u32 launchFlags = ctx->launchedProgramLaunchFlags; + + ctx->flags |= GDB_FLAG_TERMINATE_PROCESS; + if (ctx->flags & GDB_FLAG_EXTENDED_REMOTE) + GDB_DetachImmediatelyExtended(ctx); + + RecursiveLock_Lock(&ctx->lock); + Result r = GDB_CreateProcess(ctx, &progInfo, launchFlags); + if (R_FAILED(r) && ctx->debug != 0) + GDB_DetachImmediatelyExtended(ctx); + RecursiveLock_Unlock(&ctx->lock); + return 0; +} + +GDB_DECLARE_VERBOSE_HANDLER(Attach) +{ + // Extended remote only + if (!(ctx->flags & GDB_FLAG_EXTENDED_REMOTE)) + return GDB_ReplyErrno(ctx, EPERM); + + u32 pid; + if(GDB_ParseHexIntegerList(&pid, ctx->commandData, 1, 0) == NULL) + return GDB_ReplyErrno(ctx, EILSEQ); + + RecursiveLock_Lock(&ctx->lock); + ctx->pid = pid; + Result r = GDB_AttachToProcess(ctx); + if(R_FAILED(r)) + GDB_DetachImmediatelyExtended(ctx); + RecursiveLock_Unlock(&ctx->lock); + return R_SUCCEEDED(r) ? GDB_SendStopReply(ctx, &ctx->latestDebugEvent) : GDB_ReplyErrno(ctx, EPERM); +} + +/* + 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 +*/ + +GDB_DECLARE_HANDLER(Detach) +{ + ctx->state = GDB_STATE_DETACHING; + if (ctx->flags & GDB_FLAG_EXTENDED_REMOTE) + GDB_DetachImmediatelyExtended(ctx); + return GDB_ReplyOk(ctx); +} + +GDB_DECLARE_HANDLER(Kill) +{ + ctx->state = GDB_STATE_DETACHING; + ctx->flags |= GDB_FLAG_TERMINATE_PROCESS; + if (ctx->flags & GDB_FLAG_EXTENDED_REMOTE) + GDB_DetachImmediatelyExtended(ctx); + + return 0; +} + +GDB_DECLARE_HANDLER(Break) +{ + if(!(ctx->flags & GDB_FLAG_PROCESS_CONTINUING)) + return GDB_SendPacket(ctx, "S02", 3); + else + { + ctx->flags &= ~GDB_FLAG_PROCESS_CONTINUING; + return 0; + } +} + +void GDB_ContinueExecution(GDBContext *ctx) +{ + ctx->selectedThreadId = ctx->selectedThreadIdForContinuing = 0; + svcContinueDebugEvent(ctx->debug, ctx->continueFlags); + ctx->flags |= GDB_FLAG_PROCESS_CONTINUING; +} + +GDB_DECLARE_HANDLER(Continue) +{ + char *addrStart = NULL; + u32 addr = 0; + + if(ctx->selectedThreadIdForContinuing != 0 && ctx->selectedThreadIdForContinuing != ctx->currentThreadId) + return 0; + + if(ctx->commandData[-1] == 'C') + { + if(ctx->commandData[0] == 0 || ctx->commandData[1] == 0 || (ctx->commandData[2] != 0 && ctx->commandData[2] == ';')) + return GDB_ReplyErrno(ctx, EILSEQ); + + // Signal ignored... + + if(ctx->commandData[2] == ';') + addrStart = ctx->commandData + 3; + } + else + { + 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); + return 0; +} + +GDB_DECLARE_VERBOSE_HANDLER(Continue) +{ + char *pos = ctx->commandData; + bool currentThreadFound = false; + while(pos != NULL && *pos != 0 && !currentThreadFound) + { + if(*pos != 'c' && *pos != 'C') + return GDB_ReplyErrno(ctx, EPERM); + + pos += *pos == 'C' ? 3 : 1; + + if(*pos++ != ':') // default action found + { + currentThreadFound = true; + break; + } + + char *nextpos = (char *)strchr(pos, ';'); + if(strncmp(pos, "-1", 2) == 0) + currentThreadFound = true; + else + { + u32 threadId; + if(GDB_ParseHexIntegerList(&threadId, pos, 1, ';') == NULL) + return GDB_ReplyErrno(ctx, EILSEQ); + currentThreadFound = currentThreadFound || threadId == ctx->currentThreadId; + } + + pos = nextpos; + } + + if(ctx->currentThreadId == 0 || currentThreadFound) + GDB_ContinueExecution(ctx); + + 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 (ctx->debug == 0) { + return GDB_SendPacket(ctx, "W00", 3); + } else { + return GDB_SendStopReply(ctx, &ctx->latestDebugEvent); + } +} + +static int GDB_ParseCommonThreadInfo(char *out, GDBContext *ctx, int sig) +{ + u32 threadId = ctx->currentThreadId; + ThreadContext regs; + s64 dummy; + u32 core; + Result r = svcGetDebugThreadContext(®s, ctx->debug, threadId, THREADCONTEXT_CONTROL_ALL); + int n = sprintf(out, "T%02xthread:%lx;", sig, threadId); + + if(R_FAILED(r)) + return n; + + r = svcGetDebugThreadParam(&dummy, &core, ctx->debug, ctx->currentThreadId, DBGTHREAD_PARAMETER_CPU_CREATOR); // Creator = "first ran, and running the thread" + + if(R_SUCCEEDED(r)) + n += sprintf(out + n, "core:%lx;", core); + + for(u32 i = 0; i <= 12; i++) + n += sprintf(out + n, "%lx:%08lx;", i, __builtin_bswap32(regs.cpu_registers.r[i])); + + n += sprintf(out + n, "d:%08lx;e:%08lx;f:%08lx;19:%08lx;", + __builtin_bswap32(regs.cpu_registers.sp), __builtin_bswap32(regs.cpu_registers.lr), __builtin_bswap32(regs.cpu_registers.pc), + __builtin_bswap32(regs.cpu_registers.cpsr)); + + for(u32 i = 0; i < 16; i++) + { + u64 val; + memcpy(&val, ®s.fpu_registers.d[i], 8); + n += sprintf(out + n, "%lx:%016llx;", 26 + i, __builtin_bswap64(val)); + } + + n += sprintf(out + n, "2a:%08lx;2b:%08lx;", __builtin_bswap32(regs.fpu_registers.fpscr), __builtin_bswap32(regs.fpu_registers.fpexc)); + + return n; +} + +void GDB_PreprocessDebugEvent(GDBContext *ctx, DebugEventInfo *info) +{ + switch(info->type) + { + case DBGEVENT_ATTACH_PROCESS: + { + ctx->pid = info->attach_process.process_id; + break; + } + + case DBGEVENT_ATTACH_THREAD: + { + if(ctx->nbThreads == MAX_DEBUG_THREAD) + svcBreak(USERBREAK_ASSERT); + else + { + ++ctx->totalNbCreatedThreads; + ctx->threadInfos[ctx->nbThreads].id = info->thread_id; + ctx->threadInfos[ctx->nbThreads++].tls = info->attach_thread.thread_local_storage; + } + + break; + } + + case DBGEVENT_EXIT_THREAD: + { + u32 i; + for(i = 0; i < ctx->nbThreads && ctx->threadInfos[i].id != info->thread_id; i++); + if(i == ctx->nbThreads || ctx->threadInfos[i].id != info->thread_id) + svcBreak(USERBREAK_ASSERT); + else + { + for(u32 j = i; j < ctx->nbThreads - 1; j++) + memcpy(ctx->threadInfos + j, ctx->threadInfos + j + 1, sizeof(ThreadInfo)); + memset(ctx->threadInfos + --ctx->nbThreads, 0, sizeof(ThreadInfo)); + } + + break; + } + + case DBGEVENT_EXIT_PROCESS: + { + ctx->processEnded = true; + ctx->processExited = info->exit_process.reason == EXITPROCESS_EVENT_EXIT; + + break; + } + + case DBGEVENT_OUTPUT_STRING: + { + if(info->output_string.string_addr >= 0xFFFFFFFE) + { + u32 sz = info->output_string.string_size, addr = info->output_string.string_addr, threadId = info->thread_id; + memset(info, 0, sizeof(DebugEventInfo)); + info->type = (addr == 0xFFFFFFFF) ? DBGEVENT_SYSCALL_OUT : DBGEVENT_SYSCALL_IN; + info->thread_id = threadId; + info->flags = 1; + info->syscall.syscall = sz; + } + else if (info->output_string.string_size == 0) + GDB_FetchPackedHioRequest(ctx, info->output_string.string_addr); + + break; + } + + case DBGEVENT_EXCEPTION: + { + switch(info->exception.type) + { + case EXCEVENT_UNDEFINED_INSTRUCTION: + { + // kernel bugfix for thumb mode + ThreadContext regs; + Result r = svcGetDebugThreadContext(®s, ctx->debug, info->thread_id, THREADCONTEXT_CONTROL_CPU_SPRS); + if(R_SUCCEEDED(r) && (regs.cpu_registers.cpsr & 0x20) != 0) + { + regs.cpu_registers.pc += 2; + r = svcSetDebugThreadContext(ctx->debug, info->thread_id, ®s, THREADCONTEXT_CONTROL_CPU_SPRS); + } + + break; + } + + default: + break; + } + } + default: + break; + } +} + +int GDB_SendStopReply(GDBContext *ctx, const DebugEventInfo *info) +{ + char buffer[GDB_BUF_LEN + 1]; + + switch(info->type) + { + case DBGEVENT_ATTACH_PROCESS: + break; // Dismissed + + case DBGEVENT_ATTACH_THREAD: + { + if((ctx->flags & GDB_FLAG_ATTACHED_AT_START) && ctx->totalNbCreatedThreads == 1) + { + // Main thread created + ctx->currentThreadId = info->thread_id; + GDB_ParseCommonThreadInfo(buffer, ctx, SIGINT); + return GDB_SendFormattedPacket(ctx, "%s", buffer); + } + else if(info->attach_thread.creator_thread_id == 0 || !ctx->catchThreadEvents) + break; // Dismissed + else + { + ctx->currentThreadId = info->thread_id; + return GDB_SendPacket(ctx, "T05create:;", 10); + } + } + + case DBGEVENT_EXIT_THREAD: + { + if(ctx->catchThreadEvents && info->exit_thread.reason < EXITTHREAD_EVENT_EXIT_PROCESS) + { + // no signal, SIGTERM, SIGQUIT (process exited), SIGTERM (process terminated) + static int threadExitRepliesSigs[] = { 0, SIGTERM, SIGQUIT, SIGTERM }; + return GDB_SendFormattedPacket(ctx, "w%02x;%lx", threadExitRepliesSigs[(u32)info->exit_thread.reason], info->thread_id); + } + break; + } + + case DBGEVENT_EXIT_PROCESS: + { + // exited (no error / unhandled exception), SIGTERM (process terminated) * 2 + static const char *processExitReplies[] = { "W00", "X0f", "X0f" }; + return GDB_SendPacket(ctx, processExitReplies[(u32)info->exit_process.reason], 3); + } + + case DBGEVENT_EXCEPTION: + { + ExceptionEvent exc = info->exception; + + switch(exc.type) + { + case EXCEVENT_UNDEFINED_INSTRUCTION: + case EXCEVENT_PREFETCH_ABORT: // doesn't include hardware breakpoints + case EXCEVENT_DATA_ABORT: // doesn't include hardware watchpoints + case EXCEVENT_UNALIGNED_DATA_ACCESS: + case EXCEVENT_UNDEFINED_SYSCALL: + { + u32 signum = exc.type == EXCEVENT_UNDEFINED_INSTRUCTION ? SIGILL : + (exc.type == EXCEVENT_UNDEFINED_SYSCALL ? SIGSYS : SIGSEGV); + + ctx->currentThreadId = info->thread_id; + GDB_ParseCommonThreadInfo(buffer, ctx, signum); + return GDB_SendFormattedPacket(ctx, "%s", buffer); + } + + case EXCEVENT_ATTACH_BREAK: + return GDB_SendPacket(ctx, "S00", 3); + + case EXCEVENT_STOP_POINT: + { + ctx->currentThreadId = info->thread_id; + + switch(exc.stop_point.type) + { + case STOPPOINT_SVC_FF: + { + GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP); + return GDB_SendFormattedPacket(ctx, "%sswbreak:;", buffer); + break; + } + + case STOPPOINT_BREAKPOINT: + { + // /!\ Not actually implemented (and will never be) + GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP); + return GDB_SendFormattedPacket(ctx, "%shwbreak:;", buffer); + break; + } + + case STOPPOINT_WATCHPOINT: + { + const char *kinds[] = { "", "r", "", "a" }; + WatchpointKind kind = GDB_GetWatchpointKind(ctx, exc.stop_point.fault_information); + if(kind == WATCHPOINT_DISABLED) + GDB_SendDebugString(ctx, "Warning: unknown watchpoint encountered!\n"); + + GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP); + return GDB_SendFormattedPacket(ctx, "%s%swatch:%08lx;", buffer, kinds[(u32)kind], exc.stop_point.fault_information); + break; + } + + default: + break; + } + break; + } + + case EXCEVENT_USER_BREAK: + { + ctx->currentThreadId = info->thread_id; + GDB_ParseCommonThreadInfo(buffer, ctx, SIGINT); + return GDB_SendFormattedPacket(ctx, "%s", buffer); + //TODO + } + + case EXCEVENT_DEBUGGER_BREAK: + { + u32 threadIds[4]; + u32 nbThreads = 0; + + for(u32 i = 0; i < 4; i++) + { + if(exc.debugger_break.thread_ids[i] > 0) + threadIds[nbThreads++] = (u32)exc.debugger_break.thread_ids[i]; + } + + u32 currentThreadId = nbThreads > 0 ? GDB_GetCurrentThreadFromList(ctx, threadIds, nbThreads) : GDB_GetCurrentThread(ctx); + s64 dummy; + u32 mask = 0; + + svcGetDebugThreadParam(&dummy, &mask, ctx->debug, currentThreadId, DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW); + + if(mask == 1) + { + ctx->currentThreadId = currentThreadId; + GDB_ParseCommonThreadInfo(buffer, ctx, SIGINT); + return GDB_SendFormattedPacket(ctx, "%s", buffer); + } + else + return GDB_SendPacket(ctx, "S02", 3); + } + + default: + break; + } + + break; + } + + case DBGEVENT_SYSCALL_IN: + { + ctx->currentThreadId = info->thread_id; + GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP); + return GDB_SendFormattedPacket(ctx, "%ssyscall_entry:%02x;", buffer, info->syscall.syscall); + } + + case DBGEVENT_SYSCALL_OUT: + { + ctx->currentThreadId = info->thread_id; + GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP); + return GDB_SendFormattedPacket(ctx, "%ssyscall_return:%02x;", buffer, info->syscall.syscall); + } + + case DBGEVENT_OUTPUT_STRING: + { + // Regular "output string" + if (!GDB_IsHioInProgress(ctx)) + { + u32 addr = info->output_string.string_addr; + u32 remaining = info->output_string.string_size; + u32 sent = 0; + int total = 0; + while(remaining > 0) + { + u32 pending = (GDB_BUF_LEN - 1) / 2; + pending = pending < remaining ? pending : remaining; + + int res = GDB_SendMemory(ctx, "O", 1, addr + sent, pending); + if(res < 0 || (u32) res != 5 + 2 * pending) + break; + + sent += pending; + remaining -= pending; + total += res; + } + + return total; + } + else // HIO + { + return GDB_SendCurrentHioRequest(ctx); + } + + } + default: + break; + } + + return 0; +} + +/* + Only 1 blocking event can be enqueued at a time: they preempt all the other threads. + The only "non-blocking" event that is implemented is EXIT PROCESS (but it's a very special case) +*/ +int GDB_HandleDebugEvents(GDBContext *ctx) +{ + if(ctx->state == GDB_STATE_DETACHING) + return -1; + + DebugEventInfo info; + Result rdbg = svcGetProcessDebugEvent(&info, ctx->debug); + + if(R_FAILED(rdbg)) + return -1; + + GDB_PreprocessDebugEvent(ctx, &info); + + int ret = 0; + bool continueAutomatically = (info.type == DBGEVENT_OUTPUT_STRING && !GDB_IsHioInProgress(ctx)) || + info.type == DBGEVENT_ATTACH_PROCESS || + (info.type == DBGEVENT_ATTACH_THREAD && (info.attach_thread.creator_thread_id == 0 || !ctx->catchThreadEvents)) || + (info.type == DBGEVENT_EXIT_THREAD && (info.exit_thread.reason >= EXITTHREAD_EVENT_EXIT_PROCESS || !ctx->catchThreadEvents)) || + info.type == DBGEVENT_EXIT_PROCESS || !(info.flags & 1); + + if(continueAutomatically) + { + Result r = 0; + ret = GDB_SendStopReply(ctx, &info); + if(info.flags & 1) + r = svcContinueDebugEvent(ctx->debug, ctx->continueFlags); + + if(r == (Result)0xD8A02008) // process ended + return -2; + + return -ret - 3; + } + else + { + int ret; + + if(ctx->processEnded) + return -2; + + ctx->latestDebugEvent = info; + ret = GDB_SendStopReply(ctx, &info); + ctx->flags &= ~GDB_FLAG_PROCESS_CONTINUING; + return ret; + } +} diff --git a/thermosphere/src/gdb/debug.h b/thermosphere/src/gdb/debug.h new file mode 100644 index 000000000..95f65c4ae --- /dev/null +++ b/thermosphere/src/gdb/debug.h @@ -0,0 +1,26 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#pragma once + +#include "gdb.h" + +GDB_DECLARE_VERBOSE_HANDLER(Run); +GDB_DECLARE_HANDLER(Restart); +GDB_DECLARE_VERBOSE_HANDLER(Attach); +GDB_DECLARE_HANDLER(Detach); +GDB_DECLARE_HANDLER(Kill); +GDB_DECLARE_HANDLER(Break); +GDB_DECLARE_HANDLER(Continue); +GDB_DECLARE_VERBOSE_HANDLER(Continue); +GDB_DECLARE_HANDLER(GetStopReason); + +void GDB_ContinueExecution(GDBContext *ctx); +void GDB_PreprocessDebugEvent(GDBContext *ctx, DebugEventInfo *info); +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/hio.c b/thermosphere/src/gdb/hio.c new file mode 100644 index 000000000..31b1f89f0 --- /dev/null +++ b/thermosphere/src/gdb/hio.c @@ -0,0 +1,133 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#include + +#include "gdb/hio.h" +#include "gdb/net.h" +#include "gdb/mem.h" +#include "gdb/debug.h" +#include "fmt.h" + +bool GDB_FetchPackedHioRequest(GDBContext *ctx, u32 addr) +{ + u32 total = GDB_ReadTargetMemory(&ctx->currentHioRequest, ctx, addr, sizeof(PackedGdbHioRequest)); + if (total != sizeof(PackedGdbHioRequest) || memcmp(&ctx->currentHioRequest.magic, "GDB\x00", 4) != 0) + { + memset(&ctx->currentHioRequest, 0, sizeof(PackedGdbHioRequest)); + ctx->currentHioRequestTargetAddr = 0; + return false; + } + else + { + ctx->currentHioRequestTargetAddr = addr; + return true; + } +} + +bool GDB_IsHioInProgress(GDBContext *ctx) +{ + return ctx->currentHioRequestTargetAddr != 0; +} + +int GDB_SendCurrentHioRequest(GDBContext *ctx) +{ + char buf[256+1]; + char tmp[32+1]; + u32 nStr = 0; + + sprintf(buf, "F%s", ctx->currentHioRequest.functionName); + + for (u32 i = 0; i < 8 && ctx->currentHioRequest.paramFormat[i] != 0; i++) + { + switch (ctx->currentHioRequest.paramFormat[i]) + { + case 'i': + case 'I': + case 'p': + sprintf(tmp, ",%lx", (u32)ctx->currentHioRequest.parameters[i]); + break; + case 'l': + case 'L': + sprintf(tmp, ",%llx", ctx->currentHioRequest.parameters[i]); + break; + case 's': + sprintf(tmp, ",%lx/%x", (u32)ctx->currentHioRequest.parameters[i], ctx->currentHioRequest.stringLengths[nStr++]); + break; + default: + tmp[0] = 0; + break; + } + strcat(buf, tmp); + } + + return GDB_SendPacket(ctx, buf, strlen(buf)); +} + +GDB_DECLARE_HANDLER(HioReply) +{ + if (!GDB_IsHioInProgress(ctx)) + return GDB_ReplyErrno(ctx, EPERM); + + // Reply in the form of Fretcode,errno,Ctrl-C flag;call-specific attachment + // "Call specific attachement" is always empty, though. + + const char *pos = ctx->commandData; + u64 retval; + + if (*pos == 0 || *pos == ',') + return GDB_ReplyErrno(ctx, EILSEQ); + else if (*pos == '-') + { + pos++; + ctx->currentHioRequest.retval = -1ll; + } + else if (*pos == '+') + { + pos++; + ctx->currentHioRequest.retval = 1; + } + else + ctx->currentHioRequest.retval = 1; + + pos = GDB_ParseHexIntegerList64(&retval, pos, 1, ','); + + if (pos == NULL) + return GDB_ReplyErrno(ctx, EILSEQ); + + ctx->currentHioRequest.retval *= retval; + ctx->currentHioRequest.gdbErrno = 0; + ctx->currentHioRequest.ctrlC = false; + + if (*pos != 0) + { + u32 errno_; + // GDB protocol technically allows errno to have a +/- prefix but this will never happen. + pos = GDB_ParseHexIntegerList(&errno_, ++pos, 1, ','); + ctx->currentHioRequest.gdbErrno = (int)errno_; + if (pos == NULL) + return GDB_ReplyErrno(ctx, EILSEQ); + + if (*pos != 0) + { + if (*pos != 'C') + return GDB_ReplyErrno(ctx, EILSEQ); + + ctx->currentHioRequest.ctrlC = true; + } + } + + memset(ctx->currentHioRequest.paramFormat, 0, sizeof(ctx->currentHioRequest.paramFormat)); + + u32 total = GDB_WriteTargetMemory(ctx, &ctx->currentHioRequest, ctx->currentHioRequestTargetAddr, sizeof(PackedGdbHioRequest)); + + memset(&ctx->currentHioRequest, 0, sizeof(PackedGdbHioRequest)); + ctx->currentHioRequestTargetAddr = 0; + + GDB_ContinueExecution(ctx); + return total == sizeof(PackedGdbHioRequest) ? 0 : GDB_ReplyErrno(ctx, EFAULT); +} diff --git a/thermosphere/src/gdb/hio.h b/thermosphere/src/gdb/hio.h new file mode 100644 index 000000000..123ba5685 --- /dev/null +++ b/thermosphere/src/gdb/hio.h @@ -0,0 +1,16 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#pragma once + +#include "gdb.h" + +bool GDB_FetchPackedHioRequest(GDBContext *ctx, u32 addr); +bool GDB_IsHioInProgress(GDBContext *ctx); +int GDB_SendCurrentHioRequest(GDBContext *ctx); + +GDB_DECLARE_HANDLER(HioReply); \ No newline at end of file diff --git a/thermosphere/src/gdb/mem.c b/thermosphere/src/gdb/mem.c new file mode 100644 index 000000000..836640304 --- /dev/null +++ b/thermosphere/src/gdb/mem.c @@ -0,0 +1,331 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#include "gdb/mem.h" +#include "gdb/net.h" +#include "utils.h" + +static void *k_memcpy_no_interrupt(void *dst, const void *src, u32 len) +{ + __asm__ volatile("cpsid aif"); + return memcpy(dst, src, len); +} + +Result GDB_ReadTargetMemoryInPage(void *out, GDBContext *ctx, u32 addr, u32 len) +{ + s64 TTBCR; + svcGetSystemInfo(&TTBCR, 0x10002, 0); + + if(addr < (1u << (32 - (u32)TTBCR))) // Note: UB with user-mapped MMIO (uses memcpy). + return svcReadProcessMemory(out, ctx->debug, addr, len); + else if(!ctx->enableExternalMemoryAccess) + return -1; + else if(addr >= 0x80000000 && addr < 0xB0000000) + { + if(addr >= 0x90000000 && addr < 0x98000000) // IO + { + for(u32 off = 0; off < len; ) + { + if((addr + off) & 1) + { + *((u8 *)out + off) = *(vu8 *)(addr + off); + off += 1; + } + else if((addr + off) & 3) + { + *((u16 *)out + off) = *(vu16 *)(addr + off); + off += 2; + } + else + { + *((u32 *)out + off) = *(vu32 *)(addr + off); + off += 4; + } + } + } + else memcpy(out, (const void *)addr, len); + return 0; + } + else + { + u32 PA = svcConvertVAToPA((const void *)addr, false); + + if(PA == 0) + return -1; + else + { + svcCustomBackdoor(k_memcpy_no_interrupt, out, (const void *)addr, len); + return 0; + } + } +} + +Result GDB_WriteTargetMemoryInPage(GDBContext *ctx, const void *in, u32 addr, u32 len) +{ + s64 TTBCR; + svcGetSystemInfo(&TTBCR, 0x10002, 0); + + if(addr < (1u << (32 - (u32)TTBCR))) + return svcWriteProcessMemory(ctx->debug, in, addr, len); // not sure if it checks if it's IO or not. It probably does + else if(!ctx->enableExternalMemoryAccess) + return -1; + else if(addr >= 0x80000000 && addr < 0xB0000000) + { + if(addr >= 0x90000000 && addr < 0x98000000) // IO + { + for(u32 off = 0; off < len; ) + { + if((addr + off) & 1) + { + *(vu8 *)(addr + off) = *((u8 *)in + off); + off += 1; + } + else if((addr + off) & 3) + { + *(vu16 *)(addr + off) = *((u16 *)in + off); + off += 2; + } + else + { + *(vu32 *)(addr + off) = *((u32 *)in + off); + off += 4; + } + } + } + else memcpy((void *)addr, in, len); + return 0; + } + else + { + u32 PA = svcConvertVAToPA((const void *)addr, true); + + if(PA != 0) + { + svcCustomBackdoor(k_memcpy_no_interrupt, (void *)addr, in, len); + return 0; + } + else + { + // Unreliable, use at your own risk + + svcFlushEntireDataCache(); + svcInvalidateEntireInstructionCache(); + Result ret = GDB_WriteTargetMemoryInPage(ctx, PA_FROM_VA_PTR(in), addr, len); + svcFlushEntireDataCache(); + svcInvalidateEntireInstructionCache(); + return ret; + } + } +} + +u32 GDB_ReadTargetMemory(void *out, GDBContext *ctx, u32 addr, u32 len) +{ + Result r = 0; + u32 remaining = len, total = 0; + u8 *out8 = (u8 *)out; + do + { + u32 nb = (remaining > 0x1000 - (addr & 0xFFF)) ? 0x1000 - (addr & 0xFFF) : remaining; + r = GDB_ReadTargetMemoryInPage(out8 + total, ctx, addr, nb); + if(R_SUCCEEDED(r)) + { + addr += nb; + total += nb; + remaining -= nb; + } + } + while(remaining > 0 && R_SUCCEEDED(r)); + + return total; +} + +u32 GDB_WriteTargetMemory(GDBContext *ctx, const void *in, u32 addr, u32 len) +{ + Result r = 0; + u32 remaining = len, total = 0; + do + { + u32 nb = (remaining > 0x1000 - (addr & 0xFFF)) ? 0x1000 - (addr & 0xFFF) : remaining; + r = GDB_WriteTargetMemoryInPage(ctx, (u8 *)in + total, addr, nb); + if(R_SUCCEEDED(r)) + { + addr += nb; + total += nb; + remaining -= nb; + } + } + while(remaining > 0 && R_SUCCEEDED(r)); + + return total; +} + +int GDB_SendMemory(GDBContext *ctx, const char *prefix, u32 prefixLen, u32 addr, u32 len) +{ + char buf[GDB_BUF_LEN]; + u8 membuf[GDB_BUF_LEN / 2]; + + if(prefix != NULL) + memcpy(buf, prefix, prefixLen); + else + prefixLen = 0; + + if(prefixLen + 2 * len > GDB_BUF_LEN) // gdb shouldn't send requests which responses don't fit in a packet + return prefix == NULL ? GDB_ReplyErrno(ctx, ENOMEM) : -1; + + u32 total = GDB_ReadTargetMemory(membuf, ctx, addr, len); + if(total == 0) + return prefix == NULL ? GDB_ReplyErrno(ctx, EFAULT) : -EFAULT; + else + { + GDB_EncodeHex(buf + prefixLen, membuf, total); + return GDB_SendPacket(ctx, buf, prefixLen + 2 * total); + } +} + +int GDB_WriteMemory(GDBContext *ctx, const void *buf, u32 addr, u32 len) +{ + u32 total = GDB_WriteTargetMemory(ctx, buf, addr, len); + if(total != len) + return GDB_ReplyErrno(ctx, EFAULT); + else + return GDB_ReplyOk(ctx); +} + +u32 GDB_SearchMemory(bool *found, GDBContext *ctx, u32 addr, u32 len, const void *pattern, u32 patternLen) +{ + u8 buf[0x1000 + 0x1000 * ((GDB_BUF_LEN + 0xFFF) / 0x1000)]; + u32 maxNbPages = 1 + ((GDB_BUF_LEN + 0xFFF) / 0x1000); + u32 curAddr = addr; + + s64 TTBCR; + svcGetSystemInfo(&TTBCR, 0x10002, 0); + while(curAddr < addr + len) + { + u32 nbPages; + u32 addrBase = curAddr & ~0xFFF, addrDispl = curAddr & 0xFFF; + + for(nbPages = 0; nbPages < maxNbPages; nbPages++) + { + if(addr >= (1u << (32 - (u32)TTBCR))) + { + u32 PA = svcConvertVAToPA((const void *)addr, false); + if(PA == 0 || (PA >= 0x10000000 && PA <= 0x18000000)) + break; + } + + if(R_FAILED(GDB_ReadTargetMemoryInPage(buf + 0x1000 * nbPages, ctx, addrBase + nbPages * 0x1000, 0x1000))) + break; + } + + u8 *pos = NULL; + if(addrDispl + patternLen <= 0x1000 * nbPages) + pos = memsearch(buf + addrDispl, pattern, 0x1000 * nbPages - addrDispl, patternLen); + + if(pos != NULL) + { + *found = true; + return addrBase + (pos - buf); + } + + curAddr = addrBase + 0x1000; + } + + *found = false; + return 0; +} + +GDB_DECLARE_HANDLER(ReadMemory) +{ + u32 lst[2]; + if(GDB_ParseHexIntegerList(lst, ctx->commandData, 2, 0) == NULL) + return GDB_ReplyErrno(ctx, EILSEQ); + + u32 addr = lst[0]; + u32 len = lst[1]; + + return GDB_SendMemory(ctx, NULL, 0, addr, len); +} + +GDB_DECLARE_HANDLER(WriteMemory) +{ + u32 lst[2]; + const char *dataStart = GDB_ParseHexIntegerList(lst, ctx->commandData, 2, ':'); + if(dataStart == NULL || *dataStart != ':') + return GDB_ReplyErrno(ctx, EILSEQ); + + dataStart++; + u32 addr = lst[0]; + u32 len = lst[1]; + + if(dataStart + 2 * len >= ctx->buffer + 4 + GDB_BUF_LEN) + return GDB_ReplyErrno(ctx, ENOMEM); + + u8 data[GDB_BUF_LEN / 2]; + u32 n = GDB_DecodeHex(data, dataStart, len); + + if(n != len) + return GDB_ReplyErrno(ctx, EILSEQ); + + return GDB_WriteMemory(ctx, data, addr, len); +} + +GDB_DECLARE_HANDLER(WriteMemoryRaw) +{ + u32 lst[2]; + const char *dataStart = GDB_ParseHexIntegerList(lst, ctx->commandData, 2, ':'); + if(dataStart == NULL || *dataStart != ':') + return GDB_ReplyErrno(ctx, EILSEQ); + + dataStart++; + u32 addr = lst[0]; + u32 len = lst[1]; + + if(dataStart + len >= ctx->buffer + 4 + GDB_BUF_LEN) + return GDB_ReplyErrno(ctx, ENOMEM); + + u8 data[GDB_BUF_LEN]; + u32 n = GDB_UnescapeBinaryData(data, dataStart, len); + + if(n != len) + return GDB_ReplyErrno(ctx, n); + + return GDB_WriteMemory(ctx, data, addr, len); +} + +GDB_DECLARE_QUERY_HANDLER(SearchMemory) +{ + u32 lst[2]; + u32 addr, len; + u8 pattern[GDB_BUF_LEN]; + const char *patternStart; + u32 patternLen; + bool found; + u32 foundAddr; + + if(strncmp(ctx->commandData, "memory:", 7) != 0) + return GDB_ReplyErrno(ctx, EILSEQ); + + ctx->commandData += 7; + patternStart = GDB_ParseIntegerList(lst, ctx->commandData, 2, ';', ';', 16, false); + if(patternStart == NULL || *patternStart != ';') + return GDB_ReplyErrno(ctx, EILSEQ); + + addr = lst[0]; + len = lst[1]; + + patternStart++; + patternLen = ctx->commandEnd - patternStart; + + patternLen = GDB_UnescapeBinaryData(pattern, patternStart, patternLen); + + foundAddr = GDB_SearchMemory(&found, ctx, addr, len, patternStart, patternLen); + + if(found) + return GDB_SendFormattedPacket(ctx, "1,%x", foundAddr); + else + return GDB_SendPacket(ctx, "0", 1); +} diff --git a/thermosphere/src/gdb/mem.h b/thermosphere/src/gdb/mem.h new file mode 100644 index 000000000..90ba33280 --- /dev/null +++ b/thermosphere/src/gdb/mem.h @@ -0,0 +1,24 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#pragma once + +#include "gdb.h" + +Result GDB_ReadTargetMemoryInPage(void *out, GDBContext *ctx, u32 addr, u32 len); +Result GDB_WriteTargetMemoryInPage(GDBContext *ctx, const void *in, u32 addr, u32 len); +u32 GDB_ReadTargetMemory(void *out, GDBContext *ctx, u32 addr, u32 len); +u32 GDB_WriteTargetMemory(GDBContext *ctx, const void *in, u32 addr, u32 len); + +int GDB_SendMemory(GDBContext *ctx, const char *prefix, u32 prefixLen, u32 addr, u32 len); +int GDB_WriteMemory(GDBContext *ctx, const void *buf, u32 addr, u32 len); +u32 GDB_SearchMemory(bool *found, GDBContext *ctx, u32 addr, u32 len, const void *pattern, u32 patternLen); + +GDB_DECLARE_HANDLER(ReadMemory); +GDB_DECLARE_HANDLER(WriteMemory); +GDB_DECLARE_HANDLER(WriteMemoryRaw); +GDB_DECLARE_QUERY_HANDLER(SearchMemory); diff --git a/thermosphere/src/gdb/net.c b/thermosphere/src/gdb/net.c new file mode 100644 index 000000000..b328b4b83 --- /dev/null +++ b/thermosphere/src/gdb/net.c @@ -0,0 +1,376 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#include "gdb/net.h" +#include +#include +#include "fmt.h" +#include "minisoc.h" + +u8 GDB_ComputeChecksum(const char *packetData, u32 len) +{ + u8 cksum = 0; + for(u32 i = 0; i < len; i++) + cksum += (u8)packetData[i]; + + return cksum; +} + +void GDB_EncodeHex(char *dst, const void *src, u32 len) +{ + static const char *alphabet = "0123456789abcdef"; + const u8 *src8 = (u8 *)src; + + for(u32 i = 0; i < len; i++) + { + dst[2 * i] = alphabet[(src8[i] & 0xf0) >> 4]; + dst[2 * i + 1] = alphabet[src8[i] & 0x0f]; + } +} + +static inline u32 GDB_DecodeHexDigit(char src, bool *ok) +{ + *ok = true; + if(src >= '0' && src <= '9') return src - '0'; + else if(src >= 'a' && src <= 'f') return 0xA + (src - 'a'); + else if(src >= 'A' && src <= 'F') return 0xA + (src - 'A'); + else + { + *ok = false; + return 0; + } +} + +u32 GDB_DecodeHex(void *dst, const char *src, u32 len) +{ + u32 i = 0; + bool ok = true; + u8 *dst8 = (u8 *)dst; + for(i = 0; i < len && ok && src[2 * i] != 0 && src[2 * i + 1] != 0; i++) + dst8[i] = (GDB_DecodeHexDigit(src[2 * i], &ok) << 4) | GDB_DecodeHexDigit(src[2 * i + 1], &ok); + + return (!ok) ? i - 1 : i; +} + +u32 GDB_EscapeBinaryData(u32 *encodedCount, void *dst, const void *src, u32 len, u32 maxLen) +{ + u8 *dst8 = (u8 *)dst; + const u8 *src8 = (const u8 *)src; + + maxLen = maxLen >= len ? len : maxLen; + + while((uintptr_t)dst8 < (uintptr_t)dst + maxLen) + { + if(*src8 == '$' || *src8 == '#' || *src8 == '}' || *src8 == '*') + { + if ((uintptr_t)dst8 + 1 >= (uintptr_t)dst + maxLen) + break; + *dst8++ = '}'; + *dst8++ = *src8++ ^ 0x20; + } + else + *dst8++ = *src8++; + } + + *encodedCount = dst8 - (u8 *)dst; + return src8 - (u8 *)src; +} + +u32 GDB_UnescapeBinaryData(void *dst, const void *src, u32 len) +{ + u8 *dst8 = (u8 *)dst; + const u8 *src8 = (const u8 *)src; + + while((uintptr_t)src8 < (uintptr_t)src + len) + { + if(*src8 == '}') + { + src8++; + *dst8++ = *src8++ ^ 0x20; + } + else + *dst8++ = *src8++; + } + + return dst8 - (u8 *)dst; +} + +const char *GDB_ParseIntegerList(u32 *dst, const char *src, u32 nb, char sep, char lastSep, u32 base, bool allowPrefix) +{ + const char *pos = src; + const char *endpos; + bool ok; + + for(u32 i = 0; i < nb; i++) + { + u32 n = xstrtoul(pos, (char **)&endpos, (int) base, allowPrefix, &ok); + if(!ok || endpos == pos) + return NULL; + + if(i != nb - 1) + { + if(*endpos != sep) + return NULL; + pos = endpos + 1; + } + else + { + if(*endpos != lastSep && *endpos != 0) + return NULL; + pos = endpos; + } + + dst[i] = n; + } + + return pos; +} + +const char *GDB_ParseIntegerList64(u64 *dst, const char *src, u32 nb, char sep, char lastSep, u32 base, bool allowPrefix) +{ + const char *pos = src; + const char *endpos; + bool ok; + + for(u32 i = 0; i < nb; i++) + { + u64 n = xstrtoull(pos, (char **)&endpos, (int) base, allowPrefix, &ok); + if(!ok || endpos == pos) + return NULL; + + if(i != nb - 1) + { + if(*endpos != sep) + return NULL; + pos = endpos + 1; + } + else + { + if(*endpos != lastSep && *endpos != 0) + return NULL; + pos = endpos; + } + + dst[i] = n; + } + + return pos; +} + +const char *GDB_ParseHexIntegerList(u32 *dst, const char *src, u32 nb, char lastSep) +{ + return GDB_ParseIntegerList(dst, src, nb, ',', lastSep, 16, false); +} + +const char *GDB_ParseHexIntegerList64(u64 *dst, const char *src, u32 nb, char lastSep) +{ + return GDB_ParseIntegerList64(dst, src, nb, ',', lastSep, 16, false); +} + +int GDB_ReceivePacket(GDBContext *ctx) +{ + char backupbuf[GDB_BUF_LEN + 4]; + memcpy(backupbuf, ctx->buffer, ctx->latestSentPacketSize); + memset(ctx->buffer, 0, sizeof(ctx->buffer)); + + int r = soc_recv(ctx->super.sockfd, ctx->buffer, sizeof(ctx->buffer), MSG_PEEK); + if(r < 1) + return -1; + if(ctx->buffer[0] == '+') // GDB sometimes acknowleges TCP acknowledgment packets (yes...). IDA does it properly + { + if(ctx->flags & GDB_FLAG_NOACK) + return -1; + + // Consume it + r = soc_recv(ctx->super.sockfd, ctx->buffer, 1, 0); + if(r != 1) + return -1; + + ctx->buffer[0] = 0; + + r = soc_recv(ctx->super.sockfd, ctx->buffer, sizeof(ctx->buffer), MSG_PEEK); + + if(r == -1) + goto packet_error; + } + else if(ctx->buffer[0] == '-') + { + soc_send(ctx->super.sockfd, backupbuf, ctx->latestSentPacketSize, 0); + return 0; + } + int maxlen = r > (int)sizeof(ctx->buffer) ? (int)sizeof(ctx->buffer) : r; + + if(ctx->buffer[0] == '$') // normal packet + { + char *pos; + for(pos = ctx->buffer; pos < ctx->buffer + maxlen && *pos != '#'; pos++); + + if(pos == ctx->buffer + maxlen) // malformed packet + return -1; + + else + { + u8 checksum; + r = soc_recv(ctx->super.sockfd, ctx->buffer, 3 + pos - ctx->buffer, 0); + if(r != 3 + pos - ctx->buffer || GDB_DecodeHex(&checksum, pos + 1, 1) != 1) + goto packet_error; + else if(GDB_ComputeChecksum(ctx->buffer + 1, pos - ctx->buffer - 1) != checksum) + goto packet_error; + + ctx->commandEnd = pos; + *pos = 0; // replace trailing '#' by a NUL character + } + } + else if(ctx->buffer[0] == '\x03') + { + r = soc_recv(ctx->super.sockfd, ctx->buffer, 1, 0); + if(r != 1) + goto packet_error; + + ctx->commandEnd = ctx->buffer; + } + + if(!(ctx->flags & GDB_FLAG_NOACK)) + { + int r2 = soc_send(ctx->super.sockfd, "+", 1, 0); + if(r2 != 1) + return -1; + } + + if(ctx->noAckSent) + { + ctx->flags |= GDB_FLAG_NOACK; + ctx->noAckSent = false; + } + + return r; + +packet_error: + if(!(ctx->flags & GDB_FLAG_NOACK)) + { + r = soc_send(ctx->super.sockfd, "-", 1, 0); + if(r != 1) + return -1; + else + return 0; + } + else + return -1; +} + +static int GDB_DoSendPacket(GDBContext *ctx, u32 len) +{ + int r = soc_send(ctx->super.sockfd, ctx->buffer, len, 0); + + if(r > 0) + ctx->latestSentPacketSize = r; + return r; +} + +int GDB_SendPacket(GDBContext *ctx, const char *packetData, u32 len) +{ + ctx->buffer[0] = '$'; + + memcpy(ctx->buffer + 1, packetData, len); + + char *checksumLoc = ctx->buffer + len + 1; + *checksumLoc++ = '#'; + + hexItoa(GDB_ComputeChecksum(packetData, len), checksumLoc, 2, false); + return GDB_DoSendPacket(ctx, 4 + len); +} + +int GDB_SendFormattedPacket(GDBContext *ctx, const char *packetDataFmt, ...) +{ + // It goes without saying you shouldn't use that with user-controlled data... + char buf[GDB_BUF_LEN + 1]; + va_list args; + va_start(args, packetDataFmt); + int n = vsprintf(buf, packetDataFmt, args); + va_end(args); + + if(n < 0) return -1; + else return GDB_SendPacket(ctx, buf, (u32)n); +} + +int GDB_SendHexPacket(GDBContext *ctx, const void *packetData, u32 len) +{ + if(4 + 2 * len > GDB_BUF_LEN) + return -1; + + ctx->buffer[0] = '$'; + GDB_EncodeHex(ctx->buffer + 1, packetData, len); + + char *checksumLoc = ctx->buffer + 2 * len + 1; + *checksumLoc++ = '#'; + + hexItoa(GDB_ComputeChecksum(ctx->buffer + 1, 2 * len), checksumLoc, 2, false); + return GDB_DoSendPacket(ctx, 4 + 2 * len); +} + +int GDB_SendStreamData(GDBContext *ctx, const char *streamData, u32 offset, u32 length, u32 totalSize, bool forceEmptyLast) +{ + char buf[GDB_BUF_LEN]; + if(length > GDB_BUF_LEN - 1) + length = GDB_BUF_LEN - 1; + + if((forceEmptyLast && offset >= totalSize) || (!forceEmptyLast && offset + length >= totalSize)) + { + length = offset >= totalSize ? 0 : totalSize - offset; + buf[0] = 'l'; + memcpy(buf + 1, streamData + offset, length); + return GDB_SendPacket(ctx, buf, 1 + length); + } + else + { + buf[0] = 'm'; + memcpy(buf + 1, streamData + offset, length); + return GDB_SendPacket(ctx, buf, 1 + length); + } +} + +int GDB_SendDebugString(GDBContext *ctx, const char *fmt, ...) // unsecure +{ + /*if(ctx->state == GDB_STATE_DETACHING || !(ctx->flags & GDB_FLAG_PROCESS_CONTINUING)) + return 0;*/ + + char formatted[(GDB_BUF_LEN - 1) / 2 + 1]; + ctx->buffer[0] = '$'; + ctx->buffer[1] = 'O'; + + va_list args; + va_start(args, fmt); + int n = vsprintf(formatted, fmt, args); + va_end(args); + + if(n <= 0) return n; + GDB_EncodeHex(ctx->buffer + 2, formatted, 2 * n); + + char *checksumLoc = ctx->buffer + 2 * n + 2; + *checksumLoc++ = '#'; + + hexItoa(GDB_ComputeChecksum(ctx->buffer + 1, 2 * n + 1), checksumLoc, 2, false); + + return GDB_DoSendPacket(ctx, 5 + 2 * n); +} + +int GDB_ReplyEmpty(GDBContext *ctx) +{ + return GDB_SendPacket(ctx, "", 0); +} + +int GDB_ReplyOk(GDBContext *ctx) +{ + return GDB_SendPacket(ctx, "OK", 2); +} + +int GDB_ReplyErrno(GDBContext *ctx, int no) +{ + char buf[] = "E01"; + hexItoa((u8)no, buf + 1, 2, false); + return GDB_SendPacket(ctx, buf, 3); +} diff --git a/thermosphere/src/gdb/net.h b/thermosphere/src/gdb/net.h new file mode 100644 index 000000000..c4676a502 --- /dev/null +++ b/thermosphere/src/gdb/net.h @@ -0,0 +1,31 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#pragma once + +#include "gdb.h" +#define _REENT_ONLY +#include + +u8 GDB_ComputeChecksum(const char *packetData, u32 len); +void GDB_EncodeHex(char *dst, const void *src, u32 len); +u32 GDB_DecodeHex(void *dst, const char *src, u32 len); +u32 GDB_EscapeBinaryData(u32 *encodedCount, void *dst, const void *src, u32 len, u32 maxLen); +u32 GDB_UnescapeBinaryData(void *dst, const void *src, u32 len); +const char *GDB_ParseIntegerList(u32 *dst, const char *src, u32 nb, char sep, char lastSep, u32 base, bool allowPrefix); +const char *GDB_ParseHexIntegerList(u32 *dst, const char *src, u32 nb, char lastSep); +const char *GDB_ParseIntegerList64(u64 *dst, const char *src, u32 nb, char sep, char lastSep, u32 base, bool allowPrefix); +const char *GDB_ParseHexIntegerList64(u64 *dst, const char *src, u32 nb, char lastSep); +int GDB_ReceivePacket(GDBContext *ctx); +int GDB_SendPacket(GDBContext *ctx, const char *packetData, u32 len); +int GDB_SendFormattedPacket(GDBContext *ctx, const char *packetDataFmt, ...); +int GDB_SendHexPacket(GDBContext *ctx, const void *packetData, u32 len); +int GDB_SendStreamData(GDBContext *ctx, const char *streamData, u32 offset, u32 length, u32 totalSize, bool forceEmptyLast); +int GDB_SendDebugString(GDBContext *ctx, const char *fmt, ...); // unsecure +int GDB_ReplyEmpty(GDBContext *ctx); +int GDB_ReplyOk(GDBContext *ctx); +int GDB_ReplyErrno(GDBContext *ctx, int no); diff --git a/thermosphere/src/gdb/query.c b/thermosphere/src/gdb/query.c new file mode 100644 index 000000000..4c39b0414 --- /dev/null +++ b/thermosphere/src/gdb/query.c @@ -0,0 +1,146 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#include "gdb/query.h" +#include "gdb/xfer.h" +#include "gdb/thread.h" +#include "gdb/mem.h" +#include "gdb/net.h" +#include "gdb/remote_command.h" + +typedef enum GDBQueryDirection +{ + GDB_QUERY_DIRECTION_READ, + GDB_QUERY_DIRECTION_WRITE +} GDBQueryDirection; + +// https://gcc.gnu.org/onlinedocs/gcc-5.3.0/cpp/Stringification.html +#define xstr(s) str(s) +#define str(s) #s + +#define GDB_QUERY_HANDLER_LIST_ITEM_3(name, name2, direction) { name, GDB_QUERY_HANDLER(name2), GDB_QUERY_DIRECTION_##direction } +#define GDB_QUERY_HANDLER_LIST_ITEM(name, direction) GDB_QUERY_HANDLER_LIST_ITEM_3(xstr(name), name, direction) + +static const struct +{ + const char *name; + GDBCommandHandler handler; + GDBQueryDirection direction; +} gdbQueryHandlers[] = +{ + GDB_QUERY_HANDLER_LIST_ITEM(Supported, READ), + GDB_QUERY_HANDLER_LIST_ITEM(Xfer, READ), + GDB_QUERY_HANDLER_LIST_ITEM(StartNoAckMode, WRITE), + GDB_QUERY_HANDLER_LIST_ITEM(Attached, READ), + GDB_QUERY_HANDLER_LIST_ITEM(fThreadInfo, READ), + GDB_QUERY_HANDLER_LIST_ITEM(sThreadInfo, READ), + GDB_QUERY_HANDLER_LIST_ITEM(ThreadEvents, WRITE), + GDB_QUERY_HANDLER_LIST_ITEM(ThreadExtraInfo, READ), + GDB_QUERY_HANDLER_LIST_ITEM(GetTLSAddr, READ), + GDB_QUERY_HANDLER_LIST_ITEM_3("C", CurrentThreadId, READ), + GDB_QUERY_HANDLER_LIST_ITEM_3("Search", SearchMemory, READ), + GDB_QUERY_HANDLER_LIST_ITEM(CatchSyscalls, WRITE), + GDB_QUERY_HANDLER_LIST_ITEM(Rcmd, READ), +}; + +static int GDB_HandleQuery(GDBContext *ctx, GDBQueryDirection direction) +{ + char *nameBegin = ctx->commandData; // w/o leading 'q'/'Q' + if(*nameBegin == 0) + return GDB_ReplyErrno(ctx, EILSEQ); + + char *nameEnd; + char *queryData = NULL; + + for(nameEnd = nameBegin; *nameEnd != 0 && *nameEnd != ':' && *nameEnd != ','; nameEnd++); + if(*nameEnd != 0) + { + *nameEnd = 0; + queryData = nameEnd + 1; + } + else + queryData = nameEnd; + + for(u32 i = 0; i < sizeof(gdbQueryHandlers) / sizeof(gdbQueryHandlers[0]); i++) + { + if(strcmp(gdbQueryHandlers[i].name, nameBegin) == 0 && gdbQueryHandlers[i].direction == direction) + { + ctx->commandData = queryData; + return gdbQueryHandlers[i].handler(ctx); + } + } + + return GDB_HandleUnsupported(ctx); // No handler found! +} + +int GDB_HandleReadQuery(GDBContext *ctx) +{ + return GDB_HandleQuery(ctx, GDB_QUERY_DIRECTION_READ); +} + +int GDB_HandleWriteQuery(GDBContext *ctx) +{ + return GDB_HandleQuery(ctx, GDB_QUERY_DIRECTION_WRITE); +} + +GDB_DECLARE_QUERY_HANDLER(Supported) +{ + return GDB_SendFormattedPacket(ctx, + "PacketSize=%x;" + "qXfer:features:read+;qXfer:osdata:read+;" + "QStartNoAckMode+;QThreadEvents+;QCatchSyscalls+;" + "vContSupported+;swbreak+", + + GDB_BUF_LEN // should have been sizeof(ctx->buffer) but GDB memory functions are bugged + ); +} + +GDB_DECLARE_QUERY_HANDLER(StartNoAckMode) +{ + ctx->noAckSent = true; + return GDB_ReplyOk(ctx); +} + +GDB_DECLARE_QUERY_HANDLER(Attached) +{ + return GDB_SendPacket(ctx, (ctx->flags & GDB_FLAG_CREATED) ? "0" : "1", 1); +} + +GDB_DECLARE_QUERY_HANDLER(CatchSyscalls) +{ + if(ctx->commandData[0] == '0') + { + memset(ctx->svcMask, 0, 32); + return R_SUCCEEDED(svcKernelSetState(0x10002, ctx->pid, false)) ? GDB_ReplyOk(ctx) : GDB_ReplyErrno(ctx, EPERM); + } + else if(ctx->commandData[0] == '1') + { + if(ctx->commandData[1] == ';') + { + u32 id; + const char *pos = ctx->commandData + 1; + memset(ctx->svcMask, 0, 32); + + do + { + pos = GDB_ParseHexIntegerList(&id, pos + 1, 1, ';'); + if(pos == NULL) + return GDB_ReplyErrno(ctx, EILSEQ); + + if(id < 0xFE) + ctx->svcMask[id / 32] |= 1 << (31 - (id % 32)); + } + while(*pos != 0); + } + else + memset(ctx->svcMask, 0xFF, 32); + + return R_SUCCEEDED(svcKernelSetState(0x10002, ctx->pid, true, ctx->svcMask)) ? GDB_ReplyOk(ctx) : GDB_ReplyErrno(ctx, EPERM); + } + else + return GDB_ReplyErrno(ctx, EILSEQ); +} diff --git a/thermosphere/src/gdb/query.h b/thermosphere/src/gdb/query.h new file mode 100644 index 000000000..93ee5e862 --- /dev/null +++ b/thermosphere/src/gdb/query.h @@ -0,0 +1,18 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#pragma once + +#include "gdb.h" + +int GDB_HandleReadQuery(GDBContext *ctx); +int GDB_HandleWriteQuery(GDBContext *ctx); + +GDB_DECLARE_QUERY_HANDLER(Supported); +GDB_DECLARE_QUERY_HANDLER(StartNoAckMode); +GDB_DECLARE_QUERY_HANDLER(Attached); +GDB_DECLARE_QUERY_HANDLER(CatchSyscalls); diff --git a/thermosphere/src/gdb/regs.c b/thermosphere/src/gdb/regs.c new file mode 100644 index 000000000..9eb5dbbe5 --- /dev/null +++ b/thermosphere/src/gdb/regs.c @@ -0,0 +1,153 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#include "gdb/regs.h" +#include "gdb/net.h" + +GDB_DECLARE_HANDLER(ReadRegisters) +{ + if(ctx->selectedThreadId == 0) + ctx->selectedThreadId = ctx->currentThreadId; + + ThreadContext regs; + Result r = svcGetDebugThreadContext(®s, ctx->debug, ctx->selectedThreadId, THREADCONTEXT_CONTROL_ALL); + + if(R_FAILED(r)) + return GDB_ReplyErrno(ctx, EPERM); + + return GDB_SendHexPacket(ctx, ®s, sizeof(ThreadContext)); +} + +GDB_DECLARE_HANDLER(WriteRegisters) +{ + if(ctx->selectedThreadId == 0) + ctx->selectedThreadId = ctx->currentThreadId; + + ThreadContext regs; + + if(GDB_DecodeHex(®s, ctx->commandData, sizeof(ThreadContext)) != sizeof(ThreadContext)) + return GDB_ReplyErrno(ctx, EPERM); + + Result r = svcSetDebugThreadContext(ctx->debug, ctx->selectedThreadId, ®s, THREADCONTEXT_CONTROL_ALL); + if(R_FAILED(r)) + return GDB_ReplyErrno(ctx, EPERM); + else + return GDB_ReplyOk(ctx); +} + +static u32 GDB_ConvertRegisterNumber(ThreadContextControlFlags *flags, u32 gdbNum) +{ + if(gdbNum <= 15) + { + *flags = (gdbNum >= 13) ? THREADCONTEXT_CONTROL_CPU_SPRS : THREADCONTEXT_CONTROL_CPU_GPRS; + return gdbNum; + } + else if(gdbNum == 25) + { + *flags = THREADCONTEXT_CONTROL_CPU_SPRS; + return 16; + } + else if(gdbNum >= 26 && gdbNum <= 41) + { + *flags = THREADCONTEXT_CONTROL_FPU_GPRS; + return gdbNum - 26; + } + else if(gdbNum == 42 || gdbNum == 43) + { + *flags = THREADCONTEXT_CONTROL_FPU_SPRS; + return gdbNum - 42; + } + else + { + *flags = (ThreadContextControlFlags)0; + return 0; + } +} + +GDB_DECLARE_HANDLER(ReadRegister) +{ + if(ctx->selectedThreadId == 0) + ctx->selectedThreadId = ctx->currentThreadId; + + ThreadContext regs; + ThreadContextControlFlags flags; + u32 gdbRegNum; + + if(GDB_ParseHexIntegerList(&gdbRegNum, ctx->commandData, 1, 0) == NULL) + return GDB_ReplyErrno(ctx, EILSEQ); + + u32 n = GDB_ConvertRegisterNumber(&flags, gdbRegNum); + if(!flags) + return GDB_ReplyErrno(ctx, EINVAL); + + Result r = svcGetDebugThreadContext(®s, ctx->debug, ctx->selectedThreadId, flags); + + if(R_FAILED(r)) + return GDB_ReplyErrno(ctx, EPERM); + + if(flags & THREADCONTEXT_CONTROL_CPU_GPRS) + return GDB_SendHexPacket(ctx, ®s.cpu_registers.r[n], 4); + else if(flags & THREADCONTEXT_CONTROL_CPU_SPRS) + return GDB_SendHexPacket(ctx, ®s.cpu_registers.sp + (n - 13), 4); // hacky + else if(flags & THREADCONTEXT_CONTROL_FPU_GPRS) + return GDB_SendHexPacket(ctx, ®s.fpu_registers.d[n], 8); + else + return GDB_SendHexPacket(ctx, ®s.fpu_registers.fpscr + n, 4); // hacky +} + +GDB_DECLARE_HANDLER(WriteRegister) +{ + if(ctx->selectedThreadId == 0) + ctx->selectedThreadId = ctx->currentThreadId; + + ThreadContext regs; + ThreadContextControlFlags flags; + u32 gdbRegNum; + + const char *valueStart = GDB_ParseHexIntegerList(&gdbRegNum, ctx->commandData, 1, '='); + if(valueStart == NULL || *valueStart != '=') + return GDB_ReplyErrno(ctx, EILSEQ); + + valueStart++; + + u32 n = GDB_ConvertRegisterNumber(&flags, gdbRegNum); + u32 value; + u64 value64; + + if(flags & THREADCONTEXT_CONTROL_FPU_GPRS) + { + if(GDB_DecodeHex(&value64, valueStart, 8) != 8 || valueStart[16] != 0) + return GDB_ReplyErrno(ctx, EINVAL); + } + else if(flags) + { + if(GDB_DecodeHex(&value, valueStart, 4) != 4 || valueStart[8] != 0) + return GDB_ReplyErrno(ctx, EINVAL); + } + else + return GDB_ReplyErrno(ctx, EINVAL); + + Result r = svcGetDebugThreadContext(®s, ctx->debug, ctx->selectedThreadId, flags); + + if(R_FAILED(r)) + return GDB_ReplyErrno(ctx, EPERM); + + if(flags & THREADCONTEXT_CONTROL_CPU_GPRS) + regs.cpu_registers.r[n] = value; + else if(flags & THREADCONTEXT_CONTROL_CPU_SPRS) + *(®s.cpu_registers.sp + (n - 13)) = value; // hacky + else if(flags & THREADCONTEXT_CONTROL_FPU_GPRS) + memcpy(®s.fpu_registers.d[n], &value64, 8); + else + *(®s.fpu_registers.fpscr + n) = value; // hacky + + r = svcSetDebugThreadContext(ctx->debug, ctx->selectedThreadId, ®s, flags); + if(R_FAILED(r)) + return GDB_ReplyErrno(ctx, EPERM); + else + return GDB_ReplyOk(ctx); +} diff --git a/thermosphere/src/gdb/regs.h b/thermosphere/src/gdb/regs.h new file mode 100644 index 000000000..dcbff6017 --- /dev/null +++ b/thermosphere/src/gdb/regs.h @@ -0,0 +1,15 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#pragma once + +#include "gdb.h" + +GDB_DECLARE_HANDLER(ReadRegisters); +GDB_DECLARE_HANDLER(WriteRegisters); +GDB_DECLARE_HANDLER(ReadRegister); +GDB_DECLARE_HANDLER(WriteRegister); diff --git a/thermosphere/src/gdb/remote_command.c b/thermosphere/src/gdb/remote_command.c new file mode 100644 index 000000000..f6937cfca --- /dev/null +++ b/thermosphere/src/gdb/remote_command.c @@ -0,0 +1,338 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#include "gdb/remote_command.h" +#include "gdb/net.h" +#include "csvc.h" +#include "fmt.h" +#include "gdb/breakpoints.h" + +struct +{ + const char *name; + GDBCommandHandler handler; +} remoteCommandHandlers[] = +{ + { "syncrequestinfo" , GDB_REMOTE_COMMAND_HANDLER(SyncRequestInfo) }, + { "translatehandle" , GDB_REMOTE_COMMAND_HANDLER(TranslateHandle) }, + { "getmmuconfig" , GDB_REMOTE_COMMAND_HANDLER(GetMmuConfig) }, + { "getmemregions" , GDB_REMOTE_COMMAND_HANDLER(GetMemRegions) }, + { "flushcaches" , GDB_REMOTE_COMMAND_HANDLER(FlushCaches) }, + { "toggleextmemaccess", GDB_REMOTE_COMMAND_HANDLER(ToggleExternalMemoryAccess) }, +}; + +static const char *GDB_SkipSpaces(const char *pos) +{ + const char *nextpos; + for(nextpos = pos; *nextpos != 0 && ((*nextpos >= 9 && *nextpos <= 13) || *nextpos == ' '); nextpos++); + return nextpos; +} + +GDB_DECLARE_REMOTE_COMMAND_HANDLER(SyncRequestInfo) +{ + char outbuf[GDB_BUF_LEN / 2 + 1]; + Result r; + int n; + + if(ctx->commandData[0] != 0) + return GDB_ReplyErrno(ctx, EILSEQ); + + if(ctx->selectedThreadId == 0) + ctx->selectedThreadId = ctx->currentThreadId; + + if(ctx->selectedThreadId == 0) + { + n = sprintf(outbuf, "Cannot run this command without a selected thread.\n"); + goto end; + } + + u32 id; + u32 cmdId; + ThreadContext regs; + u32 instr; + Handle process; + r = svcOpenProcess(&process, ctx->pid); + if(R_FAILED(r)) + { + n = sprintf(outbuf, "Invalid process (wtf?)\n"); + goto end; + } + + for(id = 0; id < MAX_DEBUG_THREAD && ctx->threadInfos[id].id != ctx->selectedThreadId; id++); + + r = svcGetDebugThreadContext(®s, ctx->debug, ctx->selectedThreadId, THREADCONTEXT_CONTROL_CPU_REGS); + + if(R_FAILED(r) || id == MAX_DEBUG_THREAD) + { + n = sprintf(outbuf, "Invalid or running thread.\n"); + goto end; + } + + r = svcReadProcessMemory(&cmdId, ctx->debug, ctx->threadInfos[id].tls + 0x80, 4); + if(R_FAILED(r)) + { + n = sprintf(outbuf, "Invalid or running thread.\n"); + goto end; + } + + r = svcReadProcessMemory(&instr, ctx->debug, regs.cpu_registers.pc, (regs.cpu_registers.cpsr & 0x20) ? 2 : 4); + + if(R_SUCCEEDED(r) && (((regs.cpu_registers.cpsr & 0x20) && instr == BREAKPOINT_INSTRUCTION_THUMB) || instr == BREAKPOINT_INSTRUCTION_ARM)) + { + u32 savedInstruction; + if(GDB_GetBreakpointInstruction(&savedInstruction, ctx, regs.cpu_registers.pc) == 0) + instr = savedInstruction; + } + + if(R_FAILED(r) || ((regs.cpu_registers.cpsr & 0x20) && !(instr == 0xDF32 || (instr == 0xDFFE && regs.cpu_registers.r[12] == 0x32))) + || (!(regs.cpu_registers.cpsr & 0x20) && !(instr == 0xEF000032 || (instr == 0xEF0000FE && regs.cpu_registers.r[12] == 0x32)))) + { + n = sprintf(outbuf, "The selected thread is not currently performing a sync request (svc 0x32).\n"); + goto end; + } + + char name[12]; + Handle handle; + r = svcCopyHandle(&handle, CUR_PROCESS_HANDLE, (Handle)regs.cpu_registers.r[0], process); + if(R_FAILED(r)) + { + n = sprintf(outbuf, "Invalid handle.\n"); + goto end; + } + + r = svcControlService(SERVICEOP_GET_NAME, name, handle); + if(R_FAILED(r)) + name[0] = 0; + + n = sprintf(outbuf, "%s 0x%lx, 0x%08lx\n", name, cmdId, ctx->threadInfos[id].tls + 0x80); + +end: + svcCloseHandle(handle); + svcCloseHandle(process); + return GDB_SendHexPacket(ctx, outbuf, n); +} + +GDB_DECLARE_REMOTE_COMMAND_HANDLER(TranslateHandle) +{ + bool ok; + u32 val; + char *end; + int n; + Result r; + u32 kernelAddr; + Handle handle, process; + s64 refcountRaw; + u32 refcount; + char classBuf[32], serviceBuf[12] = { 0 }; + char outbuf[GDB_BUF_LEN / 2 + 1]; + + if(ctx->commandData[0] == 0) + return GDB_ReplyErrno(ctx, EILSEQ); + + val = xstrtoul(ctx->commandData, &end, 0, true, &ok); + + if(!ok) + return GDB_ReplyErrno(ctx, EILSEQ); + + end = (char *)GDB_SkipSpaces(end); + + if(*end != 0) + return GDB_ReplyErrno(ctx, EILSEQ); + + r = svcOpenProcess(&process, ctx->pid); + if(R_FAILED(r)) + { + n = sprintf(outbuf, "Invalid process (wtf?)\n"); + goto end; + } + + r = svcCopyHandle(&handle, CUR_PROCESS_HANDLE, (Handle)val, process); + if(R_FAILED(r)) + { + n = sprintf(outbuf, "Invalid handle.\n"); + goto end; + } + + svcTranslateHandle(&kernelAddr, classBuf, handle); + svcGetHandleInfo(&refcountRaw, handle, 1); + svcControlService(SERVICEOP_GET_NAME, serviceBuf, handle); + refcount = (u32)(refcountRaw - 1); + if(serviceBuf[0] != 0) + n = sprintf(outbuf, "(%s *)0x%08lx /* %s handle, %lu %s */\n", classBuf, kernelAddr, serviceBuf, refcount, refcount == 1 ? "reference" : "references"); + else + n = sprintf(outbuf, "(%s *)0x%08lx /* %lu %s */\n", classBuf, kernelAddr, refcount, refcount == 1 ? "reference" : "references"); + +end: + svcCloseHandle(handle); + svcCloseHandle(process); + return GDB_SendHexPacket(ctx, outbuf, n); +} + +extern bool isN3DS; +GDB_DECLARE_REMOTE_COMMAND_HANDLER(GetMmuConfig) +{ + int n; + char outbuf[GDB_BUF_LEN / 2 + 1]; + Result r; + Handle process; + + if(ctx->commandData[0] != 0) + return GDB_ReplyErrno(ctx, EILSEQ); + + r = svcOpenProcess(&process, ctx->pid); + if(R_FAILED(r)) + n = sprintf(outbuf, "Invalid process (wtf?)\n"); + else + { + s64 TTBCR, TTBR0; + svcGetSystemInfo(&TTBCR, 0x10002, 0); + svcGetProcessInfo(&TTBR0, process, 0x10008); + n = sprintf(outbuf, "TTBCR = %lu\nTTBR0 = 0x%08lx\nTTBR1 =", (u32)TTBCR, (u32)TTBR0); + for(u32 i = 0; i < (isN3DS ? 4 : 2); i++) + { + s64 TTBR1; + svcGetSystemInfo(&TTBR1, 0x10002, 1 + i); + + if(i == (isN3DS ? 3 : 1)) + n += sprintf(outbuf + n, " 0x%08lx\n", (u32)TTBR1); + else + n += sprintf(outbuf + n, " 0x%08lx /", (u32)TTBR1); + } + svcCloseHandle(process); + } + + return GDB_SendHexPacket(ctx, outbuf, n); +} + +static const char *FormatMemPerm(u32 perm) +{ + if (perm == MEMPERM_DONTCARE) + return "???"; + + static char buf[4] = {0}; + + buf[0] = perm & MEMPERM_READ ? 'r' : '-'; + buf[1] = perm & MEMPERM_WRITE ? 'w' : '-'; + buf[2] = perm & MEMPERM_EXECUTE ? 'x' : '-'; + + return buf; +} + +static const char *FormatMemState(u32 state) +{ + if (state > 11) + return "Unknown"; + + static const char *states[12] = + { + "Free", + "Reserved", + "IO", + "Static", + "Code", + "Private", + "Shared", + "Continuous", + "Aliased", + "Alias", + "AliasCode", + "Locked" + }; + + return states[state]; +} + +GDB_DECLARE_REMOTE_COMMAND_HANDLER(GetMemRegions) +{ + u32 address = 0; + u32 posInBuffer = 0; + u32 maxPosInBuffer = GDB_BUF_LEN / 2 - 35; ///< 35 is the maximum length of a formatted region + Handle handle; + MemInfo memi; + PageInfo pagei; + char outbuf[GDB_BUF_LEN / 2 + 1]; + + if(R_FAILED(svcOpenProcess(&handle, ctx->pid))) + { + posInBuffer = sprintf(outbuf, "Invalid process (wtf?)\n"); + goto end; + } + + while (address < 0x40000000 ///< Limit to check for regions + && posInBuffer < maxPosInBuffer + && R_SUCCEEDED(svcQueryProcessMemory(&memi, &pagei, handle, address))) + { + // Update the address for next region + address = memi.base_addr + memi.size; + + // If region isn't FREE then add it to the list + if (memi.state != MEMSTATE_FREE) + { + const char *perm = FormatMemPerm(memi.perm); + const char *state = FormatMemState(memi.state); + + posInBuffer += sprintf(outbuf + posInBuffer, "%08lx - %08lx %s %s\n", + memi.base_addr, address, perm, state); + } + } + + svcCloseHandle(handle); + +end: + return GDB_SendHexPacket(ctx, outbuf, posInBuffer); +} + +GDB_DECLARE_REMOTE_COMMAND_HANDLER(FlushCaches) +{ + if(ctx->commandData[0] != 0) + return GDB_ReplyErrno(ctx, EILSEQ); + + svcFlushEntireDataCache(); + svcInvalidateEntireInstructionCache(); + + return GDB_ReplyOk(ctx); +} + +GDB_DECLARE_REMOTE_COMMAND_HANDLER(ToggleExternalMemoryAccess) +{ + int n; + char outbuf[GDB_BUF_LEN / 2 + 1]; + + ctx->enableExternalMemoryAccess = !ctx->enableExternalMemoryAccess; + + n = sprintf(outbuf, "External memory access %s successfully.\n", ctx->enableExternalMemoryAccess ? "enabled" : "disabled"); + + return GDB_SendHexPacket(ctx, outbuf, n); +} + +GDB_DECLARE_QUERY_HANDLER(Rcmd) +{ + char commandData[GDB_BUF_LEN / 2 + 1]; + char *endpos; + const char *errstr = "Unrecognized command.\n"; + u32 len = strlen(ctx->commandData); + + if(len == 0 || (len % 2) == 1 || GDB_DecodeHex(commandData, ctx->commandData, len / 2) != len / 2) + return GDB_ReplyErrno(ctx, EILSEQ); + commandData[len / 2] = 0; + + for(endpos = commandData; !(*endpos >= 9 && *endpos <= 13) && *endpos != ' ' && *endpos != 0; endpos++); + + char *nextpos = (char *)GDB_SkipSpaces(endpos); + *endpos = 0; + + for(u32 i = 0; i < sizeof(remoteCommandHandlers) / sizeof(remoteCommandHandlers[0]); i++) + { + if(strcmp(commandData, remoteCommandHandlers[i].name) == 0) + { + ctx->commandData = nextpos; + return remoteCommandHandlers[i].handler(ctx); + } + } + + return GDB_SendHexPacket(ctx, errstr, strlen(errstr)); +} diff --git a/thermosphere/src/gdb/remote_command.h b/thermosphere/src/gdb/remote_command.h new file mode 100644 index 000000000..d719a2559 --- /dev/null +++ b/thermosphere/src/gdb/remote_command.h @@ -0,0 +1,22 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#pragma once + +#include "gdb.h" + +#define GDB_REMOTE_COMMAND_HANDLER(name) GDB_HANDLER(RemoteCommand##name) +#define GDB_DECLARE_REMOTE_COMMAND_HANDLER(name) GDB_DECLARE_HANDLER(RemoteCommand##name) + +GDB_DECLARE_REMOTE_COMMAND_HANDLER(SyncRequestInfo); +GDB_DECLARE_REMOTE_COMMAND_HANDLER(TranslateHandle); +GDB_DECLARE_REMOTE_COMMAND_HANDLER(GetMmuConfig); +GDB_DECLARE_REMOTE_COMMAND_HANDLER(GetMemRegions); +GDB_DECLARE_REMOTE_COMMAND_HANDLER(FlushCaches); +GDB_DECLARE_REMOTE_COMMAND_HANDLER(ToggleExternalMemoryAccess); + +GDB_DECLARE_QUERY_HANDLER(Rcmd); diff --git a/thermosphere/src/gdb/server.c b/thermosphere/src/gdb/server.c new file mode 100644 index 000000000..0536f9295 --- /dev/null +++ b/thermosphere/src/gdb/server.c @@ -0,0 +1,378 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#include "gdb/server.h" +#include "gdb/net.h" +#include "gdb/query.h" +#include "gdb/verbose.h" +#include "gdb/thread.h" +#include "gdb/debug.h" +#include "gdb/regs.h" +#include "gdb/mem.h" +#include "gdb/hio.h" +#include "gdb/watchpoints.h" +#include "gdb/breakpoints.h" +#include "gdb/stop_point.h" +#include "task_runner.h" + +Result GDB_InitializeServer(GDBServer *server) +{ + Result ret = server_init(&server->super); + if(ret != 0) + return ret; + + server->super.host = 0; + + server->super.accept_cb = (sock_accept_cb)GDB_AcceptClient; + server->super.data_cb = (sock_data_cb) GDB_DoPacket; + server->super.close_cb = (sock_close_cb) GDB_CloseClient; + + server->super.alloc = (sock_alloc_func) GDB_GetClient; + server->super.free = (sock_free_func) GDB_ReleaseClient; + + server->super.clients_per_server = 1; + + server->referenceCount = 0; + svcCreateEvent(&server->statusUpdated, RESET_ONESHOT); + svcCreateEvent(&server->statusUpdateReceived, RESET_STICKY); + + for(u32 i = 0; i < sizeof(server->ctxs) / sizeof(GDBContext); i++) + GDB_InitializeContext(server->ctxs + i); + + GDB_ResetWatchpoints(); + + return 0; +} + +void GDB_FinalizeServer(GDBServer *server) +{ + server_finalize(&server->super); + + // Kill the "next application" context if needed + for (u32 i = 0; i < MAX_DEBUG; i++) { + if (server->ctxs[i].debug != 0) + GDB_CloseClient(&server->ctxs[i]); + } + svcCloseHandle(server->statusUpdated); + svcCloseHandle(server->statusUpdateReceived); +} + +void GDB_IncrementServerReferenceCount(GDBServer *server) +{ + AtomicPostIncrement(&server->referenceCount); +} + +void GDB_DecrementServerReferenceCount(GDBServer *server) +{ + if(AtomicDecrement(&server->referenceCount) == 0) + GDB_FinalizeServer(server); +} + +void GDB_RunServer(GDBServer *server) +{ + server_bind(&server->super, GDB_PORT_BASE); + server_bind(&server->super, GDB_PORT_BASE + 1); + server_bind(&server->super, GDB_PORT_BASE + 2); + + server_bind(&server->super, GDB_PORT_BASE + 3); // next application + + server_run(&server->super); +} + +void GDB_LockAllContexts(GDBServer *server) +{ + for (u32 i = 0; i < MAX_DEBUG; i++) + RecursiveLock_Lock(&server->ctxs[i].lock); +} + +void GDB_UnlockAllContexts(GDBServer *server) +{ + for (u32 i = MAX_DEBUG; i > 0; i--) + RecursiveLock_Unlock(&server->ctxs[i - 1].lock); +} + +GDBContext *GDB_SelectAvailableContext(GDBServer *server, u16 minPort, u16 maxPort) +{ + GDBContext *ctx; + u16 port; + + GDB_LockAllContexts(server); + + // Get a context + u32 id; + for(id = 0; id < MAX_DEBUG && (server->ctxs[id].flags & GDB_FLAG_ALLOCATED_MASK); id++); + if(id < MAX_DEBUG) + ctx = &server->ctxs[id]; + else + { + GDB_UnlockAllContexts(server); + return NULL; + } + + // Get a port + for (port = minPort; port < maxPort; port++) + { + bool portUsed = false; + for(id = 0; id < MAX_DEBUG; id++) + { + if((server->ctxs[id].flags & GDB_FLAG_ALLOCATED_MASK) && server->ctxs[id].localPort == port) + portUsed = true; + } + + if (!portUsed) + break; + } + + if (port >= maxPort) + { + ctx->flags = ~GDB_FLAG_SELECTED; + ctx = NULL; + } + else + { + ctx->flags |= GDB_FLAG_SELECTED; + ctx->localPort = port; + ctx->parent = server; + } + + GDB_UnlockAllContexts(server); + return ctx; +} + +GDBContext *GDB_FindAllocatedContextByPid(GDBServer *server, u32 pid) +{ + GDB_LockAllContexts(server); + GDBContext *ctx = NULL; + for(u32 i = 0; i < MAX_DEBUG; i++) + { + if( + ((server->ctxs[i].flags & GDB_FLAG_SELECTED) || + (server->ctxs[i].state >= GDB_STATE_ATTACHED && server->ctxs[i].state < GDB_STATE_DETACHING)) + && server->ctxs[i].pid == pid + ) + ctx = &server->ctxs[i]; + } + GDB_UnlockAllContexts(server); + return ctx; +} + +int GDB_AcceptClient(GDBContext *ctx) +{ + Result r = 0; + + RecursiveLock_Lock(&ctx->lock); + ctx->state = GDB_STATE_CONNECTED; + ctx->latestSentPacketSize = 0; + + if (ctx->flags & GDB_FLAG_SELECTED) + r = GDB_AttachToProcess(ctx); + + RecursiveLock_Unlock(&ctx->lock); + + return R_SUCCEEDED(r) ? 0 : -1; +} + +int GDB_CloseClient(GDBContext *ctx) +{ + RecursiveLock_Lock(&ctx->lock); + svcClearEvent(ctx->processAttachedEvent); + ctx->eventToWaitFor = ctx->processAttachedEvent; + svcClearEvent(ctx->parent->statusUpdateReceived); + svcSignalEvent(ctx->parent->statusUpdated); // note: monitor will be waiting for lock + RecursiveLock_Unlock(&ctx->lock); + + if(ctx->parent->referenceCount >= 2) + svcWaitSynchronization(ctx->parent->statusUpdateReceived, -1LL); + + RecursiveLock_Lock(&ctx->lock); + if (ctx->state >= GDB_STATE_ATTACHED || ctx->debug != 0) + GDB_DetachFromProcess(ctx); + + ctx->localPort = 0; + ctx->enableExternalMemoryAccess = false; + ctx->flags = 0; + ctx->state = GDB_STATE_DISCONNECTED; + + ctx->catchThreadEvents = false; + + memset(&ctx->latestDebugEvent, 0, sizeof(DebugEventInfo)); + memset(ctx->memoryOsInfoXmlData, 0, sizeof(ctx->memoryOsInfoXmlData)); + memset(ctx->processesOsInfoXmlData, 0, sizeof(ctx->processesOsInfoXmlData)); + + for (u32 i = 0; i < MAX_TIO_OPEN_FILE; i++) + IFile_Close(&ctx->openTioFileInfos[i].f); + memset(ctx->openTioFileInfos, 0, sizeof(ctx->openTioFileInfos)); + ctx->numOpenTioFiles = 0; + + RecursiveLock_Unlock(&ctx->lock); + return 0; +} + +GDBContext *GDB_GetClient(GDBServer *server, u16 port) +{ + GDB_LockAllContexts(server); + GDBContext *ctx = NULL; + for (u32 i = 0; i < MAX_DEBUG; i++) + { + if (server->ctxs[i].localPort == port) + { + ctx = &server->ctxs[i]; + break; + } + } + + if (ctx != NULL) + { + // Context already tied to a port/selected + if (ctx->flags & GDB_FLAG_USED) + { + GDB_UnlockAllContexts(server); + return NULL; + } + + ctx->flags |= GDB_FLAG_USED; + ctx->state = GDB_STATE_CONNECTED; + ctx->parent = server; + } + else if (port >= GDB_PORT_BASE && port < GDB_PORT_BASE + MAX_DEBUG) + { + // Grab a free context + u32 id; + for(id = 0; id < MAX_DEBUG && (server->ctxs[id].flags & GDB_FLAG_ALLOCATED_MASK); id++); + if(id < MAX_DEBUG) + ctx = &server->ctxs[id]; + else + { + GDB_UnlockAllContexts(server); + return NULL; + } + + ctx->localPort = port; + ctx->flags |= GDB_FLAG_USED; + ctx->state = GDB_STATE_CONNECTED; + ctx->parent = server; + } + + GDB_UnlockAllContexts(server); + if (port == GDB_PORT_BASE + MAX_DEBUG && ctx != NULL) + { + // this is not sufficient/foolproof and is buggy: TaskRunner_WaitReady(); // Finish grabbing new process debug, if anything... + bool ok = false; + do + { + svcSleepThread(5 * 1000 * 1000LL); + RecursiveLock_Lock(&ctx->lock); + ok = ctx->debug != 0; + RecursiveLock_Unlock(&ctx->lock); + } + while (!ok); + } + + return ctx; +} + +void GDB_ReleaseClient(GDBServer *server, GDBContext *ctx) +{ + (void)server; + (void)ctx; +} + +static const struct +{ + char command; + GDBCommandHandler handler; +} gdbCommandHandlers[] = +{ + { '?', GDB_HANDLER(GetStopReason) }, + { '!', GDB_HANDLER(EnableExtendedMode) }, + { 'c', GDB_HANDLER(Continue) }, + { 'C', GDB_HANDLER(Continue) }, + { 'D', GDB_HANDLER(Detach) }, + { 'F', GDB_HANDLER(HioReply) }, + { 'g', GDB_HANDLER(ReadRegisters) }, + { 'G', GDB_HANDLER(WriteRegisters) }, + { 'H', GDB_HANDLER(SetThreadId) }, + { 'k', GDB_HANDLER(Kill) }, + { 'm', GDB_HANDLER(ReadMemory) }, + { 'M', GDB_HANDLER(WriteMemory) }, + { 'p', GDB_HANDLER(ReadRegister) }, + { 'P', GDB_HANDLER(WriteRegister) }, + { 'q', GDB_HANDLER(ReadQuery) }, + { 'Q', GDB_HANDLER(WriteQuery) }, + { 'R', GDB_HANDLER(Restart) }, + { 'T', GDB_HANDLER(IsThreadAlive) }, + { 'v', GDB_HANDLER(VerboseCommand) }, + { 'X', GDB_HANDLER(WriteMemoryRaw) }, + { 'z', GDB_HANDLER(ToggleStopPoint) }, + { 'Z', GDB_HANDLER(ToggleStopPoint) }, +}; + +static inline GDBCommandHandler GDB_GetCommandHandler(char command) +{ + static const u32 nbHandlers = sizeof(gdbCommandHandlers) / sizeof(gdbCommandHandlers[0]); + + u32 i; + for(i = 0; i < nbHandlers && gdbCommandHandlers[i].command != command; i++); + + return i < nbHandlers ? gdbCommandHandlers[i].handler : GDB_HANDLER(Unsupported); +} + +int GDB_DoPacket(GDBContext *ctx) +{ + int ret; + + RecursiveLock_Lock(&ctx->lock); + u32 oldFlags = ctx->flags; + + if(ctx->state == GDB_STATE_DISCONNECTED) + return -1; + + int r = GDB_ReceivePacket(ctx); + if(r == 0) + ret = 0; + else if(r == -1) + ret = -1; + else if(ctx->buffer[0] == '\x03') + { + GDB_HandleBreak(ctx); + ret = 0; + } + else if(ctx->buffer[0] == '$') + { + GDBCommandHandler handler = GDB_GetCommandHandler(ctx->buffer[1]); + ctx->commandData = ctx->buffer + 2; + ret = handler(ctx); + } + else + ret = 0; + + if(ctx->state == GDB_STATE_DETACHING) + { + if(ctx->flags & GDB_FLAG_EXTENDED_REMOTE) + { + ctx->state = GDB_STATE_CONNECTED; + RecursiveLock_Unlock(&ctx->lock); + return ret; + } + else + { + RecursiveLock_Unlock(&ctx->lock); + return -1; + } + } + + if((oldFlags & GDB_FLAG_PROCESS_CONTINUING) && !(ctx->flags & GDB_FLAG_PROCESS_CONTINUING)) + { + if(R_FAILED(svcBreakDebugProcess(ctx->debug))) + ctx->flags |= GDB_FLAG_PROCESS_CONTINUING; + } + else if(!(oldFlags & GDB_FLAG_PROCESS_CONTINUING) && (ctx->flags & GDB_FLAG_PROCESS_CONTINUING)) + svcSignalEvent(ctx->continuedEvent); + + RecursiveLock_Unlock(&ctx->lock); + return ret; +} diff --git a/thermosphere/src/gdb/server.h b/thermosphere/src/gdb/server.h new file mode 100644 index 000000000..5e2354c75 --- /dev/null +++ b/thermosphere/src/gdb/server.h @@ -0,0 +1,43 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#pragma once + +#include "gdb.h" + +#ifndef GDB_PORT_BASE +#define GDB_PORT_BASE 4000 +#endif + +typedef struct GDBServer +{ + sock_server super; + s32 referenceCount; + Handle statusUpdated; + Handle statusUpdateReceived; + GDBContext ctxs[MAX_DEBUG]; +} GDBServer; + +Result GDB_InitializeServer(GDBServer *server); +void GDB_FinalizeServer(GDBServer *server); + +void GDB_IncrementServerReferenceCount(GDBServer *server); +void GDB_DecrementServerReferenceCount(GDBServer *server); + +void GDB_RunServer(GDBServer *server); + +void GDB_LockAllContexts(GDBServer *server); +void GDB_UnlockAllContexts(GDBServer *server); + +GDBContext *GDB_SelectAvailableContext(GDBServer *server, u16 minPort, u16 maxPort); +GDBContext *GDB_FindAllocatedContextByPid(GDBServer *server, u32 pid); + +int GDB_AcceptClient(GDBContext *ctx); +int GDB_CloseClient(GDBContext *ctx); +GDBContext *GDB_GetClient(GDBServer *server, u16 port); +void GDB_ReleaseClient(GDBServer *server, GDBContext *ctx); +int GDB_DoPacket(GDBContext *ctx); diff --git a/thermosphere/src/gdb/stop_point.c b/thermosphere/src/gdb/stop_point.c new file mode 100644 index 000000000..64f8b1fe5 --- /dev/null +++ b/thermosphere/src/gdb/stop_point.c @@ -0,0 +1,52 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#include "gdb.h" +#include "gdb/net.h" +#include "gdb/watchpoints.h" +#include "gdb/breakpoints.h" + +GDB_DECLARE_HANDLER(ToggleStopPoint) +{ + bool add = ctx->commandData[-1] == 'Z'; + u32 lst[3]; + + const char *pos = GDB_ParseHexIntegerList(lst, ctx->commandData, 3, ';'); + if(pos == NULL) + return GDB_ReplyErrno(ctx, EILSEQ); + bool persist = *pos != 0 && strncmp(pos, ";cmds:1", 7) == 0; + + u32 kind = lst[0]; + u32 addr = lst[1]; + u32 size = lst[2]; + + int res; + static const WatchpointKind kinds[3] = { WATCHPOINT_WRITE, WATCHPOINT_READ, WATCHPOINT_READWRITE }; + switch(kind) + { + case 0: // software breakpoint + if(size != 2 && size != 4) + return GDB_ReplyEmpty(ctx); + else + { + res = add ? GDB_AddBreakpoint(ctx, addr, size == 2, persist) : + GDB_RemoveBreakpoint(ctx, addr); + return res == 0 ? GDB_ReplyOk(ctx) : GDB_ReplyErrno(ctx, -res); + } + + // Watchpoints + case 2: + case 3: + case 4: + res = add ? GDB_AddWatchpoint(ctx, addr, size, kinds[kind - 2]) : + GDB_RemoveWatchpoint(ctx, addr, kinds[kind - 2]); + return res == 0 ? GDB_ReplyOk(ctx) : GDB_ReplyErrno(ctx, -res); + + default: + return GDB_ReplyEmpty(ctx); + } +} diff --git a/thermosphere/src/gdb/stop_point.h b/thermosphere/src/gdb/stop_point.h new file mode 100644 index 000000000..7b3807218 --- /dev/null +++ b/thermosphere/src/gdb/stop_point.h @@ -0,0 +1,12 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#pragma once + +#include "gdb.h" + +GDB_DECLARE_HANDLER(ToggleStopPoint); diff --git a/thermosphere/src/gdb/thread.c b/thermosphere/src/gdb/thread.c new file mode 100644 index 000000000..20f655f93 --- /dev/null +++ b/thermosphere/src/gdb/thread.c @@ -0,0 +1,309 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#include "gdb/thread.h" +#include "gdb/net.h" +#include "fmt.h" +#include + +static s32 GDB_GetDynamicThreadPriority(GDBContext *ctx, u32 threadId) +{ + Handle process, thread; + Result r; + s32 prio = 65; + + r = svcOpenProcess(&process, ctx->pid); + if(R_FAILED(r)) + return 65; + + r = svcOpenThread(&thread, process, threadId); + if(R_FAILED(r)) + goto cleanup; + + r = svcGetThreadPriority(&prio, thread); + +cleanup: + svcCloseHandle(thread); + svcCloseHandle(process); + + return prio; +} + +struct ThreadIdWithCtx +{ + GDBContext *ctx; + u32 id; +}; + +static int thread_compare_func(const void *a_, const void *b_) +{ + const struct ThreadIdWithCtx *a = (const struct ThreadIdWithCtx *)a_; + const struct ThreadIdWithCtx *b = (const struct ThreadIdWithCtx *)b_; + + u32 maskA = 2, maskB = 2; + s32 prioAStatic = 65, prioBStatic = 65; + s32 prioADynamic = GDB_GetDynamicThreadPriority(a->ctx, a->id); + s32 prioBDynamic = GDB_GetDynamicThreadPriority(b->ctx, b->id); + s64 dummy; + + svcGetDebugThreadParam(&dummy, &maskA, a->ctx->debug, a->id, DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW); + svcGetDebugThreadParam(&dummy, &maskB, b->ctx->debug, b->id, DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW); + svcGetDebugThreadParam(&dummy, (u32 *)&prioAStatic, a->ctx->debug, a->id, DBGTHREAD_PARAMETER_PRIORITY); + svcGetDebugThreadParam(&dummy, (u32 *)&prioBStatic, b->ctx->debug, b->id, DBGTHREAD_PARAMETER_PRIORITY); + + if(maskA == 1 && maskB != 1) + return -1; + else if(maskA != 1 && maskB == 1) + return 1; + else if(prioADynamic != prioBDynamic) + return prioADynamic - prioBDynamic; + else + return prioAStatic - prioBStatic; +} + +u32 GDB_GetCurrentThreadFromList(GDBContext *ctx, u32 *threadIds, u32 nbThreads) +{ + struct ThreadIdWithCtx lst[MAX_DEBUG_THREAD]; + for(u32 i = 0; i < nbThreads; i++) + { + lst[i].ctx = ctx; + lst[i].id = threadIds[i]; + } + + qsort(lst, nbThreads, sizeof(struct ThreadIdWithCtx), thread_compare_func); + return lst[0].id; +} + +u32 GDB_GetCurrentThread(GDBContext *ctx) +{ + u32 threadIds[MAX_DEBUG_THREAD]; + + if(ctx->nbThreads == 0) + return 0; + + for(u32 i = 0; i < ctx->nbThreads; i++) + threadIds[i] = ctx->threadInfos[i].id; + + return GDB_GetCurrentThreadFromList(ctx, threadIds, ctx->nbThreads); +} + +GDB_DECLARE_HANDLER(SetThreadId) +{ + if(ctx->commandData[0] == 'g') + { + if(strncmp(ctx->commandData + 1, "-1", 2) == 0) + return GDB_ReplyErrno(ctx, EILSEQ); // a thread must be specified + + u32 id; + if(GDB_ParseHexIntegerList(&id, ctx->commandData + 1, 1, 0) == NULL) + return GDB_ReplyErrno(ctx, EILSEQ); + ctx->selectedThreadId = id; + return GDB_ReplyOk(ctx); + } + else if(ctx->commandData[0] == 'c') + { + // We can't stop/continue particular threads (uncompliant behavior) + if(strncmp(ctx->commandData + 1, "-1", 2) == 0) + ctx->selectedThreadIdForContinuing = 0; + else + { + u32 id; + if(GDB_ParseHexIntegerList(&id, ctx->commandData + 1, 1, 0) == NULL) + return GDB_ReplyErrno(ctx, EILSEQ); + ctx->selectedThreadIdForContinuing = id; + } + + return GDB_ReplyOk(ctx); + } + else + return GDB_ReplyErrno(ctx, EPERM); +} + +GDB_DECLARE_HANDLER(IsThreadAlive) +{ + u32 threadId; + s64 dummy; + u32 mask; + + if(GDB_ParseHexIntegerList(&threadId, ctx->commandData, 1, 0) == NULL) + return GDB_ReplyErrno(ctx, EILSEQ); + + Result r = svcGetDebugThreadParam(&dummy, &mask, ctx->debug, threadId, DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW); + if(R_SUCCEEDED(r) && mask != 2) + return GDB_ReplyOk(ctx); + else + return GDB_ReplyErrno(ctx, EPERM); +} + +GDB_DECLARE_QUERY_HANDLER(CurrentThreadId) +{ + if(ctx->currentThreadId == 0) + ctx->currentThreadId = GDB_GetCurrentThread(ctx); + + return ctx->currentThreadId != 0 ? GDB_SendFormattedPacket(ctx, "QC%lx", ctx->currentThreadId) : GDB_ReplyErrno(ctx, EPERM); +} + +static void GDB_GenerateThreadListData(GDBContext *ctx) +{ + u32 aliveThreadIds[MAX_DEBUG_THREAD]; + u32 nbAliveThreads = 0; // just in case. This is probably redundant + + for(u32 i = 0; i < ctx->nbThreads; i++) + { + s64 dummy; + u32 mask; + + Result r = svcGetDebugThreadParam(&dummy, &mask, ctx->debug, ctx->threadInfos[i].id, DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW); + if(R_SUCCEEDED(r) && mask != 2) + aliveThreadIds[nbAliveThreads++] = ctx->threadInfos[i].id; + } + + if(nbAliveThreads == 0) + ctx->threadListData[0] = 0; + + char *bufptr = ctx->threadListData; + + for(u32 i = 0; i < nbAliveThreads; i++) + bufptr += sprintf(bufptr, i == (nbAliveThreads - 1) ? "%lx" : "%lx,", aliveThreadIds[i]); +} + +static int GDB_SendThreadData(GDBContext *ctx) +{ + u32 sz = strlen(ctx->threadListData); + u32 len; + if(ctx->threadListDataPos >= sz) + len = 0; + else if(sz - ctx->threadListDataPos <= GDB_BUF_LEN - 1) + len = sz - ctx->threadListDataPos; + else + { + for(len = GDB_BUF_LEN - 1; ctx->threadListData[ctx->threadListDataPos + len] != ',' && len > 0; len--); + if(len > 0) + len--; + } + + int n = GDB_SendStreamData(ctx, ctx->threadListData, ctx->threadListDataPos, len, sz, true); + + if(ctx->threadListDataPos >= sz) + { + ctx->threadListDataPos = 0; + ctx->threadListData[0] = 0; + } + else + ctx->threadListDataPos += len; + + return n; +} + +GDB_DECLARE_QUERY_HANDLER(fThreadInfo) +{ + if(ctx->threadListData[0] == 0) + GDB_GenerateThreadListData(ctx); + + return GDB_SendThreadData(ctx); +} + +GDB_DECLARE_QUERY_HANDLER(sThreadInfo) +{ + return GDB_SendThreadData(ctx); +} + +GDB_DECLARE_QUERY_HANDLER(ThreadEvents) +{ + switch(ctx->commandData[0]) + { + case '0': + ctx->catchThreadEvents = false; + return GDB_ReplyOk(ctx); + case '1': + ctx->catchThreadEvents = true; + return GDB_ReplyOk(ctx); + default: + return GDB_ReplyErrno(ctx, EILSEQ); + } +} + +GDB_DECLARE_QUERY_HANDLER(ThreadExtraInfo) +{ + u32 id; + s64 dummy; + u32 val; + Result r; + int n; + + const char *sStatus; + char sThreadDynamicPriority[64], sThreadStaticPriority[64]; + char sCoreIdeal[64], sCoreCreator[64]; + char buf[512]; + + u32 tls = 0; + + if(GDB_ParseHexIntegerList(&id, ctx->commandData, 1, 0) == NULL) + return GDB_ReplyErrno(ctx, EILSEQ); + + for(u32 i = 0; i < MAX_DEBUG_THREAD; i++) + { + if(ctx->threadInfos[i].id == id) + tls = ctx->threadInfos[i].tls; + } + + r = svcGetDebugThreadParam(&dummy, &val, ctx->debug, id, DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW); + sStatus = R_SUCCEEDED(r) ? (val == 1 ? ", running, " : ", idle, ") : ""; + + val = (u32)GDB_GetDynamicThreadPriority(ctx, id); + if(val == 65) + sThreadDynamicPriority[0] = 0; + else + sprintf(sThreadDynamicPriority, "dynamic prio.: %ld, ", (s32)val); + + r = svcGetDebugThreadParam(&dummy, &val, ctx->debug, id, DBGTHREAD_PARAMETER_PRIORITY); + if(R_FAILED(r)) + sThreadStaticPriority[0] = 0; + else + sprintf(sThreadStaticPriority, "static prio.: %ld, ", (s32)val); + + r = svcGetDebugThreadParam(&dummy, &val, ctx->debug, id, DBGTHREAD_PARAMETER_CPU_IDEAL); + if(R_FAILED(r)) + sCoreIdeal[0] = 0; + else + sprintf(sCoreIdeal, "ideal core: %lu, ", val); + + r = svcGetDebugThreadParam(&dummy, &val, ctx->debug, id, DBGTHREAD_PARAMETER_CPU_CREATOR); // Creator = "first ran, and running the thread" + if(R_FAILED(r)) + sCoreCreator[0] = 0; + else + sprintf(sCoreCreator, "running on core %lu", val); + + n = sprintf(buf, "TLS: 0x%08lx%s%s%s%s%s", tls, sStatus, sThreadDynamicPriority, sThreadStaticPriority, + sCoreIdeal, sCoreCreator); + + return GDB_SendHexPacket(ctx, buf, (u32)n); +} + +GDB_DECLARE_QUERY_HANDLER(GetTLSAddr) +{ + u32 lst[3]; + if(GDB_ParseHexIntegerList(lst, ctx->commandData, 3, 0) == NULL) + return GDB_ReplyErrno(ctx, EILSEQ); + + // We don't care about the 'lm' parameter... + u32 id = lst[0]; + u32 offset = lst[1]; + + u32 tls = 0; + + for(u32 i = 0; i < MAX_DEBUG_THREAD; i++) + { + if(ctx->threadInfos[i].id == id) + tls = ctx->threadInfos[i].tls; + } + + if(tls == 0) + return GDB_ReplyErrno(ctx, EINVAL); + + return GDB_SendFormattedPacket(ctx, "%08lx", tls + offset); +} diff --git a/thermosphere/src/gdb/thread.h b/thermosphere/src/gdb/thread.h new file mode 100644 index 000000000..5e1a06081 --- /dev/null +++ b/thermosphere/src/gdb/thread.h @@ -0,0 +1,23 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#pragma once + +#include "gdb.h" + +u32 GDB_GetCurrentThreadFromList(GDBContext *ctx, u32 *threadIds, u32 nbThreads); +u32 GDB_GetCurrentThread(GDBContext *ctx); + +GDB_DECLARE_HANDLER(SetThreadId); +GDB_DECLARE_HANDLER(IsThreadAlive); + +GDB_DECLARE_QUERY_HANDLER(CurrentThreadId); +GDB_DECLARE_QUERY_HANDLER(fThreadInfo); +GDB_DECLARE_QUERY_HANDLER(sThreadInfo); +GDB_DECLARE_QUERY_HANDLER(ThreadEvents); +GDB_DECLARE_QUERY_HANDLER(ThreadExtraInfo); +GDB_DECLARE_QUERY_HANDLER(GetTLSAddr); diff --git a/thermosphere/src/gdb/verbose.c b/thermosphere/src/gdb/verbose.c new file mode 100644 index 000000000..c95066dbc --- /dev/null +++ b/thermosphere/src/gdb/verbose.c @@ -0,0 +1,58 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#include "gdb/verbose.h" +#include "gdb/net.h" +#include "gdb/debug.h" +#include "gdb/tio.h" + +static const struct +{ + const char *name; + GDBCommandHandler handler; +} gdbVerboseCommandHandlers[] = +{ + { "Attach", GDB_VERBOSE_HANDLER(Attach) }, + { "Cont?", GDB_VERBOSE_HANDLER(ContinueSupported) }, + { "Cont", GDB_VERBOSE_HANDLER(Continue) }, + { "File", GDB_VERBOSE_HANDLER(File) }, + { "MustReplyEmpty", GDB_HANDLER(Unsupported) }, + { "Run", GDB_VERBOSE_HANDLER(Run) }, +}; + +GDB_DECLARE_HANDLER(VerboseCommand) +{ + char *nameBegin = ctx->commandData; // w/o leading 'v' + if(*nameBegin == 0) + return GDB_ReplyErrno(ctx, EILSEQ); + + char *nameEnd; + char *vData = NULL; + + for(nameEnd = nameBegin; *nameEnd != 0 && *nameEnd != ';' && *nameEnd != ':'; nameEnd++); + if(*nameEnd != 0) + { + *nameEnd = 0; + vData = nameEnd + 1; + } + + for(u32 i = 0; i < sizeof(gdbVerboseCommandHandlers) / sizeof(gdbVerboseCommandHandlers[0]); i++) + { + if(strcmp(gdbVerboseCommandHandlers[i].name, nameBegin) == 0) + { + ctx->commandData = vData; + return gdbVerboseCommandHandlers[i].handler(ctx); + } + } + + return GDB_HandleUnsupported(ctx); // No handler found! +} + +GDB_DECLARE_VERBOSE_HANDLER(ContinueSupported) +{ + return GDB_SendPacket(ctx, "vCont;c;C", 9); +} diff --git a/thermosphere/src/gdb/verbose.h b/thermosphere/src/gdb/verbose.h new file mode 100644 index 000000000..2ae67ca79 --- /dev/null +++ b/thermosphere/src/gdb/verbose.h @@ -0,0 +1,13 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#pragma once + +#include "gdb.h" + +GDB_DECLARE_HANDLER(VerboseCommand); +GDB_DECLARE_VERBOSE_HANDLER(ContinueSupported); diff --git a/thermosphere/src/gdb/watchpoints.c b/thermosphere/src/gdb/watchpoints.c new file mode 100644 index 000000000..54762f6f6 --- /dev/null +++ b/thermosphere/src/gdb/watchpoints.c @@ -0,0 +1,151 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#include "gdb/watchpoints.h" +#include "csvc.h" + +#define _REENT_ONLY +#include + +/* + There are only 2 Watchpoint Register Pairs on MPCORE ARM11 CPUs, + and only 2 Breakpoint Register Pairs with context ID capabilities (BRP4-5) as well. + + We'll reserve and use all 4 of them +*/ + +RecursiveLock watchpointManagerLock; + +typedef struct Watchpoint +{ + u32 address; + u32 size; + WatchpointKind kind; + Handle debug; // => context ID +} Watchpoint; + +typedef struct WatchpointManager +{ + u32 total; + Watchpoint watchpoints[2]; +} WatchpointManager; + +static WatchpointManager manager; + +void GDB_ResetWatchpoints(void) +{ + static bool lockInitialized = false; + if(!lockInitialized) + { + RecursiveLock_Init(&watchpointManagerLock); + lockInitialized = true; + } + RecursiveLock_Lock(&watchpointManagerLock); + + svcKernelSetState(0x10003); // enable monitor mode debugging + svcKernelSetState(0x10004, 0); // disable watchpoint 0 + svcKernelSetState(0x10004, 1); // disable watchpoint 1 + + memset(&manager, 0, sizeof(WatchpointManager)); + + RecursiveLock_Unlock(&watchpointManagerLock); +} + +int GDB_AddWatchpoint(GDBContext *ctx, u32 address, u32 size, WatchpointKind kind) +{ + RecursiveLock_Lock(&watchpointManagerLock); + + u32 offset = address - (address & ~3); + + if(manager.total == 2) + return -EBUSY; + + if(size == 0 || (offset + size) > 4 || kind == WATCHPOINT_DISABLED) + return -EINVAL; + + if(GDB_GetWatchpointKind(ctx, address) != WATCHPOINT_DISABLED) + // Disallow duplicate watchpoints: the kernel doesn't give us sufficient info to differentiate them by kind (DFSR) + return -EINVAL; + + u32 id = manager.watchpoints[0].kind == WATCHPOINT_DISABLED ? 0 : 1; + u32 selectMask = ((1 << size) - 1) << offset; + + u32 WCR = (1 << 20) | /* linked */ + ((4 + id) << 16) | /* ID of the linked BRP */ + (selectMask << 5) | /* byte address select */ + ((u32)kind << 3) | /* kind */ + (2 << 1) | /* user mode only */ + (1 << 0) ; /* enabled */ + + s64 out; + + Result r = svcGetHandleInfo(&out, ctx->debug, 0x10000); // context ID + + if(R_SUCCEEDED(r)) + { + svcKernelSetState(id == 0 ? 0x10005 : 0x10006, address, WCR, (u32)out); // set watchpoint + Watchpoint *watchpoint = &manager.watchpoints[id]; + manager.total++; + watchpoint->address = address; + watchpoint->size = size; + watchpoint->kind = kind; + watchpoint->debug = ctx->debug; + ctx->watchpoints[ctx->nbWatchpoints++] = address; + RecursiveLock_Unlock(&watchpointManagerLock); + return 0; + } + else + { + RecursiveLock_Unlock(&watchpointManagerLock); + return -EINVAL; + } +} + +int GDB_RemoveWatchpoint(GDBContext *ctx, u32 address, WatchpointKind kind) +{ + RecursiveLock_Lock(&watchpointManagerLock); + + u32 id; + for(id = 0; id < 2 && manager.watchpoints[id].address != address && manager.watchpoints[id].debug != ctx->debug; id++); + + if(id == 2 || (kind != WATCHPOINT_DISABLED && manager.watchpoints[id].kind != kind)) + { + RecursiveLock_Unlock(&watchpointManagerLock); + return -EINVAL; + } + else + { + svcKernelSetState(0x10004, id); // disable watchpoint + + memset(&manager.watchpoints[id], 0, sizeof(Watchpoint)); + manager.total--; + + if(ctx->watchpoints[0] == address) + { + ctx->watchpoints[0] = ctx->watchpoints[1]; + ctx->watchpoints[1] = 0; + ctx->nbWatchpoints--; + } + else if(ctx->watchpoints[1] == address) + { + ctx->watchpoints[1] = 0; + ctx->nbWatchpoints--; + } + + RecursiveLock_Unlock(&watchpointManagerLock); + + return 0; + } +} + +WatchpointKind GDB_GetWatchpointKind(GDBContext *ctx, u32 address) +{ + u32 id; + for(id = 0; id < 2 && (manager.watchpoints[id].address != address || manager.watchpoints[id].debug != ctx->debug); id++); + + return id == 2 ? WATCHPOINT_DISABLED : manager.watchpoints[id].kind; +} diff --git a/thermosphere/src/gdb/watchpoints.h b/thermosphere/src/gdb/watchpoints.h new file mode 100644 index 000000000..0e304dbfb --- /dev/null +++ b/thermosphere/src/gdb/watchpoints.h @@ -0,0 +1,25 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#pragma once + +#include "gdb.h" + +typedef enum WatchpointKind +{ + WATCHPOINT_DISABLED = 0, + WATCHPOINT_READ, + WATCHPOINT_WRITE, + WATCHPOINT_READWRITE +} WatchpointKind; + +void GDB_ResetWatchpoints(void); // needed for software breakpoints to be detected as debug events as well + +int GDB_AddWatchpoint(GDBContext *ctx, u32 address, u32 size, WatchpointKind kind); +int GDB_RemoveWatchpoint(GDBContext *ctx, u32 address, WatchpointKind kind); + +WatchpointKind GDB_GetWatchpointKind(GDBContext *ctx, u32 address); diff --git a/thermosphere/src/gdb/xfer.c b/thermosphere/src/gdb/xfer.c new file mode 100644 index 000000000..e6a1fcdea --- /dev/null +++ b/thermosphere/src/gdb/xfer.c @@ -0,0 +1,257 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#include <3ds/os.h> + +#include "gdb/xfer.h" +#include "gdb/net.h" +#include "fmt.h" + +#include "osdata_cfw_version_template_xml.h" +#include "osdata_memory_template_xml.h" +#include "osdata_xml.h" +#include "target_xml.h" + +struct +{ + const char *name; + int (*handler)(GDBContext *ctx, bool write, const char *annex, u32 offset, u32 length); +} xferCommandHandlers[] = +{ + { "features", GDB_XFER_HANDLER(Features) }, + { "osdata", GDB_XFER_HANDLER(OsData) }, +}; + +GDB_DECLARE_XFER_HANDLER(Features) +{ + if(strcmp(annex, "target.xml") != 0 || write) + return GDB_ReplyEmpty(ctx); + else + return GDB_SendStreamData(ctx, (const char *)target_xml, offset, length, target_xml_size, false); +} + +struct +{ + const char *name; + int (*handler)(GDBContext *ctx, bool write, u32 offset, u32 length); +} xferOsDataCommandHandlers[] = +{ + { "cfwversion", GDB_XFER_OSDATA_HANDLER(CfwVersion) }, + { "memory", GDB_XFER_OSDATA_HANDLER(Memory) }, + { "processes", GDB_XFER_OSDATA_HANDLER(Processes) }, +}; + +GDB_DECLARE_XFER_OSDATA_HANDLER(CfwVersion) +{ + if(write) + return GDB_HandleUnsupported(ctx); + else + { + char buf[512]; // Make sure this doesn't overflow + char versionString[16]; + s64 out; + u32 version, commitHash; + bool isRelease; + u32 sz; + + svcGetSystemInfo(&out, 0x10000, 0); + version = (u32)out; + + svcGetSystemInfo(&out, 0x10000, 1); + commitHash = (u32)out; + + svcGetSystemInfo(&out, 0x10000, 0x200); + isRelease = (bool)out; + + if(GET_VERSION_REVISION(version) == 0) + sprintf(versionString, "v%lu.%lu", GET_VERSION_MAJOR(version), GET_VERSION_MINOR(version)); + else + sprintf(versionString, "v%lu.%lu.%lu", GET_VERSION_MAJOR(version), GET_VERSION_MINOR(version), GET_VERSION_REVISION(version)); + + sz = (u32)sprintf(buf, (const char *)osdata_cfw_version_template_xml, versionString, commitHash, isRelease ? "Yes" : "No"); + + return GDB_SendStreamData(ctx, buf, offset, length, sz, false); + } +} + +GDB_DECLARE_XFER_OSDATA_HANDLER(Memory) +{ + if(write) + return GDB_HandleUnsupported(ctx); + else + { + if(ctx->memoryOsInfoXmlData[0] == 0) + { + s64 out; + u32 applicationUsed, systemUsed, baseUsed; + u32 applicationTotal = *(vu32 *)0x1FF80040, systemTotal = *(vu32 *)0x1FF80044, baseTotal = *(vu32 *)0x1FF80048; + + svcGetSystemInfo(&out, 0, 1); + applicationUsed = (u32)out; + + svcGetSystemInfo(&out, 0, 2); + systemUsed = (u32)out; + + svcGetSystemInfo(&out, 0, 3); + baseUsed = (u32)out; + + sprintf(ctx->memoryOsInfoXmlData, (const char *)osdata_memory_template_xml, + applicationUsed, applicationTotal - applicationUsed, applicationTotal, (u32)((5ULL + ((1000ULL * applicationUsed) / applicationTotal)) / 10ULL), + systemUsed, systemTotal - systemUsed, systemTotal, (u32)((5ULL + ((1000ULL * systemUsed) / systemTotal)) / 10ULL), + baseUsed, baseTotal - baseUsed, baseTotal, (u32)((5ULL + ((1000ULL * baseUsed) / baseTotal)) / 10ULL) + ); + } + + u32 size = strlen(ctx->memoryOsInfoXmlData); + int n = GDB_SendStreamData(ctx, ctx->memoryOsInfoXmlData, offset, length, size, false); + + if(offset + length >= size) + ctx->memoryOsInfoXmlData[0] = 0; // we're done, invalidate + + return n; + } +} + +GDB_DECLARE_XFER_OSDATA_HANDLER(Processes) +{ + if(write) + return GDB_HandleUnsupported(ctx); + else + { + if(ctx->processesOsInfoXmlData[0] == 0) + { + static const char header[] = + /*"" + "" IDA rejects the xml header*/ + ""; + + static const char item[] = + "" + "%lu" + "%s" + ""; + + static const char footer[] = ""; + + int n; + u32 pos = 0; + + u32 pidList[0x40]; + s32 processAmount; + + strcpy(ctx->processesOsInfoXmlData, header); + pos = sizeof(header) - 1; + svcGetProcessList(&processAmount, pidList, 0x40); + + for(s32 i = 0; i < processAmount; i++) + { + u32 pid = pidList[i]; + char name[9] = { 0 }; + s64 out; + Handle processHandle; + Result res = svcOpenProcess(&processHandle, pidList[i]); + if(R_FAILED(res)) + continue; + + svcGetProcessInfo(&out, processHandle, 0x10000); + memcpy(name, &out, 8); + svcCloseHandle(processHandle); + + n = sprintf(ctx->processesOsInfoXmlData + pos, item, pid, name); + pos += (u32)n; + } + + strcpy(ctx->processesOsInfoXmlData + pos, footer); + pos = sizeof(footer) - 1; + } + + u32 size = strlen(ctx->processesOsInfoXmlData); + int n = GDB_SendStreamData(ctx, ctx->processesOsInfoXmlData, offset, length, size, false); + + if(offset + length >= size) + ctx->processesOsInfoXmlData[0] = 0; // we're done, invalidate + + return n; + } +} + +GDB_DECLARE_XFER_HANDLER(OsData) +{ + if(strcmp(annex, "") == 0 && !write) + return GDB_SendStreamData(ctx, (const char *)osdata_xml, offset, length, osdata_xml_size, false); + else + { + for(u32 i = 0; i < sizeof(xferOsDataCommandHandlers) / sizeof(xferOsDataCommandHandlers[0]); i++) + { + if(strcmp(annex, xferOsDataCommandHandlers[i].name) == 0) + return xferOsDataCommandHandlers[i].handler(ctx, write, offset, length); + } + } + + return GDB_HandleUnsupported(ctx); +} + +GDB_DECLARE_QUERY_HANDLER(Xfer) +{ + const char *objectStart = ctx->commandData; + char *objectEnd = (char*)strchr(objectStart, ':'); + if(objectEnd == NULL) return -1; + *objectEnd = 0; + + char *opStart = objectEnd + 1; + char *opEnd = (char*)strchr(opStart, ':'); + if(opEnd == NULL) return -1; + *opEnd = 0; + + char *annexStart = opEnd + 1; + char *annexEnd = (char*)strchr(annexStart, ':'); + if(annexEnd == NULL) return -1; + *annexEnd = 0; + + const char *offStart = annexEnd + 1; + u32 offset, length; + + bool write; + const char *pos; + if(strcmp(opStart, "read") == 0) + { + u32 lst[2]; + if(GDB_ParseHexIntegerList(lst, offStart, 2, 0) == NULL) + return GDB_ReplyErrno(ctx, EILSEQ); + + offset = lst[0]; + length = lst[1]; + write = false; + } + else if(strcmp(opStart, "write") == 0) + { + pos = GDB_ParseHexIntegerList(&offset, offStart, 1, ':'); + if(pos == NULL || *pos++ != ':') + return GDB_ReplyErrno(ctx, EILSEQ); + + u32 len = strlen(pos); + if(len == 0 || (len % 2) != 0) + return GDB_ReplyErrno(ctx, EILSEQ); + length = len / 2; + write = true; + } + else + return GDB_ReplyErrno(ctx, EILSEQ); + + for(u32 i = 0; i < sizeof(xferCommandHandlers) / sizeof(xferCommandHandlers[0]); i++) + { + if(strcmp(objectStart, xferCommandHandlers[i].name) == 0) + { + if(write) + ctx->commandData = (char *)pos; + + return xferCommandHandlers[i].handler(ctx, write, annexStart, offset, length); + } + } + + return GDB_HandleUnsupported(ctx); +} diff --git a/thermosphere/src/gdb/xfer.h b/thermosphere/src/gdb/xfer.h new file mode 100644 index 000000000..68556a251 --- /dev/null +++ b/thermosphere/src/gdb/xfer.h @@ -0,0 +1,26 @@ +/* +* This file is part of Luma3DS. +* Copyright (C) 2016-2019 Aurora Wright, TuxSH +* +* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) +*/ + +#pragma once + +#include "gdb.h" + +#define GDB_XFER_HANDLER(name) GDB_HANDLER(Xfer##name) +#define GDB_DECLARE_XFER_HANDLER(name) int GDB_XFER_HANDLER(name)(GDBContext *ctx, bool write, const char *annex, u32 offset, u32 length) + +#define GDB_XFER_OSDATA_HANDLER(name) GDB_XFER_HANDLER(OsData##name) +#define GDB_DECLARE_XFER_OSDATA_HANDLER(name) int GDB_XFER_OSDATA_HANDLER(name)(GDBContext *ctx, bool write, u32 offset, u32 length) + +GDB_DECLARE_XFER_HANDLER(Features); + +GDB_DECLARE_XFER_OSDATA_HANDLER(CfwVersion); +GDB_DECLARE_XFER_OSDATA_HANDLER(Memory); +GDB_DECLARE_XFER_OSDATA_HANDLER(Processes); + +GDB_DECLARE_XFER_HANDLER(OsData); + +GDB_DECLARE_QUERY_HANDLER(Xfer);