This repository has been archived on 2022-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
Luma3DS-3GX/sysmodules/rosalina/source/gdb/thread.c
2017-06-19 16:04:19 +02:00

329 lines
9.5 KiB
C

/*
* This file is part of Luma3DS
* Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#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%x", 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) ? "%x" : "%x,", 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.: %d, ", (s32)val);
r = svcGetDebugThreadParam(&dummy, &val, ctx->debug, id, DBGTHREAD_PARAMETER_PRIORITY);
if(R_FAILED(r))
sThreadStaticPriority[0] = 0;
else
sprintf(sThreadStaticPriority, "static prio.: %d, ", (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: %u, ", 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 %u", val);
n = sprintf(buf, "TLS: 0x%08x%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, "%08x", tls + offset);
}