Restore extended-remote support & map <GDB PID>-><1 + 3DS PID> (breaking 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)
This commit is contained in:
TuxSH 2022-04-08 23:47:31 +01:00
parent 2b5da40a1d
commit 01ebbf114c
9 changed files with 230 additions and 41 deletions

View File

@ -124,6 +124,7 @@ typedef struct GDBContext
Handle processAttachedEvent, continuedEvent; Handle processAttachedEvent, continuedEvent;
Handle eventToWaitFor; Handle eventToWaitFor;
bool multiprocessExtEnabled;
bool catchThreadEvents; bool catchThreadEvents;
bool processEnded, processExited; bool processEnded, processExited;

View File

@ -14,6 +14,7 @@ GDB_DECLARE_HANDLER(Restart);
GDB_DECLARE_VERBOSE_HANDLER(Attach); GDB_DECLARE_VERBOSE_HANDLER(Attach);
GDB_DECLARE_HANDLER(Detach); GDB_DECLARE_HANDLER(Detach);
GDB_DECLARE_HANDLER(Kill); GDB_DECLARE_HANDLER(Kill);
GDB_DECLARE_VERBOSE_HANDLER(Kill);
GDB_DECLARE_HANDLER(Break); GDB_DECLARE_HANDLER(Break);
GDB_DECLARE_HANDLER(Continue); GDB_DECLARE_HANDLER(Continue);
GDB_DECLARE_VERBOSE_HANDLER(Continue); GDB_DECLARE_VERBOSE_HANDLER(Continue);

View File

@ -9,6 +9,20 @@
#include "gdb.h" #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_GetCurrentThreadFromList(GDBContext *ctx, u32 *threadIds, u32 nbThreads);
u32 GDB_GetCurrentThread(GDBContext *ctx); u32 GDB_GetCurrentThread(GDBContext *ctx);

View File

@ -138,7 +138,7 @@ GDB_DECLARE_VERBOSE_HANDLER(Attach)
return GDB_ReplyErrno(ctx, EILSEQ); return GDB_ReplyErrno(ctx, EILSEQ);
RecursiveLock_Lock(&ctx->lock); RecursiveLock_Lock(&ctx->lock);
ctx->pid = pid; ctx->pid = GDB_ConvertToRealPid(pid);
Result r = GDB_AttachToProcess(ctx); Result r = GDB_AttachToProcess(ctx);
if(R_FAILED(r)) if(R_FAILED(r))
GDB_DetachImmediatelyExtended(ctx); GDB_DetachImmediatelyExtended(ctx);
@ -154,6 +154,16 @@ GDB_DECLARE_VERBOSE_HANDLER(Attach)
GDB_DECLARE_HANDLER(Detach) 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; ctx->state = GDB_STATE_DETACHING;
if (ctx->flags & GDB_FLAG_EXTENDED_REMOTE) if (ctx->flags & GDB_FLAG_EXTENDED_REMOTE)
GDB_DetachImmediatelyExtended(ctx); GDB_DetachImmediatelyExtended(ctx);
@ -170,6 +180,29 @@ GDB_DECLARE_HANDLER(Kill)
return 0; 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) GDB_DECLARE_HANDLER(Break)
{ {
if(!(ctx->flags & GDB_FLAG_PROCESS_CONTINUING)) if(!(ctx->flags & GDB_FLAG_PROCESS_CONTINUING))
@ -232,7 +265,7 @@ GDB_DECLARE_HANDLER(Continue)
GDB_DECLARE_VERBOSE_HANDLER(Continue) GDB_DECLARE_VERBOSE_HANDLER(Continue)
{ {
char *pos = ctx->commandData; const char *pos = ctx->commandData;
bool currentThreadFound = false; bool currentThreadFound = false;
while(pos != NULL && *pos != 0 && !currentThreadFound) while(pos != NULL && *pos != 0 && !currentThreadFound)
{ {
@ -247,18 +280,21 @@ GDB_DECLARE_VERBOSE_HANDLER(Continue)
break; break;
} }
char *nextpos = (char *)strchr(pos, ';'); u32 pid, tid;
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; const char *nextpos = GDB_ParseThreadId(ctx, &pid, &tid, pos, ';');
if (nextpos == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
if (tid == 0)
currentThreadFound = true;
if (pid != (u32)-1 && pid != ctx->pid)
return GDB_ReplyErrno(ctx, EPERM);
else
currentThreadFound = currentThreadFound || tid == ctx->currentThreadId;
if (nextpos != NULL && *nextpos != '\0')
pos = nextpos + 1;
} }
if(ctx->currentThreadId == 0 || currentThreadFound) if(ctx->currentThreadId == 0 || currentThreadFound)
@ -269,12 +305,18 @@ GDB_DECLARE_VERBOSE_HANDLER(Continue)
GDB_DECLARE_HANDLER(GetStopReason) 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) { if (ctx->processEnded && ctx->processExited) {
return GDB_SendPacket(ctx, "W00", 3); return GDB_SendFormattedPacket(ctx, "W00%s", pidbuf);
} else if (ctx->processEnded && !ctx->processExited) { } else if (ctx->processEnded && !ctx->processExited) {
return GDB_SendPacket(ctx, "X0f", 3); return GDB_SendFormattedPacket(ctx, "X0f%s", pidbuf);
} else if (ctx->debug == 0) { } else if (ctx->debug == 0) {
return GDB_SendPacket(ctx, "W00", 3); return GDB_SendFormattedPacket(ctx, "W00%s", pidbuf);
} else { } else {
return GDB_SendStopReply(ctx, &ctx->latestDebugEvent); return GDB_SendStopReply(ctx, &ctx->latestDebugEvent);
} }
@ -287,7 +329,10 @@ static int GDB_ParseCommonThreadInfo(char *out, GDBContext *ctx, int sig)
s64 dummy; s64 dummy;
u32 core; u32 core;
Result r = svcGetDebugThreadContext(&regs, ctx->debug, threadId, THREADCONTEXT_CONTROL_ALL); 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)) if(R_FAILED(r))
return n; return n;
@ -450,7 +495,12 @@ int GDB_SendStopReply(GDBContext *ctx, const DebugEventInfo *info)
{ {
// exited (no error / unhandled exception), SIGTERM (process terminated) * 2 // exited (no error / unhandled exception), SIGTERM (process terminated) * 2
static const char *processExitReplies[] = { "W00", "X0f", "X0f" }; 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: case DBGEVENT_EXCEPTION:

View File

@ -5,6 +5,7 @@
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) * SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/ */
#define _GNU_SOURCE
#include "gdb/query.h" #include "gdb/query.h"
#include "gdb/xfer.h" #include "gdb/xfer.h"
#include "gdb/thread.h" #include "gdb/thread.h"
@ -89,11 +90,23 @@ int GDB_HandleWriteQuery(GDBContext *ctx)
GDB_DECLARE_QUERY_HANDLER(Supported) 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, return GDB_SendFormattedPacket(ctx,
"PacketSize=%x;" "PacketSize=%x;"
"qXfer:features:read+;qXfer:osdata:read+;" "qXfer:features:read+;qXfer:osdata:read+;"
"QStartNoAckMode+;QThreadEvents+;QCatchSyscalls+;" "QStartNoAckMode+;QThreadEvents+;QCatchSyscalls+;"
"vContSupported+;swbreak+", "vContSupported+;swbreak+;multiprocess+",
GDB_BUF_LEN // should have been sizeof(ctx->buffer) but GDB memory functions are bugged GDB_BUF_LEN // should have been sizeof(ctx->buffer) but GDB memory functions are bugged
); );

