Skip to content

Commit

Permalink
Restore extended-remote support & map <GDB PID>-><1 + 3DS PID> (break…
Browse files Browse the repository at this point in the history
…ing change)

Once more, the "official" gdb client is the one than is the least compliant to its very own stub specs (compared to, say, IDA)
  • Loading branch information
TuxSH committed Apr 8, 2022
1 parent 2b5da40 commit 4e9c7ef
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 39 deletions.
1 change: 1 addition & 0 deletions sysmodules/rosalina/include/gdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ typedef struct GDBContext
Handle processAttachedEvent, continuedEvent;
Handle eventToWaitFor;

bool multiprocessExtEnabled;
bool catchThreadEvents;
bool processEnded, processExited;

Expand Down
1 change: 1 addition & 0 deletions sysmodules/rosalina/include/gdb/debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ GDB_DECLARE_HANDLER(Restart);
GDB_DECLARE_VERBOSE_HANDLER(Attach);
GDB_DECLARE_HANDLER(Detach);
GDB_DECLARE_HANDLER(Kill);
GDB_DECLARE_VERBOSE_HANDLER(Kill);
GDB_DECLARE_HANDLER(Break);
GDB_DECLARE_HANDLER(Continue);
GDB_DECLARE_VERBOSE_HANDLER(Continue);
Expand Down
14 changes: 14 additions & 0 deletions sysmodules/rosalina/include/gdb/thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@

#include "gdb.h"

static inline u32 GDB_ConvertFromRealPid(u32 pid)
{
return pid + 1;
}

static inline u32 GDB_ConvertToRealPid(u32 pid)
{
return pid - 1;
}

const char *GDB_ParseThreadId(GDBContext *ctx, u32 *outPid, u32 *outTid, const char *str, char lastSep);
u32 GDB_ParseDecodeSingleThreadId(GDBContext *ctx, const char *str, char lastSep);
int GDB_EncodeThreadId(GDBContext *ctx, char *outbuf, u32 tid);

u32 GDB_GetCurrentThreadFromList(GDBContext *ctx, u32 *threadIds, u32 nbThreads);
u32 GDB_GetCurrentThread(GDBContext *ctx);

Expand Down
82 changes: 66 additions & 16 deletions sysmodules/rosalina/source/gdb/debug.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ GDB_DECLARE_VERBOSE_HANDLER(Attach)
return GDB_ReplyErrno(ctx, EILSEQ);

RecursiveLock_Lock(&ctx->lock);
ctx->pid = pid;
ctx->pid = GDB_ConvertToRealPid(pid);
Result r = GDB_AttachToProcess(ctx);
if(R_FAILED(r))
GDB_DetachImmediatelyExtended(ctx);
Expand All @@ -154,6 +154,16 @@ GDB_DECLARE_VERBOSE_HANDLER(Attach)

