mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2025-01-03 11:11:14 +00:00
thermosphere: copy paste lots of gdb luma files (but don't build them yet)
This commit is contained in:
parent
61fec56c6e
commit
f23fb45956
30 changed files with 4139 additions and 0 deletions
199
thermosphere/src/gdb.c
Normal file
199
thermosphere/src/gdb.c
Normal 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
168
thermosphere/src/gdb.h
Normal 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);
|
116
thermosphere/src/gdb/breakpoints.c
Normal file
116
thermosphere/src/gdb/breakpoints.c
Normal 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;
|
||||
}
|
||||
}
|
20
thermosphere/src/gdb/breakpoints.h
Normal file
20
thermosphere/src/gdb/breakpoints.h
Normal 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);
|
660
thermosphere/src/gdb/debug.c
Normal file
660
thermosphere/src/gdb/debug.c
Normal 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(®s, ctx->debug, ctx->currentThreadId, THREADCONTEXT_CONTROL_CPU_SPRS);
|
||||
if(R_SUCCEEDED(r))
|
||||
{
|
||||
regs.cpu_registers.pc = addr;
|
||||
r = svcSetDebugThreadContext(ctx->debug, ctx->currentThreadId, ®s, THREADCONTEXT_CONTROL_CPU_SPRS);
|
||||
}
|
||||
}
|
||||
|
||||
GDB_ContinueExecution(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
GDB_DECLARE_VERBOSE_HANDLER(Continue)
|
||||
{
|
||||
char *pos = ctx->commandData;
|
||||
bool currentThreadFound = false;
|
||||
while(pos != NULL && *pos != 0 && !currentThreadFound)
|
||||
{
|
||||
if(*pos != 'c' && *pos != 'C')
|
||||
return GDB_ReplyErrno(ctx, EPERM);
|
||||
|
||||
pos += *pos == 'C' ? 3 : 1;
|
||||
|
||||
if(*pos++ != ':') // default action found
|
||||
{
|
||||
currentThreadFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
char *nextpos = (char *)strchr(pos, ';');
|
||||
if(strncmp(pos, "-1", 2) == 0)
|
||||
currentThreadFound = true;
|
||||
else
|
||||
{
|
||||
u32 threadId;
|
||||
if(GDB_ParseHexIntegerList(&threadId, pos, 1, ';') == NULL)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
currentThreadFound = currentThreadFound || threadId == ctx->currentThreadId;
|
||||
}
|
||||
|
||||
pos = nextpos;
|
||||
}
|
||||
|
||||
if(ctx->currentThreadId == 0 || currentThreadFound)
|
||||
GDB_ContinueExecution(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
GDB_DECLARE_HANDLER(GetStopReason)
|
||||
{
|
||||
if (ctx->processEnded && ctx->processExited) {
|
||||
return GDB_SendPacket(ctx, "W00", 3);
|
||||
} else if (ctx->processEnded && !ctx->processExited) {
|
||||
return GDB_SendPacket(ctx, "X0f", 3);
|
||||
} else if (ctx->debug == 0) {
|
||||
return GDB_SendPacket(ctx, "W00", 3);
|
||||
} else {
|
||||
return GDB_SendStopReply(ctx, &ctx->latestDebugEvent);
|
||||
}
|
||||
}
|
||||
|
||||
static int GDB_ParseCommonThreadInfo(char *out, GDBContext *ctx, int sig)
|
||||
{
|
||||
u32 threadId = ctx->currentThreadId;
|
||||
ThreadContext regs;
|
||||
s64 dummy;
|
||||
u32 core;
|
||||
Result r = svcGetDebugThreadContext(®s, ctx->debug, threadId, THREADCONTEXT_CONTROL_ALL);
|
||||
int n = sprintf(out, "T%02xthread:%lx;", sig, threadId);
|
||||
|
||||
if(R_FAILED(r))
|
||||
return n;
|
||||
|
||||
r = svcGetDebugThreadParam(&dummy, &core, ctx->debug, ctx->currentThreadId, DBGTHREAD_PARAMETER_CPU_CREATOR); // Creator = "first ran, and running the thread"
|
||||
|
||||
if(R_SUCCEEDED(r))
|
||||
n += sprintf(out + n, "core:%lx;", core);
|
||||
|
||||
for(u32 i = 0; i <= 12; i++)
|
||||
n += sprintf(out + n, "%lx:%08lx;", i, __builtin_bswap32(regs.cpu_registers.r[i]));
|
||||
|
||||
n += sprintf(out + n, "d:%08lx;e:%08lx;f:%08lx;19:%08lx;",
|
||||
__builtin_bswap32(regs.cpu_registers.sp), __builtin_bswap32(regs.cpu_registers.lr), __builtin_bswap32(regs.cpu_registers.pc),
|
||||
__builtin_bswap32(regs.cpu_registers.cpsr));
|
||||
|
||||
for(u32 i = 0; i < 16; i++)
|
||||
{
|
||||
u64 val;
|
||||
memcpy(&val, ®s.fpu_registers.d[i], 8);
|
||||
n += sprintf(out + n, "%lx:%016llx;", 26 + i, __builtin_bswap64(val));
|
||||
}
|
||||
|
||||
n += sprintf(out + n, "2a:%08lx;2b:%08lx;", __builtin_bswap32(regs.fpu_registers.fpscr), __builtin_bswap32(regs.fpu_registers.fpexc));
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
void GDB_PreprocessDebugEvent(GDBContext *ctx, DebugEventInfo *info)
|
||||
{
|
||||
switch(info->type)
|
||||
{
|
||||
case DBGEVENT_ATTACH_PROCESS:
|
||||
{
|
||||
ctx->pid = info->attach_process.process_id;
|
||||
break;
|
||||
}
|
||||
|
||||
case DBGEVENT_ATTACH_THREAD:
|
||||
{
|
||||
if(ctx->nbThreads == MAX_DEBUG_THREAD)
|
||||
svcBreak(USERBREAK_ASSERT);
|
||||
else
|
||||
{
|
||||
++ctx->totalNbCreatedThreads;
|
||||
ctx->threadInfos[ctx->nbThreads].id = info->thread_id;
|
||||
ctx->threadInfos[ctx->nbThreads++].tls = info->attach_thread.thread_local_storage;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DBGEVENT_EXIT_THREAD:
|
||||
{
|
||||
u32 i;
|
||||
for(i = 0; i < ctx->nbThreads && ctx->threadInfos[i].id != info->thread_id; i++);
|
||||
if(i == ctx->nbThreads || ctx->threadInfos[i].id != info->thread_id)
|
||||
svcBreak(USERBREAK_ASSERT);
|
||||
else
|
||||
{
|
||||
for(u32 j = i; j < ctx->nbThreads - 1; j++)
|
||||
memcpy(ctx->threadInfos + j, ctx->threadInfos + j + 1, sizeof(ThreadInfo));
|
||||
memset(ctx->threadInfos + --ctx->nbThreads, 0, sizeof(ThreadInfo));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DBGEVENT_EXIT_PROCESS:
|
||||
{
|
||||
ctx->processEnded = true;
|
||||
ctx->processExited = info->exit_process.reason == EXITPROCESS_EVENT_EXIT;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DBGEVENT_OUTPUT_STRING:
|
||||
{
|
||||
if(info->output_string.string_addr >= 0xFFFFFFFE)
|
||||
{
|
||||
u32 sz = info->output_string.string_size, addr = info->output_string.string_addr, threadId = info->thread_id;
|
||||
memset(info, 0, sizeof(DebugEventInfo));
|
||||
info->type = (addr == 0xFFFFFFFF) ? DBGEVENT_SYSCALL_OUT : DBGEVENT_SYSCALL_IN;
|
||||
info->thread_id = threadId;
|
||||
info->flags = 1;
|
||||
info->syscall.syscall = sz;
|
||||
}
|
||||
else if (info->output_string.string_size == 0)
|
||||
GDB_FetchPackedHioRequest(ctx, info->output_string.string_addr);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DBGEVENT_EXCEPTION:
|
||||
{
|
||||
switch(info->exception.type)
|
||||
{
|
||||
case EXCEVENT_UNDEFINED_INSTRUCTION:
|
||||
{
|
||||
// kernel bugfix for thumb mode
|
||||
ThreadContext regs;
|
||||
Result r = svcGetDebugThreadContext(®s, ctx->debug, info->thread_id, THREADCONTEXT_CONTROL_CPU_SPRS);
|
||||
if(R_SUCCEEDED(r) && (regs.cpu_registers.cpsr & 0x20) != 0)
|
||||
{
|
||||
regs.cpu_registers.pc += 2;
|
||||
r = svcSetDebugThreadContext(ctx->debug, info->thread_id, ®s, THREADCONTEXT_CONTROL_CPU_SPRS);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int GDB_SendStopReply(GDBContext *ctx, const DebugEventInfo *info)
|
||||
{
|
||||
char buffer[GDB_BUF_LEN + 1];
|
||||
|
||||
switch(info->type)
|
||||
{
|
||||
case DBGEVENT_ATTACH_PROCESS:
|
||||
break; // Dismissed
|
||||
|
||||
case DBGEVENT_ATTACH_THREAD:
|
||||
{
|
||||
if((ctx->flags & GDB_FLAG_ATTACHED_AT_START) && ctx->totalNbCreatedThreads == 1)
|
||||
{
|
||||
// Main thread created
|
||||
ctx->currentThreadId = info->thread_id;
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, SIGINT);
|
||||
return GDB_SendFormattedPacket(ctx, "%s", buffer);
|
||||
}
|
||||
else if(info->attach_thread.creator_thread_id == 0 || !ctx->catchThreadEvents)
|
||||
break; // Dismissed
|
||||
else
|
||||
{
|
||||
ctx->currentThreadId = info->thread_id;
|
||||
return GDB_SendPacket(ctx, "T05create:;", 10);
|
||||
}
|
||||
}
|
||||
|
||||
case DBGEVENT_EXIT_THREAD:
|
||||
{
|
||||
if(ctx->catchThreadEvents && info->exit_thread.reason < EXITTHREAD_EVENT_EXIT_PROCESS)
|
||||
{
|
||||
// no signal, SIGTERM, SIGQUIT (process exited), SIGTERM (process terminated)
|
||||
static int threadExitRepliesSigs[] = { 0, SIGTERM, SIGQUIT, SIGTERM };
|
||||
return GDB_SendFormattedPacket(ctx, "w%02x;%lx", threadExitRepliesSigs[(u32)info->exit_thread.reason], info->thread_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case DBGEVENT_EXIT_PROCESS:
|
||||
{
|
||||
// exited (no error / unhandled exception), SIGTERM (process terminated) * 2
|
||||
static const char *processExitReplies[] = { "W00", "X0f", "X0f" };
|
||||
return GDB_SendPacket(ctx, processExitReplies[(u32)info->exit_process.reason], 3);
|
||||
}
|
||||
|
||||
case DBGEVENT_EXCEPTION:
|
||||
{
|
||||
ExceptionEvent exc = info->exception;
|
||||
|
||||
switch(exc.type)
|
||||
{
|
||||
case EXCEVENT_UNDEFINED_INSTRUCTION:
|
||||
case EXCEVENT_PREFETCH_ABORT: // doesn't include hardware breakpoints
|
||||
case EXCEVENT_DATA_ABORT: // doesn't include hardware watchpoints
|
||||
case EXCEVENT_UNALIGNED_DATA_ACCESS:
|
||||
case EXCEVENT_UNDEFINED_SYSCALL:
|
||||
{
|
||||
u32 signum = exc.type == EXCEVENT_UNDEFINED_INSTRUCTION ? SIGILL :
|
||||
(exc.type == EXCEVENT_UNDEFINED_SYSCALL ? SIGSYS : SIGSEGV);
|
||||
|
||||
ctx->currentThreadId = info->thread_id;
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, signum);
|
||||
return GDB_SendFormattedPacket(ctx, "%s", buffer);
|
||||
}
|
||||
|
||||
case EXCEVENT_ATTACH_BREAK:
|
||||
return GDB_SendPacket(ctx, "S00", 3);
|
||||
|
||||
case EXCEVENT_STOP_POINT:
|
||||
{
|
||||
ctx->currentThreadId = info->thread_id;
|
||||
|
||||
switch(exc.stop_point.type)
|
||||
{
|
||||
case STOPPOINT_SVC_FF:
|
||||
{
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP);
|
||||
return GDB_SendFormattedPacket(ctx, "%sswbreak:;", buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
case STOPPOINT_BREAKPOINT:
|
||||
{
|
||||
// /!\ Not actually implemented (and will never be)
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP);
|
||||
return GDB_SendFormattedPacket(ctx, "%shwbreak:;", buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
case STOPPOINT_WATCHPOINT:
|
||||
{
|
||||
const char *kinds[] = { "", "r", "", "a" };
|
||||
WatchpointKind kind = GDB_GetWatchpointKind(ctx, exc.stop_point.fault_information);
|
||||
if(kind == WATCHPOINT_DISABLED)
|
||||
GDB_SendDebugString(ctx, "Warning: unknown watchpoint encountered!\n");
|
||||
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP);
|
||||
return GDB_SendFormattedPacket(ctx, "%s%swatch:%08lx;", buffer, kinds[(u32)kind], exc.stop_point.fault_information);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EXCEVENT_USER_BREAK:
|
||||
{
|
||||
ctx->currentThreadId = info->thread_id;
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, SIGINT);
|
||||
return GDB_SendFormattedPacket(ctx, "%s", buffer);
|
||||
//TODO
|
||||
}
|
||||
|
||||
case EXCEVENT_DEBUGGER_BREAK:
|
||||
{
|
||||
u32 threadIds[4];
|
||||
u32 nbThreads = 0;
|
||||
|
||||
for(u32 i = 0; i < 4; i++)
|
||||
{
|
||||
if(exc.debugger_break.thread_ids[i] > 0)
|
||||
threadIds[nbThreads++] = (u32)exc.debugger_break.thread_ids[i];
|
||||
}
|
||||
|
||||
u32 currentThreadId = nbThreads > 0 ? GDB_GetCurrentThreadFromList(ctx, threadIds, nbThreads) : GDB_GetCurrentThread(ctx);
|
||||
s64 dummy;
|
||||
u32 mask = 0;
|
||||
|
||||
svcGetDebugThreadParam(&dummy, &mask, ctx->debug, currentThreadId, DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW);
|
||||
|
||||
if(mask == 1)
|
||||
{
|
||||
ctx->currentThreadId = currentThreadId;
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, SIGINT);
|
||||
return GDB_SendFormattedPacket(ctx, "%s", buffer);
|
||||
}
|
||||
else
|
||||
return GDB_SendPacket(ctx, "S02", 3);
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DBGEVENT_SYSCALL_IN:
|
||||
{
|
||||
ctx->currentThreadId = info->thread_id;
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP);
|
||||
return GDB_SendFormattedPacket(ctx, "%ssyscall_entry:%02x;", buffer, info->syscall.syscall);
|
||||
}
|
||||
|
||||
case DBGEVENT_SYSCALL_OUT:
|
||||
{
|
||||
ctx->currentThreadId = info->thread_id;
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP);
|
||||
return GDB_SendFormattedPacket(ctx, "%ssyscall_return:%02x;", buffer, info->syscall.syscall);
|
||||
}
|
||||
|
||||
case DBGEVENT_OUTPUT_STRING:
|
||||
{
|
||||
// Regular "output string"
|
||||
if (!GDB_IsHioInProgress(ctx))
|
||||
{
|
||||
u32 addr = info->output_string.string_addr;
|
||||
u32 remaining = info->output_string.string_size;
|
||||
u32 sent = 0;
|
||||
int total = 0;
|
||||
while(remaining > 0)
|
||||
{
|
||||
u32 pending = (GDB_BUF_LEN - 1) / 2;
|
||||
pending = pending < remaining ? pending : remaining;
|
||||
|
||||
int res = GDB_SendMemory(ctx, "O", 1, addr + sent, pending);
|
||||
if(res < 0 || (u32) res != 5 + 2 * pending)
|
||||
break;
|
||||
|
||||
sent += pending;
|
||||
remaining -= pending;
|
||||
total += res;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
else // HIO
|
||||
{
|
||||
return GDB_SendCurrentHioRequest(ctx);
|
||||
}
|
||||
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Only 1 blocking event can be enqueued at a time: they preempt all the other threads.
|
||||
The only "non-blocking" event that is implemented is EXIT PROCESS (but it's a very special case)
|
||||
*/
|
||||
int GDB_HandleDebugEvents(GDBContext *ctx)
|
||||
{
|
||||
if(ctx->state == GDB_STATE_DETACHING)
|
||||
return -1;
|
||||
|
||||
DebugEventInfo info;
|
||||
Result rdbg = svcGetProcessDebugEvent(&info, ctx->debug);
|
||||
|
||||
if(R_FAILED(rdbg))
|
||||
return -1;
|
||||
|
||||
GDB_PreprocessDebugEvent(ctx, &info);
|
||||
|
||||
int ret = 0;
|
||||
bool continueAutomatically = (info.type == DBGEVENT_OUTPUT_STRING && !GDB_IsHioInProgress(ctx)) ||
|
||||
info.type == DBGEVENT_ATTACH_PROCESS ||
|
||||
(info.type == DBGEVENT_ATTACH_THREAD && (info.attach_thread.creator_thread_id == 0 || !ctx->catchThreadEvents)) ||
|
||||
(info.type == DBGEVENT_EXIT_THREAD && (info.exit_thread.reason >= EXITTHREAD_EVENT_EXIT_PROCESS || !ctx->catchThreadEvents)) ||
|
||||
info.type == DBGEVENT_EXIT_PROCESS || !(info.flags & 1);
|
||||
|
||||
if(continueAutomatically)
|
||||
{
|
||||
Result r = 0;
|
||||
ret = GDB_SendStopReply(ctx, &info);
|
||||
if(info.flags & 1)
|
||||
r = svcContinueDebugEvent(ctx->debug, ctx->continueFlags);
|
||||
|
||||
if(r == (Result)0xD8A02008) // process ended
|
||||
return -2;
|
||||
|
||||
return -ret - 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
int ret;
|
||||
|
||||
if(ctx->processEnded)
|
||||
return -2;
|
||||
|
||||
ctx->latestDebugEvent = info;
|
||||
ret = GDB_SendStopReply(ctx, &info);
|
||||
ctx->flags &= ~GDB_FLAG_PROCESS_CONTINUING;
|
||||
return ret;
|
||||
}
|
||||
}
|
26
thermosphere/src/gdb/debug.h
Normal file
26
thermosphere/src/gdb/debug.h
Normal 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
133
thermosphere/src/gdb/hio.c
Normal 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);
|
||||
}
|
16
thermosphere/src/gdb/hio.h
Normal file
16
thermosphere/src/gdb/hio.h
Normal 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
331
thermosphere/src/gdb/mem.c
Normal 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);
|
||||
}
|
24
thermosphere/src/gdb/mem.h
Normal file
24
thermosphere/src/gdb/mem.h
Normal 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
376
thermosphere/src/gdb/net.c
Normal 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);
|
||||
}
|
31
thermosphere/src/gdb/net.h
Normal file
31
thermosphere/src/gdb/net.h
Normal 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);
|
146
thermosphere/src/gdb/query.c
Normal file
146
thermosphere/src/gdb/query.c
Normal 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);
|
||||
}
|
18
thermosphere/src/gdb/query.h
Normal file
18
thermosphere/src/gdb/query.h
Normal 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
153
thermosphere/src/gdb/regs.c
Normal 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(®s, ctx->debug, ctx->selectedThreadId, THREADCONTEXT_CONTROL_ALL);
|
||||
|
||||
if(R_FAILED(r))
|
||||
return GDB_ReplyErrno(ctx, EPERM);
|
||||
|
||||
return GDB_SendHexPacket(ctx, ®s, sizeof(ThreadContext));
|
||||
}
|
||||
|
||||
GDB_DECLARE_HANDLER(WriteRegisters)
|
||||
{
|
||||
if(ctx->selectedThreadId == 0)
|
||||
ctx->selectedThreadId = ctx->currentThreadId;
|
||||
|
||||
ThreadContext regs;
|
||||
|
||||
if(GDB_DecodeHex(®s, ctx->commandData, sizeof(ThreadContext)) != sizeof(ThreadContext))
|
||||
return GDB_ReplyErrno(ctx, EPERM);
|
||||
|
||||
Result r = svcSetDebugThreadContext(ctx->debug, ctx->selectedThreadId, ®s, THREADCONTEXT_CONTROL_ALL);
|
||||
if(R_FAILED(r))
|
||||
return GDB_ReplyErrno(ctx, EPERM);
|
||||
else
|
||||
return GDB_ReplyOk(ctx);
|
||||
}
|
||||
|
||||
static u32 GDB_ConvertRegisterNumber(ThreadContextControlFlags *flags, u32 gdbNum)
|
||||
{
|
||||
if(gdbNum <= 15)
|
||||
{
|
||||
*flags = (gdbNum >= 13) ? THREADCONTEXT_CONTROL_CPU_SPRS : THREADCONTEXT_CONTROL_CPU_GPRS;
|
||||
return gdbNum;
|
||||
}
|
||||
else if(gdbNum == 25)
|
||||
{
|
||||
*flags = THREADCONTEXT_CONTROL_CPU_SPRS;
|
||||
return 16;
|
||||
}
|
||||
else if(gdbNum >= 26 && gdbNum <= 41)
|
||||
{
|
||||
*flags = THREADCONTEXT_CONTROL_FPU_GPRS;
|
||||
return gdbNum - 26;
|
||||
}
|
||||
else if(gdbNum == 42 || gdbNum == 43)
|
||||
{
|
||||
*flags = THREADCONTEXT_CONTROL_FPU_SPRS;
|
||||
return gdbNum - 42;
|
||||
}
|
||||
else
|
||||
{
|
||||
*flags = (ThreadContextControlFlags)0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
GDB_DECLARE_HANDLER(ReadRegister)
|
||||
{
|
||||
if(ctx->selectedThreadId == 0)
|
||||
ctx->selectedThreadId = ctx->currentThreadId;
|
||||
|
||||
ThreadContext regs;
|
||||
ThreadContextControlFlags flags;
|
||||
u32 gdbRegNum;
|
||||
|
||||
if(GDB_ParseHexIntegerList(&gdbRegNum, ctx->commandData, 1, 0) == NULL)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
u32 n = GDB_ConvertRegisterNumber(&flags, gdbRegNum);
|
||||
if(!flags)
|
||||
return GDB_ReplyErrno(ctx, EINVAL);
|
||||
|
||||
Result r = svcGetDebugThreadContext(®s, ctx->debug, ctx->selectedThreadId, flags);
|
||||
|
||||
if(R_FAILED(r))
|
||||
return GDB_ReplyErrno(ctx, EPERM);
|
||||
|
||||
if(flags & THREADCONTEXT_CONTROL_CPU_GPRS)
|
||||
return GDB_SendHexPacket(ctx, ®s.cpu_registers.r[n], 4);
|
||||
else if(flags & THREADCONTEXT_CONTROL_CPU_SPRS)
|
||||
return GDB_SendHexPacket(ctx, ®s.cpu_registers.sp + (n - 13), 4); // hacky
|
||||
else if(flags & THREADCONTEXT_CONTROL_FPU_GPRS)
|
||||
return GDB_SendHexPacket(ctx, ®s.fpu_registers.d[n], 8);
|
||||
else
|
||||
return GDB_SendHexPacket(ctx, ®s.fpu_registers.fpscr + n, 4); // hacky
|
||||
}
|
||||
|
||||
GDB_DECLARE_HANDLER(WriteRegister)
|
||||
{
|
||||
if(ctx->selectedThreadId == 0)
|
||||
ctx->selectedThreadId = ctx->currentThreadId;
|
||||
|
||||
ThreadContext regs;
|
||||
ThreadContextControlFlags flags;
|
||||
u32 gdbRegNum;
|
||||
|
||||
const char *valueStart = GDB_ParseHexIntegerList(&gdbRegNum, ctx->commandData, 1, '=');
|
||||
if(valueStart == NULL || *valueStart != '=')
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
valueStart++;
|
||||
|
||||
u32 n = GDB_ConvertRegisterNumber(&flags, gdbRegNum);
|
||||
u32 value;
|
||||
u64 value64;
|
||||
|
||||
if(flags & THREADCONTEXT_CONTROL_FPU_GPRS)
|
||||
{
|
||||
if(GDB_DecodeHex(&value64, valueStart, 8) != 8 || valueStart[16] != 0)
|
||||
return GDB_ReplyErrno(ctx, EINVAL);
|
||||
}
|
||||
else if(flags)
|
||||
{
|
||||
if(GDB_DecodeHex(&value, valueStart, 4) != 4 || valueStart[8] != 0)
|
||||
return GDB_ReplyErrno(ctx, EINVAL);
|
||||
}
|
||||
else
|
||||
return GDB_ReplyErrno(ctx, EINVAL);
|
||||
|
||||
Result r = svcGetDebugThreadContext(®s, ctx->debug, ctx->selectedThreadId, flags);
|
||||
|
||||
if(R_FAILED(r))
|
||||
return GDB_ReplyErrno(ctx, EPERM);
|
||||
|
||||
if(flags & THREADCONTEXT_CONTROL_CPU_GPRS)
|
||||
regs.cpu_registers.r[n] = value;
|
||||
else if(flags & THREADCONTEXT_CONTROL_CPU_SPRS)
|
||||
*(®s.cpu_registers.sp + (n - 13)) = value; // hacky
|
||||
else if(flags & THREADCONTEXT_CONTROL_FPU_GPRS)
|
||||
memcpy(®s.fpu_registers.d[n], &value64, 8);
|
||||
else
|
||||
*(®s.fpu_registers.fpscr + n) = value; // hacky
|
||||
|
||||
r = svcSetDebugThreadContext(ctx->debug, ctx->selectedThreadId, ®s, flags);
|
||||
if(R_FAILED(r))
|
||||
return GDB_ReplyErrno(ctx, EPERM);
|
||||
else
|
||||
return GDB_ReplyOk(ctx);
|
||||
}
|
15
thermosphere/src/gdb/regs.h
Normal file
15
thermosphere/src/gdb/regs.h
Normal 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);
|
338
thermosphere/src/gdb/remote_command.c
Normal file
338
thermosphere/src/gdb/remote_command.c
Normal 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(®s, ctx->debug, ctx->selectedThreadId, THREADCONTEXT_CONTROL_CPU_REGS);
|
||||
|
||||
if(R_FAILED(r) || id == MAX_DEBUG_THREAD)
|
||||
{
|
||||
n = sprintf(outbuf, "Invalid or running thread.\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
r = svcReadProcessMemory(&cmdId, ctx->debug, ctx->threadInfos[id].tls + 0x80, 4);
|
||||
if(R_FAILED(r))
|
||||
{
|
||||
n = sprintf(outbuf, "Invalid or running thread.\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
r = svcReadProcessMemory(&instr, ctx->debug, regs.cpu_registers.pc, (regs.cpu_registers.cpsr & 0x20) ? 2 : 4);
|
||||
|
||||
if(R_SUCCEEDED(r) && (((regs.cpu_registers.cpsr & 0x20) && instr == BREAKPOINT_INSTRUCTION_THUMB) || instr == BREAKPOINT_INSTRUCTION_ARM))
|
||||
{
|
||||
u32 savedInstruction;
|
||||
if(GDB_GetBreakpointInstruction(&savedInstruction, ctx, regs.cpu_registers.pc) == 0)
|
||||
instr = savedInstruction;
|
||||
}
|
||||
|
||||
if(R_FAILED(r) || ((regs.cpu_registers.cpsr & 0x20) && !(instr == 0xDF32 || (instr == 0xDFFE && regs.cpu_registers.r[12] == 0x32)))
|
||||
|| (!(regs.cpu_registers.cpsr & 0x20) && !(instr == 0xEF000032 || (instr == 0xEF0000FE && regs.cpu_registers.r[12] == 0x32))))
|
||||
{
|
||||
n = sprintf(outbuf, "The selected thread is not currently performing a sync request (svc 0x32).\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
char name[12];
|
||||
Handle handle;
|
||||
r = svcCopyHandle(&handle, CUR_PROCESS_HANDLE, (Handle)regs.cpu_registers.r[0], process);
|
||||
if(R_FAILED(r))
|
||||
{
|
||||
n = sprintf(outbuf, "Invalid handle.\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
r = svcControlService(SERVICEOP_GET_NAME, name, handle);
|
||||
if(R_FAILED(r))
|
||||
name[0] = 0;
|
||||
|
||||
n = sprintf(outbuf, "%s 0x%lx, 0x%08lx\n", name, cmdId, ctx->threadInfos[id].tls + 0x80);
|
||||
|
||||
end:
|
||||
svcCloseHandle(handle);
|
||||
svcCloseHandle(process);
|
||||
return GDB_SendHexPacket(ctx, outbuf, n);
|
||||
}
|
||||
|
||||
GDB_DECLARE_REMOTE_COMMAND_HANDLER(TranslateHandle)
|
||||
{
|
||||
bool ok;
|
||||
u32 val;
|
||||
char *end;
|
||||
int n;
|
||||
Result r;
|
||||
u32 kernelAddr;
|
||||
Handle handle, process;
|
||||
s64 refcountRaw;
|
||||
u32 refcount;
|
||||
char classBuf[32], serviceBuf[12] = { 0 };
|
||||
char outbuf[GDB_BUF_LEN / 2 + 1];
|
||||
|
||||
if(ctx->commandData[0] == 0)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
val = xstrtoul(ctx->commandData, &end, 0, true, &ok);
|
||||
|
||||
if(!ok)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
end = (char *)GDB_SkipSpaces(end);
|
||||
|
||||
if(*end != 0)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
r = svcOpenProcess(&process, ctx->pid);
|
||||
if(R_FAILED(r))
|
||||
{
|
||||
n = sprintf(outbuf, "Invalid process (wtf?)\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
r = svcCopyHandle(&handle, CUR_PROCESS_HANDLE, (Handle)val, process);
|
||||
if(R_FAILED(r))
|
||||
{
|
||||
n = sprintf(outbuf, "Invalid handle.\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
svcTranslateHandle(&kernelAddr, classBuf, handle);
|
||||
svcGetHandleInfo(&refcountRaw, handle, 1);
|
||||
svcControlService(SERVICEOP_GET_NAME, serviceBuf, handle);
|
||||
refcount = (u32)(refcountRaw - 1);
|
||||
if(serviceBuf[0] != 0)
|
||||
n = sprintf(outbuf, "(%s *)0x%08lx /* %s handle, %lu %s */\n", classBuf, kernelAddr, serviceBuf, refcount, refcount == 1 ? "reference" : "references");
|
||||
else
|
||||
n = sprintf(outbuf, "(%s *)0x%08lx /* %lu %s */\n", classBuf, kernelAddr, refcount, refcount == 1 ? "reference" : "references");
|
||||
|
||||
end:
|
||||
svcCloseHandle(handle);
|
||||
svcCloseHandle(process);
|
||||
return GDB_SendHexPacket(ctx, outbuf, n);
|
||||
}
|
||||
|
||||
extern bool isN3DS;
|
||||
GDB_DECLARE_REMOTE_COMMAND_HANDLER(GetMmuConfig)
|
||||
{
|
||||
int n;
|
||||
char outbuf[GDB_BUF_LEN / 2 + 1];
|
||||
Result r;
|
||||
Handle process;
|
||||
|
||||
if(ctx->commandData[0] != 0)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
r = svcOpenProcess(&process, ctx->pid);
|
||||
if(R_FAILED(r))
|
||||
n = sprintf(outbuf, "Invalid process (wtf?)\n");
|
||||
else
|
||||
{
|
||||
s64 TTBCR, TTBR0;
|
||||
svcGetSystemInfo(&TTBCR, 0x10002, 0);
|
||||
svcGetProcessInfo(&TTBR0, process, 0x10008);
|
||||
n = sprintf(outbuf, "TTBCR = %lu\nTTBR0 = 0x%08lx\nTTBR1 =", (u32)TTBCR, (u32)TTBR0);
|
||||
for(u32 i = 0; i < (isN3DS ? 4 : 2); i++)
|
||||
{
|
||||
s64 TTBR1;
|
||||
svcGetSystemInfo(&TTBR1, 0x10002, 1 + i);
|
||||
|
||||
if(i == (isN3DS ? 3 : 1))
|
||||
n += sprintf(outbuf + n, " 0x%08lx\n", (u32)TTBR1);
|
||||
else
|
||||
n += sprintf(outbuf + n, " 0x%08lx /", (u32)TTBR1);
|
||||
}
|
||||
svcCloseHandle(process);
|
||||
}
|
||||
|
||||
return GDB_SendHexPacket(ctx, outbuf, n);
|
||||
}
|
||||
|
||||
static const char *FormatMemPerm(u32 perm)
|
||||
{
|
||||
if (perm == MEMPERM_DONTCARE)
|
||||
return "???";
|
||||
|
||||
static char buf[4] = {0};
|
||||
|
||||
buf[0] = perm & MEMPERM_READ ? 'r' : '-';
|
||||
buf[1] = perm & MEMPERM_WRITE ? 'w' : '-';
|
||||
buf[2] = perm & MEMPERM_EXECUTE ? 'x' : '-';
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *FormatMemState(u32 state)
|
||||
{
|
||||
if (state > 11)
|
||||
return "Unknown";
|
||||
|
||||
static const char *states[12] =
|
||||
{
|
||||
"Free",
|
||||
"Reserved",
|
||||
"IO",
|
||||
"Static",
|
||||
"Code",
|
||||
"Private",
|
||||
"Shared",
|
||||
"Continuous",
|
||||
"Aliased",
|
||||
"Alias",
|
||||
"AliasCode",
|
||||
"Locked"
|
||||
};
|
||||
|
||||
return states[state];
|
||||
}
|
||||
|
||||
GDB_DECLARE_REMOTE_COMMAND_HANDLER(GetMemRegions)
|
||||
{
|
||||
u32 address = 0;
|
||||
u32 posInBuffer = 0;
|
||||
u32 maxPosInBuffer = GDB_BUF_LEN / 2 - 35; ///< 35 is the maximum length of a formatted region
|
||||
Handle handle;
|
||||
MemInfo memi;
|
||||
PageInfo pagei;
|
||||
char outbuf[GDB_BUF_LEN / 2 + 1];
|
||||
|
||||
if(R_FAILED(svcOpenProcess(&handle, ctx->pid)))
|
||||
{
|
||||
posInBuffer = sprintf(outbuf, "Invalid process (wtf?)\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
while (address < 0x40000000 ///< Limit to check for regions
|
||||
&& posInBuffer < maxPosInBuffer
|
||||
&& R_SUCCEEDED(svcQueryProcessMemory(&memi, &pagei, handle, address)))
|
||||
{
|
||||
// Update the address for next region
|
||||
address = memi.base_addr + memi.size;
|
||||
|
||||
// If region isn't FREE then add it to the list
|
||||
if (memi.state != MEMSTATE_FREE)
|
||||
{
|
||||
const char *perm = FormatMemPerm(memi.perm);
|
||||
const char *state = FormatMemState(memi.state);
|
||||
|
||||
posInBuffer += sprintf(outbuf + posInBuffer, "%08lx - %08lx %s %s\n",
|
||||
memi.base_addr, address, perm, state);
|
||||
}
|
||||
}
|
||||
|
||||
svcCloseHandle(handle);
|
||||
|
||||
end:
|
||||
return GDB_SendHexPacket(ctx, outbuf, posInBuffer);
|
||||
}
|
||||
|
||||
GDB_DECLARE_REMOTE_COMMAND_HANDLER(FlushCaches)
|
||||
{
|
||||
if(ctx->commandData[0] != 0)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
svcFlushEntireDataCache();
|
||||
svcInvalidateEntireInstructionCache();
|
||||
|
||||
return GDB_ReplyOk(ctx);
|
||||
}
|
||||
|
||||
GDB_DECLARE_REMOTE_COMMAND_HANDLER(ToggleExternalMemoryAccess)
|
||||
{
|
||||
int n;
|
||||
char outbuf[GDB_BUF_LEN / 2 + 1];
|
||||
|
||||
ctx->enableExternalMemoryAccess = !ctx->enableExternalMemoryAccess;
|
||||
|
||||
n = sprintf(outbuf, "External memory access %s successfully.\n", ctx->enableExternalMemoryAccess ? "enabled" : "disabled");
|
||||
|
||||
return GDB_SendHexPacket(ctx, outbuf, n);
|
||||
}
|
||||
|
||||
GDB_DECLARE_QUERY_HANDLER(Rcmd)
|
||||
{
|
||||
char commandData[GDB_BUF_LEN / 2 + 1];
|
||||
char *endpos;
|
||||
const char *errstr = "Unrecognized command.\n";
|
||||
u32 len = strlen(ctx->commandData);
|
||||
|
||||
if(len == 0 || (len % 2) == 1 || GDB_DecodeHex(commandData, ctx->commandData, len / 2) != len / 2)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
commandData[len / 2] = 0;
|
||||
|
||||
for(endpos = commandData; !(*endpos >= 9 && *endpos <= 13) && *endpos != ' ' && *endpos != 0; endpos++);
|
||||
|
||||
char *nextpos = (char *)GDB_SkipSpaces(endpos);
|
||||
*endpos = 0;
|
||||
|
||||
for(u32 i = 0; i < sizeof(remoteCommandHandlers) / sizeof(remoteCommandHandlers[0]); i++)
|
||||
{
|
||||
if(strcmp(commandData, remoteCommandHandlers[i].name) == 0)
|
||||
{
|
||||
ctx->commandData = nextpos;
|
||||
return remoteCommandHandlers[i].handler(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
return GDB_SendHexPacket(ctx, errstr, strlen(errstr));
|
||||
}
|
22
thermosphere/src/gdb/remote_command.h
Normal file
22
thermosphere/src/gdb/remote_command.h
Normal 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);
|
378
thermosphere/src/gdb/server.c
Normal file
378
thermosphere/src/gdb/server.c
Normal 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;
|
||||
}
|
43
thermosphere/src/gdb/server.h
Normal file
43
thermosphere/src/gdb/server.h
Normal 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);
|
52
thermosphere/src/gdb/stop_point.c
Normal file
52
thermosphere/src/gdb/stop_point.c
Normal 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);
|
||||
}
|
||||
}
|
12
thermosphere/src/gdb/stop_point.h
Normal file
12
thermosphere/src/gdb/stop_point.h
Normal 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);
|
309
thermosphere/src/gdb/thread.c
Normal file
309
thermosphere/src/gdb/thread.c
Normal 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);
|
||||
}
|
23
thermosphere/src/gdb/thread.h
Normal file
23
thermosphere/src/gdb/thread.h
Normal 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);
|
58
thermosphere/src/gdb/verbose.c
Normal file
58
thermosphere/src/gdb/verbose.c
Normal 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);
|
||||
}
|
13
thermosphere/src/gdb/verbose.h
Normal file
13
thermosphere/src/gdb/verbose.h
Normal 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);
|
151
thermosphere/src/gdb/watchpoints.c
Normal file
151
thermosphere/src/gdb/watchpoints.c
Normal 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;
|
||||
}
|
25
thermosphere/src/gdb/watchpoints.h
Normal file
25
thermosphere/src/gdb/watchpoints.h
Normal 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
257
thermosphere/src/gdb/xfer.c
Normal 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);
|
||||
}
|
26
thermosphere/src/gdb/xfer.h
Normal file
26
thermosphere/src/gdb/xfer.h
Normal 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);
|
Loading…
Reference in a new issue