/* * 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 . * * 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 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); }