GDB_DECLARE_HANDLER(Detach)
{
if (ctx->multiprocessExtEnabled)
{
//;pid
u32 pid;
if(ctx->commandData[0] != ';' || GDB_ParseHexIntegerList(&pid, ctx->commandData + 1, 1, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
pid = GDB_ConvertToRealPid(pid);
if (pid != ctx->pid)
return GDB_ReplyErrno(ctx, EPERM);
}
ctx->state = GDB_STATE_DETACHING;
if (ctx->flags & GDB_FLAG_EXTENDED_REMOTE)
GDB_DetachImmediatelyExtended(ctx);
Expand All @@ -170,6 +180,29 @@ GDB_DECLARE_HANDLER(Kill)
return 0;
}

GDB_DECLARE_VERBOSE_HANDLER(Kill)
{
if (ctx->multiprocessExtEnabled)
{
//;pid . ';' is already consumed by our caller
u32 pid;
if(GDB_ParseHexIntegerList(&pid, ctx->commandData, 1, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
pid = GDB_ConvertToRealPid(pid);
if (pid != ctx->pid)
return GDB_ReplyErrno(ctx, EPERM);
}

int ret = GDB_ReplyOk(ctx);

ctx->state = GDB_STATE_DETACHING;
ctx->flags |= GDB_FLAG_TERMINATE_PROCESS;
if (ctx->flags & GDB_FLAG_EXTENDED_REMOTE)
GDB_DetachImmediatelyExtended(ctx);

return ret;
}

GDB_DECLARE_HANDLER(Break)
{
if(!(ctx->flags & GDB_FLAG_PROCESS_CONTINUING))
Expand Down Expand Up @@ -232,7 +265,7 @@ GDB_DECLARE_HANDLER(Continue)

GDB_DECLARE_VERBOSE_HANDLER(Continue)
{
char *pos = ctx->commandData;
const char *pos = ctx->commandData;
bool currentThreadFound = false;
while(pos != NULL && *pos != 0 && !currentThreadFound)
{
Expand All @@ -247,18 +280,21 @@ GDB_DECLARE_VERBOSE_HANDLER(Continue)
break;
}

char *nextpos = (char *)strchr(pos, ';');
if(strncmp(pos, "-1", 2) == 0)
u32 pid, tid;

const char *nextpos = GDB_ParseThreadId(ctx, &pid, &tid, pos, ';');
if (nextpos == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);

if (tid == 0)
currentThreadFound = true;
if (pid != ctx->pid)
return GDB_ReplyErrno(ctx, EPERM);
else
{
u32 threadId;
if(GDB_ParseHexIntegerList(&threadId, pos, 1, ';') == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
currentThreadFound = currentThreadFound || threadId == ctx->currentThreadId;
}
currentThreadFound = currentThreadFound || tid == ctx->currentThreadId;

pos = nextpos;
if (nextpos != NULL && *nextpos != '\0')
pos = nextpos + 1;
}

if(ctx->currentThreadId == 0 || currentThreadFound)
Expand All @@ -269,12 +305,18 @@ GDB_DECLARE_VERBOSE_HANDLER(Continue)

GDB_DECLARE_HANDLER(GetStopReason)
{
char pidbuf[32];
if (ctx->multiprocessExtEnabled && ctx->state == GDB_STATE_ATTACHED)
sprintf(pidbuf, ";process:%lx", GDB_ConvertFromRealPid(ctx->pid));
else
pidbuf[0] = '\0';

if (ctx->processEnded && ctx->processExited) {
return GDB_SendPacket(ctx, "W00", 3);
return GDB_SendFormattedPacket(ctx, "W00%s", pidbuf);
} else if (ctx->processEnded && !ctx->processExited) {
return GDB_SendPacket(ctx, "X0f", 3);
return GDB_SendFormattedPacket(ctx, "X0f%s", pidbuf);
} else if (ctx->debug == 0) {
return GDB_SendPacket(ctx, "W00", 3);
return GDB_SendFormattedPacket(ctx, "W00%s", pidbuf);
} else {
return GDB_SendStopReply(ctx, &ctx->latestDebugEvent);
}
Expand All @@ -287,7 +329,10 @@ static int GDB_ParseCommonThreadInfo(char *out, GDBContext *ctx, int sig)
s64 dummy;
u32 core;
Result r = svcGetDebugThreadContext(&regs, ctx->debug, threadId, THREADCONTEXT_CONTROL_ALL);
int n = sprintf(out, "T%02xthread:%lx;", sig, threadId);

char tidbuf[32];
GDB_EncodeThreadId(ctx, tidbuf, ctx->currentThreadId);
int n = sprintf(out, "T%02xthread:%s;", sig, tidbuf);

if(R_FAILED(r))
return n;
Expand Down Expand Up @@ -450,7 +495,12 @@ int GDB_SendStopReply(GDBContext *ctx, const DebugEventInfo *info)
{
// 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);
char pidbuf[32];
if (ctx->multiprocessExtEnabled && ctx->state == GDB_STATE_ATTACHED)
sprintf(pidbuf, ";process:%lx", GDB_ConvertFromRealPid(ctx->pid));
else
pidbuf[0] = '\0';
return GDB_SendFormattedPacket(ctx, "%s%s", processExitReplies[(u32)info->exit_process.reason], pidbuf);
}

case DBGEVENT_EXCEPTION:
Expand Down
15 changes: 14 additions & 1 deletion sysmodules/rosalina/source/gdb/query.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/

#define _GNU_SOURCE
#include "gdb/query.h"
#include "gdb/xfer.h"
#include "gdb/thread.h"
Expand Down Expand Up @@ -89,11 +90,23 @@ int GDB_HandleWriteQuery(GDBContext *ctx)

GDB_DECLARE_QUERY_HANDLER(Supported)
{
const char *pos = ctx->commandData, *nextpos = pos;
do
{
pos = nextpos;
nextpos = strchrnul(pos, ';');
if (*nextpos != ';' && *nextpos != '\0')
return GDB_ReplyErrno(ctx, EILSEQ);

if (strncmp(pos, "multiprocess+", nextpos - pos) == 0)
ctx->multiprocessExtEnabled = true;
} while (*nextpos++ != '\0');

return GDB_SendFormattedPacket(ctx,
"PacketSize=%x;"
"qXfer:features:read+;qXfer:osdata:read+;"
"QStartNoAckMode+;QThreadEvents+;QCatchSyscalls+;"
"vContSupported+;swbreak+",
"vContSupported+;swbreak+;multiprocess+",

GDB_BUF_LEN // should have been sizeof(ctx->buffer) but GDB memory functions are bugged
);
Expand Down
1 change: 1 addition & 0 deletions sysmodules/rosalina/source/gdb/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ int GDB_CloseClient(GDBContext *ctx)
ctx->state = GDB_STATE_DISCONNECTED;

ctx->catchThreadEvents = false;
ctx->multiprocessExtEnabled = false;

memset(&ctx->latestDebugEvent, 0, sizeof(DebugEventInfo));
memset(ctx->memoryOsInfoXmlData, 0, sizeof(ctx->memoryOsInfoXmlData));
Expand Down
120 changes: 99 additions & 21 deletions sysmodules/rosalina/source/gdb/thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,79 @@
#include "fmt.h"
#include <stdlib.h>

const char *GDB_ParseThreadId(GDBContext *ctx, u32 *outPid, u32 *outTid, const char *str, char lastSep)
{
const char *pos = str;
u32 pid = ctx->pid;
u32 tid = 0;

// pPID.TID
if (ctx->multiprocessExtEnabled)
{
// Check for 'p'
if (*pos++ != 'p')
return NULL;
// -1 pid?
if (strncmp(pos, "-1.", 3) == 0)
{
pid = (u32)-1; // Should encode as 0 but oh well
pos += 3;
}
else
{
pos = GDB_ParseHexIntegerList(&pid, pos, 1, '.');
if (pos == NULL)
return NULL;
pid = GDB_ConvertToRealPid(pid);
++pos;
}
}

// Fallthrough
// TID
if (strncmp(pos, "-1", 2) == 0)
{
tid = 0; // TID 0 is always invalid
pos += 2;
}
else
{
if (pid == (u32)-1)
return NULL; // this is never allowed

pos = GDB_ParseHexIntegerList(&tid, pos, 1, lastSep);
if (pos == NULL)
return NULL;
}

if (pos != NULL)
{
*outPid = pid;
*outTid = tid;
}
return pos;
}

u32 GDB_ParseDecodeSingleThreadId(GDBContext *ctx, const char *str, char lastSep)
{
u32 pid, tid;
if (GDB_ParseThreadId(ctx, &pid, &tid, str, lastSep) == NULL)
return 0;

if (pid != ctx->pid)
return 0;

return tid;
}

int GDB_EncodeThreadId(GDBContext *ctx, char *outbuf, u32 tid)
{
if (ctx->multiprocessExtEnabled)
return sprintf(outbuf, "p%lx.%lx", GDB_ConvertFromRealPid(ctx->pid), tid);
else
return sprintf(outbuf, "%lx", tid);
}

static s32 GDB_GetDynamicThreadPriority(GDBContext *ctx, u32 threadId)
{
Handle process, thread;
Expand Down Expand Up @@ -95,27 +168,25 @@ 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)
u32 tid = GDB_ParseDecodeSingleThreadId(ctx, ctx->commandData + 1, 0);
if (tid == 0)
return GDB_ReplyErrno(ctx, EILSEQ);
ctx->selectedThreadId = id;

ctx->selectedThreadId = tid;
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;
}

u32 pid, tid;
if (GDB_ParseThreadId(ctx, &pid, &tid, ctx->commandData + 1, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);

if (pid != ctx->pid)
return GDB_ReplyErrno(ctx, EPERM);

ctx->selectedThreadIdForContinuing = tid;

return GDB_ReplyOk(ctx);
}
Expand All @@ -125,14 +196,14 @@ GDB_DECLARE_HANDLER(SetThreadId)

GDB_DECLARE_HANDLER(IsThreadAlive)
{
u32 threadId;
s64 dummy;
u32 mask;

if(GDB_ParseHexIntegerList(&threadId, ctx->commandData, 1, 0) == NULL)
u32 tid = GDB_ParseDecodeSingleThreadId(ctx, ctx->commandData, 0);
if (tid == 0)
return GDB_ReplyErrno(ctx, EILSEQ);

Result r = svcGetDebugThreadParam(&dummy, &mask, ctx->debug, threadId, DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW);
Result r = svcGetDebugThreadParam(&dummy, &mask, ctx->debug, tid, DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW);
if(R_SUCCEEDED(r) && mask != 2)
return GDB_ReplyOk(ctx);
else
Expand All @@ -144,7 +215,9 @@ 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);
char buf[32];
GDB_EncodeThreadId(ctx, buf, ctx->currentThreadId);
return ctx->currentThreadId != 0 ? GDB_SendFormattedPacket(ctx, "QC%s", buf) : GDB_ReplyErrno(ctx, EPERM);
}

static void GDB_GenerateThreadListData(GDBContext *ctx)
Expand All @@ -168,7 +241,11 @@ static void GDB_GenerateThreadListData(GDBContext *ctx)
char *bufptr = ctx->threadListData;

for(u32 i = 0; i < nbAliveThreads; i++)
bufptr += sprintf(bufptr, i == (nbAliveThreads - 1) ? "%lx" : "%lx,", aliveThreadIds[i]);
{
bufptr += GDB_EncodeThreadId(ctx, bufptr, aliveThreadIds[i]);
if (i < nbAliveThreads - 1)
*bufptr++ = ',';
}
}

static int GDB_SendThreadData(GDBContext *ctx)
Expand Down Expand Up @@ -242,7 +319,8 @@ GDB_DECLARE_QUERY_HANDLER(ThreadExtraInfo)

u32 tls = 0;

if(GDB_ParseHexIntegerList(&id, ctx->commandData, 1, 0) == NULL)
id = GDB_ParseDecodeSingleThreadId(ctx, ctx->commandData, 0);
if (id == 0)
return GDB_ReplyErrno(ctx, EILSEQ);

for(u32 i = 0; i < MAX_DEBUG_THREAD; i++)
Expand Down

0 comments on commit 4e9c7ef

Please sign in to comment.