gdb: add support to run new processes

Only titleId [mediaType [launchFlags]] is supported, and the launched title shouldn't rely on APT and all 3 parameters should be hex-encoded.

usage example, with titleId+mediaType:
(gdb) set remote file-exec 0004013000003702
(gdb) r 0
This commit is contained in:
TuxSH 2019-04-13 19:18:47 +02:00
parent 763a1de8d3
commit e11cc090b2
12 changed files with 290 additions and 30 deletions

View File

@ -30,6 +30,7 @@
#include <3ds/svc.h>
#include <3ds/synchronization.h>
#include <3ds/result.h>
#include "pmdbgext.h"
#include "sock_util.h"
#include "memory.h"
@ -64,17 +65,19 @@ enum
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_NOACK_SENT,
GDB_STATE_DETACHING
GDB_STATE_ATTACHED,
GDB_STATE_DETACHING,
} GDBState;
typedef struct ThreadInfo
@ -95,9 +98,15 @@ typedef struct GDBContext
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;
@ -138,6 +147,7 @@ void GDB_FinalizeContext(GDBContext *ctx);
Result GDB_AttachToProcess(GDBContext *ctx);
void GDB_DetachFromProcess(GDBContext *ctx);
Result GDB_CreateProcess(GDBContext *ctx, const FS_ProgramInfo *progInfo, u32 launchFlags);
GDB_DECLARE_HANDLER(Unsupported);
GDB_DECLARE_HANDLER(EnableExtendedMode);

View File

@ -28,6 +28,8 @@
#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);

View File

@ -36,6 +36,8 @@ u32 GDB_DecodeHex(void *dst, const char *src, u32 len);
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, ...);

View File

@ -32,3 +32,4 @@
u8 *memsearch(u8 *startPos, const void *pattern, u32 size, u32 patternSize);
void hexItoa(u64 number, char *out, u32 digits, bool uppercase);
unsigned long int xstrtoul(const char *nptr, char **endptr, int base, bool allowPrefix, bool *ok);
unsigned long long int xstrtoull(const char *nptr, char **endptr, int base, bool allowPrefix, bool *ok);

View File

@ -79,9 +79,7 @@ Result GDB_AttachToProcess(GDBContext *ctx)
{
// Note: ctx->pid will be (re)set while processing 'attach process'
DebugEventInfo *info = &ctx->latestDebugEvent;
ctx->state = GDB_STATE_CONNECTED;
ctx->processExited = ctx->processEnded = false;
ctx->latestSentPacketSize = 0;
if (!(ctx->flags & GDB_FLAG_ATTACHED_AT_START))
{
while(R_SUCCEEDED(svcGetProcessDebugEvent(info, ctx->debug)) &&
@ -115,7 +113,10 @@ Result GDB_AttachToProcess(GDBContext *ctx)
else
return r;
return svcSignalEvent(ctx->processAttachedEvent);
r = svcSignalEvent(ctx->processAttachedEvent);
if (R_SUCCEEDED(r))
ctx->state = GDB_STATE_ATTACHED;
return r;
}
void GDB_DetachFromProcess(GDBContext *ctx)
@ -168,7 +169,8 @@ void GDB_DetachFromProcess(GDBContext *ctx)
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);
@ -177,6 +179,24 @@ void GDB_DetachFromProcess(GDBContext *ctx)
ctx->nbThreads = 0;
ctx->totalNbCreatedThreads = 0;
memset(ctx->threadInfos, 0, sizeof(ctx->threadInfos));
ctx->state = GDB_STATE_CONNECTED;
}
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)

View File