View File

@ -199,6 +199,7 @@ int GDB_CloseClient(GDBContext *ctx)
ctx->state = GDB_STATE_DISCONNECTED; ctx->state = GDB_STATE_DISCONNECTED;
ctx->catchThreadEvents = false; ctx->catchThreadEvents = false;
ctx->multiprocessExtEnabled = false;
memset(&ctx->latestDebugEvent, 0, sizeof(DebugEventInfo)); memset(&ctx->latestDebugEvent, 0, sizeof(DebugEventInfo));
memset(ctx->memoryOsInfoXmlData, 0, sizeof(ctx->memoryOsInfoXmlData)); memset(ctx->memoryOsInfoXmlData, 0, sizeof(ctx->memoryOsInfoXmlData));

View File

@ -10,6 +10,103 @@
#include "fmt.h" #include "fmt.h"
#include <stdlib.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 | PID
if (ctx->multiprocessExtEnabled)
{
// Check for 'p'
if (*pos != 'p')
{
// Test -1 first.
// Note: this means we parse -1, p-1.0, p0.0 the same which is a bug
if (strcmp(pos, "-1") == 0)
{
pid = (u32)-1;
tid = 0;
pos += 2;
}
else
{
pos = GDB_ParseHexIntegerList(&pid, pos, 1, 0);
if (pos == NULL)
return NULL;
pid = GDB_ConvertToRealPid(pid);
tid = 0;
}
*outPid = pid;
*outTid = 0;
return pos;
}
else
++pos;
// -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
{
pos = GDB_ParseHexIntegerList(&tid, pos, 1, lastSep);
if (pos == NULL)
return NULL;
}
if (pid == (u32)-1 && tid != 0)
return NULL; // this is never allowed
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) static s32 GDB_GetDynamicThreadPriority(GDBContext *ctx, u32 threadId)
{ {
Handle process, thread; Handle process, thread;
@ -95,27 +192,30 @@ GDB_DECLARE_HANDLER(SetThreadId)
{ {
if(ctx->commandData[0] == 'g') if(ctx->commandData[0] == 'g')
{ {
if(strncmp(ctx->commandData + 1, "-1", 2) == 0) u32 pid = 0, tid = 0;
return GDB_ReplyErrno(ctx, EILSEQ); // a thread must be specified if (GDB_ParseThreadId(ctx, &pid, &tid, ctx->commandData + 1, 0) == NULL)
u32 id;
if(GDB_ParseHexIntegerList(&id, ctx->commandData + 1, 1, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ); return GDB_ReplyErrno(ctx, EILSEQ);
ctx->selectedThreadId = id;
// Allow ptid = p0.0; note that this catches some -1 forms, which is a bug
if (pid != (u32)-1 && pid != ctx->pid)
return GDB_ReplyErrno(ctx, EILSEQ);
ctx->selectedThreadId = tid;
return GDB_ReplyOk(ctx); return GDB_ReplyOk(ctx);
} }
else if(ctx->commandData[0] == 'c') else if(ctx->commandData[0] == 'c')
{ {
// We can't stop/continue particular threads (uncompliant behavior) // We can't stop/continue particular threads (uncompliant behavior)
if(strncmp(ctx->commandData + 1, "-1", 2) == 0)
ctx->selectedThreadIdForContinuing = 0; u32 pid, tid;
else if (GDB_ParseThreadId(ctx, &pid, &tid, ctx->commandData + 1, 0) == NULL)
{
u32 id;
if(GDB_ParseHexIntegerList(&id, ctx->commandData + 1, 1, 0) == NULL)
return GDB_ReplyErrno(ctx, EILSEQ); return GDB_ReplyErrno(ctx, EILSEQ);
ctx->selectedThreadIdForContinuing = id;
} // Allow gdb pid.tid = -1.-1, (we're also accepting pid = 0 but that's a bug)
if (pid != (u32)-1 && pid != ctx->pid)
return GDB_ReplyErrno(ctx, EPERM);
ctx->selectedThreadIdForContinuing = tid;
return GDB_ReplyOk(ctx); return GDB_ReplyOk(ctx);
} }
@ -125,14 +225,14 @@ GDB_DECLARE_HANDLER(SetThreadId)
GDB_DECLARE_HANDLER(IsThreadAlive) GDB_DECLARE_HANDLER(IsThreadAlive)
{ {
u32 threadId;
s64 dummy; s64 dummy;
u32 mask; 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); 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) if(R_SUCCEEDED(r) && mask != 2)
return GDB_ReplyOk(ctx); return GDB_ReplyOk(ctx);
else else
@ -144,7 +244,9 @@ GDB_DECLARE_QUERY_HANDLER(CurrentThreadId)
if(ctx->currentThreadId == 0) if(ctx->currentThreadId == 0)
ctx->currentThreadId = GDB_GetCurrentThread(ctx); 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) static void GDB_GenerateThreadListData(GDBContext *ctx)
@ -168,7 +270,11 @@ static void GDB_GenerateThreadListData(GDBContext *ctx)
char *bufptr = ctx->threadListData; char *bufptr = ctx->threadListData;
for(u32 i = 0; i < nbAliveThreads; i++) 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) static int GDB_SendThreadData(GDBContext *ctx)
@ -242,7 +348,8 @@ GDB_DECLARE_QUERY_HANDLER(ThreadExtraInfo)
u32 tls = 0; 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); return GDB_ReplyErrno(ctx, EILSEQ);
for(u32 i = 0; i < MAX_DEBUG_THREAD; i++) for(u32 i = 0; i < MAX_DEBUG_THREAD; i++)

View File

@ -22,6 +22,7 @@ static const struct
{ "File", GDB_VERBOSE_HANDLER(File) }, { "File", GDB_VERBOSE_HANDLER(File) },
{ "MustReplyEmpty", GDB_HANDLER(Unsupported) }, { "MustReplyEmpty", GDB_HANDLER(Unsupported) },
{ "Run", GDB_VERBOSE_HANDLER(Run) }, { "Run", GDB_VERBOSE_HANDLER(Run) },
{ "Kill", GDB_VERBOSE_HANDLER(Kill) },
}; };
GDB_DECLARE_HANDLER(VerboseCommand) GDB_DECLARE_HANDLER(VerboseCommand)

View File

@ -9,6 +9,7 @@
#include "gdb/xfer.h" #include "gdb/xfer.h"
#include "gdb/net.h" #include "gdb/net.h"
#include "gdb/thread.h"
#include "fmt.h" #include "fmt.h"
#include "osdata_cfw_version_template_xml.h" #include "osdata_cfw_version_template_xml.h"
@ -161,7 +162,7 @@ GDB_DECLARE_XFER_OSDATA_HANDLER(Processes)
memcpy(name, &out, 8); memcpy(name, &out, 8);
svcCloseHandle(processHandle); svcCloseHandle(processHandle);
n = sprintf(ctx->processesOsInfoXmlData + pos, item, pid, name); n = sprintf(ctx->processesOsInfoXmlData + pos, item, GDB_ConvertFromRealPid(pid), name);
pos += (u32)n; pos += (u32)n;
} }