Added Rosalina, see details
- see release notes - ( ͡° ͜ʖ ͡°)( ͡° ͜ʖ ͡°)( ͡° ͜ʖ ͡°) - (∩ ͡° ͜ʖ ͡°)⊃━☆゚ - ( ͡ᵔ ͜ʖ ͡ᵔ) ♫┌( ͡° ͜ʖ ͡°)┘♪ ♫└( ͡° ͜ʖ ͡°)┐♪
This commit is contained in:
130
sysmodules/rosalina/source/gdb/breakpoints.c
Normal file
130
sysmodules/rosalina/source/gdb/breakpoints.c
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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/breakpoints.h"
|
||||
|
||||
#define _REENT_ONLY
|
||||
#include <errno.h>
|
||||
|
||||
u32 GDB_FindClosestBreakpointSlot(GDBContext *ctx, u32 address)
|
||||
{
|
||||
if(ctx->nbBreakpoints == 0 || address <= ctx->breakpoints[0].address)
|
||||
return 0;
|
||||
else if(address > ctx->breakpoints[ctx->nbBreakpoints - 1].address)
|
||||
return ctx->nbBreakpoints;
|
||||
|
||||
u32 a = 0, b = ctx->nbBreakpoints - 1, m;
|
||||
|
||||
do
|
||||
{
|
||||
m = (a + b) / 2;
|
||||
if(ctx->breakpoints[m].address < address)
|
||||
a = m;
|
||||
else if(ctx->breakpoints[m].address > address)
|
||||
b = m;
|
||||
else
|
||||
return m;
|
||||
}
|
||||
while(b - a > 1);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
int GDB_GetBreakpointInstruction(u32 *instruction, GDBContext *ctx, u32 address)
|
||||
{
|
||||
u32 id = GDB_FindClosestBreakpointSlot(ctx, address);
|
||||
|
||||
if(id == ctx->nbBreakpoints || ctx->breakpoints[id].address != address)
|
||||
return -EINVAL;
|
||||
|
||||
if(instruction != NULL)
|
||||
*instruction = ctx->breakpoints[id].savedInstruction;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int GDB_AddBreakpoint(GDBContext *ctx, u32 address, bool thumb, bool persist)
|
||||
{
|
||||
u32 id = GDB_FindClosestBreakpointSlot(ctx, address);
|
||||
|
||||
if(id != ctx->nbBreakpoints && ctx->breakpoints[id].instructionSize != 0 && ctx->breakpoints[id].address == address)
|
||||
return 0;
|
||||
else if(ctx->nbBreakpoints == MAX_BREAKPOINT)
|
||||
return -EBUSY;
|
||||
else if((thumb && (address & 1) != 0) || (!thumb && (address & 3) != 0))
|
||||
return -EINVAL;
|
||||
|
||||
for(u32 i = ctx->nbBreakpoints; i > id && i != 0; i--)
|
||||
ctx->breakpoints[i] = ctx->breakpoints[i - 1];
|
||||
|
||||
ctx->nbBreakpoints++;
|
||||
|
||||
Breakpoint *bkpt = &ctx->breakpoints[id];
|
||||
u32 instr = thumb ? BREAKPOINT_INSTRUCTION_THUMB : BREAKPOINT_INSTRUCTION_ARM;
|
||||
if(R_FAILED(svcReadProcessMemory(&bkpt->savedInstruction, ctx->debug, address, thumb ? 2 : 4)) ||
|
||||
R_FAILED(svcWriteProcessMemory(ctx->debug, &instr, address, thumb ? 2 : 4)))
|
||||
{
|
||||
for(u32 i = id; i < ctx->nbBreakpoints - 1; i++)
|
||||
ctx->breakpoints[i] = ctx->breakpoints[i + 1];
|
||||
|
||||
memset(&ctx->breakpoints[--ctx->nbBreakpoints], 0, sizeof(Breakpoint));
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
bkpt->instructionSize = thumb ? 2 : 4;
|
||||
bkpt->address = address;
|
||||
bkpt->persistent = persist;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int GDB_DisableBreakpointById(GDBContext *ctx, u32 id)
|
||||
{
|
||||
Breakpoint *bkpt = &ctx->breakpoints[id];
|
||||
if(R_FAILED(svcWriteProcessMemory(ctx->debug, &bkpt->savedInstruction, bkpt->address, bkpt->instructionSize)))
|
||||
return -EFAULT;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
int GDB_RemoveBreakpoint(GDBContext *ctx, u32 address)
|
||||
{
|
||||
u32 id = GDB_FindClosestBreakpointSlot(ctx, address);
|
||||
if(id == ctx->nbBreakpoints || ctx->breakpoints[id].address != address)
|
||||
return -EINVAL;
|
||||
|
||||
int r = GDB_DisableBreakpointById(ctx, id);
|
||||
if(r != 0)
|
||||
return r;
|
||||
else
|
||||
{
|
||||
for(u32 i = id; i < ctx->nbBreakpoints - 1; i++)
|
||||
ctx->breakpoints[i] = ctx->breakpoints[i + 1];
|
||||
|
||||
memset(&ctx->breakpoints[--ctx->nbBreakpoints], 0, sizeof(Breakpoint));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
512
sysmodules/rosalina/source/gdb/debug.c
Normal file
512
sysmodules/rosalina/source/gdb/debug.c
Normal file
@@ -0,0 +1,512 @@
|
||||
/*
|
||||
* 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/debug.h"
|
||||
#include "gdb/verbose.h"
|
||||
#include "gdb/net.h"
|
||||
#include "gdb/thread.h"
|
||||
#include "gdb/mem.h"
|
||||
#include "gdb/watchpoints.h"
|
||||
#include "fmt.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
|
||||
/*
|
||||
Since we can't select particular threads to continue (and that's uncompliant behavior):
|
||||
- if we continue the current thread, continue all threads
|
||||
- otherwise, leaves all threads stopped but make the client believe it's continuing
|
||||
*/
|
||||
|
||||
GDB_DECLARE_HANDLER(Detach)
|
||||
{
|
||||
ctx->state = GDB_STATE_CLOSING;
|
||||
return GDB_ReplyOk(ctx);
|
||||
}
|
||||
|
||||
GDB_DECLARE_HANDLER(Kill)
|
||||
{
|
||||
ctx->state = GDB_STATE_CLOSING;
|
||||
ctx->flags |= GDB_FLAG_TERMINATE_PROCESS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
GDB_DECLARE_HANDLER(Break)
|
||||
{
|
||||
if(!(ctx->flags & GDB_FLAG_PROCESS_CONTINUING))
|
||||
return GDB_SendPacket(ctx, "S02", 3);
|
||||
else
|
||||
{
|
||||
ctx->flags &= ~GDB_FLAG_PROCESS_CONTINUING;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void GDB_ContinueExecution(GDBContext *ctx)
|
||||
{
|
||||
ctx->selectedThreadId = ctx->selectedThreadIdForContinuing = 0;
|
||||
svcContinueDebugEvent(ctx->debug, ctx->continueFlags);
|
||||
ctx->flags |= GDB_FLAG_PROCESS_CONTINUING;
|
||||
}
|
||||
|
||||
GDB_DECLARE_HANDLER(Continue)
|
||||
{
|
||||
char *addrStart = NULL;
|
||||
u32 addr = 0;
|
||||
|
||||
if(ctx->selectedThreadIdForContinuing != 0 && ctx->selectedThreadIdForContinuing != ctx->currentThreadId)
|
||||
return 0;
|
||||
|
||||
if(ctx->commandData[-1] == 'C')
|
||||
{
|
||||
if(ctx->commandData[0] == 0 || ctx->commandData[1] == 0 || (ctx->commandData[2] != 0 && ctx->commandData[2] == ';'))
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
// Signal ignored...
|
||||
|
||||
if(ctx->commandData[2] == ';')
|
||||
addrStart = ctx->commandData + 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(ctx->commandData[0] != 0)
|
||||
addrStart = ctx->commandData;
|
||||
}
|
||||
|
||||
if(addrStart != NULL && ctx->currentThreadId != 0)
|
||||
{
|
||||
ThreadContext regs;
|
||||
if(GDB_ParseHexIntegerList(&addr, ctx->commandData + 3, 1, 0) == NULL)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
Result r = svcGetDebugThreadContext(®s, ctx->debug, ctx->currentThreadId, THREADCONTEXT_CONTROL_CPU_SPRS);
|
||||
if(R_SUCCEEDED(r))
|
||||
{
|
||||
regs.cpu_registers.pc = addr;
|
||||
r = svcSetDebugThreadContext(ctx->debug, ctx->currentThreadId, ®s, THREADCONTEXT_CONTROL_CPU_SPRS);
|
||||
}
|
||||
}
|
||||
|
||||
GDB_ContinueExecution(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
GDB_DECLARE_VERBOSE_HANDLER(Continue)
|
||||
{
|
||||
char *pos = ctx->commandData;
|
||||
bool currentThreadFound = false;
|
||||
while(pos != NULL && *pos != 0 && !currentThreadFound)
|
||||
{
|
||||
if(*pos != 'c' && *pos != 'C')
|
||||
return GDB_ReplyErrno(ctx, EPERM);
|
||||
|
||||
pos += *pos == 'C' ? 3 : 1;
|
||||
|
||||
if(*pos++ != ':') // default action found
|
||||
{
|
||||
currentThreadFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
char *nextpos = (char *)strchr(pos, ';');
|
||||
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;
|
||||
}
|
||||
|
||||
if(ctx->currentThreadId == 0 || currentThreadFound)
|
||||
GDB_ContinueExecution(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
GDB_DECLARE_HANDLER(GetStopReason)
|
||||
{
|
||||
return GDB_SendStopReply(ctx, &ctx->latestDebugEvent);
|
||||
}
|
||||
|
||||
static int GDB_ParseCommonThreadInfo(char *out, GDBContext *ctx, int sig)
|
||||
{
|
||||
u32 threadId = ctx->currentThreadId;
|
||||
ThreadContext regs;
|
||||
s64 dummy;
|
||||
u32 core;
|
||||
Result r = svcGetDebugThreadContext(®s, ctx->debug, threadId, THREADCONTEXT_CONTROL_ALL);
|
||||
int n = sprintf(out, "T%02xthread:%x;", sig, threadId);
|
||||
|
||||
if(R_FAILED(r))
|
||||
return n;
|
||||
|
||||
r = svcGetDebugThreadParam(&dummy, &core, ctx->debug, ctx->currentThreadId, DBGTHREAD_PARAMETER_CPU_CREATOR); // Creator = "first ran, and running the thread"
|
||||
|
||||
if(R_SUCCEEDED(r))
|
||||
n += sprintf(out + n, "core:%x;", core);
|
||||
|
||||
if(ctx->isGDB)
|
||||
{
|
||||
for(u32 i = 0; i <= 12; i++)
|
||||
n += sprintf(out + n, "%x:%08x;", i, __builtin_bswap32(regs.cpu_registers.r[i]));
|
||||
}
|
||||
n += sprintf(out + n, "d:%08x;e:%08x;f:%08x;19:%08x;",
|
||||
__builtin_bswap32(regs.cpu_registers.sp), __builtin_bswap32(regs.cpu_registers.lr), __builtin_bswap32(regs.cpu_registers.pc),
|
||||
__builtin_bswap32(regs.cpu_registers.cpsr));
|
||||
|
||||
if(ctx->isGDB)
|
||||
{
|
||||
for(u32 i = 0; i < 16; i++)
|
||||
{
|
||||
u64 val;
|
||||
memcpy(&val, ®s.fpu_registers.d[i], 8);
|
||||
n += sprintf(out + n, "%x:%016llx;", 26 + i, __builtin_bswap64(val));
|
||||
}
|
||||
|
||||
n += sprintf(out + n, "2a:%08x;2b:%08x;", __builtin_bswap32(regs.fpu_registers.fpscr), __builtin_bswap32(regs.fpu_registers.fpexc));
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void GDB_PreprocessDebugEvent(GDBContext *ctx, DebugEventInfo *info)
|
||||
{
|
||||
switch(info->type)
|
||||
{
|
||||
case DBGEVENT_ATTACH_THREAD:
|
||||
{
|
||||
if(ctx->nbThreads == MAX_DEBUG_THREAD)
|
||||
svcBreak(USERBREAK_ASSERT);
|
||||
else
|
||||
{
|
||||
ctx->threadInfos[ctx->nbThreads].id = info->thread_id;
|
||||
ctx->threadInfos[ctx->nbThreads++].tls = info->attach_thread.thread_local_storage;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DBGEVENT_EXIT_THREAD:
|
||||
{
|
||||
u32 i;
|
||||
for(i = 0; i < ctx->nbThreads && ctx->threadInfos[i].id != info->thread_id; i++);
|
||||
if(i == ctx->nbThreads || ctx->threadInfos[i].id != info->thread_id)
|
||||
svcBreak(USERBREAK_ASSERT);
|
||||
else
|
||||
{
|
||||
for(u32 j = i; j < ctx->nbThreads - 1; j++)
|
||||
memcpy(ctx->threadInfos + j, ctx->threadInfos + j + 1, sizeof(ThreadInfo));
|
||||
memset(ctx->threadInfos + --ctx->nbThreads, 0, sizeof(ThreadInfo));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DBGEVENT_EXIT_PROCESS:
|
||||
{
|
||||
ctx->processEnded = true;
|
||||
ctx->processExited = info->exit_process.reason == EXITPROCESS_EVENT_EXIT;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DBGEVENT_OUTPUT_STRING:
|
||||
{
|
||||
if(info->output_string.string_addr >= 0xFFFFFFFE)
|
||||
{
|
||||
u32 sz = info->output_string.string_size, addr = info->output_string.string_addr, threadId = info->thread_id;
|
||||
memset(info, 0, sizeof(DebugEventInfo));
|
||||
info->type = (addr == 0xFFFFFFFF) ? DBGEVENT_SYSCALL_OUT : DBGEVENT_SYSCALL_IN;
|
||||
info->thread_id = threadId;
|
||||
info->flags = 1;
|
||||
info->syscall.syscall = sz;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DBGEVENT_EXCEPTION:
|
||||
{
|
||||
switch(info->exception.type)
|
||||
{
|
||||
case EXCEVENT_UNDEFINED_INSTRUCTION:
|
||||
{
|
||||
// kernel bugfix for thumb mode
|
||||
ThreadContext regs;
|
||||
Result r = svcGetDebugThreadContext(®s, ctx->debug, info->thread_id, THREADCONTEXT_CONTROL_CPU_SPRS);
|
||||
if(R_SUCCEEDED(r) && (regs.cpu_registers.cpsr & 0x20) != 0)
|
||||
{
|
||||
regs.cpu_registers.pc += 2;
|
||||
r = svcSetDebugThreadContext(ctx->debug, info->thread_id, ®s, THREADCONTEXT_CONTROL_CPU_SPRS);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int GDB_SendStopReply(GDBContext *ctx, const DebugEventInfo *info)
|
||||
{
|
||||
char buffer[GDB_BUF_LEN + 1];
|
||||
|
||||
switch(info->type)
|
||||
{
|
||||
case DBGEVENT_ATTACH_PROCESS:
|
||||
break; // Dismissed
|
||||
|
||||
case DBGEVENT_ATTACH_THREAD:
|
||||
{
|
||||
if(info->attach_thread.creator_thread_id == 0 || !ctx->catchThreadEvents)
|
||||
break; // Dismissed
|
||||
else
|
||||
{
|
||||
ctx->currentThreadId = info->thread_id;
|
||||
return GDB_SendPacket(ctx, "T05create:;", 10);
|
||||
}
|
||||
}
|
||||
|
||||
case DBGEVENT_EXIT_THREAD:
|
||||
{
|
||||
if(ctx->catchThreadEvents && info->exit_thread.reason < EXITTHREAD_EVENT_EXIT_PROCESS)
|
||||
{
|
||||
// no signal, SIGTERM, SIGQUIT (process exited), SIGTERM (process terminated)
|
||||
static int threadExitRepliesSigs[] = { 0, SIGTERM, SIGQUIT, SIGTERM };
|
||||
return GDB_SendFormattedPacket(ctx, "w%02x;%x", threadExitRepliesSigs[(u32)info->exit_thread.reason], info->thread_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case DBGEVENT_EXIT_PROCESS:
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
case DBGEVENT_EXCEPTION:
|
||||
{
|
||||
ExceptionEvent exc = info->exception;
|
||||
|
||||
switch(exc.type)
|
||||
{
|
||||
case EXCEVENT_UNDEFINED_INSTRUCTION:
|
||||
case EXCEVENT_PREFETCH_ABORT: // doesn't include hardware breakpoints
|
||||
case EXCEVENT_DATA_ABORT: // doesn't include hardware watchpoints
|
||||
case EXCEVENT_UNALIGNED_DATA_ACCESS:
|
||||
case EXCEVENT_UNDEFINED_SYSCALL:
|
||||
{
|
||||
u32 signum = exc.type == EXCEVENT_UNDEFINED_INSTRUCTION ? SIGILL :
|
||||
(exc.type == EXCEVENT_UNDEFINED_SYSCALL ? SIGSYS : SIGSEGV);
|
||||
|
||||
ctx->currentThreadId = info->thread_id;
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, signum);
|
||||
return GDB_SendFormattedPacket(ctx, "%s", buffer);
|
||||
}
|
||||
|
||||
case EXCEVENT_ATTACH_BREAK:
|
||||
return GDB_SendPacket(ctx, "S00", 3);
|
||||
|
||||
case EXCEVENT_STOP_POINT:
|
||||
{
|
||||
ctx->currentThreadId = info->thread_id;
|
||||
|
||||
switch(exc.stop_point.type)
|
||||
{
|
||||
case STOPPOINT_SVC_FF:
|
||||
{
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP);
|
||||
return GDB_SendFormattedPacket(ctx, "%sswbreak:;", buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
case STOPPOINT_BREAKPOINT:
|
||||
{
|
||||
// /!\ Not actually implemented (and will never be)
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP);
|
||||
return GDB_SendFormattedPacket(ctx, "%shwbreak:;", buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
case STOPPOINT_WATCHPOINT:
|
||||
{
|
||||
const char *kinds = "arwa";
|
||||
WatchpointKind kind = GDB_GetWatchpointKind(ctx, exc.stop_point.fault_information);
|
||||
if(kind == WATCHPOINT_DISABLED)
|
||||
GDB_SendDebugString(ctx, "Warning: unknown watchpoint encountered!\n");
|
||||
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP);
|
||||
return GDB_SendFormattedPacket(ctx, "%s%cwatch:%08x;", buffer, kinds[(u32)kind], exc.stop_point.fault_information);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
case EXCEVENT_USER_BREAK:
|
||||
{
|
||||
ctx->currentThreadId = info->thread_id;
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, SIGINT);
|
||||
return GDB_SendFormattedPacket(ctx, "%s", buffer);
|
||||
//TODO
|
||||
}
|
||||
|
||||
case EXCEVENT_DEBUGGER_BREAK:
|
||||
{
|
||||
u32 threadIds[4];
|
||||
u32 nbThreads = 0;
|
||||
|
||||
for(u32 i = 0; i < 4; i++)
|
||||
{
|
||||
if(exc.debugger_break.thread_ids[i] > 0)
|
||||
threadIds[nbThreads++] = (u32)exc.debugger_break.thread_ids[i];
|
||||
}
|
||||
|
||||
u32 currentThreadId = nbThreads > 0 ? GDB_GetCurrentThreadFromList(ctx, threadIds, nbThreads) : GDB_GetCurrentThread(ctx);
|
||||
s64 dummy;
|
||||
u32 mask = 0;
|
||||
|
||||
svcGetDebugThreadParam(&dummy, &mask, ctx->debug, currentThreadId, DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW);
|
||||
|
||||
if(mask == 1)
|
||||
{
|
||||
ctx->currentThreadId = currentThreadId;
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, SIGINT);
|
||||
return GDB_SendFormattedPacket(ctx, "%s", buffer);
|
||||
}
|
||||
else
|
||||
return GDB_SendPacket(ctx, "S02", 3);
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
case DBGEVENT_SYSCALL_IN:
|
||||
{
|
||||
ctx->currentThreadId = info->thread_id;
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP);
|
||||
return GDB_SendFormattedPacket(ctx, "%ssyscall_entry:%02x;", buffer, info->syscall.syscall);
|
||||
}
|
||||
|
||||
case DBGEVENT_SYSCALL_OUT:
|
||||
{
|
||||
ctx->currentThreadId = info->thread_id;
|
||||
GDB_ParseCommonThreadInfo(buffer, ctx, SIGTRAP);
|
||||
return GDB_SendFormattedPacket(ctx, "%ssyscall_return:%02x;", buffer, info->syscall.syscall);
|
||||
}
|
||||
|
||||
case DBGEVENT_OUTPUT_STRING:
|
||||
{
|
||||
u32 addr = info->output_string.string_addr;
|
||||
u32 remaining = info->output_string.string_size;
|
||||
u32 sent = 0;
|
||||
int total = 0;
|
||||
while(remaining > 0)
|
||||
{
|
||||
u32 pending = (GDB_BUF_LEN - 1) / 2;
|
||||
pending = pending < remaining ? pending : remaining;
|
||||
|
||||
int res = GDB_SendMemory(ctx, "O", 1, addr + sent, pending);
|
||||
if(res < 0 || (u32) res != 5 + 2 * pending)
|
||||
break;
|
||||
|
||||
sent += pending;
|
||||
remaining -= pending;
|
||||
total += res;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Only 1 blocking event can be enqueued at a time: they preempt all the other threads.
|
||||
The only "non-blocking" event that is implemented is EXIT PROCESS (but it's a very special case)
|
||||
*/
|
||||
int GDB_HandleDebugEvents(GDBContext *ctx)
|
||||
{
|
||||
if(ctx->state == GDB_STATE_CLOSING)
|
||||
return -1;
|
||||
|
||||
DebugEventInfo info;
|
||||
Result rdbg = svcGetProcessDebugEvent(&info, ctx->debug);
|
||||
|
||||
if(R_FAILED(rdbg))
|
||||
return -1;
|
||||
|
||||
GDB_PreprocessDebugEvent(ctx, &info);
|
||||
|
||||
int ret = 0;
|
||||
bool continueAutomatically = info.type == DBGEVENT_OUTPUT_STRING || info.type == DBGEVENT_ATTACH_PROCESS ||
|
||||
(info.type == DBGEVENT_ATTACH_THREAD && (info.attach_thread.creator_thread_id == 0 || !ctx->catchThreadEvents)) ||
|
||||
(info.type == DBGEVENT_EXIT_THREAD && (info.exit_thread.reason >= EXITTHREAD_EVENT_EXIT_PROCESS || !ctx->catchThreadEvents)) ||
|
||||
info.type == DBGEVENT_EXIT_PROCESS || !(info.flags & 1);
|
||||
|
||||
|
||||
if(continueAutomatically)
|
||||
{
|
||||
Result r = 0;
|
||||
ret = GDB_SendStopReply(ctx, &info);
|
||||
if(info.flags & 1)
|
||||
r = svcContinueDebugEvent(ctx->debug, ctx->continueFlags);
|
||||
|
||||
if(r == (Result)0xD8A02008) // process ended
|
||||
return -2;
|
||||
|
||||
return -ret - 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
int ret;
|
||||
|
||||
if(ctx->processEnded)
|
||||
return -2;
|
||||
|
||||
ctx->latestDebugEvent = info;
|
||||
ret = GDB_SendStopReply(ctx, &info);
|
||||
ctx->flags &= ~GDB_FLAG_PROCESS_CONTINUING;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
292
sysmodules/rosalina/source/gdb/mem.c
Normal file
292
sysmodules/rosalina/source/gdb/mem.c
Normal file
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* 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/mem.h"
|
||||
#include "gdb/net.h"
|
||||
#include "utils.h"
|
||||
|
||||
static void *k_memcpy_no_interrupt(void *dst, const void *src, u32 len)
|
||||
{
|
||||
__asm__ volatile("cpsid aif");
|
||||
return memcpy(dst, src, len);
|
||||
}
|
||||
|
||||
Result GDB_ReadMemoryInPage(void *out, GDBContext *ctx, u32 addr, u32 len)
|
||||
{
|
||||
s64 TTBCR;
|
||||
svcGetSystemInfo(&TTBCR, 0x10002, 0);
|
||||
|
||||
if(addr < (1u << (32 - (u32)TTBCR)))
|
||||
return svcReadProcessMemory(out, ctx->debug, addr, len);
|
||||
else if(addr >= 0x80000000 && addr < 0xB0000000)
|
||||
{
|
||||
memcpy(out, (const void *)addr, len);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 PA = svcConvertVAToPA((const void *)addr, false);
|
||||
|
||||
if(PA == 0)
|
||||
return -1;
|
||||
else
|
||||
{
|
||||
svcCustomBackdoor(k_memcpy_no_interrupt, out, (const void *)addr, len);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result GDB_WriteMemoryInPage(GDBContext *ctx, const void *in, u32 addr, u32 len)
|
||||
{
|
||||
s64 TTBCR;
|
||||
svcGetSystemInfo(&TTBCR, 0x10002, 0);
|
||||
|
||||
if(addr < (1u << (32 - (u32)TTBCR)))
|
||||
return svcWriteProcessMemory(ctx->debug, in, addr, len); // not sure if it checks if it's IO or not. It probably does
|
||||
|
||||
else if(addr >= 0x80000000 && addr < 0xB0000000)
|
||||
{
|
||||
memcpy((void *)addr, in, len);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 PA = svcConvertVAToPA((const void *)addr, true);
|
||||
|
||||
if(PA != 0)
|
||||
{
|
||||
svcCustomBackdoor(k_memcpy_no_interrupt, (void *)addr, in, len);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unreliable, use at your own risk
|
||||
|
||||
svcFlushEntireDataCache();
|
||||
svcInvalidateEntireInstructionCache();
|
||||
Result ret = GDB_WriteMemoryInPage(ctx, PA_FROM_VA_PTR(in), addr, len);
|
||||
svcFlushEntireDataCache();
|
||||
svcInvalidateEntireInstructionCache();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int GDB_SendMemory(GDBContext *ctx, const char *prefix, u32 prefixLen, u32 addr, u32 len)
|
||||
{
|
||||
char buf[GDB_BUF_LEN];
|
||||
u8 membuf[GDB_BUF_LEN / 2];
|
||||
|
||||
if(prefix != NULL)
|
||||
memcpy(buf, prefix, prefixLen);
|
||||
else
|
||||
prefixLen = 0;
|
||||
|
||||
if(prefixLen + 2 * len > GDB_BUF_LEN) // gdb shouldn't send requests which responses don't fit in a packet
|
||||
return prefix == NULL ? GDB_ReplyErrno(ctx, ENOMEM) : -1;
|
||||
|
||||
Result r = 0;
|
||||
u32 remaining = len, total = 0;
|
||||
do
|
||||
{
|
||||
u32 nb = (remaining > 0x1000 - (addr & 0xFFF)) ? 0x1000 - (addr & 0xFFF) : remaining;
|
||||
r = GDB_ReadMemoryInPage(membuf + total, ctx, addr, nb);
|
||||
if(R_SUCCEEDED(r))
|
||||
{
|
||||
addr += nb;
|
||||
total += nb;
|
||||
remaining -= nb;
|
||||
}
|
||||
}
|
||||
while(remaining > 0 && R_SUCCEEDED(r));
|
||||
|
||||
if(total == 0)
|
||||
return prefix == NULL ? GDB_ReplyErrno(ctx, EFAULT) : -EFAULT;
|
||||
else
|
||||
{
|
||||
GDB_EncodeHex(buf + prefixLen, membuf, total);
|
||||
return GDB_SendPacket(ctx, buf, prefixLen + 2 * total);
|
||||
}
|
||||
}
|
||||
|
||||
int GDB_WriteMemory(GDBContext *ctx, const void *buf, u32 addr, u32 len)
|
||||
{
|
||||
Result r = 0;
|
||||
u32 remaining = len, total = 0;
|
||||
do
|
||||
{
|
||||
u32 nb = (remaining > 0x1000 - (addr & 0xFFF)) ? 0x1000 - (addr & 0xFFF) : remaining;
|
||||
r = GDB_WriteMemoryInPage(ctx, (u8 *)buf + total, addr, nb);
|
||||
if(R_SUCCEEDED(r))
|
||||
{
|
||||
addr += nb;
|
||||
total += nb;
|
||||
remaining -= nb;
|
||||
}
|
||||
}
|
||||
while(remaining > 0 && R_SUCCEEDED(r));
|
||||
|
||||
if(R_FAILED(r))
|
||||
return GDB_ReplyErrno(ctx, EFAULT);
|
||||
else
|
||||
return GDB_ReplyOk(ctx);
|
||||
}
|
||||
|
||||
u32 GDB_SearchMemory(bool *found, GDBContext *ctx, u32 addr, u32 len, const void *pattern, u32 patternLen)
|
||||
{
|
||||
u8 buf[0x1000 + 0x1000 * ((GDB_BUF_LEN + 0xFFF) / 0x1000)];
|
||||
u32 maxNbPages = 1 + ((GDB_BUF_LEN + 0xFFF) / 0x1000);
|
||||
u32 curAddr = addr;
|
||||
|
||||
s64 TTBCR;
|
||||
svcGetSystemInfo(&TTBCR, 0x10002, 0);
|
||||
while(curAddr < addr + len)
|
||||
{
|
||||
u32 nbPages;
|
||||
u32 addrBase = curAddr & ~0xFFF, addrDispl = curAddr & 0xFFF;
|
||||
|
||||
for(nbPages = 0; nbPages < maxNbPages; nbPages++)
|
||||
{
|
||||
if(addr >= (1u << (32 - (u32)TTBCR)))
|
||||
{
|
||||
u32 PA = svcConvertVAToPA((const void *)addr, false);
|
||||
if(PA == 0 || (PA >= 0x10000000 && PA <= 0x18000000))
|
||||
break;
|
||||
}
|
||||
|
||||
if(R_FAILED(GDB_ReadMemoryInPage(buf + 0x1000 * nbPages, ctx, addrBase + nbPages * 0x1000, 0x1000)))
|
||||
break;
|
||||
}
|
||||
|
||||
u8 *pos = NULL;
|
||||
if(addrDispl + patternLen <= 0x1000 * nbPages)
|
||||
pos = memsearch(buf + addrDispl, pattern, 0x1000 * nbPages - addrDispl, patternLen);
|
||||
|
||||
if(pos != NULL)
|
||||
{
|
||||
*found = true;
|
||||
return addrBase + (pos - buf);
|
||||
}
|
||||
|
||||
curAddr = addrBase + 0x1000;
|
||||
}
|
||||
|
||||
*found = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
GDB_DECLARE_HANDLER(ReadMemory)
|
||||
{
|
||||
u32 lst[2];
|
||||
if(GDB_ParseHexIntegerList(lst, ctx->commandData, 2, 0) == NULL)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
u32 addr = lst[0];
|
||||
u32 len = lst[1];
|
||||
|
||||
return GDB_SendMemory(ctx, NULL, 0, addr, len);
|
||||
}
|
||||
|
||||
GDB_DECLARE_HANDLER(WriteMemory)
|
||||
{
|
||||
u32 lst[2];
|
||||
const char *dataStart = GDB_ParseHexIntegerList(lst, ctx->commandData, 2, ':');
|
||||
if(dataStart == NULL || *dataStart != ':')
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
dataStart++;
|
||||
u32 addr = lst[0];
|
||||
u32 len = lst[1];
|
||||
|
||||
if(dataStart + 2 * len >= ctx->buffer + 4 + GDB_BUF_LEN)
|
||||
return GDB_ReplyErrno(ctx, ENOMEM);
|
||||
|
||||
u8 data[GDB_BUF_LEN / 2];
|
||||
u32 n = GDB_DecodeHex(data, dataStart, len);
|
||||
|
||||
if(n != len)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
return GDB_WriteMemory(ctx, data, addr, len);
|
||||
}
|
||||
|
||||
GDB_DECLARE_HANDLER(WriteMemoryRaw)
|
||||
{
|
||||
u32 lst[2];
|
||||
const char *dataStart = GDB_ParseHexIntegerList(lst, ctx->commandData, 2, ':');
|
||||
if(dataStart == NULL || *dataStart != ':')
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
dataStart++;
|
||||
u32 addr = lst[0];
|
||||
u32 len = lst[1];
|
||||
|
||||
if(dataStart + len >= ctx->buffer + 4 + GDB_BUF_LEN)
|
||||
return GDB_ReplyErrno(ctx, ENOMEM);
|
||||
|
||||
u8 data[GDB_BUF_LEN];
|
||||
u32 n = GDB_UnescapeBinaryData(data, dataStart, len);
|
||||
|
||||
if(n != len)
|
||||
return GDB_ReplyErrno(ctx, n);
|
||||
|
||||
return GDB_WriteMemory(ctx, data, addr, len);
|
||||
}
|
||||
|
||||
GDB_DECLARE_QUERY_HANDLER(SearchMemory)
|
||||
{
|
||||
u32 lst[2];
|
||||
u32 addr, len;
|
||||
u8 pattern[GDB_BUF_LEN];
|
||||
const char *patternStart;
|
||||
u32 patternLen;
|
||||
bool found;
|
||||
u32 foundAddr;
|
||||
|
||||
if(strncmp(ctx->commandData, "memory:", 7) != 0)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
ctx->commandData += 7;
|
||||
patternStart = GDB_ParseIntegerList(lst, ctx->commandData, 2, ';', ';', 16, false);
|
||||
if(patternStart == NULL || *patternStart != ';')
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
addr = lst[0];
|
||||
len = lst[1];
|
||||
|
||||
patternStart++;
|
||||
patternLen = ctx->commandEnd - patternStart;
|
||||
|
||||
patternLen = GDB_UnescapeBinaryData(pattern, patternStart, patternLen);
|
||||
|
||||
foundAddr = GDB_SearchMemory(&found, ctx, addr, len, patternStart, patternLen);
|
||||
|
||||
if(found)
|
||||
return GDB_SendFormattedPacket(ctx, "1,%x", foundAddr);
|
||||
else
|
||||
return GDB_SendPacket(ctx, "0", 1);
|
||||
}
|
||||
94
sysmodules/rosalina/source/gdb/monitor.c
Normal file
94
sysmodules/rosalina/source/gdb/monitor.c
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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/monitor.h"
|
||||
#include "gdb/net.h"
|
||||
#include "gdb/debug.h"
|
||||
|
||||
extern Handle terminationRequestEvent;
|
||||
extern bool terminationRequest;
|
||||
|
||||
void GDB_RunMonitor(GDBServer *server)
|
||||
{
|
||||
Handle handles[3 + MAX_DEBUG];
|
||||
Result r = 0;
|
||||
|
||||
handles[0] = terminationRequestEvent;
|
||||
handles[1] = server->super.shall_terminate_event;
|
||||
handles[2] = server->statusUpdated;
|
||||
|
||||
do
|
||||
{
|
||||
for(int i = 0; i < MAX_DEBUG; i++)
|
||||
{
|
||||
GDBContext *ctx = &server->ctxs[i];
|
||||
handles[3 + i] = ctx->eventToWaitFor;
|
||||
}
|
||||
|
||||
s32 idx = -1;
|
||||
r = svcWaitSynchronizationN(&idx, handles, 3 + MAX_DEBUG, false, -1LL);
|
||||
|
||||
if(R_FAILED(r) || idx < 2)
|
||||
break;
|
||||
else if(idx == 2)
|
||||
continue;
|
||||
else
|
||||
{
|
||||
GDBContext *ctx = &server->ctxs[idx - 3];
|
||||
|
||||
RecursiveLock_Lock(&ctx->lock);
|
||||
if(ctx->state == GDB_STATE_DISCONNECTED || ctx->state == GDB_STATE_CLOSING)
|
||||
{
|
||||
svcClearEvent(ctx->clientAcceptedEvent);
|
||||
ctx->eventToWaitFor = ctx->clientAcceptedEvent;
|
||||
RecursiveLock_Unlock(&ctx->lock);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(ctx->eventToWaitFor == ctx->clientAcceptedEvent)
|
||||
ctx->eventToWaitFor = ctx->continuedEvent;
|
||||
else if(ctx->eventToWaitFor == ctx->continuedEvent)
|
||||
ctx->eventToWaitFor = ctx->debug;
|
||||
else
|
||||
{
|
||||
int res = GDB_HandleDebugEvents(ctx);
|
||||
if(res >= 0)
|
||||
ctx->eventToWaitFor = ctx->continuedEvent;
|
||||
else if(res == -2)
|
||||
{
|
||||
while(GDB_HandleDebugEvents(ctx) != -1) // until we've got all the remaining debug events
|
||||
svcSleepThread(1 * 1000 * 1000LL); // sleep just in case
|
||||
|
||||
svcClearEvent(ctx->clientAcceptedEvent);
|
||||
ctx->eventToWaitFor = ctx->clientAcceptedEvent;
|
||||
}
|
||||
}
|
||||
|
||||
RecursiveLock_Unlock(&ctx->lock);
|
||||
}
|
||||
}
|
||||
while(!terminationRequest && server->super.running);
|
||||
}
|
||||
332
sysmodules/rosalina/source/gdb/net.c
Normal file
332
sysmodules/rosalina/source/gdb/net.c
Normal file
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* 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/net.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include "fmt.h"
|
||||
#include "minisoc.h"
|
||||
|
||||
u8 GDB_ComputeChecksum(const char *packetData, u32 len)
|
||||
{
|
||||
u8 cksum = 0;
|
||||
for(u32 i = 0; i < len; i++)
|
||||
cksum += (u8)packetData[i];
|
||||
|
||||
return cksum;
|
||||
}
|
||||
|
||||
void GDB_EncodeHex(char *dst, const void *src, u32 len)
|
||||
{
|
||||
static const char *alphabet = "0123456789abcdef";
|
||||
const u8 *src8 = (u8 *)src;
|
||||
|
||||
for(u32 i = 0; i < len; i++)
|
||||
{
|
||||
dst[2 * i] = alphabet[(src8[i] & 0xf0) >> 4];
|
||||
dst[2 * i + 1] = alphabet[src8[i] & 0x0f];
|
||||
}
|
||||
}
|
||||
|
||||
static inline u32 GDB_DecodeHexDigit(char src, bool *ok)
|
||||
{
|
||||
*ok = true;
|
||||
if(src >= '0' && src <= '9') return src - '0';
|
||||
else if(src >= 'a' && src <= 'f') return 0xA + (src - 'a');
|
||||
else if(src >= 'A' && src <= 'F') return 0xA + (src - 'A');
|
||||
else
|
||||
{
|
||||
*ok = false;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
u32 GDB_DecodeHex(void *dst, const char *src, u32 len)
|
||||
{
|
||||
u32 i = 0;
|
||||
bool ok = true;
|
||||
u8 *dst8 = (u8 *)dst;
|
||||
for(i = 0; i < len && ok && src[2 * i] != 0 && src[2 * i + 1] != 0; i++)
|
||||
dst8[i] = (GDB_DecodeHexDigit(src[2 * i], &ok) << 4) | GDB_DecodeHexDigit(src[2 * i + 1], &ok);
|
||||
|
||||
return (!ok) ? i - 1 : i;
|
||||
}
|
||||
|
||||
u32 GDB_UnescapeBinaryData(void *dst, const void *src, u32 len)
|
||||
{
|
||||
u8 *dst8 = (u8 *)dst;
|
||||
const u8 *src8 = (const u8 *)src;
|
||||
|
||||
for(u32 i = 0; i < len; i++)
|
||||
{
|
||||
if(*src8 == '}')
|
||||
{
|
||||
src8++;
|
||||
*dst8++ = *src8++ ^ 0x20;
|
||||
}
|
||||
else
|
||||
*dst8++ = *src8++;
|
||||
}
|
||||
|
||||
return dst8 - (u8 *)dst;
|
||||
}
|
||||
|
||||
const char *GDB_ParseIntegerList(u32 *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++)
|
||||
{
|
||||
u32 n = xstrtoul(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);
|
||||
}
|
||||
|
||||
int GDB_ReceivePacket(GDBContext *ctx)
|
||||
{
|
||||
char backupbuf[GDB_BUF_LEN + 4];
|
||||
memcpy(backupbuf, ctx->buffer, ctx->latestSentPacketSize);
|
||||
memset(ctx->buffer, 0, sizeof(ctx->buffer));
|
||||
|
||||
int r = soc_recv(ctx->super.sockfd, ctx->buffer, sizeof(ctx->buffer), MSG_PEEK);
|
||||
if(r < 1)
|
||||
return -1;
|
||||
if(ctx->buffer[0] == '+') // GDB sometimes acknowleges TCP acknowledgment packets (yes...). IDA does it properly
|
||||
{
|
||||
if(ctx->state == GDB_STATE_NOACK)
|
||||
return -1;
|
||||
|
||||
// Consume it
|
||||
r = soc_recv(ctx->super.sockfd, ctx->buffer, 1, 0);
|
||||
if(r != 1)
|
||||
return -1;
|
||||
|
||||
ctx->buffer[0] = 0;
|
||||
|
||||
r = soc_recv(ctx->super.sockfd, ctx->buffer, sizeof(ctx->buffer), MSG_PEEK);
|
||||
|
||||
if(r == -1)
|
||||
goto packet_error;
|
||||
}
|
||||
else if(ctx->buffer[0] == '-')
|
||||
{
|
||||
soc_send(ctx->super.sockfd, backupbuf, ctx->latestSentPacketSize, 0);
|
||||
return 0;
|
||||
}
|
||||
int maxlen = r > (int)sizeof(ctx->buffer) ? (int)sizeof(ctx->buffer) : r;
|
||||
|
||||
if(ctx->buffer[0] == '$') // normal packet
|
||||
{
|
||||
char *pos;
|
||||
for(pos = ctx->buffer; pos < ctx->buffer + maxlen && *pos != '#'; pos++);
|
||||
|
||||
if(pos == ctx->buffer + maxlen) // malformed packet
|
||||
return -1;
|
||||
|
||||
else
|
||||
{
|
||||
u8 checksum;
|
||||
r = soc_recv(ctx->super.sockfd, ctx->buffer, 3 + pos - ctx->buffer, 0);
|
||||
if(r != 3 + pos - ctx->buffer || GDB_DecodeHex(&checksum, pos + 1, 1) != 1)
|
||||
goto packet_error;
|
||||
else if(GDB_ComputeChecksum(ctx->buffer + 1, pos - ctx->buffer - 1) != checksum)
|
||||
goto packet_error;
|
||||
|
||||
ctx->commandEnd = pos;
|
||||
*pos = 0; // replace trailing '#' by a NUL character
|
||||
}
|
||||
}
|
||||
else if(ctx->buffer[0] == '\x03')
|
||||
{
|
||||
r = soc_recv(ctx->super.sockfd, ctx->buffer, 1, 0);
|
||||
if(r != 1)
|
||||
goto packet_error;
|
||||
|
||||
ctx->commandEnd = ctx->buffer;
|
||||
}
|
||||
|
||||
if(ctx->state >= GDB_STATE_CONNECTED && ctx->state < GDB_STATE_NOACK)
|
||||
{
|
||||
int r2 = soc_send(ctx->super.sockfd, "+", 1, 0);
|
||||
if(r2 != 1)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(ctx->state == GDB_STATE_NOACK_SENT)
|
||||
ctx->state = GDB_STATE_NOACK;
|
||||
|
||||
return r;
|
||||
|
||||
packet_error:
|
||||
if(ctx->state >= GDB_STATE_CONNECTED && ctx->state < GDB_STATE_NOACK)
|
||||
{
|
||||
r = soc_send(ctx->super.sockfd, "-", 1, 0);
|
||||
if(r != 1)
|
||||
return -1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int GDB_DoSendPacket(GDBContext *ctx, u32 len)
|
||||
{
|
||||
int r = soc_send(ctx->super.sockfd, ctx->buffer, len, 0);
|
||||
|
||||
if(r > 0)
|
||||
ctx->latestSentPacketSize = r;
|
||||
return r;
|
||||
}
|
||||
|
||||
int GDB_SendPacket(GDBContext *ctx, const char *packetData, u32 len)
|
||||
{
|
||||
ctx->buffer[0] = '$';
|
||||
|
||||
memcpy(ctx->buffer + 1, packetData, len);
|
||||
|
||||
char *checksumLoc = ctx->buffer + len + 1;
|
||||
*checksumLoc++ = '#';
|
||||
|
||||
hexItoa(GDB_ComputeChecksum(packetData, len), checksumLoc, 2, false);
|
||||
return GDB_DoSendPacket(ctx, 4 + len);
|
||||
}
|
||||
|
||||
int GDB_SendFormattedPacket(GDBContext *ctx, const char *packetDataFmt, ...)
|
||||
{
|
||||
// It goes without saying you shouldn't use that with user-controlled data...
|
||||
char buf[GDB_BUF_LEN + 1];
|
||||
va_list args;
|
||||
va_start(args, packetDataFmt);
|
||||
int n = vsprintf(buf, packetDataFmt, args);
|
||||
va_end(args);
|
||||
|
||||
if(n < 0) return -1;
|
||||
else return GDB_SendPacket(ctx, buf, (u32)n);
|
||||
}
|
||||
|
||||
int GDB_SendHexPacket(GDBContext *ctx, const void *packetData, u32 len)
|
||||
{
|
||||
if(4 + 2 * len > GDB_BUF_LEN)
|
||||
return -1;
|
||||
|
||||
ctx->buffer[0] = '$';
|
||||
GDB_EncodeHex(ctx->buffer + 1, packetData, len);
|
||||
|
||||
char *checksumLoc = ctx->buffer + 2 * len + 1;
|
||||
*checksumLoc++ = '#';
|
||||
|
||||
hexItoa(GDB_ComputeChecksum(ctx->buffer + 1, 2 * len), checksumLoc, 2, false);
|
||||
return GDB_DoSendPacket(ctx, 4 + 2 * len);
|
||||
}
|
||||
|
||||
int GDB_SendStreamData(GDBContext *ctx, const char *streamData, u32 offset, u32 length, u32 totalSize, bool forceEmptyLast)
|
||||
{
|
||||
char buf[GDB_BUF_LEN];
|
||||
if(length > GDB_BUF_LEN - 1)
|
||||
length = GDB_BUF_LEN - 1;
|
||||
|
||||
if((forceEmptyLast && offset >= totalSize) || (!forceEmptyLast && offset + length >= totalSize))
|
||||
{
|
||||
length = totalSize - offset;
|
||||
buf[0] = 'l';
|
||||
memcpy(buf + 1, streamData + offset, length);
|
||||
return GDB_SendPacket(ctx, buf, 1 + length);
|
||||
}
|
||||
else
|
||||
{
|
||||
buf[0] = 'm';
|
||||
memcpy(buf + 1, streamData + offset, length);
|
||||
return GDB_SendPacket(ctx, buf, 1 + length);
|
||||
}
|
||||
}
|
||||
|
||||
int GDB_SendDebugString(GDBContext *ctx, const char *fmt, ...) // unsecure
|
||||
{
|
||||
if(ctx->state == GDB_STATE_CLOSING || !(ctx->flags & GDB_FLAG_PROCESS_CONTINUING))
|
||||
return 0;
|
||||
|
||||
char formatted[(GDB_BUF_LEN - 1) / 2 + 1];
|
||||
ctx->buffer[0] = '$';
|
||||
ctx->buffer[1] = 'O';
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int n = vsprintf(formatted, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if(n <= 0) return n;
|
||||
GDB_EncodeHex(ctx->buffer + 2, formatted, 2 * n);
|
||||
|
||||
char *checksumLoc = ctx->buffer + 2 * n + 2;
|
||||
*checksumLoc++ = '#';
|
||||
|
||||
hexItoa(GDB_ComputeChecksum(ctx->buffer + 1, 2 * n + 1), checksumLoc, 2, false);
|
||||
|
||||
return GDB_DoSendPacket(ctx, 5 + 2 * n);
|
||||
}
|
||||
|
||||
int GDB_ReplyEmpty(GDBContext *ctx)
|
||||
{
|
||||
return GDB_SendPacket(ctx, "", 0);
|
||||
}
|
||||
|
||||
int GDB_ReplyOk(GDBContext *ctx)
|
||||
{
|
||||
return GDB_SendPacket(ctx, "OK", 2);
|
||||
}
|
||||
|
||||
int GDB_ReplyErrno(GDBContext *ctx, int no)
|
||||
{
|
||||
char buf[] = "E01";
|
||||
hexItoa((u8)no, buf + 1, 2, false);
|
||||
return GDB_SendPacket(ctx, buf, 3);
|
||||
}
|
||||
162
sysmodules/rosalina/source/gdb/query.c
Normal file
162
sysmodules/rosalina/source/gdb/query.c
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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/query.h"
|
||||
#include "gdb/xfer.h"
|
||||
#include "gdb/thread.h"
|
||||
#include "gdb/mem.h"
|
||||
#include "gdb/net.h"
|
||||
#include "gdb/remote_command.h"
|
||||
|
||||
typedef enum GDBQueryDirection
|
||||
{
|
||||
GDB_QUERY_DIRECTION_READ,
|
||||
GDB_QUERY_DIRECTION_WRITE
|
||||
} GDBQueryDirection;
|
||||
|
||||
// https://gcc.gnu.org/onlinedocs/gcc-5.3.0/cpp/Stringification.html
|
||||
#define xstr(s) str(s)
|
||||
#define str(s) #s
|
||||
|
||||
#define GDB_QUERY_HANDLER_LIST_ITEM_3(name, name2, direction) { name, GDB_QUERY_HANDLER(name2), GDB_QUERY_DIRECTION_##direction }
|
||||
#define GDB_QUERY_HANDLER_LIST_ITEM(name, direction) GDB_QUERY_HANDLER_LIST_ITEM_3(xstr(name), name, direction)
|
||||
|
||||
static const struct
|
||||
{
|
||||
const char *name;
|
||||
GDBCommandHandler handler;
|
||||
GDBQueryDirection direction;
|
||||
} gdbQueryHandlers[] =
|
||||
{
|
||||
GDB_QUERY_HANDLER_LIST_ITEM(Supported, READ),
|
||||
GDB_QUERY_HANDLER_LIST_ITEM(Xfer, READ),
|
||||
GDB_QUERY_HANDLER_LIST_ITEM(StartNoAckMode, WRITE),
|
||||
GDB_QUERY_HANDLER_LIST_ITEM(Attached, READ),
|
||||
GDB_QUERY_HANDLER_LIST_ITEM(fThreadInfo, READ),
|
||||
GDB_QUERY_HANDLER_LIST_ITEM(sThreadInfo, READ),
|
||||
GDB_QUERY_HANDLER_LIST_ITEM(ThreadEvents, WRITE),
|
||||
GDB_QUERY_HANDLER_LIST_ITEM(ThreadExtraInfo, READ),
|
||||
GDB_QUERY_HANDLER_LIST_ITEM_3("C", CurrentThreadId, READ),
|
||||
GDB_QUERY_HANDLER_LIST_ITEM_3("Search", SearchMemory, READ),
|
||||
GDB_QUERY_HANDLER_LIST_ITEM(CatchSyscalls, WRITE),
|
||||
GDB_QUERY_HANDLER_LIST_ITEM(Rcmd, READ),
|
||||
};
|
||||
|
||||
static int GDB_HandleQuery(GDBContext *ctx, GDBQueryDirection direction)
|
||||
{
|
||||
char *nameBegin = ctx->commandData; // w/o leading 'q'/'Q'
|
||||
if(*nameBegin == 0)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
char *nameEnd;
|
||||
char *queryData = NULL;
|
||||
|
||||
for(nameEnd = nameBegin; *nameEnd != 0 && *nameEnd != ':' && *nameEnd != ','; nameEnd++);
|
||||
if(*nameEnd != 0)
|
||||
{
|
||||
*nameEnd = 0;
|
||||
queryData = nameEnd + 1;
|
||||
}
|
||||
else
|
||||
queryData = nameEnd;
|
||||
|
||||
for(u32 i = 0; i < sizeof(gdbQueryHandlers) / sizeof(gdbQueryHandlers[0]); i++)
|
||||
{
|
||||
if(strcmp(gdbQueryHandlers[i].name, nameBegin) == 0 && gdbQueryHandlers[i].direction == direction)
|
||||
{
|
||||
ctx->commandData = queryData;
|
||||
return gdbQueryHandlers[i].handler(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
return GDB_HandleUnsupported(ctx); // No handler found!
|
||||
}
|
||||
|
||||
int GDB_HandleReadQuery(GDBContext *ctx)
|
||||
{
|
||||
return GDB_HandleQuery(ctx, GDB_QUERY_DIRECTION_READ);
|
||||
}
|
||||
|
||||
int GDB_HandleWriteQuery(GDBContext *ctx)
|
||||
{
|
||||
return GDB_HandleQuery(ctx, GDB_QUERY_DIRECTION_WRITE);
|
||||
}
|
||||
|
||||
GDB_DECLARE_QUERY_HANDLER(Supported)
|
||||
{
|
||||
return GDB_SendFormattedPacket(ctx,
|
||||
"PacketSize=%x;"
|
||||
"qXfer:features:read+;qXfer:osdata:read+;"
|
||||
"QStartNoAckMode+;QThreadEvents+;QCatchSyscalls+;"
|
||||
"vContSupported+;swbreak+", sizeof(ctx->buffer));
|
||||
}
|
||||
|
||||
GDB_DECLARE_QUERY_HANDLER(StartNoAckMode)
|
||||
{
|
||||
ctx->isGDB = true;
|
||||
ctx->state = GDB_STATE_NOACK_SENT;
|
||||
return GDB_ReplyOk(ctx);
|
||||
}
|
||||
|
||||
GDB_DECLARE_QUERY_HANDLER(Attached)
|
||||
{
|
||||
return GDB_SendPacket(ctx, "1", 1);
|
||||
}
|
||||
|
||||
GDB_DECLARE_QUERY_HANDLER(CatchSyscalls)
|
||||
{
|
||||
if(ctx->commandData[0] == '0')
|
||||
{
|
||||
memset(ctx->svcMask, 0, 32);
|
||||
return R_SUCCEEDED(svcKernelSetState(0x10002, ctx->pid, false)) ? GDB_ReplyOk(ctx) : GDB_ReplyErrno(ctx, EPERM);
|
||||
}
|
||||
else if(ctx->commandData[0] == '1')
|
||||
{
|
||||
if(ctx->commandData[1] == ';')
|
||||
{
|
||||
u32 id;
|
||||
const char *pos = ctx->commandData + 1;
|
||||
memset(ctx->svcMask, 0, 32);
|
||||
|
||||
do
|
||||
{
|
||||
pos = GDB_ParseHexIntegerList(&id, pos + 1, 1, ';');
|
||||
if(pos == NULL)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
if(id < 0xFE)
|
||||
ctx->svcMask[id / 32] |= 1 << (31 - (id % 32));
|
||||
}
|
||||
while(*pos != 0);
|
||||
}
|
||||
else
|
||||
memset(ctx->svcMask, 0xFF, 32);
|
||||
|
||||
return R_SUCCEEDED(svcKernelSetState(0x10002, ctx->pid, true, ctx->svcMask)) ? GDB_ReplyOk(ctx) : GDB_ReplyErrno(ctx, EPERM);
|
||||
}
|
||||
else
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
}
|
||||
172
sysmodules/rosalina/source/gdb/regs.c
Normal file
172
sysmodules/rosalina/source/gdb/regs.c
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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/regs.h"
|
||||
#include "gdb/net.h"
|
||||
|
||||
GDB_DECLARE_HANDLER(ReadRegisters)
|
||||
{
|
||||
if(ctx->selectedThreadId == 0)
|
||||
ctx->selectedThreadId = ctx->currentThreadId;
|
||||
|
||||
ThreadContext regs;
|
||||
Result r = svcGetDebugThreadContext(®s, ctx->debug, ctx->selectedThreadId, THREADCONTEXT_CONTROL_ALL);
|
||||
|
||||
if(R_FAILED(r))
|
||||
return GDB_ReplyErrno(ctx, EPERM);
|
||||
|
||||
return GDB_SendHexPacket(ctx, ®s, sizeof(ThreadContext));
|
||||
}
|
||||
|
||||
GDB_DECLARE_HANDLER(WriteRegisters)
|
||||
{
|
||||
if(ctx->selectedThreadId == 0)
|
||||
ctx->selectedThreadId = ctx->currentThreadId;
|
||||
|
||||
ThreadContext regs;
|
||||
|
||||
if(GDB_DecodeHex(®s, ctx->commandData, sizeof(ThreadContext)) != sizeof(ThreadContext))
|
||||
return GDB_ReplyErrno(ctx, EPERM);
|
||||
|
||||
Result r = svcSetDebugThreadContext(ctx->debug, ctx->selectedThreadId, ®s, THREADCONTEXT_CONTROL_ALL);
|
||||
if(R_FAILED(r))
|
||||
return GDB_ReplyErrno(ctx, EPERM);
|
||||
else
|
||||
return GDB_ReplyOk(ctx);
|
||||
}
|
||||
|
||||
static u32 GDB_ConvertRegisterNumber(ThreadContextControlFlags *flags, u32 gdbNum)
|
||||
{
|
||||
if(gdbNum <= 15)
|
||||
{
|
||||
*flags = (gdbNum >= 13) ? THREADCONTEXT_CONTROL_CPU_SPRS : THREADCONTEXT_CONTROL_CPU_GPRS;
|
||||
return gdbNum;
|
||||
}
|
||||
else if(gdbNum == 25)
|
||||
{
|
||||
*flags = THREADCONTEXT_CONTROL_CPU_SPRS;
|
||||
return 16;
|
||||
}
|
||||
else if(gdbNum >= 26 && gdbNum <= 41)
|
||||
{
|
||||
*flags = THREADCONTEXT_CONTROL_FPU_GPRS;
|
||||
return gdbNum - 26;
|
||||
}
|
||||
else if(gdbNum == 42 || gdbNum == 43)
|
||||
{
|
||||
*flags = THREADCONTEXT_CONTROL_FPU_SPRS;
|
||||
return gdbNum - 42;
|
||||
}
|
||||
else
|
||||
{
|
||||
*flags = (ThreadContextControlFlags)0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
GDB_DECLARE_HANDLER(ReadRegister)
|
||||
{
|
||||
if(ctx->selectedThreadId == 0)
|
||||
ctx->selectedThreadId = ctx->currentThreadId;
|
||||
|
||||
ThreadContext regs;
|
||||
ThreadContextControlFlags flags;
|
||||
u32 gdbRegNum;
|
||||
|
||||
if(GDB_ParseHexIntegerList(&gdbRegNum, ctx->commandData, 1, 0) == NULL)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
u32 n = GDB_ConvertRegisterNumber(&flags, gdbRegNum);
|
||||
if(!flags)
|
||||
return GDB_ReplyErrno(ctx, EINVAL);
|
||||
|
||||
Result r = svcGetDebugThreadContext(®s, ctx->debug, ctx->selectedThreadId, flags);
|
||||
|
||||
if(R_FAILED(r))
|
||||
return GDB_ReplyErrno(ctx, EPERM);
|
||||
|
||||
if(flags & THREADCONTEXT_CONTROL_CPU_GPRS)
|
||||
return GDB_SendHexPacket(ctx, ®s.cpu_registers.r[n], 4);
|
||||
else if(flags & THREADCONTEXT_CONTROL_CPU_SPRS)
|
||||
return GDB_SendHexPacket(ctx, ®s.cpu_registers.sp + (n - 13), 4); // hacky
|
||||
else if(flags & THREADCONTEXT_CONTROL_FPU_GPRS)
|
||||
return GDB_SendHexPacket(ctx, ®s.fpu_registers.d[n], 8);
|
||||
else
|
||||
return GDB_SendHexPacket(ctx, ®s.fpu_registers.fpscr + n, 4); // hacky
|
||||
}
|
||||
|
||||
GDB_DECLARE_HANDLER(WriteRegister)
|
||||
{
|
||||
if(ctx->selectedThreadId == 0)
|
||||
ctx->selectedThreadId = ctx->currentThreadId;
|
||||
|
||||
ThreadContext regs;
|
||||
ThreadContextControlFlags flags;
|
||||
u32 gdbRegNum;
|
||||
|
||||
const char *valueStart = GDB_ParseHexIntegerList(&gdbRegNum, ctx->commandData, 1, '=');
|
||||
if(valueStart == NULL || *valueStart != '=')
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
valueStart++;
|
||||
|
||||
u32 n = GDB_ConvertRegisterNumber(&flags, gdbRegNum);
|
||||
u32 value;
|
||||
u64 value64;
|
||||
|
||||
if(flags & THREADCONTEXT_CONTROL_FPU_GPRS)
|
||||
{
|
||||
if(GDB_DecodeHex(&value64, valueStart, 8) != 8 || valueStart[16] != 0)
|
||||
return GDB_ReplyErrno(ctx, EINVAL);
|
||||
}
|
||||
else if(flags)
|
||||
{
|
||||
if(GDB_DecodeHex(&value, valueStart, 4) != 4 || valueStart[8] != 0)
|
||||
return GDB_ReplyErrno(ctx, EINVAL);
|
||||
}
|
||||
else
|
||||
return GDB_ReplyErrno(ctx, EINVAL);
|
||||
|
||||
Result r = svcGetDebugThreadContext(®s, ctx->debug, ctx->selectedThreadId, flags);
|
||||
|
||||
if(R_FAILED(r))
|
||||
return GDB_ReplyErrno(ctx, EPERM);
|
||||
|
||||
if(flags & THREADCONTEXT_CONTROL_CPU_GPRS)
|
||||
regs.cpu_registers.r[n] = value;
|
||||
else if(flags & THREADCONTEXT_CONTROL_CPU_SPRS)
|
||||
*(®s.cpu_registers.sp + (n - 13)) = value; // hacky
|
||||
else if(flags & THREADCONTEXT_CONTROL_FPU_GPRS)
|
||||
memcpy(®s.fpu_registers.d[n], &value64, 8);
|
||||
else
|
||||
*(®s.fpu_registers.fpscr + n) = value; // hacky
|
||||
|
||||
r = svcSetDebugThreadContext(ctx->debug, ctx->selectedThreadId, ®s, flags);
|
||||
if(R_FAILED(r))
|
||||
return GDB_ReplyErrno(ctx, EPERM);
|
||||
else
|
||||
return GDB_ReplyOk(ctx);
|
||||
}
|
||||
265
sysmodules/rosalina/source/gdb/remote_command.c
Normal file
265
sysmodules/rosalina/source/gdb/remote_command.c
Normal file
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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/remote_command.h"
|
||||
#include "gdb/net.h"
|
||||
#include "csvc.h"
|
||||
#include "fmt.h"
|
||||
#include "gdb/breakpoints.h"
|
||||
|
||||
struct
|
||||
{
|
||||
const char *name;
|
||||
GDBCommandHandler handler;
|
||||
} remoteCommandHandlers[] =
|
||||
{
|
||||
{ "syncrequestinfo", GDB_REMOTE_COMMAND_HANDLER(SyncRequestInfo) },
|
||||
{ "translatehandle", GDB_REMOTE_COMMAND_HANDLER(TranslateHandle) },
|
||||
{ "getmmuconfig" , GDB_REMOTE_COMMAND_HANDLER(GetMmuConfig) },
|
||||
{ "flushcaches" , GDB_REMOTE_COMMAND_HANDLER(FlushCaches) },
|
||||
};
|
||||
|
||||
static const char *GDB_SkipSpaces(const char *pos)
|
||||
{
|
||||
const char *nextpos;
|
||||
for(nextpos = pos; *nextpos != 0 && ((*nextpos >= 9 && *nextpos <= 13) || *nextpos == ' '); nextpos++);
|
||||
return nextpos;
|
||||
}
|
||||
|
||||
GDB_DECLARE_REMOTE_COMMAND_HANDLER(SyncRequestInfo)
|
||||
{
|
||||
char outbuf[GDB_BUF_LEN / 2 + 1];
|
||||
Result r;
|
||||
int n;
|
||||
|
||||
if(ctx->commandData[0] != 0)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
if(ctx->selectedThreadId == 0)
|
||||
ctx->selectedThreadId = ctx->currentThreadId;
|
||||
|
||||
if(ctx->selectedThreadId == 0)
|
||||
{
|
||||
n = sprintf(outbuf, "Cannot run this command without a selected thread.\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
u32 id;
|
||||
u32 cmdId;
|
||||
ThreadContext regs;
|
||||
u32 instr;
|
||||
Handle process;
|
||||
r = svcOpenProcess(&process, ctx->pid);
|
||||
if(R_FAILED(r))
|
||||
{
|
||||
n = sprintf(outbuf, "Invalid process (wtf?)\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
for(id = 0; id < MAX_DEBUG_THREAD && ctx->threadInfos[id].id != ctx->selectedThreadId; id++);
|
||||
|
||||
r = svcGetDebugThreadContext(®s, ctx->debug, ctx->selectedThreadId, THREADCONTEXT_CONTROL_CPU_REGS);
|
||||
|
||||
if(R_FAILED(r) || id == MAX_DEBUG_THREAD)
|
||||
{
|
||||
n = sprintf(outbuf, "Invalid or running thread.\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
r = svcReadProcessMemory(&cmdId, ctx->debug, ctx->threadInfos[id].tls + 0x80, 4);
|
||||
if(R_FAILED(r))
|
||||
{
|
||||
n = sprintf(outbuf, "Invalid or running thread.\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
r = svcReadProcessMemory(&instr, ctx->debug, regs.cpu_registers.pc, (regs.cpu_registers.cpsr & 0x20) ? 2 : 4);
|
||||
|
||||
if(R_SUCCEEDED(r) && (((regs.cpu_registers.cpsr & 0x20) && instr == BREAKPOINT_INSTRUCTION_THUMB) || instr == BREAKPOINT_INSTRUCTION_ARM))
|
||||
{
|
||||
u32 savedInstruction;
|
||||
if(GDB_GetBreakpointInstruction(&savedInstruction, ctx, regs.cpu_registers.pc) == 0)
|
||||
instr = savedInstruction;
|
||||
}
|
||||
|
||||
if(R_FAILED(r) || ((regs.cpu_registers.cpsr & 0x20) && !(instr == 0xDF32 || (instr == 0xDFFE && regs.cpu_registers.r[12] == 0x32)))
|
||||
|| (!(regs.cpu_registers.cpsr & 0x20) && !(instr == 0xEF000032 || (instr == 0xEF0000FE && regs.cpu_registers.r[12] == 0x32))))
|
||||
{
|
||||
n = sprintf(outbuf, "The selected thread is not currently performing a sync request (svc 0x32).\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
char name[12];
|
||||
Handle handle;
|
||||
r = svcCopyHandle(&handle, CUR_PROCESS_HANDLE, (Handle)regs.cpu_registers.r[0], process);
|
||||
if(R_FAILED(r))
|
||||
{
|
||||
n = sprintf(outbuf, "Invalid handle.\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
r = svcControlService(SERVICEOP_GET_NAME, name, handle);
|
||||
if(R_FAILED(r))
|
||||
name[0] = 0;
|
||||
|
||||
n = sprintf(outbuf, "%s 0x%x, 0x%08x\n", name, cmdId, ctx->threadInfos[id].tls + 0x80);
|
||||
|
||||
end:
|
||||
svcCloseHandle(handle);
|
||||
svcCloseHandle(process);
|
||||
return GDB_SendHexPacket(ctx, outbuf, n);
|
||||
}
|
||||
|
||||
GDB_DECLARE_REMOTE_COMMAND_HANDLER(TranslateHandle)
|
||||
{
|
||||
bool ok;
|
||||
u32 val;
|
||||
char *end;
|
||||
int n;
|
||||
Result r;
|
||||
u32 kernelAddr;
|
||||
Handle handle, process;
|
||||
s64 refcountRaw;
|
||||
u32 refcount;
|
||||
char classBuf[32], serviceBuf[12] = { 0 };
|
||||
char outbuf[GDB_BUF_LEN / 2 + 1];
|
||||
|
||||
if(ctx->commandData[0] == 0)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
val = xstrtoul(ctx->commandData, &end, 0, true, &ok);
|
||||
|
||||
if(!ok)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
end = (char *)GDB_SkipSpaces(end);
|
||||
|
||||
if(*end != 0)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
r = svcOpenProcess(&process, ctx->pid);
|
||||
if(R_FAILED(r))
|
||||
{
|
||||
n = sprintf(outbuf, "Invalid process (wtf?)\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
r = svcCopyHandle(&handle, CUR_PROCESS_HANDLE, (Handle)val, process);
|
||||
if(R_FAILED(r))
|
||||
{
|
||||
n = sprintf(outbuf, "Invalid handle.\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
svcTranslateHandle(&kernelAddr, classBuf, handle);
|
||||
svcGetHandleInfo(&refcountRaw, handle, 1);
|
||||
svcControlService(SERVICEOP_GET_NAME, serviceBuf, handle);
|
||||
refcount = (u32)(refcountRaw - 1);
|
||||
if(serviceBuf[0] != 0)
|
||||
n = sprintf(outbuf, "(%s *)0x%08x /* %s handle, %u %s */\n", classBuf, kernelAddr, serviceBuf, refcount, refcount == 1 ? "reference" : "references");
|
||||
else
|
||||
n = sprintf(outbuf, "(%s *)0x%08x /* %u %s */\n", classBuf, kernelAddr, refcount, refcount == 1 ? "reference" : "references");
|
||||
|
||||
end:
|
||||
svcCloseHandle(handle);
|
||||
svcCloseHandle(process);
|
||||
return GDB_SendHexPacket(ctx, outbuf, n);
|
||||
}
|
||||
|
||||
extern bool isN3DS;
|
||||
GDB_DECLARE_REMOTE_COMMAND_HANDLER(GetMmuConfig)
|
||||
{
|
||||
int n;
|
||||
char outbuf[GDB_BUF_LEN / 2 + 1];
|
||||
Result r;
|
||||
Handle process;
|
||||
|
||||
if(ctx->commandData[0] != 0)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
r = svcOpenProcess(&process, ctx->pid);
|
||||
if(R_FAILED(r))
|
||||
n = sprintf(outbuf, "Invalid process (wtf?)\n");
|
||||
else
|
||||
{
|
||||
s64 TTBCR, TTBR0;
|
||||
svcGetSystemInfo(&TTBCR, 0x10002, 0);
|
||||
svcGetProcessInfo(&TTBR0, process, 0x10008);
|
||||
n = sprintf(outbuf, "TTBCR = %u\nTTBR0 = 0x%08x\nTTBR1 =", (u32)TTBCR, (u32)TTBR0);
|
||||
for(u32 i = 0; i < (isN3DS ? 4 : 2); i++)
|
||||
{
|
||||
s64 TTBR1;
|
||||
svcGetSystemInfo(&TTBR1, 0x10002, 1 + i);
|
||||
|
||||
if(i == (isN3DS ? 3 : 1))
|
||||
n += sprintf(outbuf + n, " 0x%08x\n", (u32)TTBR1);
|
||||
else
|
||||
n += sprintf(outbuf + n, " 0x%08x /", (u32)TTBR1);
|
||||
}
|
||||
svcCloseHandle(process);
|
||||
}
|
||||
|
||||
return GDB_SendHexPacket(ctx, outbuf, n);
|
||||
}
|
||||
|
||||
GDB_DECLARE_REMOTE_COMMAND_HANDLER(FlushCaches)
|
||||
{
|
||||
if(ctx->commandData[0] != 0)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
svcFlushEntireDataCache();
|
||||
svcInvalidateEntireInstructionCache();
|
||||
|
||||
return GDB_ReplyOk(ctx);
|
||||
}
|
||||
|
||||
GDB_DECLARE_QUERY_HANDLER(Rcmd)
|
||||
{
|
||||
char commandData[GDB_BUF_LEN / 2 + 1];
|
||||
char *endpos;
|
||||
const char *errstr = "Unrecognized command.\n";
|
||||
u32 len = strlen(ctx->commandData);
|
||||
|
||||
if(len == 0 || (len % 2) == 1 || GDB_DecodeHex(commandData, ctx->commandData, len / 2) != len / 2)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
commandData[len / 2] = 0;
|
||||
|
||||
for(endpos = commandData; !(*endpos >= 9 && *endpos <= 13) && *endpos != ' ' && *endpos != 0; endpos++);
|
||||
|
||||
char *nextpos = (char *)GDB_SkipSpaces(endpos);
|
||||
*endpos = 0;
|
||||
|
||||
for(u32 i = 0; i < sizeof(remoteCommandHandlers) / sizeof(remoteCommandHandlers[0]); i++)
|
||||
{
|
||||
if(strcmp(commandData, remoteCommandHandlers[i].name) == 0)
|
||||
{
|
||||
ctx->commandData = nextpos;
|
||||
return remoteCommandHandlers[i].handler(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
return GDB_SendHexPacket(ctx, errstr, strlen(errstr));
|
||||
}
|
||||
297
sysmodules/rosalina/source/gdb/server.c
Normal file
297
sysmodules/rosalina/source/gdb/server.c
Normal file
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* 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/server.h"
|
||||
#include "gdb/net.h"
|
||||
#include "gdb/query.h"
|
||||
#include "gdb/verbose.h"
|
||||
#include "gdb/thread.h"
|
||||
#include "gdb/debug.h"
|
||||
#include "gdb/regs.h"
|
||||
#include "gdb/mem.h"
|
||||
#include "gdb/watchpoints.h"
|
||||
#include "gdb/breakpoints.h"
|
||||
#include "gdb/stop_point.h"
|
||||
|
||||
Result GDB_InitializeServer(GDBServer *server)
|
||||
{
|
||||
Result ret = server_init(&server->super);
|
||||
if(ret != 0)
|
||||
return ret;
|
||||
|
||||
server->super.host = 0;
|
||||
|
||||
server->super.accept_cb = (sock_accept_cb)GDB_AcceptClient;
|
||||
server->super.data_cb = (sock_data_cb) GDB_DoPacket;
|
||||
server->super.close_cb = (sock_close_cb) GDB_CloseClient;
|
||||
|
||||
server->super.alloc = (sock_alloc_func) GDB_GetClient;
|
||||
server->super.free = (sock_free_func) GDB_ReleaseClient;
|
||||
|
||||
server->super.clients_per_server = 1;
|
||||
|
||||
server->referenceCount = 0;
|
||||
svcCreateEvent(&server->statusUpdated, RESET_ONESHOT);
|
||||
|
||||
for(u32 i = 0; i < sizeof(server->ctxs) / sizeof(GDBContext); i++)
|
||||
GDB_InitializeContext(server->ctxs + i);
|
||||
|
||||
GDB_ResetWatchpoints();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GDB_FinalizeServer(GDBServer *server)
|
||||
{
|
||||
server_finalize(&server->super);
|
||||
|
||||
svcCloseHandle(server->statusUpdated);
|
||||
}
|
||||
|
||||
void GDB_IncrementServerReferenceCount(GDBServer *server)
|
||||
{
|
||||
AtomicPostIncrement(&server->referenceCount);
|
||||
}
|
||||
|
||||
void GDB_DecrementServerReferenceCount(GDBServer *server)
|
||||
{
|
||||
if(AtomicDecrement(&server->referenceCount) == 0)
|
||||
GDB_FinalizeServer(server);
|
||||
}
|
||||
|
||||
void GDB_RunServer(GDBServer *server)
|
||||
{
|
||||
server_bind(&server->super, GDB_PORT_BASE);
|
||||
server_bind(&server->super, GDB_PORT_BASE + 1);
|
||||
server_bind(&server->super, GDB_PORT_BASE + 2);
|
||||
server_run(&server->super);
|
||||
}
|
||||
|
||||
int GDB_AcceptClient(GDBContext *ctx)
|
||||
{
|
||||
RecursiveLock_Lock(&ctx->lock);
|
||||
Result r = svcDebugActiveProcess(&ctx->debug, ctx->pid);
|
||||
if(R_SUCCEEDED(r))
|
||||
{
|
||||
DebugEventInfo *info = &ctx->latestDebugEvent;
|
||||
ctx->state = GDB_STATE_CONNECTED;
|
||||
ctx->processExited = ctx->processEnded = false;
|
||||
ctx->latestSentPacketSize = 0;
|
||||
while(R_SUCCEEDED(svcGetProcessDebugEvent(info, ctx->debug)) && info->type != DBGEVENT_EXCEPTION &&
|
||||
info->exception.type != EXCEVENT_ATTACH_BREAK)
|
||||
{
|
||||
GDB_PreprocessDebugEvent(ctx, info);
|
||||
svcContinueDebugEvent(ctx->debug, ctx->continueFlags);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RecursiveLock_Unlock(&ctx->lock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
svcSignalEvent(ctx->clientAcceptedEvent);
|
||||
RecursiveLock_Unlock(&ctx->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int GDB_CloseClient(GDBContext *ctx)
|
||||
{
|
||||
RecursiveLock_Lock(&ctx->lock);
|
||||
|
||||
for(u32 i = 0; i < ctx->nbBreakpoints; i++)
|
||||
{
|
||||
if(!ctx->breakpoints[i].persistent)
|
||||
GDB_DisableBreakpointById(ctx, i);
|
||||
}
|
||||
memset(&ctx->breakpoints, 0, sizeof(ctx->breakpoints));
|
||||
ctx->nbBreakpoints = 0;
|
||||
|
||||
for(u32 i = 0; i < ctx->nbWatchpoints; i++)
|
||||
{
|
||||
GDB_RemoveWatchpoint(ctx, ctx->watchpoints[i], WATCHPOINT_DISABLED);
|
||||
ctx->watchpoints[i] = 0;
|
||||
}
|
||||
ctx->nbWatchpoints = 0;
|
||||
|
||||
svcKernelSetState(0x10002, ctx->pid, false);
|
||||
memset(ctx->svcMask, 0, 32);
|
||||
|
||||
memset(ctx->memoryOsInfoXmlData, 0, sizeof(ctx->memoryOsInfoXmlData));
|
||||
memset(ctx->processesOsInfoXmlData, 0, sizeof(ctx->processesOsInfoXmlData));
|
||||
memset(ctx->threadListData, 0, sizeof(ctx->threadListData));
|
||||
ctx->threadListDataPos = 0;
|
||||
|
||||
svcClearEvent(ctx->clientAcceptedEvent);
|
||||
ctx->eventToWaitFor = ctx->clientAcceptedEvent;
|
||||
RecursiveLock_Unlock(&ctx->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
GDBContext *GDB_GetClient(GDBServer *server, u16 port)
|
||||
{
|
||||
if(port < GDB_PORT_BASE || port >= GDB_PORT_BASE + sizeof(server->ctxs) / sizeof(GDBContext))
|
||||
return NULL;
|
||||
|
||||
GDBContext *ctx = &server->ctxs[port - GDB_PORT_BASE];
|
||||
if(!(ctx->flags & GDB_FLAG_USED) && (ctx->flags & GDB_FLAG_SELECTED))
|
||||
{
|
||||
RecursiveLock_Lock(&ctx->lock);
|
||||
ctx->flags |= GDB_FLAG_USED;
|
||||
ctx->state = GDB_STATE_CONNECTED;
|
||||
RecursiveLock_Unlock(&ctx->lock);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void GDB_ReleaseClient(GDBServer *server, GDBContext *ctx)
|
||||
{
|
||||
DebugEventInfo dummy;
|
||||
|
||||
svcSignalEvent(server->statusUpdated);
|
||||
|
||||
RecursiveLock_Lock(&ctx->lock);
|
||||
|
||||
/*
|
||||
There's a possibility of a race condition with a possible user exception handler, but you shouldn't
|
||||
use 'kill' on APPLICATION titles in the first place (reboot hanging because the debugger is still running, etc).
|
||||
*/
|
||||
|
||||
ctx->continueFlags = (DebugFlags)0;
|
||||
|
||||
while(R_SUCCEEDED(svcGetProcessDebugEvent(&dummy, ctx->debug)));
|
||||
while(R_SUCCEEDED(svcContinueDebugEvent(ctx->debug, ctx->continueFlags)));
|
||||
if(ctx->flags & GDB_FLAG_TERMINATE_PROCESS)
|
||||
{
|
||||
svcTerminateDebugProcess(ctx->debug);
|
||||
ctx->processEnded = true;
|
||||
ctx->processExited = false;
|
||||
}
|
||||
|
||||
while(R_SUCCEEDED(svcGetProcessDebugEvent(&dummy, ctx->debug)));
|
||||
while(R_SUCCEEDED(svcContinueDebugEvent(ctx->debug, ctx->continueFlags)));
|
||||
|
||||
svcCloseHandle(ctx->debug);
|
||||
ctx->debug = 0;
|
||||
|
||||
ctx->flags = (GDBFlags)0;
|
||||
ctx->state = GDB_STATE_DISCONNECTED;
|
||||
|
||||
ctx->eventToWaitFor = ctx->clientAcceptedEvent;
|
||||
ctx->continueFlags = (DebugFlags)(DBG_SIGNAL_FAULT_EXCEPTION_EVENTS | DBG_INHIBIT_USER_CPU_EXCEPTION_HANDLERS);
|
||||
ctx->pid = 0;
|
||||
ctx->currentThreadId = ctx->selectedThreadId = ctx->selectedThreadIdForContinuing = 0;
|
||||
ctx->nbThreads = 0;
|
||||
memset(ctx->threadInfos, 0, sizeof(ctx->threadInfos));
|
||||
ctx->catchThreadEvents = false;
|
||||
ctx->isGDB = false;
|
||||
RecursiveLock_Unlock(&ctx->lock);
|
||||
}
|
||||
|
||||
static const struct
|
||||
{
|
||||
char command;
|
||||
GDBCommandHandler handler;
|
||||
} gdbCommandHandlers[] =
|
||||
{
|
||||
{ '?', GDB_HANDLER(GetStopReason) },
|
||||
{ 'c', GDB_HANDLER(Continue) },
|
||||
{ 'C', GDB_HANDLER(Continue) },
|
||||
{ 'D', GDB_HANDLER(Detach) },
|
||||
{ 'g', GDB_HANDLER(ReadRegisters) },
|
||||
{ 'G', GDB_HANDLER(WriteRegisters) },
|
||||
{ 'H', GDB_HANDLER(SetThreadId) },
|
||||
{ 'k', GDB_HANDLER(Kill) },
|
||||
{ 'm', GDB_HANDLER(ReadMemory) },
|
||||
{ 'M', GDB_HANDLER(WriteMemory) },
|
||||
{ 'p', GDB_HANDLER(ReadRegister) },
|
||||
{ 'P', GDB_HANDLER(WriteRegister) },
|
||||
{ 'q', GDB_HANDLER(ReadQuery) },
|
||||
{ 'Q', GDB_HANDLER(WriteQuery) },
|
||||
{ 'T', GDB_HANDLER(IsThreadAlive) },
|
||||
{ 'v', GDB_HANDLER(VerboseCommand) },
|
||||
{ 'X', GDB_HANDLER(WriteMemoryRaw) },
|
||||
{ 'z', GDB_HANDLER(ToggleStopPoint) },
|
||||
{ 'Z', GDB_HANDLER(ToggleStopPoint) },
|
||||
};
|
||||
|
||||
static inline GDBCommandHandler GDB_GetCommandHandler(char command)
|
||||
{
|
||||
static const u32 nbHandlers = sizeof(gdbCommandHandlers) / sizeof(gdbCommandHandlers[0]);
|
||||
|
||||
u32 i;
|
||||
for(i = 0; i < nbHandlers && gdbCommandHandlers[i].command != command; i++);
|
||||
|
||||
return i < nbHandlers ? gdbCommandHandlers[i].handler : GDB_HANDLER(Unsupported);
|
||||
}
|
||||
|
||||
int GDB_DoPacket(GDBContext *ctx)
|
||||
{
|
||||
int ret;
|
||||
|
||||
RecursiveLock_Lock(&ctx->lock);
|
||||
GDBFlags oldFlags = ctx->flags;
|
||||
|
||||
if(ctx->state == GDB_STATE_DISCONNECTED)
|
||||
return -1;
|
||||
|
||||
int r = GDB_ReceivePacket(ctx);
|
||||
if(r == 0)
|
||||
ret = 0;
|
||||
else if(r == -1)
|
||||
ret = -1;
|
||||
else if(ctx->buffer[0] == '\x03')
|
||||
{
|
||||
GDB_HandleBreak(ctx);
|
||||
ret = 0;
|
||||
}
|
||||
else if(ctx->buffer[0] == '$')
|
||||
{
|
||||
GDBCommandHandler handler = GDB_GetCommandHandler(ctx->buffer[1]);
|
||||
ctx->commandData = ctx->buffer + 2;
|
||||
ret = handler(ctx);
|
||||
}
|
||||
else
|
||||
ret = 0;
|
||||
|
||||
RecursiveLock_Unlock(&ctx->lock);
|
||||
if(ctx->state == GDB_STATE_CLOSING)
|
||||
return -1;
|
||||
|
||||
if((oldFlags & GDB_FLAG_PROCESS_CONTINUING) && !(ctx->flags & GDB_FLAG_PROCESS_CONTINUING))
|
||||
{
|
||||
if(R_FAILED(svcBreakDebugProcess(ctx->debug)))
|
||||
ctx->flags |= GDB_FLAG_PROCESS_CONTINUING;
|
||||
}
|
||||
else if(!(oldFlags & GDB_FLAG_PROCESS_CONTINUING) && (ctx->flags & GDB_FLAG_PROCESS_CONTINUING))
|
||||
svcSignalEvent(ctx->continuedEvent);
|
||||
|
||||
return ret;
|
||||
}
|
||||
71
sysmodules/rosalina/source/gdb/stop_point.c
Normal file
71
sysmodules/rosalina/source/gdb/stop_point.c
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.h"
|
||||
#include "gdb/net.h"
|
||||
#include "gdb/watchpoints.h"
|
||||
#include "gdb/breakpoints.h"
|
||||
|
||||
GDB_DECLARE_HANDLER(ToggleStopPoint)
|
||||
{
|
||||
bool add = ctx->commandData[-1] == 'Z';
|
||||
u32 lst[3];
|
||||
|
||||
const char *pos = GDB_ParseHexIntegerList(lst, ctx->commandData, 3, ';');
|
||||
if(pos == NULL)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
bool persist = *pos != 0 && strncmp(pos, ";cmds:1", 7) == 0;
|
||||
|
||||
u32 kind = lst[0];
|
||||
u32 addr = lst[1];
|
||||
u32 size = lst[2];
|
||||
|
||||
int res;
|
||||
static const WatchpointKind kinds[3] = { WATCHPOINT_WRITE, WATCHPOINT_READ, WATCHPOINT_READWRITE };
|
||||
switch(kind)
|
||||
{
|
||||
case 0: // software breakpoint
|
||||
if(size != 2 && size != 4)
|
||||
return GDB_ReplyEmpty(ctx);
|
||||
else
|
||||
{
|
||||
res = add ? GDB_AddBreakpoint(ctx, addr, size == 2, persist) :
|
||||
GDB_RemoveBreakpoint(ctx, addr);
|
||||
return res == 0 ? GDB_ReplyOk(ctx) : GDB_ReplyErrno(ctx, -res);
|
||||
}
|
||||
|
||||
// Watchpoints
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
res = add ? GDB_AddWatchpoint(ctx, addr, size, kinds[kind - 2]) :
|
||||
GDB_RemoveWatchpoint(ctx, addr, kinds[kind - 2]);
|
||||
return res == 0 ? GDB_ReplyOk(ctx) : GDB_ReplyErrno(ctx, -res);
|
||||
|
||||
default:
|
||||
return GDB_ReplyEmpty(ctx);
|
||||
}
|
||||
}
|
||||
304
sysmodules/rosalina/source/gdb/thread.c
Normal file
304
sysmodules/rosalina/source/gdb/thread.c
Normal file
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
73
sysmodules/rosalina/source/gdb/verbose.c
Normal file
73
sysmodules/rosalina/source/gdb/verbose.c
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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/verbose.h"
|
||||
#include "gdb/net.h"
|
||||
#include "gdb/debug.h"
|
||||
|
||||
static const struct
|
||||
{
|
||||
const char *name;
|
||||
GDBCommandHandler handler;
|
||||
} gdbVerboseCommandHandlers[] =
|
||||
{
|
||||
{ "Cont?", GDB_VERBOSE_HANDLER(ContinueSupported) },
|
||||
{ "Cont", GDB_VERBOSE_HANDLER(Continue) },
|
||||
{ "MustReplyEmpty", GDB_HANDLER(Unsupported) },
|
||||
};
|
||||
|
||||
GDB_DECLARE_HANDLER(VerboseCommand)
|
||||
{
|
||||
char *nameBegin = ctx->commandData; // w/o leading 'v'
|
||||
if(*nameBegin == 0)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
char *nameEnd;
|
||||
char *vData = NULL;
|
||||
|
||||
for(nameEnd = nameBegin; *nameEnd != 0 && *nameEnd != ';' && *nameEnd != ':'; nameEnd++);
|
||||
if(*nameEnd != 0)
|
||||
{
|
||||
*nameEnd = 0;
|
||||
vData = nameEnd + 1;
|
||||
}
|
||||
|
||||
for(u32 i = 0; i < sizeof(gdbVerboseCommandHandlers) / sizeof(gdbVerboseCommandHandlers[0]); i++)
|
||||
{
|
||||
if(strcmp(gdbVerboseCommandHandlers[i].name, nameBegin) == 0)
|
||||
{
|
||||
ctx->commandData = vData;
|
||||
return gdbVerboseCommandHandlers[i].handler(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
return GDB_HandleUnsupported(ctx); // No handler found!
|
||||
}
|
||||
|
||||
GDB_DECLARE_VERBOSE_HANDLER(ContinueSupported)
|
||||
{
|
||||
return GDB_SendPacket(ctx, "vCont;c;C", 9);
|
||||
}
|
||||
177
sysmodules/rosalina/source/gdb/watchpoints.c
Normal file
177
sysmodules/rosalina/source/gdb/watchpoints.c
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* 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/watchpoints.h"
|
||||
#define _REENT_ONLY
|
||||
#include <errno.h>
|
||||
|
||||
/*
|
||||
There are only 2 Watchpoint Register Pairs on MPCORE ARM11 CPUs,
|
||||
and only 2 Breakpoint Register Pairs with context ID capabilities (BRP4-5) as well.
|
||||
|
||||
We'll reserve and use all 4 of them
|
||||
*/
|
||||
|
||||
RecursiveLock watchpointManagerLock;
|
||||
|
||||
typedef struct Watchpoint
|
||||
{
|
||||
u32 address;
|
||||
u32 size;
|
||||
WatchpointKind kind;
|
||||
Handle debug; // => context ID
|
||||
} Watchpoint;
|
||||
|
||||
typedef struct WatchpointManager
|
||||
{
|
||||
u32 total;
|
||||
Watchpoint watchpoints[2];
|
||||
} WatchpointManager;
|
||||
|
||||
static WatchpointManager manager;
|
||||
|
||||
void GDB_ResetWatchpoints(void)
|
||||
{
|
||||
static bool lockInitialized = false;
|
||||
if(!lockInitialized)
|
||||
{
|
||||
RecursiveLock_Init(&watchpointManagerLock);
|
||||
lockInitialized = true;
|
||||
}
|
||||
RecursiveLock_Lock(&watchpointManagerLock);
|
||||
|
||||
svcSetHardwareBreakPoint(4, 0, 0);
|
||||
svcSetHardwareBreakPoint(0x100, 0, 0);
|
||||
|
||||
svcSetHardwareBreakPoint(5, 0, 0);
|
||||
svcSetHardwareBreakPoint(0x101, 0, 0);
|
||||
|
||||
memset(&manager, 0, sizeof(WatchpointManager));
|
||||
|
||||
RecursiveLock_Unlock(&watchpointManagerLock);
|
||||
}
|
||||
|
||||
int GDB_AddWatchpoint(GDBContext *ctx, u32 address, u32 size, WatchpointKind kind)
|
||||
{
|
||||
RecursiveLock_Lock(&watchpointManagerLock);
|
||||
|
||||
u32 offset = address - (address & ~3);
|
||||
|
||||
if(manager.total == 2)
|
||||
return -EBUSY;
|
||||
|
||||
if(size == 0 || (offset + size) > 4 || kind == WATCHPOINT_DISABLED)
|
||||
return -EINVAL;
|
||||
|
||||
if(GDB_GetWatchpointKind(ctx, address) != WATCHPOINT_DISABLED)
|
||||
// Disallow duplicate watchpoints: the kernel doesn't give us sufficient info to differentiate them by kind (DFSR)
|
||||
return -EINVAL;
|
||||
|
||||
u32 id = manager.watchpoints[0].kind == WATCHPOINT_DISABLED ? 0 : 1;
|
||||
u32 selectMask = ((1 << size) - 1) << offset;
|
||||
|
||||
u32 BCR = (1 << 21) | /* compare with context ID */
|
||||
(1 << 20) | /* linked (with a WRP in our case) */
|
||||
(0xf << 5) | /* byte address select, +0 to +3 as mandated when linking with a WRP */
|
||||
(3 << 1) | /* either privileged modes or user mode, as mandated when linking with a WRP */
|
||||
(1 << 0) ; /* enabled */
|
||||
|
||||
u32 WCR = (1 << 20) | /* linked */
|
||||
((4 + id) << 16) | /* ID of the linked BRP */
|
||||
(selectMask << 5) | /* byte address select */
|
||||
((u32)kind << 3) | /* kind */
|
||||
(2 << 1) | /* user mode only */
|
||||
(1 << 0) ; /* enabled */
|
||||
|
||||
Result r = svcSetHardwareBreakPoint(0x100 | id, WCR, address & ~3);
|
||||
|
||||
if(R_SUCCEEDED(r))
|
||||
r = svcSetHardwareBreakPoint(4 + id, BCR, (u32)ctx->debug);
|
||||
|
||||
if(R_SUCCEEDED(r))
|
||||
{
|
||||
Watchpoint *watchpoint = &manager.watchpoints[id];
|
||||
manager.total++;
|
||||
watchpoint->address = address;
|
||||
watchpoint->size = size;
|
||||
watchpoint->kind = kind;
|
||||
watchpoint->debug = ctx->debug;
|
||||
ctx->watchpoints[ctx->nbWatchpoints++] = address;
|
||||
RecursiveLock_Unlock(&watchpointManagerLock);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
RecursiveLock_Unlock(&watchpointManagerLock);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
int GDB_RemoveWatchpoint(GDBContext *ctx, u32 address, WatchpointKind kind)
|
||||
{
|
||||
RecursiveLock_Lock(&watchpointManagerLock);
|
||||
|
||||
u32 id;
|
||||
for(id = 0; id < 2 && manager.watchpoints[id].address != address && manager.watchpoints[id].debug != ctx->debug; id++);
|
||||
|
||||
if(id == 2 || (kind != WATCHPOINT_DISABLED && manager.watchpoints[id].kind != kind))
|
||||
{
|
||||
RecursiveLock_Unlock(&watchpointManagerLock);
|
||||
return -EINVAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
svcSetHardwareBreakPoint(4 + id, 0, 0);
|
||||
svcSetHardwareBreakPoint(0x100 | id, 0, 0);
|
||||
|
||||
memset(&manager.watchpoints[id], 0, sizeof(Watchpoint));
|
||||
manager.total--;
|
||||
|
||||
if(ctx->watchpoints[0] == address)
|
||||
{
|
||||
ctx->watchpoints[0] = ctx->watchpoints[1];
|
||||
ctx->watchpoints[1] = 0;
|
||||
ctx->nbWatchpoints--;
|
||||
}
|
||||
else if(ctx->watchpoints[1] == address)
|
||||
{
|
||||
ctx->watchpoints[1] = 0;
|
||||
ctx->nbWatchpoints--;
|
||||
}
|
||||
|
||||
RecursiveLock_Unlock(&watchpointManagerLock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
WatchpointKind GDB_GetWatchpointKind(GDBContext *ctx, u32 address)
|
||||
{
|
||||
u32 id;
|
||||
for(id = 0; id < 2 && manager.watchpoints[id].address != address && manager.watchpoints[id].debug != ctx->debug; id++);
|
||||
|
||||
return id == 2 ? WATCHPOINT_DISABLED : manager.watchpoints[id].kind;
|
||||
}
|
||||
271
sysmodules/rosalina/source/gdb/xfer.c
Normal file
271
sysmodules/rosalina/source/gdb/xfer.c
Normal file
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* 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/xfer.h"
|
||||
#include "gdb/net.h"
|
||||
#include "../../build/xml_data.h"
|
||||
#include <3ds/os.h>
|
||||
#include "fmt.h"
|
||||
|
||||
struct
|
||||
{
|
||||
const char *name;
|
||||
int (*handler)(GDBContext *ctx, bool write, const char *annex, u32 offset, u32 length);
|
||||
} xferCommandHandlers[] =
|
||||
{
|
||||
{ "features", GDB_XFER_HANDLER(Features) },
|
||||
{ "osdata", GDB_XFER_HANDLER(OsData) },
|
||||
};
|
||||
|
||||
GDB_DECLARE_XFER_HANDLER(Features)
|
||||
{
|
||||
if(strcmp(annex, "target.xml") != 0 || write)
|
||||
return GDB_ReplyEmpty(ctx);
|
||||
else
|
||||
return GDB_SendStreamData(ctx, target_xml, offset, length, sizeof(target_xml) - 1, false);
|
||||
}
|
||||
|
||||
struct
|
||||
{
|
||||
const char *name;
|
||||
int (*handler)(GDBContext *ctx, bool write, u32 offset, u32 length);
|
||||
} xferOsDataCommandHandlers[] =
|
||||
{
|
||||
{ "cfwversion", GDB_XFER_OSDATA_HANDLER(CfwVersion) },
|
||||
{ "memory", GDB_XFER_OSDATA_HANDLER(Memory) },
|
||||
{ "processes", GDB_XFER_OSDATA_HANDLER(Processes) },
|
||||
};
|
||||
|
||||
GDB_DECLARE_XFER_OSDATA_HANDLER(CfwVersion)
|
||||
{
|
||||
if(write)
|
||||
return GDB_HandleUnsupported(ctx);
|
||||
else
|
||||
{
|
||||
char buf[sizeof(osdata_cfw_version_template_xml) + 64];
|
||||
char versionString[16];
|
||||
s64 out;
|
||||
u32 version, commitHash;
|
||||
bool isRelease;
|
||||
u32 sz;
|
||||
|
||||
svcGetSystemInfo(&out, 0x10000, 0);
|
||||
version = (u32)out;
|
||||
|
||||
svcGetSystemInfo(&out, 0x10000, 1);
|
||||
commitHash = (u32)out;
|
||||
|
||||
svcGetSystemInfo(&out, 0x10000, 3);
|
||||
isRelease = (bool)out;
|
||||
|
||||
if(GET_VERSION_REVISION(version) == 0)
|
||||
sprintf(versionString, "v%u.%u", GET_VERSION_MAJOR(version), GET_VERSION_MINOR(version));
|
||||
else
|
||||
sprintf(versionString, "v%u.%u.%u", GET_VERSION_MAJOR(version), GET_VERSION_MINOR(version), GET_VERSION_REVISION(version));
|
||||
|
||||
sz = (u32)sprintf(buf, osdata_cfw_version_template_xml, versionString, commitHash, isRelease ? "Yes" : "No");
|
||||
|
||||
return GDB_SendStreamData(ctx, buf, offset, length, sz, false);
|
||||
}
|
||||
}
|
||||
|
||||
GDB_DECLARE_XFER_OSDATA_HANDLER(Memory)
|
||||
{
|
||||
if(write)
|
||||
return GDB_HandleUnsupported(ctx);
|
||||
else
|
||||
{
|
||||
if(ctx->memoryOsInfoXmlData[0] == 0)
|
||||
{
|
||||
s64 out;
|
||||
u32 applicationUsed, systemUsed, baseUsed;
|
||||
u32 applicationTotal = *(vu32 *)0x1FF80040, systemTotal = *(vu32 *)0x1FF80044, baseTotal = *(vu32 *)0x1FF80048;
|
||||
|
||||
svcGetSystemInfo(&out, 0, 1);
|
||||
applicationUsed = (u32)out;
|
||||
|
||||
svcGetSystemInfo(&out, 0, 2);
|
||||
systemUsed = (u32)out;
|
||||
|
||||
svcGetSystemInfo(&out, 0, 3);
|
||||
baseUsed = (u32)out;
|
||||
|
||||
sprintf(ctx->memoryOsInfoXmlData, osdata_memory_template_xml,
|
||||
applicationUsed, applicationTotal - applicationUsed, applicationTotal, (u32)((5ULL + ((1000ULL * applicationUsed) / applicationTotal)) / 10ULL),
|
||||
systemUsed, systemTotal - systemUsed, systemTotal, (u32)((5ULL + ((1000ULL * systemUsed) / systemTotal)) / 10ULL),
|
||||
baseUsed, baseTotal - baseUsed, baseTotal, (u32)((5ULL + ((1000ULL * baseUsed) / baseTotal)) / 10ULL)
|
||||
);
|
||||
}
|
||||
|
||||
u32 size = strlen(ctx->memoryOsInfoXmlData);
|
||||
int n = GDB_SendStreamData(ctx, ctx->memoryOsInfoXmlData, offset, length, size, false);
|
||||
|
||||
if(offset + length >= size)
|
||||
ctx->memoryOsInfoXmlData[0] = 0; // we're done, invalidate
|
||||
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
GDB_DECLARE_XFER_OSDATA_HANDLER(Processes)
|
||||
{
|
||||
if(write)
|
||||
return GDB_HandleUnsupported(ctx);
|
||||
else
|
||||
{
|
||||
if(ctx->processesOsInfoXmlData[0] == 0)
|
||||
{
|
||||
static const char header[] =
|
||||
"<?xml version=\"1.0\"?>\n"
|
||||
"<!DOCTYPE target SYSTEM \"osdata.dtd\">\n"
|
||||
"<osdata type=\"processes\">\n";
|
||||
|
||||
static const char item[] =
|
||||
" <item>\n"
|
||||
" <column name=\"pid\">%u</column>\n"
|
||||
" <column name=\"command\">%s</column>\n"
|
||||
" </item>\n";
|
||||
|
||||
static const char footer[] = "</osdata>\n";
|
||||
|
||||
int n;
|
||||
u32 pos = 0;
|
||||
|
||||
u32 pidList[0x40];
|
||||
s32 processAmount;
|
||||
|
||||
strcpy(ctx->processesOsInfoXmlData, header);
|
||||
pos = sizeof(header) - 1;
|
||||
svcGetProcessList(&processAmount, pidList, 0x40);
|
||||
|
||||
for(s32 i = 0; i < processAmount; i++)
|
||||
{
|
||||
u32 pid = pidList[i];
|
||||
char name[9] = { 0 };
|
||||
s64 out;
|
||||
Handle processHandle;
|
||||
Result res = svcOpenProcess(&processHandle, pidList[i]);
|
||||
if(R_FAILED(res))
|
||||
continue;
|
||||
|
||||
svcGetProcessInfo(&out, processHandle, 0x10000);
|
||||
memcpy(name, &out, 8);
|
||||
svcCloseHandle(processHandle);
|
||||
|
||||
n = sprintf(ctx->processesOsInfoXmlData + pos, item, pid, name);
|
||||
pos += (u32)n;
|
||||
}
|
||||
|
||||
strcpy(ctx->processesOsInfoXmlData + pos, footer);
|
||||
pos = sizeof(footer) - 1;
|
||||
}
|
||||
|
||||
u32 size = strlen(ctx->processesOsInfoXmlData);
|
||||
int n = GDB_SendStreamData(ctx, ctx->processesOsInfoXmlData, offset, length, size, false);
|
||||
|
||||
if(offset + length >= size)
|
||||
ctx->processesOsInfoXmlData[0] = 0; // we're done, invalidate
|
||||
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
GDB_DECLARE_XFER_HANDLER(OsData)
|
||||
{
|
||||
if(strcmp(annex, "") == 0 && !write)
|
||||
return GDB_SendStreamData(ctx, osdata_xml, offset, length, sizeof(osdata_xml) - 1, false);
|
||||
else
|
||||
{
|
||||
for(u32 i = 0; i < sizeof(xferOsDataCommandHandlers) / sizeof(xferOsDataCommandHandlers[0]); i++)
|
||||
{
|
||||
if(strcmp(annex, xferOsDataCommandHandlers[i].name) == 0)
|
||||
return xferOsDataCommandHandlers[i].handler(ctx, write, offset, length);
|
||||
}
|
||||
}
|
||||
|
||||
return GDB_HandleUnsupported(ctx);
|
||||
}
|
||||
|
||||
GDB_DECLARE_QUERY_HANDLER(Xfer)
|
||||
{
|
||||
const char *objectStart = ctx->commandData;
|
||||
char *objectEnd = (char*)strchr(objectStart, ':');
|
||||
if(objectEnd == NULL) return -1;
|
||||
*objectEnd = 0;
|
||||
|
||||
char *opStart = objectEnd + 1;
|
||||
char *opEnd = (char*)strchr(opStart, ':');
|
||||
if(opEnd == NULL) return -1;
|
||||
*opEnd = 0;
|
||||
|
||||
char *annexStart = opEnd + 1;
|
||||
char *annexEnd = (char*)strchr(annexStart, ':');
|
||||
if(annexEnd == NULL) return -1;
|
||||
*annexEnd = 0;
|
||||
|
||||
const char *offStart = annexEnd + 1;
|
||||
u32 offset, length;
|
||||
|
||||
bool write;
|
||||
const char *pos;
|
||||
if(strcmp(opStart, "read") == 0)
|
||||
{
|
||||
u32 lst[2];
|
||||
if(GDB_ParseHexIntegerList(lst, offStart, 2, 0) == NULL)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
offset = lst[0];
|
||||
length = lst[1];
|
||||
write = false;
|
||||
}
|
||||
else if(strcmp(opStart, "write") == 0)
|
||||
{
|
||||
pos = GDB_ParseHexIntegerList(&offset, offStart, 1, ':');
|
||||
if(pos == NULL || *pos++ != ':')
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
u32 len = strlen(pos);
|
||||
if(len == 0 || (len % 2) != 0)
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
length = len / 2;
|
||||
write = true;
|
||||
}
|
||||
else
|
||||
return GDB_ReplyErrno(ctx, EILSEQ);
|
||||
|
||||
for(u32 i = 0; i < sizeof(xferCommandHandlers) / sizeof(xferCommandHandlers[0]); i++)
|
||||
{
|
||||
if(strcmp(objectStart, xferCommandHandlers[i].name) == 0)
|
||||
{
|
||||
if(write)
|
||||
ctx->commandData = (char *)pos;
|
||||
|
||||
return xferCommandHandlers[i].handler(ctx, write, annexStart, offset, length);
|
||||
}
|
||||
}
|
||||
|
||||
return GDB_HandleUnsupported(ctx);
|
||||
}
|
||||
19
sysmodules/rosalina/source/gdb/xml/osdata.xml
Normal file
19
sysmodules/rosalina/source/gdb/xml/osdata.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE target SYSTEM "osdata.dtd">
|
||||
<osdata type="types">
|
||||
<item>
|
||||
<column name="Type">cfwversion</column>
|
||||
<column name="Description">Listing of the Luma3DS version, commit hash, etc.</column>
|
||||
<column name="Title">CFW</column>
|
||||
</item>
|
||||
<item>
|
||||
<column name="Type">memory</column>
|
||||
<column name="Description">Listing of memory usage per region</column>
|
||||
<column name="Title">Memory</column>
|
||||
</item>
|
||||
<item>
|
||||
<column name="Type">processes</column>
|
||||
<column name="Description">Listing of all processes</column>
|
||||
<column name="Title">Processes</column>
|
||||
</item>
|
||||
</osdata>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE target SYSTEM "osdata.dtd">
|
||||
<osdata type="cfwversion">
|
||||
<item>
|
||||
<column name="Version">%s</column>
|
||||
<column name="Commit hash">%08x</column>
|
||||
<column name="Release">%s</column>
|
||||
</item>
|
||||
</osdata>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE target SYSTEM "osdata.dtd">
|
||||
<osdata type="memory">
|
||||
<item>
|
||||
<column name="Region">APPLICATION</column>
|
||||
<column name="Bytes used">%u</column>
|
||||
<column name="Bytes free">%u</column>
|
||||
<column name="Total size">%u</column>
|
||||
<column name="Percentage used">%u%%</column>
|
||||
</item>
|
||||
<item>
|
||||
<column name="Region">SYSTEM</column>
|
||||
<column name="Bytes used">%u</column>
|
||||
<column name="Bytes free">%u</column>
|
||||
<column name="Total size">%u</column>
|
||||
<column name="Percentage used">%u%%</column>
|
||||
</item>
|
||||
<item>
|
||||
<column name="Region">BASE</column>
|
||||
<column name="Bytes used">%u</column>
|
||||
<column name="Bytes free">%u</column>
|
||||
<column name="Total size">%u</column>
|
||||
<column name="Percentage used">%u%%</column>
|
||||
</item>
|
||||
</osdata>
|
||||
45
sysmodules/rosalina/source/gdb/xml/target.xml
Normal file
45
sysmodules/rosalina/source/gdb/xml/target.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE target SYSTEM "gdb-target.dtd">
|
||||
<target version="1.0">
|
||||
<architecture>arm</architecture>
|
||||
<osabi>3DS</osabi>
|
||||
<feature name="org.gnu.gdb.arm.core">
|
||||
<reg name="r0" bitsize="32"/>
|
||||
<reg name="r1" bitsize="32"/>
|
||||
<reg name="r2" bitsize="32"/>
|
||||
<reg name="r3" bitsize="32"/>
|
||||
<reg name="r4" bitsize="32"/>
|
||||
<reg name="r5" bitsize="32"/>
|
||||
<reg name="r6" bitsize="32"/>
|
||||
<reg name="r7" bitsize="32"/>
|
||||
<reg name="r8" bitsize="32"/>
|
||||
<reg name="r9" bitsize="32"/>
|
||||
<reg name="r10" bitsize="32"/>
|
||||
<reg name="r11" bitsize="32"/>
|
||||
<reg name="r12" bitsize="32"/>
|
||||
<reg name="sp" bitsize="32" type="data_ptr"/>
|
||||
<reg name="lr" bitsize="32"/>
|
||||
<reg name="pc" bitsize="32" type="code_ptr"/>
|
||||
<reg name="cpsr" bitsize="32" regnum="25"/>
|
||||
</feature>
|
||||
<feature name="org.gnu.gdb.arm.vfp">
|
||||
<reg name="d0" bitsize="64" type="float"/>
|
||||
<reg name="d1" bitsize="64" type="float"/>
|
||||
<reg name="d2" bitsize="64" type="float"/>
|
||||
<reg name="d3" bitsize="64" type="float"/>
|
||||
<reg name="d4" bitsize="64" type="float"/>
|
||||
<reg name="d5" bitsize="64" type="float"/>
|
||||
<reg name="d6" bitsize="64" type="float"/>
|
||||
<reg name="d7" bitsize="64" type="float"/>
|
||||
<reg name="d8" bitsize="64" type="float"/>
|
||||
<reg name="d9" bitsize="64" type="float"/>
|
||||
<reg name="d10" bitsize="64" type="float"/>
|
||||
<reg name="d11" bitsize="64" type="float"/>
|
||||
<reg name="d12" bitsize="64" type="float"/>
|
||||
<reg name="d13" bitsize="64" type="float"/>
|
||||
<reg name="d14" bitsize="64" type="float"/>
|
||||
<reg name="d15" bitsize="64" type="float"/>
|
||||
<reg name="fpscr" bitsize="32" type="int" group="float"/>
|
||||
<reg name="fpexc" bitsize="32" type="int" group="float"/>
|
||||
</feature>
|
||||
</target>
|
||||
Reference in New Issue
Block a user