@ -24,6 +24,7 @@
* reasonable ways as different from the original version.
*/
#define _GNU_SOURCE // for strchrnul
#include "gdb/debug.h"
#include "gdb/server.h"
#include "gdb/verbose.h"
@ -35,6 +36,105 @@
#include <stdlib.h>
#include <signal.h>
#include "pmdbgext.h"
static void GDB_DetachImmediatelyExtended(GDBContext *ctx)
{
// detach immediately
RecursiveLock_Lock(&ctx->lock);
svcSignalEvent(ctx->parent->statusUpdated); // note: monitor will be waiting for lock
ctx->state = GDB_STATE_DETACHING;
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)
{
@ -49,6 +149,8 @@ GDB_DECLARE_VERBOSE_HANDLER(Attach)
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);
}
@ -63,14 +165,7 @@ GDB_DECLARE_HANDLER(Detach)
{
ctx->state = GDB_STATE_DETACHING;
if (ctx->flags & GDB_FLAG_EXTENDED_REMOTE)
{
// detach immediately
RecursiveLock_Lock(&ctx->lock);
svcSignalEvent(ctx->parent->statusUpdated); // note: monitor will be waiting for lock
GDB_DetachFromProcess(ctx);
ctx->flags = GDB_FLAG_USED;
RecursiveLock_Unlock(&ctx->lock);
}
GDB_DetachImmediatelyExtended(ctx);
return GDB_ReplyOk(ctx);
}
@ -79,13 +174,8 @@ GDB_DECLARE_HANDLER(Kill)
ctx->state = GDB_STATE_DETACHING;
ctx->flags |= GDB_FLAG_TERMINATE_PROCESS;
if (ctx->flags & GDB_FLAG_EXTENDED_REMOTE)
{
// detach & kill immediately
RecursiveLock_Lock(&ctx->lock);
svcSignalEvent(ctx->parent->statusUpdated); // note: monitor will be waiting for lock
GDB_DetachFromProcess(ctx);
RecursiveLock_Unlock(&ctx->lock);
}
GDB_DetachImmediatelyExtended(ctx);
return 0;
}

View File

@ -125,11 +125,47 @@ const char *GDB_ParseIntegerList(u32 *dst, const char *src, u32 nb, char sep, ch
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];
@ -200,8 +236,11 @@ int GDB_ReceivePacket(GDBContext *ctx)
return -1;
}
if(ctx->state == GDB_STATE_NOACK_SENT)
if(ctx->noAckSent)
{
ctx->flags |= GDB_FLAG_NOACK;
ctx->noAckSent = false;
}
return r;

View File

@ -120,13 +120,13 @@ GDB_DECLARE_QUERY_HANDLER(Supported)
GDB_DECLARE_QUERY_HANDLER(StartNoAckMode)
{
ctx->state = GDB_STATE_NOACK_SENT;
ctx->noAckSent = true;
return GDB_ReplyOk(ctx);
}
GDB_DECLARE_QUERY_HANDLER(Attached)
{
return GDB_SendPacket(ctx, "1", 1);
return GDB_SendPacket(ctx, (ctx->flags & GDB_FLAG_CREATED) ? "0" : "1", 1);
}
GDB_DECLARE_QUERY_HANDLER(CatchSyscalls)

View File

