thermosphere: copy paste lots of gdb luma files (but don't build them yet)

This commit is contained in:
TuxSH 2020-01-21 22:38:29 +00:00
parent 61fec56c6e
commit f23fb45956
30 changed files with 4139 additions and 0 deletions

199
thermosphere/src/gdb.c Normal file
View file

@ -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

168
thermosphere/src/gdb.h Normal file
View file

@ -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 $#<checksum>, 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);

View file

@ -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 <errno.h>
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;
}
}

View file

@ -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);

View file

@ -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 <stdlib.h>
#include <signal.h>
#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(&regs, ctx->debug, ctx->currentThreadId, THREADCONTEXT_CONTROL_CPU_SPRS);
if(R_SUCCEEDED(r))
{
regs.cpu_registers.pc = addr;
r = svcSetDebugThreadContext(ctx->debug, ctx->currentThreadId, &regs, 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(&regs, 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, &regs.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(&regs, 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, &regs, 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;
}
}

View file

@ -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);

133
thermosphere/src/gdb/hio.c Normal file
View file

@ -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 <string.h>
#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);
}

View file

@ -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);

331
thermosphere/src/gdb/mem.c Normal file
View file

@ -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);
}

View file

@ -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);

376
thermosphere/src/gdb/net.c Normal file
View file

@ -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 <stdarg.h>
#include <stdlib.h>
#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);
}

View file

@ -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 <errno.h>
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);

View file

@ -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);
}

View file

@ -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);

153
thermosphere/src/gdb/regs.c Normal file
View file

@ -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(&regs, ctx->debug, ctx->selectedThreadId, THREADCONTEXT_CONTROL_ALL);
if(R_FAILED(r))
return GDB_ReplyErrno(ctx, EPERM);
return GDB_SendHexPacket(ctx, &regs, sizeof(ThreadContext));
}
GDB_DECLARE_HANDLER(WriteRegisters)
{
if(ctx->selectedThreadId == 0)
ctx->selectedThreadId = ctx->currentThreadId;
ThreadContext regs;
if(GDB_DecodeHex(&regs, ctx->commandData, sizeof(ThreadContext)) != sizeof(ThreadContext))
return GDB_ReplyErrno(ctx, EPERM);
Result r = svcSetDebugThreadContext(ctx->debug, ctx->selectedThreadId, &regs, 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(&regs, ctx->debug, ctx->selectedThreadId, flags);
if(R_FAILED(r))
return GDB_ReplyErrno(ctx, EPERM);
if(flags & THREADCONTEXT_CONTROL_CPU_GPRS)
return GDB_SendHexPacket(ctx, &regs.cpu_registers.r[n], 4);
else if(flags & THREADCONTEXT_CONTROL_CPU_SPRS)
return GDB_SendHexPacket(ctx, &regs.cpu_registers.sp + (n - 13), 4); // hacky
else if(flags & THREADCONTEXT_CONTROL_FPU_GPRS)
return GDB_SendHexPacket(ctx, &regs.fpu_registers.d[n], 8);
else
return GDB_SendHexPacket(ctx, &regs.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(&regs, 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)
*(&regs.cpu_registers.sp + (n - 13)) = value; // hacky
else if(flags & THREADCONTEXT_CONTROL_FPU_GPRS)
memcpy(&regs.fpu_registers.d[n], &value64, 8);
else
*(&regs.fpu_registers.fpscr + n) = value; // hacky
r = svcSetDebugThreadContext(ctx->debug, ctx->selectedThreadId, &regs, flags);
if(R_FAILED(r))
return GDB_ReplyErrno(ctx, EPERM);
else
return GDB_ReplyOk(ctx);
}

View file

@ -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);

View file

@ -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(&regs, 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));
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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 <stdlib.h>
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);
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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);

View file

@ -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 <errno.h>
/*
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;
}

View file

@ -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);

257
thermosphere/src/gdb/xfer.c Normal file
View file

@ -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[] =
/*"<?xml version=\"1.0\"?>"
"<!DOCTYPE target SYSTEM \"osdata.dtd\">" IDA rejects the xml header*/
"<osdata type=\"processes\">";
static const char item[] =
"<item>"
"<column name=\"pid\">%lu</column>"
"<column name=\"command\">%s</column>"
"</item>";
static const char footer[] = "</osdata>";
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);
}

View file

@ -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);