@ -158,7 +158,11 @@ GDBContext *GDB_FindAllocatedContextByPid(GDBServer *server, u32 pid)
GDBContext *ctx = NULL;
for(u32 i = 0; i < MAX_DEBUG; i++)
{
if((server->ctxs[i].flags & GDB_FLAG_ALLOCATED_MASK) && server->ctxs[i].pid == pid)
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);
@ -170,8 +174,12 @@ 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;
@ -182,6 +190,7 @@ int GDB_CloseClient(GDBContext *ctx)
RecursiveLock_Lock(&ctx->lock);
svcSignalEvent(ctx->parent->statusUpdated); // note: monitor will be waiting for lock
GDB_DetachFromProcess(ctx);
ctx->state = GDB_STATE_DISCONNECTED;
RecursiveLock_Unlock(&ctx->lock);
return 0;
}
@ -202,7 +211,6 @@ GDBContext *GDB_GetClient(GDBServer *server, u16 port)
if (ctx != NULL)
{
// Context already tied to a port/selected
// Extended remote support disabled
if (ctx->flags & GDB_FLAG_USED)
{
GDB_UnlockAllContexts(server);
@ -274,6 +282,7 @@ static const struct
{ '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) },

View File

@ -38,6 +38,7 @@ static const struct
{ "Cont?", GDB_VERBOSE_HANDLER(ContinueSupported) },
{ "Cont", GDB_VERBOSE_HANDLER(Continue) },
{ "MustReplyEmpty", GDB_HANDLER(Unsupported) },
{ "Run", GDB_VERBOSE_HANDLER(Run) },
};
GDB_DECLARE_HANDLER(VerboseCommand)

View File

@ -152,3 +152,89 @@ unsigned long int xstrtoul(const char *nptr, char **endptr, int base, bool allow
*endptr = (char *) (any ? (char *)s - 1 : nptr);
return (acc);
}
// Copied from newlib, without the reent stuff + some other stuff
unsigned long long int xstrtoull(const char *nptr, char **endptr, int base, bool allowPrefix, bool *ok)
{
register const unsigned char *s = (const unsigned char *)nptr;
register unsigned long long acc;
register int c;
register unsigned long long cutoff;
register int neg = 0, any, cutlim;
if(ok != NULL)
*ok = true;
/*
* See strtol for comments as to the logic used.
*/
do {
c = *s++;
} while ((c >= 9 && c <= 13) || c == ' ');
if (c == '-') {
if(!allowPrefix) {
if(ok != NULL)
*ok = false;
return 0;
}
neg = 1;
c = *s++;
} else if (c == '+'){
if(!allowPrefix) {
if(ok != NULL)
*ok = false;
return 0;
}
c = *s++;
}
if ((base == 0 || base == 16) &&
c == '0' && (*s == 'x' || *s == 'X')) {
if(!allowPrefix) {
if(ok != NULL)
*ok = false;
return 0;
}
c = s[1];
s += 2;
base = 16;
}
if (base == 0) {
if(!allowPrefix) {
if(ok != NULL)
*ok = false;
return 0;
}
base = c == '0' ? 8 : 10;
}
cutoff = (unsigned long long)(-1ull) / (unsigned long long)base;
cutlim = (unsigned long long)(-1ull) % (unsigned long long)base;
for (acc = 0, any = 0;; c = *s++) {
if (c >= '0' && c <= '9')
c -= '0';
else if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
c -= c >= 'A' && c <= 'Z' ? 'A' - 10 : 'a' - 10;
else
break;
if (c >= base)
break;
if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim))
any = -1;
else {
any = 1;
acc *= base;
acc += c;
}
}
if (any < 0) {
acc = (unsigned long long)-1ull;
if(ok != NULL)
*ok = false;
// rptr->_errno = ERANGE;
} else if (neg)
acc = -acc;
if (endptr != 0)
*endptr = (char *) (any ? (char *)s - 1 : nptr);
return (acc);
}

View File

@ -71,13 +71,13 @@ static inline int ProcessListMenu_FormatInfoLine(char *out, const ProcessInfo *i
else if(gdbServer.super.running && ctx != NULL)
{
if(ctx->state >= GDB_STATE_CONNECTED && ctx->state < GDB_STATE_DETACHING)
if(ctx->state >= GDB_STATE_ATTACHED && ctx->state < GDB_STATE_DETACHING)
{
u8 *addr = (u8 *)&ctx->super.addr_in.sin_addr;
checkbox = "(A) ";
sprintf(commentBuf, "Remote: %hhu.%hhu.%hhu.%hhu", addr[0], addr[1], addr[2], addr[3]);
}
else if (ctx->localPort >= GDB_PORT_BASE && ctx->localPort < GDB_PORT_BASE + MAX_DEBUG)
else if ((ctx->flags & GDB_FLAG_SELECTED) && (ctx->localPort >= GDB_PORT_BASE && ctx->localPort < GDB_PORT_BASE + MAX_DEBUG))
{
checkbox = "(W) ";
sprintf(commentBuf, "Port: %hu", ctx->localPort);
@ -596,7 +596,7 @@ static inline void ProcessListMenu_HandleSelected(const ProcessInfo *info)
if(ctx != NULL)
{
if(ctx->flags & GDB_FLAG_USED)
if((ctx->flags & GDB_FLAG_USED) && (ctx->flags & GDB_FLAG_SELECTED))
{
RecursiveLock_Lock(&ctx->lock);
ctx->super.should_close = true;
@ -605,7 +605,7 @@ static inline void ProcessListMenu_HandleSelected(const ProcessInfo *info)
while(ctx->super.should_close)
svcSleepThread(12 * 1000 * 1000LL);
}
else if (ctx->localPort >= GDB_PORT_BASE && ctx->localPort < GDB_PORT_BASE + MAX_DEBUG)
else if ((ctx->flags & GDB_FLAG_SELECTED) && (ctx->localPort >= GDB_PORT_BASE && ctx->localPort < GDB_PORT_BASE + MAX_DEBUG))
{
RecursiveLock_Lock(&ctx->lock);
ctx->flags &= ~GDB_FLAG_SELECTED;