Added Rosalina, see details
- see release notes - ( ͡° ͜ʖ ͡°)( ͡° ͜ʖ ͡°)( ͡° ͜ʖ ͡°) - (∩ ͡° ͜ʖ ͡°)⊃━☆゚ - ( ͡ᵔ ͜ʖ ͡ᵔ) ♫┌( ͡° ͜ʖ ͡°)┘♪ ♫└( ͡° ͜ʖ ͡°)┐♪
This commit is contained in:
284
sysmodules/rosalina/source/3dsx.c
Normal file
284
sysmodules/rosalina/source/3dsx.c
Normal file
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* This file was entirely written by fincs */
|
||||
|
||||
#include <3ds.h>
|
||||
#include "3dsx.h"
|
||||
#include "memory.h"
|
||||
|
||||
#define Log_PrintP(...) ((void)0)
|
||||
|
||||
#define MAXRELOCS 512
|
||||
static _3DSX_Reloc s_relocBuf[MAXRELOCS];
|
||||
u32 ldrArgvBuf[ARGVBUF_SIZE/4];
|
||||
|
||||
#define SEC_ASSERT(x) do { if (!(x)) { Log_PrintP("Assertion failed: %s", #x); return false; } } while (0)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
void* segPtrs[3]; // code, rodata & data
|
||||
u32 segAddrs[3];
|
||||
u32 segSizes[3];
|
||||
} _3DSX_LoadInfo;
|
||||
|
||||
static inline u32 TranslateAddr(u32 off, _3DSX_LoadInfo* d, u32* offsets)
|
||||
{
|
||||
if (off < offsets[0])
|
||||
return d->segAddrs[0] + off;
|
||||
if (off < offsets[1])
|
||||
return d->segAddrs[1] + off - offsets[0];
|
||||
return d->segAddrs[2] + off - offsets[1];
|
||||
}
|
||||
|
||||
u32 IFile_Read2(IFile *file, void* buffer, u32 size, u32 offset)
|
||||
{
|
||||
Result res;
|
||||
u64 total = 0;
|
||||
file->pos = offset;
|
||||
res = IFile_Read(file, &total, buffer, size);
|
||||
return R_SUCCEEDED(res) ? total : 0;
|
||||
}
|
||||
|
||||
bool Ldr_Get3dsxSize(u32* pSize, IFile *file)
|
||||
{
|
||||
_3DSX_Header hdr;
|
||||
|
||||
if (IFile_Read2(file, &hdr, sizeof(hdr), 0) != sizeof(hdr))
|
||||
{
|
||||
Log_PrintP("Cannot read 3DSX header");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hdr.magic != _3DSX_MAGIC)
|
||||
{
|
||||
Log_PrintP("Not a valid 3DSX file");
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 segSizes[3];
|
||||
segSizes[0] = (hdr.codeSegSize+0xFFF) &~ 0xFFF;
|
||||
segSizes[1] = (hdr.rodataSegSize+0xFFF) &~ 0xFFF;
|
||||
segSizes[2] = (hdr.dataSegSize+0xFFF) &~ 0xFFF;
|
||||
SEC_ASSERT(segSizes[0] >= hdr.codeSegSize);
|
||||
SEC_ASSERT(segSizes[1] >= hdr.rodataSegSize);
|
||||
SEC_ASSERT(segSizes[2] >= hdr.dataSegSize);
|
||||
|
||||
// TODO: Check int overflow
|
||||
*pSize = segSizes[0] + segSizes[1] + segSizes[2] + 0x1000; // One extra page reserved for settings/etc
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline u32 min(u32 a, u32 b)
|
||||
{
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
Handle Ldr_CodesetFrom3dsx(const char* name, u32* codePages, u32 baseAddr, IFile *file, u64 tid)
|
||||
{
|
||||
u32 i,j,k,m;
|
||||
Result res;
|
||||
_3DSX_Header hdr;
|
||||
IFile_Read2(file, &hdr, sizeof(hdr), 0);
|
||||
|
||||
_3DSX_LoadInfo d;
|
||||
d.segSizes[0] = (hdr.codeSegSize+0xFFF) &~ 0xFFF;
|
||||
d.segSizes[1] = (hdr.rodataSegSize+0xFFF) &~ 0xFFF;
|
||||
d.segSizes[2] = (hdr.dataSegSize+0xFFF) &~ 0xFFF;
|
||||
d.segPtrs[0] = codePages;
|
||||
d.segPtrs[1] = (char*)d.segPtrs[0] + d.segSizes[0];
|
||||
d.segPtrs[2] = (char*)d.segPtrs[1] + d.segSizes[1];
|
||||
d.segAddrs[0] = baseAddr;
|
||||
d.segAddrs[1] = d.segAddrs[0] + d.segSizes[0];
|
||||
d.segAddrs[2] = d.segAddrs[1] + d.segSizes[1];
|
||||
|
||||
u32 offsets[2] = { d.segSizes[0], d.segSizes[0] + d.segSizes[1] };
|
||||
u32* segLimit = d.segPtrs[2] + d.segSizes[2];
|
||||
|
||||
u32 readOffset = hdr.headerSize;
|
||||
|
||||
u32 nRelocTables = hdr.relocHdrSize/4;
|
||||
SEC_ASSERT((3*4*nRelocTables) <= 0x1000);
|
||||
u32* extraPage = (u32*)((char*)d.segPtrs[2] + d.segSizes[2]);
|
||||
u32 extraPageAddr = d.segAddrs[2] + d.segSizes[2];
|
||||
|
||||
// Read the relocation headers
|
||||
for (i = 0; i < 3; i ++)
|
||||
{
|
||||
if (IFile_Read2(file, &extraPage[i*nRelocTables], hdr.relocHdrSize, readOffset) != hdr.relocHdrSize)
|
||||
{
|
||||
Log_PrintP("Cannot read relheader %d", i);
|
||||
return 0;
|
||||
}
|
||||
readOffset += hdr.relocHdrSize;
|
||||
}
|
||||
|
||||
// Read the code segment
|
||||
if (IFile_Read2(file, d.segPtrs[0], hdr.codeSegSize, readOffset) != hdr.codeSegSize)
|
||||
{
|
||||
Log_PrintP("Cannot read code segment");
|
||||
return 0;
|
||||
}
|
||||
readOffset += hdr.codeSegSize;
|
||||
|
||||
// Read the rodata segment
|
||||
if (IFile_Read2(file, d.segPtrs[1], hdr.rodataSegSize, readOffset) != hdr.rodataSegSize)
|
||||
{
|
||||
Log_PrintP("Cannot read rodata segment");
|
||||
return 0;
|
||||
}
|
||||
readOffset += hdr.rodataSegSize;
|
||||
|
||||
// Read the data segment
|
||||
u32 dataLoadSegSize = hdr.dataSegSize - hdr.bssSize;
|
||||
if (IFile_Read2(file, d.segPtrs[2], dataLoadSegSize, readOffset) != dataLoadSegSize)
|
||||
{
|
||||
Log_PrintP("Cannot read data segment");
|
||||
return 0;
|
||||
}
|
||||
readOffset += dataLoadSegSize;
|
||||
|
||||
// Relocate the segments
|
||||
for (i = 0; i < 3; i ++)
|
||||
{
|
||||
for (j = 0; j < nRelocTables; j ++)
|
||||
{
|
||||
int nRelocs = extraPage[i*nRelocTables + j];
|
||||
if (j >= (sizeof(_3DSX_RelocHdr)/4))
|
||||
{
|
||||
// Not using this header
|
||||
readOffset += nRelocs;
|
||||
continue;
|
||||
}
|
||||
|
||||
u32* pos = (u32*)d.segPtrs[i];
|
||||
u32* endPos = pos + (d.segSizes[i]/4);
|
||||
SEC_ASSERT(endPos <= segLimit);
|
||||
|
||||
while (nRelocs)
|
||||
{
|
||||
u32 toDo = nRelocs > MAXRELOCS ? MAXRELOCS : nRelocs;
|
||||
nRelocs -= toDo;
|
||||
|
||||
u32 readSize = toDo*sizeof(_3DSX_Reloc);
|
||||
if (IFile_Read2(file, s_relocBuf, readSize, readOffset) != readSize)
|
||||
{
|
||||
Log_PrintP("Cannot read reloc table (%d,%d)", i, j);
|
||||
return 0;
|
||||
}
|
||||
readOffset += readSize;
|
||||
|
||||
for (k = 0; k < toDo && pos < endPos; k ++)
|
||||
{
|
||||
pos += s_relocBuf[k].skip;
|
||||
u32 nPatches = s_relocBuf[k].patch;
|
||||
for (m = 0; m < nPatches && pos < endPos; m ++)
|
||||
{
|
||||
u32 inAddr = baseAddr + 4*(pos - codePages);
|
||||
u32 origData = *pos;
|
||||
u32 subType = origData >> (32-4);
|
||||
u32 addr = TranslateAddr(origData &~ 0xF0000000, &d, offsets);
|
||||
//Log_PrintP("%08lX<-%08lX", inAddr, addr);
|
||||
switch (j)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
if (subType != 0)
|
||||
{
|
||||
Log_PrintP("Unsupported absolute reloc subtype (%lu)", subType);
|
||||
return 0;
|
||||
}
|
||||
*pos = addr;
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
u32 data = addr - inAddr;
|
||||
switch (subType)
|
||||
{
|
||||
case 0: *pos = data; break; // 32-bit signed offset
|
||||
case 1: *pos = data &~ BIT(31); break; // 31-bit signed offset
|
||||
default:
|
||||
Log_PrintP("Unsupported relative reloc subtype (%lu)", subType);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Detect and fill _prm structure
|
||||
PrmStruct* pst = (PrmStruct*) &codePages[1];
|
||||
if (pst->magic == _PRM_MAGIC)
|
||||
{
|
||||
memset(extraPage, 0, 0x1000);
|
||||
memcpy(extraPage, ldrArgvBuf, sizeof(ldrArgvBuf));
|
||||
pst->pSrvOverride = extraPageAddr + 0xFFC;
|
||||
pst->pArgList = extraPageAddr;
|
||||
pst->runFlags |= RUNFLAG_APTCHAINLOAD;
|
||||
s64 dummy;
|
||||
bool isN3DS = svcGetSystemInfo(&dummy, 0x10001, 0) == 0;
|
||||
if (isN3DS)
|
||||
{
|
||||
pst->heapSize = 48*1024*1024;
|
||||
pst->linearHeapSize = 64*1024*1024;
|
||||
} else
|
||||
{
|
||||
pst->heapSize = 24*1024*1024;
|
||||
pst->linearHeapSize = 32*1024*1024;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the codeset
|
||||
CodeSetInfo csinfo;
|
||||
memset(&csinfo, 0, sizeof(csinfo));
|
||||
strncpy((char*)csinfo.name, name, 8);
|
||||
csinfo.program_id = tid;
|
||||
csinfo.text_addr = d.segAddrs[0];
|
||||
csinfo.text_size = d.segSizes[0] >> 12;
|
||||
csinfo.ro_addr = d.segAddrs[1];
|
||||
csinfo.ro_size = d.segSizes[1] >> 12;
|
||||
csinfo.rw_addr = d.segAddrs[2];
|
||||
csinfo.rw_size = (d.segSizes[2] >> 12) + 1; // One extra page reserved for settings/etc
|
||||
csinfo.text_size_total = csinfo.text_size;
|
||||
csinfo.ro_size_total = csinfo.ro_size;
|
||||
csinfo.rw_size_total = csinfo.rw_size;
|
||||
Handle hCodeset = 0;
|
||||
res = svcCreateCodeSet(&hCodeset, &csinfo, d.segPtrs[0], d.segPtrs[1], d.segPtrs[2]);
|
||||
if (res)
|
||||
{
|
||||
Log_PrintP("svcCreateCodeSet: %08lX", res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return hCodeset;
|
||||
}
|
||||
60
sysmodules/rosalina/source/MyThread.c
Normal file
60
sysmodules/rosalina/source/MyThread.c
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 "MyThread.h"
|
||||
#include "memory.h"
|
||||
|
||||
static void _thread_begin(void* arg)
|
||||
{
|
||||
MyThread *t = (MyThread *)arg;
|
||||
t->ep();
|
||||
MyThread_Exit();
|
||||
}
|
||||
|
||||
Result MyThread_Create(MyThread *t, void (*entrypoint)(void), void *stack, u32 stackSize, int prio, int affinity)
|
||||
{
|
||||
t->ep = entrypoint;
|
||||
t->stacktop = (u8 *)stack + stackSize;
|
||||
|
||||
return svcCreateThread(&t->handle, _thread_begin, (u32)t, (u32*)t->stacktop, prio, affinity);
|
||||
}
|
||||
|
||||
Result MyThread_Join(MyThread *thread, s64 timeout_ns)
|
||||
{
|
||||
if (thread == NULL) return 0;
|
||||
Result res = svcWaitSynchronization(thread->handle, timeout_ns);
|
||||
if(R_FAILED(res)) return res;
|
||||
|
||||
svcCloseHandle(thread->handle);
|
||||
thread->handle = (Handle)0;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void MyThread_Exit(void)
|
||||
{
|
||||
svcExitThread();
|
||||
}
|
||||
101
sysmodules/rosalina/source/csvc.s
Normal file
101
sysmodules/rosalina/source/csvc.s
Normal file
@@ -0,0 +1,101 @@
|
||||
@ This paricular file is licensed under the following terms:
|
||||
|
||||
@ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable
|
||||
@ for any damages arising from the use of this software.
|
||||
@
|
||||
@ Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it
|
||||
@ and redistribute it freely, subject to the following restrictions:
|
||||
@
|
||||
@ The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
|
||||
@ If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
||||
@
|
||||
@ Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
@ This notice may not be removed or altered from any source distribution.
|
||||
|
||||
.arm
|
||||
.balign 4
|
||||
|
||||
.macro SVC_BEGIN name
|
||||
.section .text.\name, "ax", %progbits
|
||||
.global \name
|
||||
.type \name, %function
|
||||
.align 2
|
||||
.cfi_startproc
|
||||
\name:
|
||||
.endm
|
||||
|
||||
.macro SVC_END
|
||||
.cfi_endproc
|
||||
.endm
|
||||
|
||||
SVC_BEGIN svcCustomBackdoor
|
||||
svc 0x80
|
||||
bx lr
|
||||
SVC_END
|
||||
|
||||
SVC_BEGIN svcConvertVAToPA
|
||||
svc 0x90
|
||||
bx lr
|
||||
SVC_END
|
||||
|
||||
SVC_BEGIN svcFlushDataCacheRange
|
||||
svc 0x91
|
||||
bx lr
|
||||
SVC_END
|
||||
|
||||
SVC_BEGIN svcFlushEntireDataCache
|
||||
svc 0x92
|
||||
bx lr
|
||||
SVC_END
|
||||
|
||||
SVC_BEGIN svcInvalidateInstructionCacheRange
|
||||
svc 0x93
|
||||
bx lr
|
||||
SVC_END
|
||||
|
||||
SVC_BEGIN svcInvalidateEntireInstructionCache
|
||||
svc 0x94
|
||||
bx lr
|
||||
SVC_END
|
||||
|
||||
SVC_BEGIN svcMapProcessMemoryEx
|
||||
svc 0xA0
|
||||
bx lr
|
||||
SVC_END
|
||||
|
||||
SVC_BEGIN svcUnmapProcessMemoryEx
|
||||
svc 0xA1
|
||||
bx lr
|
||||
SVC_END
|
||||
|
||||
SVC_BEGIN svcControlMemoryEx
|
||||
push {r0, r4, r5}
|
||||
ldr r0, [sp, #0xC]
|
||||
ldr r4, [sp, #0xC+0x4]
|
||||
ldr r5, [sp, #0xC+0x8]
|
||||
svc 0xA2
|
||||
pop {r2, r4, r5}
|
||||
str r1, [r2]
|
||||
bx lr
|
||||
SVC_END
|
||||
|
||||
SVC_BEGIN svcControlService
|
||||
svc 0xB0
|
||||
bx lr
|
||||
SVC_END
|
||||
|
||||
SVC_BEGIN svcCopyHandle
|
||||
str r0, [sp, #-4]!
|
||||
svc 0xB1
|
||||
ldr r2, [sp], #4
|
||||
str r1, [r2]
|
||||
bx lr
|
||||
SVC_END
|
||||
|
||||
SVC_BEGIN svcTranslateHandle
|
||||
str r0, [sp, #-4]!
|
||||
svc 0xB2
|
||||
ldr r2, [sp], #4
|
||||
str r1, [r2]
|
||||
bx lr
|
||||
SVC_END
|
||||
277
sysmodules/rosalina/source/draw.c
Normal file
277
sysmodules/rosalina/source/draw.c
Normal file
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* 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 <3ds.h>
|
||||
#include <stdarg.h>
|
||||
#include "fmt.h"
|
||||
#include "draw.h"
|
||||
#include "font.h"
|
||||
#include "memory.h"
|
||||
#include "menu.h"
|
||||
#include "utils.h"
|
||||
|
||||
u8 framebuffer_cache[FB_BOTTOM_SIZE];
|
||||
|
||||
static u32 gpuSavedFramebufferAddr1, gpuSavedFramebufferSelect, gpuSavedFramebufferFormat, gpuSavedFramebufferStride;
|
||||
|
||||
static RecursiveLock lock;
|
||||
|
||||
void Draw_Lock(void)
|
||||
{
|
||||
static bool lockInitialized = false;
|
||||
if(!lockInitialized)
|
||||
{
|
||||
RecursiveLock_Init(&lock);
|
||||
lockInitialized = true;
|
||||
}
|
||||
|
||||
RecursiveLock_Lock(&lock);
|
||||
}
|
||||
|
||||
void Draw_Unlock(void)
|
||||
{
|
||||
RecursiveLock_Unlock(&lock);
|
||||
}
|
||||
|
||||
void Draw_DrawCharacter(u32 posX, u32 posY, u32 color, char character)
|
||||
{
|
||||
volatile u16 *const fb = (volatile u16 *const)FB_BOTTOM_VRAM_ADDR;
|
||||
|
||||
s32 y;
|
||||
for(y = 0; y < 10; y++)
|
||||
{
|
||||
char charPos = font[character * 10 + y];
|
||||
|
||||
s32 x;
|
||||
for(x = 6; x >= 1; x--)
|
||||
{
|
||||
u32 screenPos = (posX * SCREEN_BOT_HEIGHT * 2 + (SCREEN_BOT_HEIGHT - y - posY - 1) * 2) + (5 - x) * 2 * SCREEN_BOT_HEIGHT;
|
||||
u32 pixelColor = ((charPos >> x) & 1) ? color : COLOR_BLACK;
|
||||
fb[screenPos / 2] = pixelColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 Draw_DrawString(u32 posX, u32 posY, u32 color, const char *string)
|
||||
{
|
||||
for(u32 i = 0, line_i = 0; i < ((u32) strlen(string)); i++)
|
||||
{
|
||||
if(string[i] == '\n')
|
||||
{
|
||||
posY += SPACING_Y;
|
||||
line_i = 0;
|
||||
continue;
|
||||
}
|
||||
else if(line_i >= (SCREEN_BOT_WIDTH - posX) / SPACING_X)
|
||||
{
|
||||
// Make sure we never get out of the screen.
|
||||
posY += SPACING_Y;
|
||||
line_i = 0;
|
||||
if(string[i] == ' ')
|
||||
continue; // Spaces at the start look weird
|
||||
}
|
||||
|
||||
Draw_DrawCharacter(posX + line_i * SPACING_X, posY, color, string[i]);
|
||||
line_i++;
|
||||
}
|
||||
|
||||
return posY;
|
||||
}
|
||||
|
||||
u32 Draw_DrawFormattedString(u32 posX, u32 posY, u32 color, const char *fmt, ...)
|
||||
{
|
||||
char buf[DRAW_MAX_FORMATTED_STRING_SIZE + 1];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsprintf(buf, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
return Draw_DrawString(posX, posY, color, buf);
|
||||
}
|
||||
|
||||
void Draw_FillFramebuffer(u32 value)
|
||||
{
|
||||
memset32(FB_BOTTOM_VRAM_ADDR, value, FB_BOTTOM_SIZE);
|
||||
}
|
||||
|
||||
void Draw_ClearFramebuffer(void)
|
||||
{
|
||||
Draw_FillFramebuffer(0);
|
||||
}
|
||||
|
||||
void Draw_SetupFramebuffer(void)
|
||||
{
|
||||
while((GPU_PSC0_CNT | GPU_PSC1_CNT | GPU_TRANSFER_CNT | GPU_CMDLIST_CNT) & 1);
|
||||
|
||||
memcpy(framebuffer_cache, FB_BOTTOM_VRAM_ADDR, FB_BOTTOM_SIZE);
|
||||
gpuSavedFramebufferAddr1 = GPU_FB_BOTTOM_ADDR_1;
|
||||
gpuSavedFramebufferSelect = GPU_FB_BOTTOM_SEL;
|
||||
gpuSavedFramebufferFormat = GPU_FB_BOTTOM_FMT;
|
||||
gpuSavedFramebufferStride = GPU_FB_BOTTOM_STRIDE;
|
||||
|
||||
GPU_FB_BOTTOM_SEL &= ~1;
|
||||
GPU_FB_BOTTOM_ADDR_1 = FB_BOTTOM_VRAM_PA;
|
||||
GPU_FB_BOTTOM_FMT = 0x80302;
|
||||
GPU_FB_BOTTOM_STRIDE = 240 * 2;
|
||||
|
||||
Draw_FlushFramebuffer();
|
||||
}
|
||||
|
||||
void Draw_RestoreFramebuffer(void)
|
||||
{
|
||||
memcpy(FB_BOTTOM_VRAM_ADDR, framebuffer_cache, FB_BOTTOM_SIZE);
|
||||
|
||||
GPU_FB_BOTTOM_ADDR_1 = gpuSavedFramebufferAddr1;
|
||||
GPU_FB_BOTTOM_FMT = gpuSavedFramebufferFormat;
|
||||
GPU_FB_BOTTOM_STRIDE = gpuSavedFramebufferStride;
|
||||
GPU_FB_BOTTOM_SEL = gpuSavedFramebufferSelect;
|
||||
|
||||
Draw_FlushFramebuffer();
|
||||
}
|
||||
|
||||
void Draw_FlushFramebuffer(void)
|
||||
{
|
||||
svcFlushProcessDataCache(CUR_PROCESS_HANDLE, FB_BOTTOM_VRAM_ADDR, FB_BOTTOM_SIZE);
|
||||
}
|
||||
|
||||
u32 Draw_GetCurrentFramebufferAddress(bool top, bool left)
|
||||
{
|
||||
if(GPU_FB_BOTTOM_SEL & 1)
|
||||
{
|
||||
if(left)
|
||||
return top ? GPU_FB_TOP_LEFT_ADDR_2 : GPU_FB_BOTTOM_ADDR_2;
|
||||
else
|
||||
return top ? GPU_FB_TOP_RIGHT_ADDR_2 : GPU_FB_BOTTOM_ADDR_2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(left)
|
||||
return top ? GPU_FB_TOP_LEFT_ADDR_1 : GPU_FB_BOTTOM_ADDR_1;
|
||||
else
|
||||
return top ? GPU_FB_TOP_RIGHT_ADDR_1 : GPU_FB_BOTTOM_ADDR_1;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void Draw_WriteUnaligned(u8 *dst, u32 tmp, u32 size)
|
||||
{
|
||||
memcpy(dst, &tmp, size);
|
||||
}
|
||||
|
||||
void Draw_CreateBitmapHeader(u8 *dst, u32 width, u32 heigth)
|
||||
{
|
||||
static const u8 bmpHeaderTemplate[54] = {
|
||||
0x42, 0x4D, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00,
|
||||
0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x12, 0x0B, 0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
memcpy(dst, bmpHeaderTemplate, 54);
|
||||
Draw_WriteUnaligned(dst + 2, 54 + 3 * width * heigth, 4);
|
||||
Draw_WriteUnaligned(dst + 0x12, width, 4);
|
||||
Draw_WriteUnaligned(dst + 0x16, heigth, 4);
|
||||
Draw_WriteUnaligned(dst + 0x22, 3 * width * heigth, 4);
|
||||
}
|
||||
|
||||
static inline void Draw_ConvertPixelToBGR8(u8 *dst, const u8 *src, GSPGPU_FramebufferFormats srcFormat)
|
||||
{
|
||||
u8 red, green, blue;
|
||||
switch(srcFormat)
|
||||
{
|
||||
case GSP_RGBA8_OES:
|
||||
{
|
||||
u32 px = *(u32 *)src;
|
||||
dst[0] = (px >> 8) & 0xFF;
|
||||
dst[1] = (px >> 16) & 0xFF;
|
||||
dst[2] = (px >> 24) & 0xFF;
|
||||
break;
|
||||
}
|
||||
case GSP_BGR8_OES:
|
||||
{
|
||||
dst[2] = src[2];
|
||||
dst[1] = src[1];
|
||||
dst[0] = src[0];
|
||||
break;
|
||||
}
|
||||
case GSP_RGB565_OES:
|
||||
{
|
||||
// thanks neobrain
|
||||
u16 px = *(u16 *)src;
|
||||
blue = px & 0x1F;
|
||||
green = (px >> 5) & 0x3F;
|
||||
red = (px >> 11) & 0x1F;
|
||||
|
||||
dst[0] = (blue << 3) | (blue >> 2);
|
||||
dst[1] = (green << 2) | (green >> 4);
|
||||
dst[2] = (red << 3) | (red >> 2);
|
||||
|
||||
break;
|
||||
}
|
||||
case GSP_RGB5_A1_OES:
|
||||
{
|
||||
u16 px = *(u16 *)src;
|
||||
blue = px & 0x1F;
|
||||
green = (px >> 5) & 0x1F;
|
||||
red = (px >> 10) & 0x1F;
|
||||
|
||||
dst[0] = (blue << 3) | (blue >> 2);
|
||||
dst[1] = (green << 3) | (green >> 2);
|
||||
dst[2] = (red << 3) | (red >> 2);
|
||||
|
||||
break;
|
||||
}
|
||||
case GSP_RGBA4_OES:
|
||||
{
|
||||
u16 px = *(u32 *)src;
|
||||
blue = px & 0xF;
|
||||
green = (px >> 4) & 0xF;
|
||||
red = (px >> 8) & 0xF;
|
||||
|
||||
dst[0] = (blue << 4) | (blue >> 4);
|
||||
dst[1] = (green << 4) | (green >> 4);
|
||||
dst[2] = (red << 4) | (red >> 4);
|
||||
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
static u8 line[3 * 400];
|
||||
|
||||
u8 *Draw_ConvertFrameBufferLine(bool top, bool left, u32 y)
|
||||
{
|
||||
GSPGPU_FramebufferFormats fmt = top ? (GSPGPU_FramebufferFormats)(GPU_FB_TOP_FMT & 7) : (GSPGPU_FramebufferFormats)(GPU_FB_BOTTOM_FMT & 7);
|
||||
u32 width = top ? 400 : 320;
|
||||
u8 formatSizes[] = { 4, 3, 2, 2, 2 };
|
||||
|
||||
u32 pa = Draw_GetCurrentFramebufferAddress(top, left);
|
||||
u8 *addr = (u8 *)PA_PTR(pa);
|
||||
|
||||
for(u32 x = 0; x < width; x++)
|
||||
Draw_ConvertPixelToBGR8(line + x * 3 , addr + (x * 240 + y) * formatSizes[(u8)fmt], fmt);
|
||||
return line;
|
||||
}
|
||||
283
sysmodules/rosalina/source/errdisp.c
Normal file
283
sysmodules/rosalina/source/errdisp.c
Normal file
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* 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 <3ds.h>
|
||||
#include "errdisp.h"
|
||||
#include "draw.h"
|
||||
#include "menu.h"
|
||||
#include "memory.h"
|
||||
|
||||
static inline void assertSuccess(Result res)
|
||||
{
|
||||
if(R_FAILED(res))
|
||||
svcBreak(USERBREAK_PANIC);
|
||||
}
|
||||
|
||||
static MyThread errDispThread;
|
||||
static u8 ALIGN(8) errDispThreadStack[THREAD_STACK_SIZE];
|
||||
|
||||
static char userString[0x100 + 1] = {0};
|
||||
|
||||
MyThread *errDispCreateThread(void)
|
||||
{
|
||||
if(R_FAILED(MyThread_Create(&errDispThread, errDispThreadMain, errDispThreadStack, THREAD_STACK_SIZE, 0x18, CORE_SYSTEM)))
|
||||
svcBreak(USERBREAK_PANIC);
|
||||
return &errDispThread;
|
||||
}
|
||||
|
||||
static inline u32 ERRF_DisplayRegisterValue(u32 posX, u32 posY, const char *name, u32 value)
|
||||
{
|
||||
return Draw_DrawFormattedString(posX, posY, COLOR_WHITE, "%-9s %08x", name, value);
|
||||
}
|
||||
|
||||
void ERRF_DisplayError(ERRF_FatalErrInfo *info)
|
||||
{
|
||||
Draw_Lock();
|
||||
|
||||
u32 posY = Draw_DrawString(10, 10, COLOR_RED, userString[0] == 0 ? "An error occurred (ErrDisp)" : userString);
|
||||
|
||||
static const char *types[] = {
|
||||
"generic", "corrupted", "card removed", "exception", "result failure", "logged", "invalid"
|
||||
};
|
||||
|
||||
static const char *exceptionTypes[] = {
|
||||
"prefetch abort", "data abort", "undefined instruction", "VFP", "invalid"
|
||||
};
|
||||
|
||||
const char *type = (u32)info->type > (u32)ERRF_ERRTYPE_LOGGED ? types[6] : types[(u32)info->type];
|
||||
posY = posY < 30 ? 30 : posY;
|
||||
|
||||
if(info->type == ERRF_ERRTYPE_EXCEPTION)
|
||||
{
|
||||
const char *exceptionType = (u32)info->data.exception_data.excep.type > (u32)ERRF_EXCEPTION_VFP ?
|
||||
exceptionTypes[4] : exceptionTypes[(u32)info->data.exception_data.excep.type];
|
||||
|
||||
Draw_DrawFormattedString(10, posY, COLOR_WHITE, "Error type: exception (%s)", exceptionType);
|
||||
}
|
||||
else
|
||||
Draw_DrawFormattedString(10, posY, COLOR_WHITE, "Error type: %s", type);
|
||||
|
||||
if(info->type != ERRF_ERRTYPE_CARD_REMOVED)
|
||||
{
|
||||
Handle processHandle;
|
||||
Result res;
|
||||
|
||||
posY += SPACING_Y;
|
||||
posY = Draw_DrawFormattedString(10, posY + SPACING_Y, COLOR_WHITE, "Process ID: %u", info->procId);
|
||||
|
||||
res = svcOpenProcess(&processHandle, info->procId);
|
||||
if(R_SUCCEEDED(res))
|
||||
{
|
||||
u64 titleId;
|
||||
char name[9] = { 0 };
|
||||
svcGetProcessInfo((s64 *)name, processHandle, 0x10000);
|
||||
svcGetProcessInfo((s64 *)&titleId, processHandle, 0x10001);
|
||||
svcCloseHandle(processHandle);
|
||||
posY = Draw_DrawFormattedString(10, posY + SPACING_Y, COLOR_WHITE, "Process name: %s", name);
|
||||
posY = Draw_DrawFormattedString(10, posY + SPACING_Y, COLOR_WHITE, "Process title ID: 0x%016llx", titleId);
|
||||
}
|
||||
|
||||
posY += SPACING_Y;
|
||||
}
|
||||
|
||||
if(info->type == ERRF_ERRTYPE_EXCEPTION)
|
||||
{
|
||||
static const char *registerNames[] = {
|
||||
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12",
|
||||
"sp", "lr", "pc", "cpsr"
|
||||
};
|
||||
|
||||
u32 *regs = (u32 *)(&info->data.exception_data.regs);
|
||||
for(u32 i = 0; i < 17; i += 2)
|
||||
{
|
||||
posY = ERRF_DisplayRegisterValue(10, posY + SPACING_Y, registerNames[i], regs[i]);
|
||||
|
||||
if(i != 16)
|
||||
ERRF_DisplayRegisterValue(10 + 28 * SPACING_X, posY, registerNames[i + 1], i == 16 ? regs[20] : regs[i + 1]);
|
||||
}
|
||||
|
||||
if(info->data.exception_data.excep.type == ERRF_EXCEPTION_PREFETCH_ABORT
|
||||
|| info->data.exception_data.excep.type == ERRF_EXCEPTION_DATA_ABORT)
|
||||
{
|
||||
ERRF_DisplayRegisterValue(10 + 28 * SPACING_X, posY, "far", info->data.exception_data.excep.far);
|
||||
posY = ERRF_DisplayRegisterValue(10, posY + SPACING_Y, "fsr", info->data.exception_data.excep.fsr);
|
||||
}
|
||||
|
||||
else if(info->data.exception_data.excep.type == ERRF_EXCEPTION_VFP)
|
||||
{
|
||||
ERRF_DisplayRegisterValue(10 + 28 * SPACING_X, posY, "fpexc", info->data.exception_data.excep.fpexc);
|
||||
posY = ERRF_DisplayRegisterValue(10, posY + SPACING_Y, "fpinst", info->data.exception_data.excep.fpinst);
|
||||
ERRF_DisplayRegisterValue(10 + 28 * SPACING_X, posY, "fpinst2", info->data.exception_data.excep.fpinst2);
|
||||
}
|
||||
}
|
||||
|
||||
else if(info->type != ERRF_ERRTYPE_CARD_REMOVED)
|
||||
{
|
||||
if(info->type != ERRF_ERRTYPE_FAILURE)
|
||||
posY = Draw_DrawFormattedString(10, posY + SPACING_Y, COLOR_WHITE, "Address: 0x%08x", info->pcAddr);
|
||||
|
||||
posY = Draw_DrawFormattedString(10, posY + SPACING_Y, COLOR_WHITE, "Error code: 0x%08x", info->resCode);
|
||||
}
|
||||
|
||||
const char *desc;
|
||||
switch(info->type)
|
||||
{
|
||||
case ERRF_ERRTYPE_CARD_REMOVED:
|
||||
desc = "The Game Card was removed.";
|
||||
break;
|
||||
case ERRF_ERRTYPE_MEM_CORRUPT:
|
||||
desc = "The System Memory has been damaged.";
|
||||
break;
|
||||
case ERRF_ERRTYPE_FAILURE:
|
||||
info->data.failure_mesg[0x60] = 0; // make sure the last byte in the IPC buffer is NULL
|
||||
desc = info->data.failure_mesg;
|
||||
break;
|
||||
default:
|
||||
desc = "";
|
||||
break;
|
||||
}
|
||||
|
||||
posY += SPACING_Y;
|
||||
if(desc[0] != 0)
|
||||
posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, desc) + SPACING_Y;
|
||||
posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, "Press any button to reboot");
|
||||
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
}
|
||||
|
||||
void ERRF_HandleCommands(void)
|
||||
{
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
|
||||
switch(cmdbuf[0] >> 16)
|
||||
{
|
||||
case 1: // Throw
|
||||
{
|
||||
ERRF_FatalErrInfo *info = (ERRF_FatalErrInfo *)(cmdbuf + 1);
|
||||
menuEnter();
|
||||
|
||||
Draw_Lock();
|
||||
Draw_ClearFramebuffer();
|
||||
Draw_FlushFramebuffer();
|
||||
|
||||
ERRF_DisplayError(info);
|
||||
|
||||
/*
|
||||
If we ever wanted to return:
|
||||
draw_unlock();
|
||||
menuLeave();
|
||||
|
||||
cmdbuf[0] = 0x10040;
|
||||
cmdbuf[1] = 0;
|
||||
|
||||
but we don't
|
||||
*/
|
||||
waitInput();
|
||||
svcKernelSetState(7);
|
||||
__builtin_unreachable();
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: // SetUserString
|
||||
{
|
||||
if(cmdbuf[0] != 0x20042 || (cmdbuf[2] & 0x3C0F) != 2)
|
||||
{
|
||||
cmdbuf[0] = 0x40;
|
||||
cmdbuf[1] = 0xD9001830;
|
||||
}
|
||||
else
|
||||
{
|
||||
cmdbuf[0] = 0x20040;
|
||||
u32 sz = cmdbuf[1] <= 0x100 ? sz : 0x100;
|
||||
memcpy(userString, cmdbuf + 3, sz);
|
||||
userString[sz] = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void errDispThreadMain(void)
|
||||
{
|
||||
Handle handles[2];
|
||||
Handle serverHandle, clientHandle, sessionHandle = 0;
|
||||
|
||||
u32 replyTarget = 0;
|
||||
s32 index;
|
||||
|
||||
Result res;
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
|
||||
assertSuccess(svcCreatePort(&serverHandle, &clientHandle, "err:f", 1));
|
||||
|
||||
do
|
||||
{
|
||||
handles[0] = serverHandle;
|
||||
handles[1] = sessionHandle;
|
||||
|
||||
if(replyTarget == 0) // k11
|
||||
cmdbuf[0] = 0xFFFF0000;
|
||||
res = svcReplyAndReceive(&index, handles, sessionHandle == 0 ? 1 : 2, replyTarget);
|
||||
|
||||
if(R_FAILED(res))
|
||||
{
|
||||
if((u32)res == 0xC920181A) // session closed by remote
|
||||
{
|
||||
svcCloseHandle(sessionHandle);
|
||||
sessionHandle = 0;
|
||||
replyTarget = 0;
|
||||
}
|
||||
|
||||
else
|
||||
svcBreak(USERBREAK_PANIC);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if(index == 0)
|
||||
{
|
||||
Handle session;
|
||||
assertSuccess(svcAcceptSession(&session, serverHandle));
|
||||
|
||||
if(sessionHandle == 0)
|
||||
sessionHandle = session;
|
||||
else
|
||||
svcCloseHandle(session);
|
||||
}
|
||||
else
|
||||
{
|
||||
ERRF_HandleCommands();
|
||||
replyTarget = sessionHandle;
|
||||
}
|
||||
}
|
||||
}
|
||||
while(!terminationRequest);
|
||||
|
||||
svcCloseHandle(sessionHandle);
|
||||
svcCloseHandle(clientHandle);
|
||||
svcCloseHandle(serverHandle);
|
||||
}
|
||||
315
sysmodules/rosalina/source/fmt.c
Normal file
315
sysmodules/rosalina/source/fmt.c
Normal file
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* File : barebones/ee_printf.c
|
||||
This file contains an implementation of ee_printf that only requires a method to output a char to a UART without pulling in library code.
|
||||
This code is based on a file that contains the following:
|
||||
Copyright (C) 2002 Michael Ringgaard. All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the project nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
//TuxSH's changes: add support for 64-bit numbers, remove floating-point code
|
||||
|
||||
#include <3ds/types.h>
|
||||
#include "memory.h"
|
||||
#include "fmt.h"
|
||||
|
||||
#define ZEROPAD (1<<0) //Pad with zero
|
||||
#define SIGN (1<<1) //Unsigned/signed long
|
||||
#define PLUS (1<<2) //Show plus
|
||||
#define SPACE (1<<3) //Spacer
|
||||
#define LEFT (1<<4) //Left justified
|
||||
#define HEX_PREP (1<<5) //0x
|
||||
#define UPPERCASE (1<<6) //'ABCDEF'
|
||||
|
||||
#define IS_DIGIT(c) ((c) >= '0' && (c) <= '9')
|
||||
|
||||
static s32 skipAtoi(const char **s)
|
||||
{
|
||||
s32 i = 0;
|
||||
|
||||
while(IS_DIGIT(**s)) i = i * 10 + *((*s)++) - '0';
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static char *processNumber(char *str, s64 num, bool isHex, s32 size, s32 precision, u32 type)
|
||||
{
|
||||
char sign = 0;
|
||||
|
||||
if(type & SIGN)
|
||||
{
|
||||
if(num < 0)
|
||||
{
|
||||
sign = '-';
|
||||
num = -num;
|
||||
size--;
|
||||
}
|
||||
else if(type & PLUS)
|
||||
{
|
||||
sign = '+';
|
||||
size--;
|
||||
}
|
||||
else if(type & SPACE)
|
||||
{
|
||||
sign = ' ';
|
||||
size--;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *lowerDigits = "0123456789abcdef",
|
||||
*upperDigits = "0123456789ABCDEF";
|
||||
|
||||
s32 i = 0;
|
||||
char tmp[20];
|
||||
const char *dig = (type & UPPERCASE) ? upperDigits : lowerDigits;
|
||||
|
||||
if(num == 0)
|
||||
{
|
||||
if(precision != 0) tmp[i++] = '0';
|
||||
type &= ~HEX_PREP;
|
||||
}
|
||||
else
|
||||
{
|
||||
while(num != 0)
|
||||
{
|
||||
u64 base = isHex ? 16ULL : 10ULL;
|
||||
tmp[i++] = dig[(u64)num % base];
|
||||
num = (s64)((u64)num / base);
|
||||
}
|
||||
}
|
||||
|
||||
if(type & LEFT || precision != -1) type &= ~ZEROPAD;
|
||||
if(type & HEX_PREP && isHex) size -= 2;
|
||||
if(i > precision) precision = i;
|
||||
size -= precision;
|
||||
if(!(type & (ZEROPAD | LEFT))) while(size-- > 0) *str++ = ' ';
|
||||
if(sign) *str++ = sign;
|
||||
|
||||
if(type & HEX_PREP && isHex)
|
||||
{
|
||||
*str++ = '0';
|
||||
*str++ = 'x';
|
||||
}
|
||||
|
||||
if(type & ZEROPAD) while(size-- > 0) *str++ = '0';
|
||||
while(i < precision--) *str++ = '0';
|
||||
while(i-- > 0) *str++ = tmp[i];
|
||||
while(size-- > 0) *str++ = ' ';
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
int vsprintf(char *buf, const char *fmt, va_list args)
|
||||
{
|
||||
char *str;
|
||||
|
||||
for(str = buf; *fmt; fmt++)
|
||||
{
|
||||
if(*fmt != '%')
|
||||
{
|
||||
*str++ = *fmt;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Process flags
|
||||
u32 flags = 0; //Flags to number()
|
||||
bool loop = true;
|
||||
|
||||
while(loop)
|
||||
{
|
||||
switch(*++fmt)
|
||||
{
|
||||
case '-': flags |= LEFT; break;
|
||||
case '+': flags |= PLUS; break;
|
||||
case ' ': flags |= SPACE; break;
|
||||
case '#': flags |= HEX_PREP; break;
|
||||
case '0': flags |= ZEROPAD; break;
|
||||
default: loop = false; break;
|
||||
}
|
||||
}
|
||||
|
||||
//Get field width
|
||||
s32 fieldWidth = -1; //Width of output field
|
||||
if(IS_DIGIT(*fmt)) fieldWidth = skipAtoi(&fmt);
|
||||
else if(*fmt == '*')
|
||||
{
|
||||
fmt++;
|
||||
|
||||
fieldWidth = va_arg(args, s32);
|
||||
|
||||
if(fieldWidth < 0)
|
||||
{
|
||||
fieldWidth = -fieldWidth;
|
||||
flags |= LEFT;
|
||||
}
|
||||
}
|
||||
|
||||
//Get the precision
|
||||
s32 precision = -1; //Min. # of digits for integers; max number of chars for from string
|
||||
if(*fmt == '.')
|
||||
{
|
||||
fmt++;
|
||||
|
||||
if(IS_DIGIT(*fmt)) precision = skipAtoi(&fmt);
|
||||
else if(*fmt == '*')
|
||||
{
|
||||
fmt++;
|
||||
precision = va_arg(args, s32);
|
||||
}
|
||||
|
||||
if(precision < 0) precision = 0;
|
||||
}
|
||||
|
||||
//Get the conversion qualifier
|
||||
u32 integerType = 0;
|
||||
if(*fmt == 'l')
|
||||
{
|
||||
if(*++fmt == 'l')
|
||||
{
|
||||
fmt++;
|
||||
integerType = 1;
|
||||
}
|
||||
|
||||
}
|
||||
else if(*fmt == 'h')
|
||||
{
|
||||
if(*++fmt == 'h')
|
||||
{
|
||||
fmt++;
|
||||
integerType = 3;
|
||||
}
|
||||
else integerType = 2;
|
||||
}
|
||||
|
||||
bool isHex;
|
||||
|
||||
switch(*fmt)
|
||||
{
|
||||
case 'c':
|
||||
if(!(flags & LEFT)) while(--fieldWidth > 0) *str++ = ' ';
|
||||
*str++ = (u8)va_arg(args, s32);
|
||||
while(--fieldWidth > 0) *str++ = ' ';
|
||||
continue;
|
||||
|
||||
case 's':
|
||||
{
|
||||
char *s = va_arg(args, char *);
|
||||
if(!s) s = "<NULL>";
|
||||
u32 len = (precision != -1) ? strnlen(s, precision) : strlen(s);
|
||||
if(!(flags & LEFT)) while((s32)len < fieldWidth--) *str++ = ' ';
|
||||
for(u32 i = 0; i < len; i++) *str++ = *s++;
|
||||
while((s32)len < fieldWidth--) *str++ = ' ';
|
||||
continue;
|
||||
}
|
||||
|
||||
case 'p':
|
||||
if(fieldWidth == -1)
|
||||
{
|
||||
fieldWidth = 8;
|
||||
flags |= ZEROPAD;
|
||||
}
|
||||
str = processNumber(str, va_arg(args, u32), true, fieldWidth, precision, flags);
|
||||
continue;
|
||||
|
||||
//Integer number formats - set up the flags and "break"
|
||||
case 'X':
|
||||
flags |= UPPERCASE;
|
||||
|
||||
case 'x':
|
||||
isHex = true;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
case 'i':
|
||||
flags |= SIGN;
|
||||
|
||||
case 'u':
|
||||
isHex = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
if(*fmt != '%') *str++ = '%';
|
||||
if(*fmt) *str++ = *fmt;
|
||||
else fmt--;
|
||||
continue;
|
||||
}
|
||||
|
||||
s64 num;
|
||||
|
||||
if(flags & SIGN)
|
||||
{
|
||||
if(integerType == 1) num = va_arg(args, s64);
|
||||
else num = va_arg(args, s32);
|
||||
|
||||
if(integerType == 2) num = (s16)num;
|
||||
else if(integerType == 3) num = (s8)num;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(integerType == 1) num = va_arg(args, u64);
|
||||
else num = va_arg(args, u32);
|
||||
|
||||
if(integerType == 2) num = (u16)num;
|
||||
else if(integerType == 3) num = (u8)num;
|
||||
}
|
||||
|
||||
str = processNumber(str, num, isHex, fieldWidth, precision, flags);
|
||||
}
|
||||
|
||||
*str = 0;
|
||||
return str - buf;
|
||||
}
|
||||
|
||||
int sprintf(char *buf, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int res = vsprintf(buf, fmt, args);
|
||||
va_end(args);
|
||||
return res;
|
||||
}
|
||||
179
sysmodules/rosalina/source/fsreg.c
Normal file
179
sysmodules/rosalina/source/fsreg.c
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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 <3ds.h>
|
||||
#include <string.h>
|
||||
#include "fsreg.h"
|
||||
#include "services.h"
|
||||
#include "csvc.h"
|
||||
|
||||
static Handle fsregHandle;
|
||||
static int fsregRefCount;
|
||||
|
||||
Result fsregInit(void)
|
||||
{
|
||||
Result ret = 0;
|
||||
|
||||
if(AtomicPostIncrement(&fsregRefCount))
|
||||
return 0;
|
||||
|
||||
ret = svcControlService(SERVICEOP_STEAL_CLIENT_SESSION, &fsregHandle, "fs:REG");
|
||||
while(ret == 0x9401BFE)
|
||||
{
|
||||
svcSleepThread(500 * 1000LL);
|
||||
ret = svcControlService(SERVICEOP_STEAL_CLIENT_SESSION, &fsregHandle, "fs:REG");
|
||||
}
|
||||
|
||||
if(R_FAILED(ret))
|
||||
AtomicDecrement(&fsregRefCount);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void fsregExit(void)
|
||||
{
|
||||
if(AtomicDecrement(&fsregRefCount) || !fsregHandle)
|
||||
return;
|
||||
svcCloseHandle(fsregHandle);
|
||||
}
|
||||
|
||||
Handle fsregGetHandle(void)
|
||||
{
|
||||
return fsregHandle;
|
||||
}
|
||||
|
||||
Result fsregSetupPermissions(void)
|
||||
{
|
||||
u32 pid;
|
||||
Result res;
|
||||
FS_ProgramInfo info;
|
||||
u32 storage[8] = {0};
|
||||
|
||||
storage[6] = 0x800 | 0x400 | 0x80 | 0x1; // SDMC access and NAND access flags
|
||||
info.programId = 0x0004013000006902LL; // Rosalina TID
|
||||
info.mediaType = MEDIATYPE_NAND;
|
||||
|
||||
if(R_SUCCEEDED(res = svcGetProcessId(&pid, 0xFFFF8001))) // 0xFFFF8001 is an handle to the active process
|
||||
res = FSREG_Register(pid, 0xFFFF000000000000LL, &info, (u8*)storage);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Result FSREG_CheckHostLoadId(u64 prog_handle)
|
||||
{
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(0x406,2,0); // 0x4060080
|
||||
cmdbuf[1] = (u32) (prog_handle);
|
||||
cmdbuf[2] = (u32) (prog_handle >> 32);
|
||||
|
||||
Result ret = 0;
|
||||
if(R_FAILED(ret = svcSendSyncRequest(fsregHandle)))
|
||||
return ret;
|
||||
|
||||
return cmdbuf[1];
|
||||
}
|
||||
|
||||
Result FSREG_LoadProgram(u64 *prog_handle, FS_ProgramInfo *title)
|
||||
{
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(0x404,4,0); // 0x4040100
|
||||
memcpy(&cmdbuf[1], &title->programId, sizeof(u64));
|
||||
*(u8 *)&cmdbuf[3] = title->mediaType;
|
||||
memcpy(((u8 *)&cmdbuf[3])+1, &title->padding, 7);
|
||||
|
||||
Result ret = 0;
|
||||
if(R_FAILED(ret = svcSendSyncRequest(fsregHandle)))
|
||||
return ret;
|
||||
*prog_handle = *(u64 *)&cmdbuf[2];
|
||||
|
||||
return cmdbuf[1];
|
||||
}
|
||||
|
||||
Result FSREG_GetProgramInfo(exheader_header *exheader, u32 entry_count, u64 prog_handle)
|
||||
{
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(0x403,3,0); // 0x40300C0
|
||||
cmdbuf[1] = entry_count;
|
||||
*(u64 *)&cmdbuf[2] = prog_handle;
|
||||
cmdbuf[64] = ((entry_count << 10) << 14) | 2;
|
||||
cmdbuf[65] = (u32) exheader;
|
||||
|
||||
Result ret = 0;
|
||||
if(R_FAILED(ret = svcSendSyncRequest(fsregHandle)))
|
||||
return ret;
|
||||
|
||||
return cmdbuf[1];
|
||||
}
|
||||
|
||||
Result FSREG_UnloadProgram(u64 prog_handle)
|
||||
{
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(0x405,2,0); // 0x4050080
|
||||
cmdbuf[1] = (u32) (prog_handle);
|
||||
cmdbuf[2] = (u32) (prog_handle >> 32);
|
||||
|
||||
Result ret = 0;
|
||||
if(R_FAILED(ret = svcSendSyncRequest(fsregHandle)))
|
||||
return ret;
|
||||
|
||||
return cmdbuf[1];
|
||||
}
|
||||
|
||||
Result FSREG_Unregister(u32 pid)
|
||||
{
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(0x402,1,0); // 0x4020040
|
||||
cmdbuf[1] = pid;
|
||||
|
||||
Result ret = 0;
|
||||
if(R_FAILED(ret = svcSendSyncRequest(fsregHandle)))
|
||||
return ret;
|
||||
|
||||
return cmdbuf[1];
|
||||
}
|
||||
|
||||
Result FSREG_Register(u32 pid, u64 prog_handle, FS_ProgramInfo *info, void *storageinfo)
|
||||
{
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(0x401,0xf,0); // 0x40103C0
|
||||
cmdbuf[1] = pid;
|
||||
*(u64 *)&cmdbuf[2] = prog_handle;
|
||||
memcpy(&cmdbuf[4], &info->programId, sizeof(u64));
|
||||
*(u8 *)&cmdbuf[6] = info->mediaType;
|
||||
memcpy(((u8 *)&cmdbuf[6])+1, &info->padding, 7);
|
||||
memcpy((u8 *)&cmdbuf[8], storageinfo, 32);
|
||||
|
||||
Result ret = 0;
|
||||
if(R_FAILED(ret = svcSendSyncRequest(fsregHandle)))
|
||||
return ret;
|
||||
|
||||
return cmdbuf[1];
|
||||
}
|
||||
60
sysmodules/rosalina/source/gdb.c
Normal file
60
sysmodules/rosalina/source/gdb.c
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
void GDB_InitializeContext(GDBContext *ctx)
|
||||
{
|
||||
RecursiveLock_Init(&ctx->lock);
|
||||
|
||||
RecursiveLock_Lock(&ctx->lock);
|
||||
|
||||
svcCreateEvent(&ctx->continuedEvent, RESET_ONESHOT);
|
||||
svcCreateEvent(&ctx->clientAcceptedEvent, RESET_STICKY);
|
||||
|
||||
ctx->eventToWaitFor = ctx->clientAcceptedEvent;
|
||||
ctx->continueFlags = (DebugFlags)(DBG_SIGNAL_FAULT_EXCEPTION_EVENTS | DBG_INHIBIT_USER_CPU_EXCEPTION_HANDLERS);
|
||||
|
||||
RecursiveLock_Unlock(&ctx->lock);
|
||||
}
|
||||
|
||||
void GDB_FinalizeContext(GDBContext *ctx)
|
||||
{
|
||||
RecursiveLock_Lock(&ctx->lock);
|
||||
|
||||
svcClearEvent(ctx->clientAcceptedEvent);
|
||||
|
||||
svcCloseHandle(ctx->clientAcceptedEvent);
|
||||
svcCloseHandle(ctx->continuedEvent);
|
||||
|
||||
RecursiveLock_Unlock(&ctx->lock);
|
||||
}
|
||||
|
||||
GDB_DECLARE_HANDLER(Unsupported)
|
||||
{
|
||||
return GDB_ReplyEmpty(ctx);
|
||||
}
|
||||
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>
|
||||
396
sysmodules/rosalina/source/hbloader.c
Normal file
396
sysmodules/rosalina/source/hbloader.c
Normal file
@@ -0,0 +1,396 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* This file was entirely written by fincs */
|
||||
|
||||
#include <3ds.h>
|
||||
#include "hbloader.h"
|
||||
#include "3dsx.h"
|
||||
#include "menu.h"
|
||||
#include "csvc.h"
|
||||
#include "memory.h"
|
||||
#include "exheader.h"
|
||||
|
||||
#define MAP_BASE 0x10000000
|
||||
|
||||
static const char serviceList[32][8] =
|
||||
{
|
||||
"APT:U",
|
||||
"ac:u",
|
||||
"am:net",
|
||||
"boss:P",
|
||||
"cam:u",
|
||||
"cfg:nor",
|
||||
"cfg:u",
|
||||
"csnd:SND",
|
||||
"dsp::DSP",
|
||||
"fs:USER",
|
||||
"gsp::Lcd",
|
||||
"gsp::Gpu",
|
||||
"hid:USER",
|
||||
"http:C",
|
||||
"ir:USER",
|
||||
"ir:rst",
|
||||
"ir:u",
|
||||
"ldr:ro",
|
||||
"mic:u",
|
||||
"ndm:u",
|
||||
"news:s",
|
||||
"nim:s",
|
||||
"ns:s",
|
||||
"nwm::UDS",
|
||||
"nwm::EXT",
|
||||
"ptm:u",
|
||||
"ptm:sysm",
|
||||
"pxi:dev",
|
||||
"qtm:u",
|
||||
"soc:U",
|
||||
"ssl:C",
|
||||
"y2r:u",
|
||||
};
|
||||
|
||||
static const u64 dependencyList[] =
|
||||
{
|
||||
0x0004013000002402LL, //ac
|
||||
0x0004013000001502LL, //am
|
||||
0x0004013000003402LL, //boss
|
||||
0x0004013000001602LL, //camera
|
||||
0x0004013000001702LL, //cfg
|
||||
0x0004013000001802LL, //codec
|
||||
0x0004013000002702LL, //csnd
|
||||
0x0004013000002802LL, //dlp
|
||||
0x0004013000001A02LL, //dsp
|
||||
0x0004013000001B02LL, //gpio
|
||||
0x0004013000001C02LL, //gsp
|
||||
0x0004013000001D02LL, //hid
|
||||
0x0004013000002902LL, //http
|
||||
0x0004013000001E02LL, //i2c
|
||||
0x0004013000003302LL, //ir
|
||||
0x0004013000001F02LL, //mcu
|
||||
0x0004013000002002LL, //mic
|
||||
0x0004013000002B02LL, //ndm
|
||||
0x0004013000003502LL, //news
|
||||
0x0004013000002C02LL, //nim
|
||||
0x0004013000002D02LL, //nwm
|
||||
0x0004013000002102LL, //pdn
|
||||
0x0004013000003102LL, //ps
|
||||
0x0004013000002202LL, //ptm
|
||||
0x0004013000003702LL, //ro
|
||||
0x0004013000002E02LL, //socket
|
||||
0x0004013000002302LL, //spi
|
||||
0x0004013000002F02LL, //ssl
|
||||
};
|
||||
|
||||
static const u32 kernelCaps[] =
|
||||
{
|
||||
0xFC00022C, // Kernel release version: 8.0 (necessary for using the new linear mapping)
|
||||
0xFF81FF50, // RW static mapping: 0x1FF50000
|
||||
0xFF81FF58, // RW static mapping: 0x1FF58000
|
||||
0xFF81FF70, // RW static mapping: 0x1FF70000
|
||||
0xFF81FF78, // RW static mapping: 0x1FF78000
|
||||
0xFF91F000, // RO static mapping: 0x1F000000
|
||||
0xFF91F600, // RO static mapping: 0x1F600000
|
||||
0xFF002101, // Exflags: APPLICATION memtype + "Allow debug" + "Access core2"
|
||||
0xFE000200, // Handle table size: 0x200
|
||||
};
|
||||
|
||||
static inline void assertSuccess(Result res)
|
||||
{
|
||||
if(R_FAILED(res))
|
||||
svcBreak(USERBREAK_PANIC);
|
||||
}
|
||||
|
||||
static MyThread hbldrThread;
|
||||
static u8 ALIGN(8) hbldrThreadStack[THREAD_STACK_SIZE];
|
||||
static u16 hbldrTarget[PATH_MAX+1];
|
||||
|
||||
MyThread *hbldrCreateThread(void)
|
||||
{
|
||||
assertSuccess(MyThread_Create(&hbldrThread, hbldrThreadMain, hbldrThreadStack, THREAD_STACK_SIZE, 0x18, CORE_SYSTEM));
|
||||
return &hbldrThread;
|
||||
}
|
||||
|
||||
static inline void error(u32* cmdbuf, Result rc)
|
||||
{
|
||||
cmdbuf[0] = IPC_MakeHeader(0, 1, 0);
|
||||
cmdbuf[1] = rc;
|
||||
}
|
||||
|
||||
static u16 *u16_strncpy(u16 *dest, const u16 *src, u32 size)
|
||||
{
|
||||
u32 i;
|
||||
for (i = 0; i < size && src[i] != 0; i++)
|
||||
dest[i] = src[i];
|
||||
while (i < size)
|
||||
dest[i++] = 0;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
static void HBLDR_HandleCommands(void)
|
||||
{
|
||||
Result res;
|
||||
IFile file;
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
switch (cmdbuf[0] >> 16)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
if (cmdbuf[0] != IPC_MakeHeader(1, 6, 0))
|
||||
{
|
||||
error(cmdbuf, 0xD9001830);
|
||||
break;
|
||||
}
|
||||
|
||||
u32 baseAddr = cmdbuf[1];
|
||||
u32 flags = cmdbuf[2] & 0xF00;
|
||||
u64 tid = (u64)cmdbuf[3] | ((u64)cmdbuf[4]<<32);
|
||||
char name[8];
|
||||
memcpy(name, &cmdbuf[5], sizeof(name));
|
||||
|
||||
if (hbldrTarget[0] == 0)
|
||||
{
|
||||
u16_strncpy(hbldrTarget, u"/boot.3dsx", PATH_MAX);
|
||||
ldrArgvBuf[0] = 1;
|
||||
strncpy((char*)&ldrArgvBuf[1], "sdmc:/boot.3dsx", sizeof(ldrArgvBuf)-4);
|
||||
}
|
||||
|
||||
res = IFile_Open(&file, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_UTF16, hbldrTarget), FS_OPEN_READ);
|
||||
hbldrTarget[0] = 0;
|
||||
if (R_FAILED(res))
|
||||
{
|
||||
error(cmdbuf, res);
|
||||
break;
|
||||
}
|
||||
|
||||
u32 totalSize = 0;
|
||||
res = Ldr_Get3dsxSize(&totalSize, &file);
|
||||
if (R_FAILED(res))
|
||||
{
|
||||
IFile_Close(&file);
|
||||
error(cmdbuf, res);
|
||||
break;
|
||||
}
|
||||
|
||||
u32 tmp = 0;
|
||||
res = svcControlMemoryEx(&tmp, MAP_BASE, 0, totalSize, MEMOP_ALLOC | flags, MEMPERM_READ | MEMPERM_WRITE, true);
|
||||
if (R_FAILED(res))
|
||||
{
|
||||
IFile_Close(&file);
|
||||
error(cmdbuf, res);
|
||||
break;
|
||||
}
|
||||
|
||||
Handle hCodeset = Ldr_CodesetFrom3dsx(name, (u32*)MAP_BASE, baseAddr, &file, tid);
|
||||
IFile_Close(&file);
|
||||
|
||||
if (!hCodeset)
|
||||
{
|
||||
svcControlMemory(&tmp, MAP_BASE, 0, totalSize, MEMOP_FREE, 0);
|
||||
error(cmdbuf, MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_LDR, RD_NOT_FOUND));
|
||||
break;
|
||||
}
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(1, 1, 2);
|
||||
cmdbuf[1] = 0;
|
||||
cmdbuf[2] = IPC_Desc_MoveHandles(1);
|
||||
cmdbuf[3] = hCodeset;
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
if (cmdbuf[0] != IPC_MakeHeader(2, 0, 2) || (cmdbuf[1] & 0x3FFF) != 0x0002)
|
||||
{
|
||||
error(cmdbuf, 0xD9001830);
|
||||
break;
|
||||
}
|
||||
ssize_t units = utf8_to_utf16(hbldrTarget, (const uint8_t*)cmdbuf[2], PATH_MAX);
|
||||
if (units < 0 || units > PATH_MAX)
|
||||
{
|
||||
hbldrTarget[0] = 0;
|
||||
error(cmdbuf, 0xD9001830);
|
||||
break;
|
||||
}
|
||||
|
||||
hbldrTarget[units] = 0;
|
||||
cmdbuf[0] = IPC_MakeHeader(2, 1, 0);
|
||||
cmdbuf[1] = 0;
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
if (cmdbuf[0] != IPC_MakeHeader(3, 0, 2) || (cmdbuf[1] & 0x3FFF) != (0x2 | (1<<10)))
|
||||
{
|
||||
error(cmdbuf, 0xD9001830);
|
||||
break;
|
||||
}
|
||||
// No need to do anything - the kernel already copied the data to our buffer
|
||||
cmdbuf[0] = IPC_MakeHeader(3, 1, 0);
|
||||
cmdbuf[1] = 0;
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
if (cmdbuf[0] != IPC_MakeHeader(4, 0, 2) || cmdbuf[1] != IPC_Desc_Buffer(sizeof(exheader_header), IPC_BUFFER_RW))
|
||||
{
|
||||
error(cmdbuf, 0xD9001830);
|
||||
break;
|
||||
}
|
||||
|
||||
// Perform ExHeader patches
|
||||
exheader_header* exh = (exheader_header*)cmdbuf[2];
|
||||
u32 stacksize = 4096; // 3dsx/libctru don't require anymore than this
|
||||
memcpy(exh->codesetinfo.name, "3dsx_app", 8);
|
||||
memcpy(exh->codesetinfo.stacksize, &stacksize, 4);
|
||||
memset(&exh->deplist, 0, sizeof(exh->deplist));
|
||||
memcpy(exh->deplist.programid, dependencyList, sizeof(dependencyList));
|
||||
|
||||
exheader_arm11systemlocalcaps* localcaps0 = &exh->arm11systemlocalcaps;
|
||||
exheader_arm11systemlocalcaps* localcaps1 = &exh->accessdesc.arm11systemlocalcaps;
|
||||
localcaps0->flags[0] = 0x00;
|
||||
localcaps1->flags[0] = 0x00;
|
||||
localcaps0->flags[1] = 0x01;
|
||||
localcaps1->flags[1] = 0x01;
|
||||
localcaps0->flags[2] = 0x04;
|
||||
localcaps1->flags[2] = 0x05;
|
||||
localcaps0->priority = 0x30;
|
||||
localcaps1->priority = 0;
|
||||
memset(localcaps0->resourcelimitdescriptor, 0, 0x10);
|
||||
memset(localcaps1->resourcelimitdescriptor, 0, 0x10);
|
||||
memset(localcaps0->storageinfo.accessinfo, 0xFF, 7);
|
||||
memset(localcaps1->storageinfo.accessinfo, 0xFF, 7);
|
||||
memcpy(localcaps0->serviceaccesscontrol, serviceList, sizeof(serviceList));
|
||||
memcpy(localcaps1->serviceaccesscontrol, serviceList, sizeof(serviceList));
|
||||
memset(localcaps0->serviceaccesscontrol+0x20, 0, 2);
|
||||
memset(localcaps1->serviceaccesscontrol+0x20, 0, 2);
|
||||
localcaps0->resourcelimitcategory = 0;
|
||||
localcaps1->resourcelimitcategory = 0;
|
||||
|
||||
exheader_arm11kernelcapabilities* kcaps0 = &exh->arm11kernelcaps;
|
||||
exheader_arm11kernelcapabilities* kcaps1 = &exh->accessdesc.arm11kernelcaps;
|
||||
memset(kcaps0->descriptors, 0xFF, sizeof(kcaps0->descriptors));
|
||||
memset(kcaps1->descriptors, 0xFF, sizeof(kcaps1->descriptors));
|
||||
memcpy(kcaps0->descriptors, kernelCaps, sizeof(kernelCaps));
|
||||
memcpy(kcaps1->descriptors, kernelCaps, sizeof(kernelCaps));
|
||||
|
||||
u64 lastdep = sizeof(dependencyList)/8;
|
||||
if (osGetFirmVersion() >= SYSTEM_VERSION(2,50,0)) // 9.6+ FIRM
|
||||
{
|
||||
exh->deplist.programid[lastdep++] = 0x0004013000004002ULL; // nfc
|
||||
strncpy((char*)&localcaps0->serviceaccesscontrol[0x20], "nfc:u", 8);
|
||||
strncpy((char*)&localcaps1->serviceaccesscontrol[0x20], "nfc:u", 8);
|
||||
s64 dummy = 0;
|
||||
bool isN3DS = svcGetSystemInfo(&dummy, 0x10001, 0) == 0;
|
||||
if (isN3DS)
|
||||
{
|
||||
exh->deplist.programid[lastdep++] = 0x0004013020004102ULL; // mvd
|
||||
strncpy((char*)&localcaps0->serviceaccesscontrol[0x21], "mvd:STD", 8);
|
||||
strncpy((char*)&localcaps1->serviceaccesscontrol[0x21], "mvd:STD", 8);
|
||||
}
|
||||
}
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(4, 1, 2);
|
||||
cmdbuf[1] = 0;
|
||||
cmdbuf[2] = IPC_Desc_Buffer(sizeof(exheader_header), IPC_BUFFER_RW);
|
||||
cmdbuf[3] = (u32)exh;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
error(cmdbuf, 0xD900182F);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void hbldrThreadMain(void)
|
||||
{
|
||||
Handle handles[2];
|
||||
Handle serverHandle, clientHandle, sessionHandle = 0;
|
||||
|
||||
u32 replyTarget = 0;
|
||||
s32 index;
|
||||
|
||||
char ipcBuf[PATH_MAX+1];
|
||||
u32* bufPtrs = getThreadStaticBuffers();
|
||||
memset(bufPtrs, 0, 16*2*4);
|
||||
bufPtrs[0] = IPC_Desc_StaticBuffer(sizeof(ipcBuf), 0);
|
||||
bufPtrs[1] = (u32)ipcBuf;
|
||||
bufPtrs[2] = IPC_Desc_StaticBuffer(sizeof(ldrArgvBuf), 1);
|
||||
bufPtrs[3] = (u32)ldrArgvBuf;
|
||||
|
||||
Result res;
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
|
||||
assertSuccess(svcCreatePort(&serverHandle, &clientHandle, "hb:ldr", 1));
|
||||
|
||||
do
|
||||
{
|
||||
handles[0] = serverHandle;
|
||||
handles[1] = sessionHandle;
|
||||
|
||||
if(replyTarget == 0) // k11
|
||||
cmdbuf[0] = 0xFFFF0000;
|
||||
res = svcReplyAndReceive(&index, handles, sessionHandle == 0 ? 1 : 2, replyTarget);
|
||||
|
||||
if(R_FAILED(res))
|
||||
{
|
||||
if((u32)res == 0xC920181A) // session closed by remote
|
||||
{
|
||||
svcCloseHandle(sessionHandle);
|
||||
sessionHandle = 0;
|
||||
replyTarget = 0;
|
||||
}
|
||||
|
||||
else
|
||||
svcBreak(USERBREAK_PANIC);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if(index == 0)
|
||||
{
|
||||
Handle session;
|
||||
assertSuccess(svcAcceptSession(&session, serverHandle));
|
||||
|
||||
if(sessionHandle == 0)
|
||||
sessionHandle = session;
|
||||
else
|
||||
svcCloseHandle(session);
|
||||
}
|
||||
else
|
||||
{
|
||||
HBLDR_HandleCommands();
|
||||
replyTarget = sessionHandle;
|
||||
}
|
||||
}
|
||||
}
|
||||
while(!terminationRequest);
|
||||
|
||||
svcCloseHandle(sessionHandle);
|
||||
svcCloseHandle(clientHandle);
|
||||
svcCloseHandle(serverHandle);
|
||||
}
|
||||
130
sysmodules/rosalina/source/ifile.c
Normal file
130
sysmodules/rosalina/source/ifile.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 <3ds.h>
|
||||
#include "ifile.h"
|
||||
|
||||
Result IFile_Open(IFile *file, FS_ArchiveID archiveId, FS_Path archivePath, FS_Path filePath, u32 flags)
|
||||
{
|
||||
Result res;
|
||||
|
||||
res = FSUSER_OpenFileDirectly(&file->handle, archiveId, archivePath, filePath, flags, 0);
|
||||
file->pos = 0;
|
||||
file->size = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
Result IFile_Close(IFile *file)
|
||||
{
|
||||
return FSFILE_Close(file->handle);
|
||||
}
|
||||
|
||||
Result IFile_GetSize(IFile *file, u64 *size)
|
||||
{
|
||||
Result res;
|
||||
|
||||
res = FSFILE_GetSize(file->handle, size);
|
||||
file->size = *size;
|
||||
return res;
|
||||
}
|
||||
|
||||
Result IFile_Read(IFile *file, u64 *total, void *buffer, u32 len)
|
||||
{
|
||||
u32 read;
|
||||
u32 left;
|
||||
char *buf;
|
||||
u64 cur;
|
||||
Result res;
|
||||
|
||||
if (len == 0)
|
||||
{
|
||||
*total = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
buf = (char *)buffer;
|
||||
cur = 0;
|
||||
left = len;
|
||||
while (1)
|
||||
{
|
||||
res = FSFILE_Read(file->handle, &read, file->pos, buf, left);
|
||||
if (R_FAILED(res))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
cur += read;
|
||||
file->pos += read;
|
||||
if (read == left)
|
||||
{
|
||||
break;
|
||||
}
|
||||
buf += read;
|
||||
left -= read;
|
||||
}
|
||||
|
||||
*total = cur;
|
||||
return res;
|
||||
}
|
||||
|
||||
Result IFile_Write(IFile *file, u64 *total, void *buffer, u32 len, u32 flags)
|
||||
{
|
||||
u32 written;
|
||||
u32 left;
|
||||
char *buf;
|
||||
u64 cur;
|
||||
Result res;
|
||||
|
||||
if (len == 0)
|
||||
{
|
||||
*total = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
buf = (char *)buffer;
|
||||
cur = 0;
|
||||
left = len;
|
||||
while (1)
|
||||
{
|
||||
res = FSFILE_Write(file->handle, &written, file->pos, buf, left, flags);
|
||||
if (R_FAILED(res))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
cur += written;
|
||||
file->pos += written;
|
||||
if (written == left)
|
||||
{
|
||||
break;
|
||||
}
|
||||
buf += written;
|
||||
left -= written;
|
||||
}
|
||||
|
||||
*total = cur;
|
||||
return res;
|
||||
}
|
||||
306
sysmodules/rosalina/source/input_redirection.c
Normal file
306
sysmodules/rosalina/source/input_redirection.c
Normal file
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
* 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 <3ds.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "utils.h" // for makeARMBranch
|
||||
#include "minisoc.h"
|
||||
#include "input_redirection.h"
|
||||
#include "menus.h"
|
||||
#include "memory.h"
|
||||
|
||||
bool inputRedirectionEnabled = false;
|
||||
Handle inputRedirectionThreadStartedEvent;
|
||||
|
||||
static MyThread inputRedirectionThread;
|
||||
static u8 ALIGN(8) inputRedirectionThreadStack[0x4000];
|
||||
|
||||
MyThread *inputRedirectionCreateThread(void)
|
||||
{
|
||||
if(R_FAILED(MyThread_Create(&inputRedirectionThread, inputRedirectionThreadMain, inputRedirectionThreadStack, 0x4000, 0x20, CORE_SYSTEM)))
|
||||
svcBreak(USERBREAK_PANIC);
|
||||
return &inputRedirectionThread;
|
||||
}
|
||||
|
||||
// local hid, local tsrd localcprd, localtswr, localcpwr, remote hid, remote ts, remote circle
|
||||
static u32 hidData[] = { 0x00000FFF, 0x02000000, 0x007FF7FF, 0x00000000, 0x00000000, 0x00000FFF, 0x02000000, 0x007FF7FF };
|
||||
// remote ir
|
||||
static u32 irData[] = { 0x80800081 }; // Default: C-Stick at the center, no buttons.
|
||||
|
||||
int inputRedirectionStartResult;
|
||||
|
||||
void inputRedirectionThreadMain(void)
|
||||
{
|
||||
Result res = 0;
|
||||
res = miniSocInit();
|
||||
if(R_FAILED(res))
|
||||
return;
|
||||
|
||||
int sock = socSocket(AF_INET, SOCK_DGRAM, 0);
|
||||
while(sock == -1)
|
||||
{
|
||||
svcSleepThread(1000 * 0000 * 0000LL);
|
||||
sock = socSocket(AF_INET, SOCK_DGRAM, 0);
|
||||
}
|
||||
|
||||
struct sockaddr_in saddr;
|
||||
saddr.sin_family = AF_INET;
|
||||
saddr.sin_port = htons(4950);
|
||||
saddr.sin_addr.s_addr = gethostid();
|
||||
res = socBind(sock, (struct sockaddr*)&saddr, sizeof(struct sockaddr_in));
|
||||
if(res != 0)
|
||||
{
|
||||
socClose(sock);
|
||||
miniSocExit();
|
||||
inputRedirectionStartResult = res;
|
||||
return;
|
||||
}
|
||||
|
||||
inputRedirectionEnabled = true;
|
||||
svcSignalEvent(inputRedirectionThreadStartedEvent);
|
||||
|
||||
u32 *hidDataPhys = PA_FROM_VA_PTR(hidData);
|
||||
hidDataPhys += 5; // skip to +20
|
||||
|
||||
u32 *irDataPhys = PA_FROM_VA_PTR(irData);
|
||||
|
||||
char buf[20];
|
||||
u32 oldSpecialButtons = 0, specialButtons = 0;
|
||||
while(inputRedirectionEnabled && !terminationRequest)
|
||||
{
|
||||
struct pollfd pfd;
|
||||
pfd.fd = sock;
|
||||
pfd.events = POLLIN;
|
||||
pfd.revents = 0;
|
||||
|
||||
int pollres = socPoll(&pfd, 1, 10);
|
||||
if(pollres > 0 && (pfd.revents & POLLIN))
|
||||
{
|
||||
int n = soc_recvfrom(sock, buf, 20, 0, NULL, 0);
|
||||
if(n < 0)
|
||||
break;
|
||||
else if(n < 12)
|
||||
continue;
|
||||
|
||||
memcpy(hidDataPhys, buf, 12);
|
||||
if(n >= 20)
|
||||
{
|
||||
memcpy(irDataPhys, buf + 12, 4);
|
||||
|
||||
oldSpecialButtons = specialButtons;
|
||||
memcpy(&specialButtons, buf + 16, 4);
|
||||
|
||||
if(!(oldSpecialButtons & 1) && (specialButtons & 1)) // HOME button pressed
|
||||
srvPublishToSubscriber(0x204, 0);
|
||||
else if((oldSpecialButtons & 1) && !(specialButtons & 1)) // HOME button released
|
||||
srvPublishToSubscriber(0x205, 0);
|
||||
|
||||
if(!(oldSpecialButtons & 2) && (specialButtons & 2)) // POWER button pressed
|
||||
srvPublishToSubscriber(0x202, 0);
|
||||
|
||||
if(!(oldSpecialButtons & 4) && (specialButtons & 4)) // POWER button held long
|
||||
srvPublishToSubscriber(0x203, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct linger linger;
|
||||
linger.l_onoff = 1;
|
||||
linger.l_linger = 0;
|
||||
|
||||
socSetsockopt(sock, SOL_SOCKET, SO_LINGER, &linger, sizeof(struct linger));
|
||||
|
||||
socClose(sock);
|
||||
miniSocExit();
|
||||
}
|
||||
|
||||
void hidCodePatchFunc(void);
|
||||
void irCodePatchFunc(void);
|
||||
|
||||
Result InputRedirection_DoOrUndoPatches(void)
|
||||
{
|
||||
s64 startAddress, textTotalRoundedSize, rodataTotalRoundedSize, dataTotalRoundedSize;
|
||||
u32 totalSize;
|
||||
Handle processHandle;
|
||||
|
||||
Result res = OpenProcessByName("hid", &processHandle);
|
||||
|
||||
if(R_SUCCEEDED(res))
|
||||
{
|
||||
svcGetProcessInfo(&textTotalRoundedSize, processHandle, 0x10002); // only patch .text + .data
|
||||
svcGetProcessInfo(&rodataTotalRoundedSize, processHandle, 0x10003);
|
||||
svcGetProcessInfo(&dataTotalRoundedSize, processHandle, 0x10004);
|
||||
|
||||
totalSize = (u32)(textTotalRoundedSize + rodataTotalRoundedSize + dataTotalRoundedSize);
|
||||
|
||||
svcGetProcessInfo(&startAddress, processHandle, 0x10005);
|
||||
res = svcMapProcessMemoryEx(processHandle, 0x00100000, (u32) startAddress, totalSize);
|
||||
|
||||
if(R_SUCCEEDED(res))
|
||||
{
|
||||
static const u32 hidOrigRegisterAndValue[] = { 0x1EC46000, 0x4001 };
|
||||
static const u32 hidOrigCode[] = {
|
||||
0xE92D4070, // push {r4-r6, lr}
|
||||
0xE1A05001, // mov r5, r1
|
||||
0xEE1D4F70, // mrc p15, 0, r4, c13, c0, 3
|
||||
0xE3A01801, // mov r1, #0x10000
|
||||
0xE5A41080, // str r1, [r4,#0x80]!
|
||||
};
|
||||
|
||||
static u32 *hidRegPatchOffsets[2];
|
||||
static u32 *hidPatchJumpLoc;
|
||||
|
||||
if(inputRedirectionEnabled)
|
||||
{
|
||||
memcpy(hidRegPatchOffsets[0], &hidOrigRegisterAndValue, sizeof(hidOrigRegisterAndValue));
|
||||
memcpy(hidRegPatchOffsets[1], &hidOrigRegisterAndValue, sizeof(hidOrigRegisterAndValue));
|
||||
memcpy(hidPatchJumpLoc, &hidOrigCode, sizeof(hidOrigCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 hidDataPhys = (u32)PA_FROM_VA_PTR(hidData);
|
||||
u32 hidCodePhys = (u32)PA_FROM_VA_PTR(&hidCodePatchFunc);
|
||||
u32 hidHook[] = {
|
||||
0xE59F3004, // ldr r3, [pc, #4]
|
||||
0xE59FC004, // ldr r12, [pc, #4]
|
||||
0xE12FFF1C, // bx r12
|
||||
hidDataPhys,
|
||||
hidCodePhys,
|
||||
};
|
||||
|
||||
u32 *off = (u32 *)memsearch((u8 *)0x00100000, &hidOrigRegisterAndValue, totalSize, sizeof(hidOrigRegisterAndValue));
|
||||
if(off == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -1;
|
||||
}
|
||||
|
||||
u32 *off2 = (u32 *)memsearch((u8 *)off + sizeof(hidOrigRegisterAndValue), &hidOrigRegisterAndValue, totalSize - ((u32)off - 0x00100000), sizeof(hidOrigRegisterAndValue));
|
||||
if(off2 == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -2;
|
||||
}
|
||||
|
||||
u32 *off3 = (u32 *)memsearch((u8 *)0x00100000, &hidOrigCode, totalSize, sizeof(hidOrigCode));
|
||||
if(off3 == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -3;
|
||||
}
|
||||
|
||||
hidRegPatchOffsets[0] = off;
|
||||
hidRegPatchOffsets[1] = off2;
|
||||
hidPatchJumpLoc = off3;
|
||||
|
||||
*off = *off2 = hidDataPhys;
|
||||
memcpy(off3, &hidHook, sizeof(hidHook));
|
||||
}
|
||||
}
|
||||
|
||||
res = svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
}
|
||||
|
||||
res = OpenProcessByName("ir", &processHandle);
|
||||
if(R_SUCCEEDED(res))
|
||||
{
|
||||
svcGetProcessInfo(&textTotalRoundedSize, processHandle, 0x10002); // only patch .text + .data
|
||||
svcGetProcessInfo(&rodataTotalRoundedSize, processHandle, 0x10003);
|
||||
svcGetProcessInfo(&dataTotalRoundedSize, processHandle, 0x10004);
|
||||
|
||||
totalSize = (u32)(textTotalRoundedSize + rodataTotalRoundedSize + dataTotalRoundedSize);
|
||||
|
||||
svcGetProcessInfo(&startAddress, processHandle, 0x10005);
|
||||
res = svcMapProcessMemoryEx(processHandle, 0x00100000, (u32) startAddress, totalSize);
|
||||
|
||||
if(R_SUCCEEDED(res))
|
||||
{
|
||||
static u32 irOrigReadingCode[5] = {
|
||||
0xE5940000, // ldr r0, [r4]
|
||||
0xE1A01005, // mov r1, r5
|
||||
0xE3A03005, // mov r3, #5
|
||||
0xE3A02011, // mov r2, #17
|
||||
};
|
||||
|
||||
static const u32 irOrigWaitSyncCode[] = {
|
||||
0xEF000024, // svc 0x24 (WaitSynchronization)
|
||||
0xE1B01FA0, // movs r1, r0,lsr#31
|
||||
0xE1A0A000, // mov r10, r0
|
||||
};
|
||||
|
||||
static u32 *irHookLoc, *irWaitSyncLoc;
|
||||
|
||||
if(inputRedirectionEnabled)
|
||||
{
|
||||
memcpy(irHookLoc, &irOrigReadingCode, sizeof(irOrigReadingCode));
|
||||
memcpy(irWaitSyncLoc, &irOrigWaitSyncCode, sizeof(irOrigWaitSyncCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 irDataPhys = (u32)PA_FROM_VA_PTR(irData);
|
||||
u32 irCodePhys = (u32)PA_FROM_VA_PTR(&irCodePatchFunc);
|
||||
|
||||
u32 irHook[] = {
|
||||
0xE59F0004, // ldr r0, [pc, #4]
|
||||
0xE59FC004, // ldr r12, [pc, #4]
|
||||
0xE12FFF1C, // bx r12
|
||||
irDataPhys,
|
||||
irCodePhys,
|
||||
};
|
||||
|
||||
u32 *off = (u32 *)memsearch((u8 *)0x00100000, &irOrigReadingCode, totalSize, sizeof(irOrigReadingCode) - 4);
|
||||
if(off == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -4;
|
||||
}
|
||||
|
||||
u32 *off2 = (u32 *)memsearch((u8 *)0x00100000, &irOrigWaitSyncCode, totalSize, sizeof(irOrigWaitSyncCode));
|
||||
if(off2 == NULL)
|
||||
{
|
||||
svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
return -5;
|
||||
}
|
||||
|
||||
*(void **)(irCodePhys + 4) = decodeARMBranch(off + 4);
|
||||
|
||||
irHookLoc = off;
|
||||
irWaitSyncLoc = off2;
|
||||
|
||||
irOrigReadingCode[4] = off[4];
|
||||
memcpy(irHookLoc, &irHook, sizeof(irHook));
|
||||
|
||||
// This "NOP"s out a WaitSynchronisation1 (on the event bound to the 'IR' interrupt)
|
||||
*irWaitSyncLoc = 0xE3A00000; // mov r0, #0
|
||||
memcpy((u32*)irHookLoc, irHook, sizeof(irHook));
|
||||
}
|
||||
}
|
||||
|
||||
res = svcUnmapProcessMemoryEx(processHandle, 0x00100000, totalSize);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
140
sysmodules/rosalina/source/input_redirection_hooks.s
Normal file
140
sysmodules/rosalina/source/input_redirection_hooks.s
Normal file
@@ -0,0 +1,140 @@
|
||||
@ 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.
|
||||
|
||||
@ Hooks mainly written by Stary (PoC originally by Shiny Quagsire)
|
||||
|
||||
.section .rodata
|
||||
.balign 4
|
||||
.arm
|
||||
|
||||
@@@@@ HID hook @@@@@
|
||||
.global hidCodePatchFunc
|
||||
.type hidCodePatchFunc, %function
|
||||
hidCodePatchFunc:
|
||||
push {r4-r6, lr}
|
||||
|
||||
push {r3}
|
||||
mov r5, r1
|
||||
mrc p15, 0, r4, c13, c0, 3
|
||||
mov r1, #0x10000
|
||||
str r1, [r4,#0x80]!
|
||||
ldr r0, [r0]
|
||||
svc 0x32
|
||||
mov r12, r0
|
||||
pop {r3}
|
||||
|
||||
cmp r12, #0
|
||||
bne skip_touch_cp_cpy
|
||||
|
||||
ldrd r0, [r4,#8] @ load the touch/cp regs into into r0/1
|
||||
strd r0, [r3, #12] @ write to r3+12
|
||||
ldrd r0, [r3, #4] @ read from r3+4
|
||||
strd r0, [r5]
|
||||
|
||||
skip_touch_cp_cpy:
|
||||
|
||||
@ +0 +4 +8 +12 +16 +20 +24 +28
|
||||
@ local hid, local tsrd localcprd, localtswr, localcpwr, remote hid, remote ts, remote circle
|
||||
@u32 hidData[] = {0x00000FFF, 0x02000000, 0x007FF7FF, 0x00000000, 0x00000000, 0x00000FFF, 0x02000000, 0x007FF7FF};
|
||||
|
||||
mov r0, r3 @ Base address.
|
||||
ldr r1, =0x1ec46000 @ HID reg address.
|
||||
mov r3, #0xf00
|
||||
orr r3, #0xff
|
||||
|
||||
@ HID reg. Copy +20 => +0 if remote is not exactly 0xfff. Else, pass local through.
|
||||
ldr r1, [r1] @ Read HID reg.
|
||||
ldr r2, [r0, #20] @ Load remote HID reg.
|
||||
cmp r2, r3 @ Is remote 0xfff?
|
||||
movne r1, r2 @ If not, load remote.
|
||||
str r1, [r0]
|
||||
|
||||
@ Touch screen. Copy +24 => +4 if remote is not exactly 0x2000000. Else, pass local through (+12 => +4)
|
||||
cmp r12, #0 @ full success check
|
||||
|
||||
mov r3, #0x2000000
|
||||
ldreq r1, [r0, #12] @ Load the original (written) touch value.
|
||||
movne r1, r3
|
||||
ldr r2, [r0, #24] @ Load the remote touch value.
|
||||
cmp r2, r3 @ Is remote 0x2000000?
|
||||
movne r1, r2 @ If not, load remote.
|
||||
str r1, [r0, #4] @ Store.
|
||||
|
||||
@ Circle pad. Copy +28 => +8 if remote is not exactly 0x7FF7FF. Else, pass local through. (+16 => +8)
|
||||
cmp r12, #0 @ full success check
|
||||
|
||||
ldr r3, =0x7ff7ff
|
||||
ldreq r1, [r0, #16] @ Load the original (written) circle value.
|
||||
movne r1, r3
|
||||
ldr r2, [r0, #28] @ Load the remote circle value.
|
||||
cmp r2, r3 @ Is remote 0x7FF7FF?
|
||||
movne r1, r2 @ If not, load remote.
|
||||
str r1, [r0, #8] @ Store.
|
||||
|
||||
ldr r0, [r4,#4]
|
||||
|
||||
pop {r4-r6, pc}
|
||||
|
||||
.pool
|
||||
|
||||
@@@@@ IR hook @@@@@
|
||||
.global irCodePatchFunc
|
||||
.type irCodePatchFunc, %function
|
||||
irCodePatchFunc:
|
||||
b skip_vars
|
||||
|
||||
i2c_readdeviceraw_addr:
|
||||
.word 0
|
||||
|
||||
skip_vars:
|
||||
|
||||
@ Address to read from is passed by the hook, save it.
|
||||
mov r6, r0
|
||||
|
||||
ir_check_i2c:
|
||||
@ Original code. Does an i2c read at device 17 func 5.
|
||||
ldr r0, [r4]
|
||||
mov r1, r5
|
||||
mov r2, #17
|
||||
mov r3, #5
|
||||
|
||||
adr r12, i2c_readdeviceraw_addr
|
||||
ldr r12, [r12]
|
||||
blx r12
|
||||
|
||||
ldr r1, =0x80800081 @ no c-stick activity / zlzr not pressed
|
||||
cmp r0, #0 @ check if the i2c read succeeded completely
|
||||
|
||||
ldreq r2, [r5]
|
||||
tsteq r2, #0x100 @ apparently this is set only if the c-stick hasn't been moved yet
|
||||
movne r2, r1
|
||||
|
||||
cmp r1, r2
|
||||
ldreq r0, [r6] @ Pull the remote input in.
|
||||
streq r0, [r5] @ store it instead of the value read from i2c
|
||||
|
||||
@ Return!
|
||||
ldmfd sp!, {r4-r6,pc}
|
||||
|
||||
.pool
|
||||
61
sysmodules/rosalina/source/kernel_extension.s
Normal file
61
sysmodules/rosalina/source/kernel_extension.s
Normal file
@@ -0,0 +1,61 @@
|
||||
@ 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.
|
||||
|
||||
.text
|
||||
.arm
|
||||
.balign 4
|
||||
|
||||
.global svc0x2F
|
||||
.type svc0x2F, %function
|
||||
svc0x2F:
|
||||
@ custom backdoor before kernel ext. is installed
|
||||
svc 0x2F
|
||||
bx lr
|
||||
|
||||
.global convertVAToPA
|
||||
.type convertVAToPA, %function
|
||||
convertVAToPA:
|
||||
@ needs to be executed in supervisor mode
|
||||
mov r1, #0x1000
|
||||
sub r1, #1
|
||||
and r2, r0, r1
|
||||
bic r0, r1
|
||||
mcr p15, 0, r0, c7, c8, 0 @ VA to PA translation with privileged read permission check
|
||||
mrc p15, 0, r0, c7, c4, 0 @ read PA register
|
||||
tst r0, #1 @ failure bit
|
||||
bic r0, r1
|
||||
addeq r0, r2
|
||||
movne r0, #0
|
||||
bx lr
|
||||
|
||||
.section .data
|
||||
|
||||
.p2align 12
|
||||
.global kernel_extension
|
||||
kernel_extension: .incbin "build/kernel_extension.bin"
|
||||
.p2align 12
|
||||
kernel_extension_end:
|
||||
|
||||
.global kernel_extension_size
|
||||
kernel_extension_size: .word kernel_extension_end - kernel_extension
|
||||
146
sysmodules/rosalina/source/kernel_extension_setup.c
Normal file
146
sysmodules/rosalina/source/kernel_extension_setup.c
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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 "kernel_extension.h"
|
||||
#include "kernel_extension_setup.h"
|
||||
|
||||
#define MPCORE_REGS_BASE ((u32)PA_PTR(0x17E00000))
|
||||
#define MPCORE_GID_REGS_BASE (MPCORE_REGS_BASE + 0x1000)
|
||||
#define MPCORE_GID_SGI (*(vu32 *)(MPCORE_GID_REGS_BASE + 0xF00))
|
||||
|
||||
struct Parameters
|
||||
{
|
||||
void (*SGI0HandlerCallback)(struct Parameters *, u32 *);
|
||||
void *interruptManager;
|
||||
u32 *L2MMUTable; // bit31 mapping
|
||||
|
||||
void (*initFPU)(void);
|
||||
void (*mcuReboot)(void);
|
||||
void (*coreBarrier)(void);
|
||||
|
||||
u32 TTBCR;
|
||||
u32 L1MMUTableAddrs[4];
|
||||
|
||||
u32 kernelVersion;
|
||||
|
||||
struct CfwInfo
|
||||
{
|
||||
char magic[4];
|
||||
|
||||
u8 versionMajor;
|
||||
u8 versionMinor;
|
||||
u8 versionBuild;
|
||||
u8 flags;
|
||||
|
||||
u32 commitHash;
|
||||
|
||||
u32 config;
|
||||
} __attribute__((packed)) info;
|
||||
};
|
||||
|
||||
|
||||
static void K_SGI0HandlerCallback(volatile struct Parameters *p)
|
||||
{
|
||||
u32 L1MMUTableAddr;
|
||||
vu32 *L1MMUTable;
|
||||
u32 coreId;
|
||||
|
||||
__asm__ volatile("cpsid aif"); // disable interrupts
|
||||
|
||||
p->coreBarrier();
|
||||
|
||||
__asm__ volatile("mrc p15, 0, %0, c0, c0, 5" : "=r"(coreId));
|
||||
coreId &= 3;
|
||||
|
||||
__asm__ volatile("mrc p15, 0, %0, c2, c0, 1" : "=r"(L1MMUTableAddr));
|
||||
L1MMUTableAddr &= ~0x3FFF;
|
||||
p->L1MMUTableAddrs[coreId] = L1MMUTableAddr;
|
||||
L1MMUTable = (vu32 *)(L1MMUTableAddr | (1 << 31));
|
||||
|
||||
// Actually map the kernel ext
|
||||
u32 L2MMUTableAddr = (u32)(p->L2MMUTable) & ~(1 << 31);
|
||||
L1MMUTable[0x40000000 >> 20] = L2MMUTableAddr | 1;
|
||||
|
||||
__asm__ __volatile__("mcr p15, 0, %[val], c7, c10, 4" :: [val] "r" (0) : "memory");
|
||||
((void (*)(volatile struct Parameters *))0x40000000)(p);
|
||||
|
||||
p->coreBarrier();
|
||||
}
|
||||
|
||||
static u32 ALIGN(0x400) L2MMUTableFor0x40000000[256] = { 0 };
|
||||
u32 TTBCR;
|
||||
static void K_ConfigureSGI0(void)
|
||||
{
|
||||
// see /patches/k11MainHook.s
|
||||
u32 *off;
|
||||
u32 *initFPU, *mcuReboot, *coreBarrier;
|
||||
|
||||
// Search for stuff in the 0xFFFF0000 page
|
||||
for(initFPU = (u32 *)0xFFFF0000; initFPU < (u32 *)0xFFFF1000 && *initFPU != 0xE1A0D002; initFPU++);
|
||||
initFPU += 3;
|
||||
|
||||
for(mcuReboot = initFPU; mcuReboot < (u32 *)0xFFFF1000 && *mcuReboot != 0xE3A0A0C2; mcuReboot++);
|
||||
mcuReboot--;
|
||||
coreBarrier = (u32 *)decodeARMBranch(mcuReboot - 4);
|
||||
|
||||
for(off = mcuReboot; off < (u32 *)0xFFFF1000 && *off != 0x726C6468; off++); // "hdlr"
|
||||
|
||||
volatile struct Parameters *p = (struct Parameters *)PA_FROM_VA_PTR(off); // Caches? What are caches?
|
||||
p->SGI0HandlerCallback = (void (*)(struct Parameters *, u32 *))PA_FROM_VA_PTR(K_SGI0HandlerCallback);
|
||||
p->L2MMUTable = (u32 *)PA_FROM_VA_PTR(L2MMUTableFor0x40000000);
|
||||
p->initFPU = (void (*) (void))initFPU;
|
||||
p->mcuReboot = (void (*) (void))mcuReboot;
|
||||
p->coreBarrier = (void (*) (void))coreBarrier;
|
||||
|
||||
__asm__ volatile("mrc p15, 0, %0, c2, c0, 2" : "=r"(TTBCR));
|
||||
p->TTBCR = TTBCR;
|
||||
|
||||
p->kernelVersion = *(vu32 *)0x1FF80000;
|
||||
|
||||
// Now let's configure the L2 table
|
||||
|
||||
//4KB extended small pages: [SYS:RW USR:-- X TYP:NORMAL SHARED OUTER NOCACHE, INNER CACHED WB WA]
|
||||
for(u32 offset = 0; offset < kernel_extension_size; offset += 0x1000)
|
||||
L2MMUTableFor0x40000000[offset >> 12] = (u32)convertVAToPA(kernel_extension + offset) | 0x516;
|
||||
}
|
||||
|
||||
static void K_SendSGI0ToAllCores(void)
|
||||
{
|
||||
MPCORE_GID_SGI = 0xF0000; // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0360f/CACGDJJC.html
|
||||
}
|
||||
|
||||
static inline void flushAllCaches(void)
|
||||
{
|
||||
svcUnmapProcessMemory(CUR_PROCESS_HANDLE, 0, 0); // this SVC flush both caches entirely (and properly) even when returing an error
|
||||
}
|
||||
|
||||
void installKernelExtension(void)
|
||||
{
|
||||
svc0x2F(K_ConfigureSGI0);
|
||||
flushAllCaches();
|
||||
svc0x2F(K_SendSGI0ToAllCores);
|
||||
flushAllCaches();
|
||||
}
|
||||
129
sysmodules/rosalina/source/main.c
Normal file
129
sysmodules/rosalina/source/main.c
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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 <3ds.h>
|
||||
#include "memory.h"
|
||||
#include "services.h"
|
||||
#include "fsreg.h"
|
||||
#include "menu.h"
|
||||
#include "errdisp.h"
|
||||
#include "hbloader.h"
|
||||
#include "utils.h"
|
||||
#include "MyThread.h"
|
||||
#include "kernel_extension_setup.h"
|
||||
#include "menus/process_patches.h"
|
||||
|
||||
// this is called before main
|
||||
bool isN3DS;
|
||||
void __appInit()
|
||||
{
|
||||
srvSysInit();
|
||||
fsregInit();
|
||||
fsSysInit();
|
||||
|
||||
s64 dummy;
|
||||
isN3DS = svcGetSystemInfo(&dummy, 0x10001, 0) == 0;
|
||||
}
|
||||
|
||||
// this is called after main exits
|
||||
void __appExit()
|
||||
{
|
||||
fsExit();
|
||||
fsregExit();
|
||||
srvSysExit();
|
||||
}
|
||||
|
||||
|
||||
Result __sync_init(void);
|
||||
Result __sync_fini(void);
|
||||
void __libc_init_array(void);
|
||||
void __libc_fini_array(void);
|
||||
|
||||
void __ctru_exit()
|
||||
{
|
||||
__appExit();
|
||||
__sync_fini();
|
||||
__libc_fini_array();
|
||||
svcSleepThread(-1LL); // kernel-loaded sysmodules except PXI are not supposed to terminate anyways
|
||||
svcExitProcess();
|
||||
}
|
||||
|
||||
|
||||
void initSystem()
|
||||
{
|
||||
__libc_init_array();
|
||||
|
||||
HBLDR_3DSX_TID = HBLDR_DEFAULT_3DSX_TID;
|
||||
installKernelExtension();
|
||||
|
||||
__sync_init();
|
||||
__appInit();
|
||||
}
|
||||
|
||||
bool terminationRequest = false;
|
||||
Handle terminationRequestEvent;
|
||||
|
||||
int main(void)
|
||||
{
|
||||
Result res = 0;
|
||||
Handle notificationHandle;
|
||||
|
||||
MyThread *menuThread = menuCreateThread(), *errDispThread = errDispCreateThread(), *hbldrThread = hbldrCreateThread();
|
||||
|
||||
if(R_FAILED(srvEnableNotification(¬ificationHandle)))
|
||||
svcBreak(USERBREAK_ASSERT);
|
||||
|
||||
if(R_FAILED(svcCreateEvent(&terminationRequestEvent, RESET_STICKY)))
|
||||
svcBreak(USERBREAK_ASSERT);
|
||||
|
||||
do
|
||||
{
|
||||
res = svcWaitSynchronization(notificationHandle, -1LL);
|
||||
|
||||
if(R_FAILED(res))
|
||||
continue;
|
||||
|
||||
u32 notifId = 0;
|
||||
|
||||
if(R_FAILED(srvReceiveNotification(¬ifId)))
|
||||
svcBreak(USERBREAK_ASSERT);
|
||||
|
||||
if(notifId == 0x100)
|
||||
{
|
||||
// Termination request
|
||||
terminationRequest = true;
|
||||
svcSignalEvent(terminationRequestEvent);
|
||||
}
|
||||
}
|
||||
while(!terminationRequest);
|
||||
|
||||
MyThread_Join(menuThread, -1LL);
|
||||
MyThread_Join(errDispThread, -1LL);
|
||||
MyThread_Join(hbldrThread, -1LL);
|
||||
|
||||
svcCloseHandle(notificationHandle);
|
||||
return 0;
|
||||
}
|
||||
274
sysmodules/rosalina/source/memory.c
Normal file
274
sysmodules/rosalina/source/memory.c
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* 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 "memory.h"
|
||||
|
||||
void *memcpy(void *dest, const void *src, u32 size)
|
||||
{
|
||||
u8 *destc = (u8 *)dest;
|
||||
const u8 *srcc = (const u8 *)src;
|
||||
|
||||
for(u32 i = 0; i < size; i++)
|
||||
destc[i] = srcc[i];
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
int memcmp(const void *buf1, const void *buf2, u32 size)
|
||||
{
|
||||
const u8 *buf1c = (const u8 *)buf1;
|
||||
const u8 *buf2c = (const u8 *)buf2;
|
||||
|
||||
for(u32 i = 0; i < size; i++)
|
||||
{
|
||||
int cmp = buf1c[i] - buf2c[i];
|
||||
if(cmp)
|
||||
return cmp;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *memset(void *dest, u32 value, u32 size)
|
||||
{
|
||||
u8 *destc = (u8 *)dest;
|
||||
|
||||
for(u32 i = 0; i < size; i++) destc[i] = (u8)value;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
void *memset32(void *dest, u32 value, u32 size)
|
||||
{
|
||||
u32 *dest32 = (u32 *)dest;
|
||||
|
||||
for(u32 i = 0; i < size/4; i++) dest32[i] = value;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
//Boyer-Moore Horspool algorithm, adapted from http://www-igm.univ-mlv.fr/~lecroq/string/node18.html#SECTION00180
|
||||
u8 *memsearch(u8 *startPos, const void *pattern, u32 size, u32 patternSize)
|
||||
{
|
||||
const u8 *patternc = (const u8 *)pattern;
|
||||
u32 table[256];
|
||||
|
||||
//Preprocessing
|
||||
for(u32 i = 0; i < 256; i++)
|
||||
table[i] = patternSize;
|
||||
for(u32 i = 0; i < patternSize - 1; i++)
|
||||
table[patternc[i]] = patternSize - i - 1;
|
||||
|
||||
//Searching
|
||||
u32 j = 0;
|
||||
while(j <= size - patternSize)
|
||||
{
|
||||
u8 c = startPos[j + patternSize - 1];
|
||||
if(patternc[patternSize - 1] == c && memcmp(pattern, startPos + j, patternSize - 1) == 0)
|
||||
return startPos + j;
|
||||
j += table[c];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *strcpy(char *dest, const char *src)
|
||||
{
|
||||
u32 i;
|
||||
for(i = 0; src[i] != 0; i++)
|
||||
dest[i] = src[i];
|
||||
|
||||
dest[i] = 0;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
char *strncpy(char *dest, const char *src, u32 size)
|
||||
{
|
||||
u32 i;
|
||||
for(i = 0; i < size && src[i] != 0; i++)
|
||||
dest[i] = src[i];
|
||||
|
||||
for(; i < size; i++)
|
||||
dest[i] = 0;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
s32 strnlen(const char *string, s32 maxlen)
|
||||
{
|
||||
s32 size;
|
||||
for(size = 0; size < maxlen && *string; string++, size++);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
s32 strlen(const char *string)
|
||||
{
|
||||
char *stringEnd = (char *)string;
|
||||
while(*stringEnd) stringEnd++;
|
||||
|
||||
return stringEnd - string;
|
||||
}
|
||||
|
||||
s32 strcmp(const char *str1, const char *str2)
|
||||
{
|
||||
while(*str1 && (*str1 == *str2))
|
||||
{
|
||||
str1++;
|
||||
str2++;
|
||||
}
|
||||
|
||||
return *str1 - *str2;
|
||||
}
|
||||
|
||||
s32 strncmp(const char *str1, const char *str2, u32 size)
|
||||
{
|
||||
while(size && *str1 && (*str1 == *str2))
|
||||
{
|
||||
str1++;
|
||||
str2++;
|
||||
size--;
|
||||
}
|
||||
if (!size)
|
||||
return 0;
|
||||
else
|
||||
return *(u8*)str1 - *(u8*)str2;
|
||||
}
|
||||
|
||||
const char *strchr(const char *string, int c)
|
||||
{
|
||||
char *stringEnd = (char *)string;
|
||||
while(*stringEnd != 0)
|
||||
{
|
||||
if(*stringEnd == c) return stringEnd;
|
||||
stringEnd++;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void hexItoa(u64 number, char *out, u32 digits, bool uppercase)
|
||||
{
|
||||
const char hexDigits[] = "0123456789ABCDEF";
|
||||
const char hexDigitsLowercase[] = "0123456789abcdef";
|
||||
u32 i = 0;
|
||||
|
||||
while(number > 0)
|
||||
{
|
||||
out[digits - 1 - i++] = uppercase ? hexDigits[number & 0xF] : hexDigitsLowercase[number & 0xF];
|
||||
number >>= 4;
|
||||
}
|
||||
|
||||
while(i < digits) out[digits - 1 - i++] = '0';
|
||||
}
|
||||
|
||||
|
||||
// Copied from newlib, without the reent stuff + some other stuff
|
||||
unsigned long int xstrtoul(const char *nptr, char **endptr, int base, bool allowPrefix, bool *ok)
|
||||
{
|
||||
register const unsigned char *s = (const unsigned char *)nptr;
|
||||
register unsigned long acc;
|
||||
register int c;
|
||||
register unsigned long cutoff;
|
||||
register int neg = 0, any, cutlim;
|
||||
|
||||
if(ok != NULL)
|
||||
*ok = true;
|
||||
/*
|
||||
* See strtol for comments as to the logic used.
|
||||
*/
|
||||
do {
|
||||
c = *s++;
|
||||
} while ((c >= 9 && c <= 13) || c == ' ');
|
||||
if (c == '-') {
|
||||
if(!allowPrefix) {
|
||||
if(ok != NULL)
|
||||
*ok = false;
|
||||
return 0;
|
||||
}
|
||||
neg = 1;
|
||||
c = *s++;
|
||||
} else if (c == '+'){
|
||||
if(!allowPrefix) {
|
||||
if(ok != NULL)
|
||||
*ok = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
c = *s++;
|
||||
}
|
||||
if ((base == 0 || base == 16) &&
|
||||
c == '0' && (*s == 'x' || *s == 'X')) {
|
||||
|
||||
if(!allowPrefix) {
|
||||
if(ok != NULL)
|
||||
*ok = false;
|
||||
return 0;
|
||||
}
|
||||
c = s[1];
|
||||
s += 2;
|
||||
base = 16;
|
||||
}
|
||||
if (base == 0) {
|
||||
if(!allowPrefix) {
|
||||
if(ok != NULL)
|
||||
*ok = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
base = c == '0' ? 8 : 10;
|
||||
}
|
||||
cutoff = (unsigned long)(-1) / (unsigned long)base;
|
||||
cutlim = (unsigned long)(-1) % (unsigned long)base;
|
||||
for (acc = 0, any = 0;; c = *s++) {
|
||||
if (c >= '0' && c <= '9')
|
||||
c -= '0';
|
||||
else if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
|
||||
c -= c >= 'A' && c <= 'Z' ? 'A' - 10 : 'a' - 10;
|
||||
else
|
||||
break;
|
||||
if (c >= base)
|
||||
break;
|
||||
if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim))
|
||||
any = -1;
|
||||
else {
|
||||
any = 1;
|
||||
acc *= base;
|
||||
acc += c;
|
||||
}
|
||||
}
|
||||
if (any < 0) {
|
||||
acc = (unsigned long)-1;
|
||||
if(ok != NULL)
|
||||
*ok = false;
|
||||
// rptr->_errno = ERANGE;
|
||||
} else if (neg)
|
||||
acc = -acc;
|
||||
if (endptr != 0)
|
||||
*endptr = (char *) (any ? (char *)s - 1 : nptr);
|
||||
return (acc);
|
||||
}
|
||||
285
sysmodules/rosalina/source/menu.c
Normal file
285
sysmodules/rosalina/source/menu.c
Normal file
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* 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 <3ds.h>
|
||||
#include "menu.h"
|
||||
#include "draw.h"
|
||||
#include "fmt.h"
|
||||
#include "memory.h"
|
||||
#include "ifile.h"
|
||||
#include "menus.h"
|
||||
#include "utils.h"
|
||||
#include "menus/n3ds.h"
|
||||
|
||||
u32 waitInputWithTimeout(u32 msec)
|
||||
{
|
||||
bool pressedKey = false;
|
||||
u32 key = 0;
|
||||
u32 n = 0;
|
||||
|
||||
//Wait for no keys to be pressed
|
||||
while(HID_PAD && !terminationRequest && (msec == 0 || n < msec))
|
||||
{
|
||||
svcSleepThread(1 * 1000 * 1000LL);
|
||||
n++;
|
||||
}
|
||||
|
||||
if(terminationRequest || (msec != 0 && n >= msec))
|
||||
return 0;
|
||||
|
||||
do
|
||||
{
|
||||
//Wait for a key to be pressed
|
||||
while(!HID_PAD && !terminationRequest && (msec == 0 || n < msec))
|
||||
{
|
||||
svcSleepThread(1 * 1000 * 1000LL);
|
||||
n++;
|
||||
}
|
||||
|
||||
if(terminationRequest || (msec != 0 && n >= msec))
|
||||
return 0;
|
||||
|
||||
key = HID_PAD;
|
||||
|
||||
//Make sure it's pressed
|
||||
for(u32 i = 0x26000; i > 0; i --)
|
||||
{
|
||||
if(key != HID_PAD) break;
|
||||
if(i == 1) pressedKey = true;
|
||||
}
|
||||
}
|
||||
while(!pressedKey);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
u32 waitInput(void)
|
||||
{
|
||||
return waitInputWithTimeout(0);
|
||||
}
|
||||
|
||||
static Result _MCUHWC_GetBatteryLevel(u8 *out)
|
||||
{
|
||||
#define TRY(expr) if(R_FAILED(res = (expr))) { svcCloseHandle(mcuhwcHandle); return res; }
|
||||
Result res;
|
||||
Handle mcuhwcHandle;
|
||||
|
||||
TRY(srvGetServiceHandle(&mcuhwcHandle, "mcu::HWC"));
|
||||
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
cmdbuf[0] = 0x50000;
|
||||
|
||||
TRY(svcSendSyncRequest(mcuhwcHandle));
|
||||
|
||||
*out = (u8) cmdbuf[2];
|
||||
|
||||
svcCloseHandle(mcuhwcHandle);
|
||||
return cmdbuf[1];
|
||||
|
||||
#undef TRY
|
||||
}
|
||||
|
||||
static MyThread menuThread;
|
||||
static u8 ALIGN(8) menuThreadStack[THREAD_STACK_SIZE];
|
||||
static u8 batteryLevel = 255;
|
||||
|
||||
MyThread *menuCreateThread(void)
|
||||
{
|
||||
if(R_FAILED(MyThread_Create(&menuThread, menuThreadMain, menuThreadStack, THREAD_STACK_SIZE, 0x18, CORE_SYSTEM)))
|
||||
svcBreak(USERBREAK_PANIC);
|
||||
return &menuThread;
|
||||
}
|
||||
|
||||
extern bool isN3DS;
|
||||
u32 menuCombo = DEFAULT_MENU_COMBO;
|
||||
|
||||
void menuThreadMain(void)
|
||||
{
|
||||
if(!isN3DS)
|
||||
{
|
||||
rosalinaMenu.nbItems--;
|
||||
for(u32 i = 3; i <= rosalinaMenu.nbItems; i++)
|
||||
rosalinaMenu.items[i] = rosalinaMenu.items[i+1];
|
||||
}
|
||||
else
|
||||
N3DSMenu_UpdateStatus();
|
||||
|
||||
while(!terminationRequest)
|
||||
{
|
||||
if((HID_PAD & menuCombo) == menuCombo)
|
||||
{
|
||||
menuEnter();
|
||||
menuShow(&rosalinaMenu);
|
||||
menuLeave();
|
||||
}
|
||||
svcSleepThread(50 * 1000 * 1000LL);
|
||||
}
|
||||
}
|
||||
|
||||
static s32 menuRefCount = 0;
|
||||
void menuEnter(void)
|
||||
{
|
||||
if(AtomicPostIncrement(&menuRefCount) == 0)
|
||||
{
|
||||
svcKernelSetState(0x10000, 1);
|
||||
svcSleepThread(5 * 1000 * 100LL);
|
||||
Draw_SetupFramebuffer();
|
||||
Draw_ClearFramebuffer();
|
||||
}
|
||||
}
|
||||
|
||||
void menuLeave(void)
|
||||
{
|
||||
svcSleepThread(50 * 1000 * 1000);
|
||||
|
||||
if(AtomicDecrement(&menuRefCount) == 0)
|
||||
{
|
||||
Draw_Lock();
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_RestoreFramebuffer();
|
||||
Draw_Unlock();
|
||||
svcKernelSetState(0x10000, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void menuDraw(Menu *menu, u32 selected)
|
||||
{
|
||||
char versionString[16];
|
||||
s64 out;
|
||||
u32 version, commitHash;
|
||||
bool isRelease;
|
||||
|
||||
if(R_FAILED(_MCUHWC_GetBatteryLevel(&batteryLevel)))
|
||||
batteryLevel = 255;
|
||||
|
||||
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));
|
||||
|
||||
Draw_DrawString(10, 10, COLOR_TITLE, menu->title);
|
||||
|
||||
for(u32 i = 0; i < 15; i++)
|
||||
{
|
||||
if(i >= menu->nbItems)
|
||||
break;
|
||||
Draw_DrawString(30, 30 + i * SPACING_Y, COLOR_WHITE, menu->items[i].title);
|
||||
Draw_DrawCharacter(10, 30 + i * SPACING_Y, COLOR_TITLE, i == selected ? '>' : ' ');
|
||||
}
|
||||
|
||||
if(batteryLevel != 255)
|
||||
Draw_DrawFormattedString(SCREEN_BOT_WIDTH - 10 - 4 * SPACING_X, SCREEN_BOT_HEIGHT - 20, COLOR_WHITE, "%02hhu%%", batteryLevel);
|
||||
else
|
||||
Draw_DrawString(SCREEN_BOT_WIDTH - 10 - 4 * SPACING_X, SCREEN_BOT_HEIGHT - 20, COLOR_WHITE, " ");
|
||||
|
||||
if(isRelease)
|
||||
Draw_DrawFormattedString(10, SCREEN_BOT_HEIGHT - 20, COLOR_TITLE, "Luma3DS %s", versionString);
|
||||
else
|
||||
Draw_DrawFormattedString(10, SCREEN_BOT_HEIGHT - 20, COLOR_TITLE, "Luma3DS %s-%08x", versionString, commitHash);
|
||||
|
||||
Draw_FlushFramebuffer();
|
||||
}
|
||||
|
||||
void menuShow(Menu *root)
|
||||
{
|
||||
u32 selectedItem = 0;
|
||||
Menu *currentMenu = root;
|
||||
u32 nbPreviousMenus = 0;
|
||||
Menu *previousMenus[0x80];
|
||||
|
||||
Draw_Lock();
|
||||
Draw_ClearFramebuffer();
|
||||
Draw_FlushFramebuffer();
|
||||
menuDraw(currentMenu, selectedItem);
|
||||
Draw_Unlock();
|
||||
|
||||
do
|
||||
{
|
||||
u32 pressed = waitInputWithTimeout(1000);
|
||||
|
||||
if(pressed & BUTTON_A)
|
||||
{
|
||||
Draw_Lock();
|
||||
Draw_ClearFramebuffer();
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
|
||||
switch(currentMenu->items[selectedItem].action_type)
|
||||
{
|
||||
case METHOD:
|
||||
if(currentMenu->items[selectedItem].method != NULL)
|
||||
currentMenu->items[selectedItem].method();
|
||||
break;
|
||||
case MENU:
|
||||
previousMenus[nbPreviousMenus++] = currentMenu;
|
||||
currentMenu = currentMenu->items[selectedItem].menu;
|
||||
selectedItem = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
Draw_Lock();
|
||||
Draw_ClearFramebuffer();
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
}
|
||||
else if(pressed & BUTTON_B)
|
||||
{
|
||||
Draw_Lock();
|
||||
Draw_ClearFramebuffer();
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
|
||||
if(nbPreviousMenus > 0)
|
||||
currentMenu = previousMenus[--nbPreviousMenus];
|
||||
else
|
||||
break;
|
||||
}
|
||||
else if(pressed & BUTTON_DOWN)
|
||||
{
|
||||
if(++selectedItem >= currentMenu->nbItems)
|
||||
selectedItem = 0;
|
||||
}
|
||||
else if(pressed & BUTTON_UP)
|
||||
{
|
||||
if(selectedItem-- <= 0)
|
||||
selectedItem = currentMenu->nbItems - 1;
|
||||
}
|
||||
|
||||
Draw_Lock();
|
||||
menuDraw(currentMenu, selectedItem);
|
||||
Draw_Unlock();
|
||||
}
|
||||
while(!terminationRequest);
|
||||
}
|
||||
202
sysmodules/rosalina/source/menus.c
Normal file
202
sysmodules/rosalina/source/menus.c
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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 <3ds.h>
|
||||
#include "menus.h"
|
||||
#include "menu.h"
|
||||
#include "draw.h"
|
||||
#include "menus/process_list.h"
|
||||
#include "menus/process_patches.h"
|
||||
#include "menus/n3ds.h"
|
||||
#include "menus/debugger.h"
|
||||
#include "menus/miscellaneous.h"
|
||||
#include "ifile.h"
|
||||
#include "memory.h"
|
||||
#include "fmt.h"
|
||||
|
||||
Menu rosalinaMenu = {
|
||||
"Rosalina menu",
|
||||
.nbItems = 7,
|
||||
{
|
||||
{ "Process list", METHOD, .method = &RosalinaMenu_ProcessList },
|
||||
{ "Process patches menu...", MENU, .menu = &processPatchesMenu },
|
||||
{ "Take screenshot (slow!)", METHOD, .method = &RosalinaMenu_TakeScreenshot },
|
||||
{ "New 3DS menu...", MENU, .menu = &N3DSMenu },
|
||||
{ "Debugger options...", MENU, .menu = &debuggerMenu },
|
||||
{ "Miscellaneous options...", MENU, .menu = &miscellaneousMenu },
|
||||
{ "Credits", METHOD, .method = &RosalinaMenu_ShowCredits }
|
||||
}
|
||||
};
|
||||
|
||||
void RosalinaMenu_ShowCredits(void)
|
||||
{
|
||||
Draw_Lock();
|
||||
Draw_ClearFramebuffer();
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
|
||||
do
|
||||
{
|
||||
Draw_Lock();
|
||||
Draw_DrawString(10, 10, COLOR_TITLE, "Rosalina -- Luma3DS credits");
|
||||
|
||||
u32 posY = Draw_DrawString(10, 30, COLOR_WHITE, "Luma3DS (c) 2016-2017 AuroraWright, TuxSH") + SPACING_Y;
|
||||
|
||||
posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, "3DSX loading code by fincs");
|
||||
posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, "Networking code & basic GDB functionality by Stary");
|
||||
posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, "InputRedirection by Stary (PoC by ShinyQuagsire)");
|
||||
|
||||
posY += 2 * SPACING_Y;
|
||||
|
||||
Draw_DrawString(10, posY, COLOR_WHITE,
|
||||
(
|
||||
"Special thanks to:\n"
|
||||
" Bond697, WinterMute, yifanlu,\n"
|
||||
" Luma3DS contributors, ctrulib contributors,\n"
|
||||
" other people"
|
||||
));
|
||||
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
}
|
||||
while(!(waitInput() & BUTTON_B) && !terminationRequest);
|
||||
}
|
||||
|
||||
void RosalinaMenu_TakeScreenshot(void)
|
||||
{
|
||||
#define TRY(expr) if(R_FAILED(res = (expr))) goto end;
|
||||
|
||||
u64 total;
|
||||
IFile file;
|
||||
u8 buf[54];
|
||||
Result res;
|
||||
|
||||
u32 filenum;
|
||||
char filename[64];
|
||||
|
||||
FS_Archive archive;
|
||||
FS_ArchiveID archiveId;
|
||||
s64 out;
|
||||
bool isSdMode;
|
||||
|
||||
if(R_FAILED(svcGetSystemInfo(&out, 0x10000, 6))) svcBreak(USERBREAK_ASSERT);
|
||||
isSdMode = (bool)out;
|
||||
|
||||
archiveId = isSdMode ? ARCHIVE_SDMC : ARCHIVE_NAND_RW;
|
||||
Draw_Lock();
|
||||
Draw_RestoreFramebuffer();
|
||||
|
||||
res = FSUSER_OpenArchive(&archive, archiveId, fsMakePath(PATH_EMPTY, ""));
|
||||
if(R_SUCCEEDED(res))
|
||||
{
|
||||
res = FSUSER_CreateDirectory(archive, fsMakePath(PATH_ASCII, "/luma/screenshots"), 0);
|
||||
if((u32)res == 0xC82044BE) // directory already exists
|
||||
res = 0;
|
||||
FSUSER_CloseArchive(archive);
|
||||
}
|
||||
|
||||
for(filenum = 0; filenum <= 9999999; filenum++) // find an unused file name
|
||||
{
|
||||
Result res1, res2, res3;
|
||||
IFile fileR;
|
||||
|
||||
sprintf(filename, "/luma/screenshots/top_%04u.bmp", filenum);
|
||||
res1 = IFile_Open(&fileR, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, filename), FS_OPEN_READ);
|
||||
IFile_Close(&fileR);
|
||||
|
||||
sprintf(filename, "/luma/screenshots/bot_%04u.bmp", filenum);
|
||||
res2 = IFile_Open(&fileR, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, filename), FS_OPEN_READ);
|
||||
IFile_Close(&fileR);
|
||||
|
||||
sprintf(filename, "/luma/screenshots/top_right_%04u.bmp", filenum);
|
||||
res3 = IFile_Open(&fileR, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, filename), FS_OPEN_READ);
|
||||
IFile_Close(&fileR);
|
||||
|
||||
if(R_FAILED(res1) && R_FAILED(res2) && R_FAILED(res3))
|
||||
break;
|
||||
}
|
||||
|
||||
sprintf(filename, "/luma/screenshots/top_%04u.bmp", filenum);
|
||||
TRY(IFile_Open(&file, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, filename), FS_OPEN_CREATE | FS_OPEN_WRITE));
|
||||
Draw_CreateBitmapHeader(buf, 400, 240);
|
||||
TRY(IFile_Write(&file, &total, buf, 54, 0));
|
||||
|
||||
for(u32 y = 0; y < 240; y++)
|
||||
{
|
||||
u8 *line = Draw_ConvertFrameBufferLine(true, true, y);
|
||||
TRY(IFile_Write(&file, &total, line, 3 * 400, 0));
|
||||
}
|
||||
|
||||
TRY(IFile_Close(&file));
|
||||
|
||||
sprintf(filename, "/luma/screenshots/bot_%04u.bmp", filenum);
|
||||
TRY(IFile_Open(&file, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, filename), FS_OPEN_CREATE | FS_OPEN_WRITE));
|
||||
Draw_CreateBitmapHeader(buf, 320, 240);
|
||||
TRY(IFile_Write(&file, &total, buf, 54, 0));
|
||||
|
||||
for(u32 y = 0; y < 240; y++)
|
||||
{
|
||||
u8 *line = Draw_ConvertFrameBufferLine(false, true, y);
|
||||
TRY(IFile_Write(&file, &total, line, 3 * 320, 0));
|
||||
}
|
||||
|
||||
if((GPU_FB_TOP_FMT & 0x20) && (Draw_GetCurrentFramebufferAddress(true, true) != Draw_GetCurrentFramebufferAddress(true, false)))
|
||||
{
|
||||
sprintf(filename, "/luma/screenshots/top_right_%04u.bmp", filenum);
|
||||
TRY(IFile_Open(&file, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, filename), FS_OPEN_CREATE | FS_OPEN_WRITE));
|
||||
Draw_CreateBitmapHeader(buf, 400, 240);
|
||||
TRY(IFile_Write(&file, &total, buf, 54, 0));
|
||||
|
||||
for(u32 y = 0; y < 240; y++)
|
||||
{
|
||||
u8 *line = Draw_ConvertFrameBufferLine(true, false, y);
|
||||
TRY(IFile_Write(&file, &total, line, 3 * 400, 0));
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
IFile_Close(&file);
|
||||
Draw_SetupFramebuffer();
|
||||
Draw_ClearFramebuffer();
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
|
||||
do
|
||||
{
|
||||
Draw_Lock();
|
||||
Draw_DrawString(10, 10, COLOR_TITLE, "Screenshot");
|
||||
if(R_FAILED(res))
|
||||
Draw_DrawFormattedString(10, 30, COLOR_WHITE, "Operation failed (0x%08x).", (u32)res);
|
||||
else
|
||||
Draw_DrawString(10, 30, COLOR_WHITE, "Operation succeeded.");
|
||||
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
}
|
||||
while(!(waitInput() & BUTTON_B) && !terminationRequest);
|
||||
|
||||
#undef TRY
|
||||
}
|
||||
165
sysmodules/rosalina/source/menus/debugger.c
Normal file
165
sysmodules/rosalina/source/menus/debugger.c
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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 "menus/debugger.h"
|
||||
#include "memory.h"
|
||||
#include "draw.h"
|
||||
#include "minisoc.h"
|
||||
#include "fmt.h"
|
||||
#include "gdb/server.h"
|
||||
#include "gdb/debug.h"
|
||||
#include "gdb/monitor.h"
|
||||
#include "gdb/net.h"
|
||||
|
||||
Menu debuggerMenu = {
|
||||
"Debugger options menu",
|
||||
.nbItems = 2,
|
||||
{
|
||||
{ "Enable debugger", METHOD, .method = &DebuggerMenu_EnableDebugger },
|
||||
{ "Disable debugger", METHOD, .method = &DebuggerMenu_DisableDebugger }
|
||||
}
|
||||
};
|
||||
|
||||
static MyThread debuggerSocketThread;
|
||||
static MyThread debuggerDebugThread;
|
||||
static u8 ALIGN(8) debuggerSocketThreadStack[0x4000];
|
||||
static u8 ALIGN(8) debuggerDebugThreadStack[0x2000];
|
||||
|
||||
GDBServer gdbServer = { 0 };
|
||||
|
||||
void debuggerSocketThreadMain(void);
|
||||
MyThread *debuggerCreateSocketThread(void)
|
||||
{
|
||||
MyThread_Create(&debuggerSocketThread, debuggerSocketThreadMain, debuggerSocketThreadStack, 0x4000, 0x20, CORE_SYSTEM);
|
||||
return &debuggerSocketThread;
|
||||
}
|
||||
|
||||
void debuggerDebugThreadMain(void);
|
||||
MyThread *debuggerCreateDebugThread(void)
|
||||
{
|
||||
MyThread_Create(&debuggerDebugThread, debuggerDebugThreadMain, debuggerDebugThreadStack, 0x2000, 0x20, CORE_SYSTEM);
|
||||
return &debuggerDebugThread;
|
||||
}
|
||||
|
||||
void DebuggerMenu_EnableDebugger(void)
|
||||
{
|
||||
bool done = false, alreadyEnabled = gdbServer.super.running;
|
||||
Result res = 0;
|
||||
char buf[65];
|
||||
bool cantStart;
|
||||
Handle dummy;
|
||||
|
||||
res = OpenProcessByName("socket", &dummy);
|
||||
cantStart = R_FAILED(res);
|
||||
svcCloseHandle(dummy);
|
||||
|
||||
Draw_Lock();
|
||||
Draw_ClearFramebuffer();
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
|
||||
do
|
||||
{
|
||||
Draw_Lock();
|
||||
Draw_DrawString(10, 10, COLOR_TITLE, "Debugger options menu");
|
||||
|
||||
if(alreadyEnabled)
|
||||
Draw_DrawString(10, 30, COLOR_WHITE, "Already enabled!");
|
||||
else if(cantStart)
|
||||
Draw_DrawString(10, 30, COLOR_WHITE, "Can't start the debugger before the system has fi-\nnished loading.");
|
||||
else
|
||||
{
|
||||
Draw_DrawString(10, 30, COLOR_WHITE, "Starting debugger...");
|
||||
|
||||
if(!done)
|
||||
{
|
||||
res = GDB_InitializeServer(&gdbServer);
|
||||
if(R_SUCCEEDED(res))
|
||||
{
|
||||
debuggerCreateSocketThread();
|
||||
debuggerCreateDebugThread();
|
||||
res = svcWaitSynchronization(gdbServer.super.started_event, 10 * 1000 * 1000 * 1000LL);
|
||||
}
|
||||
|
||||
if(res != 0)
|
||||
sprintf(buf, "Starting debugger... failed (0x%08x).", (u32)res);
|
||||
|
||||
done = true;
|
||||
}
|
||||
if(res == 0)
|
||||
Draw_DrawString(10, 30, COLOR_WHITE, "Starting debugger... OK.");
|
||||
else
|
||||
Draw_DrawString(10, 30, COLOR_WHITE, buf);
|
||||
}
|
||||
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
}
|
||||
while(!(waitInput() & BUTTON_B) && !terminationRequest);
|
||||
}
|
||||
|
||||
void DebuggerMenu_DisableDebugger(void)
|
||||
{
|
||||
bool initialized = gdbServer.referenceCount != 0;
|
||||
Result res = 0;
|
||||
char buf[65];
|
||||
|
||||
if(initialized)
|
||||
{
|
||||
svcSignalEvent(gdbServer.super.shall_terminate_event);
|
||||
res = MyThread_Join(&debuggerDebugThread, 5 * 1000 * 1000 * 1000LL);
|
||||
if(res == 0)
|
||||
res = MyThread_Join(&debuggerSocketThread, 5 * 1000 * 1000 * 1000LL);
|
||||
svcKernelSetState(0x10000, 2);
|
||||
}
|
||||
|
||||
if(res != 0)
|
||||
sprintf(buf, "Failed to disable debugger (0x%08x).", (u32)res);
|
||||
|
||||
do
|
||||
{
|
||||
Draw_Lock();
|
||||
Draw_DrawString(10, 10, COLOR_TITLE, "Debugger options menu");
|
||||
Draw_DrawString(10, 30, COLOR_WHITE, initialized ? (res == 0 ? "Debugger disabled successfully." : buf) : "Debugger not enabled.");
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
}
|
||||
while(!(waitInput() & BUTTON_B) && !terminationRequest);
|
||||
}
|
||||
|
||||
void debuggerSocketThreadMain(void)
|
||||
{
|
||||
GDB_IncrementServerReferenceCount(&gdbServer);
|
||||
GDB_RunServer(&gdbServer);
|
||||
GDB_DecrementServerReferenceCount(&gdbServer);
|
||||
}
|
||||
|
||||
void debuggerDebugThreadMain(void)
|
||||
{
|
||||
GDB_IncrementServerReferenceCount(&gdbServer);
|
||||
GDB_RunMonitor(&gdbServer);
|
||||
GDB_DecrementServerReferenceCount(&gdbServer);
|
||||
}
|
||||
261
sysmodules/rosalina/source/menus/miscellaneous.c
Normal file
261
sysmodules/rosalina/source/menus/miscellaneous.c
Normal file
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* 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 <3ds.h>
|
||||
#include "menus/miscellaneous.h"
|
||||
#include "input_redirection.h"
|
||||
#include "memory.h"
|
||||
#include "draw.h"
|
||||
#include "hbloader.h"
|
||||
#include "fmt.h"
|
||||
#include "utils.h" // for makeARMBranch
|
||||
#include "minisoc.h"
|
||||
|
||||
Menu miscellaneousMenu = {
|
||||
"Miscellaneous options menu",
|
||||
.nbItems = 3,
|
||||
{
|
||||
{ "Switch the hb. title to the current app.", METHOD, .method = &MiscellaneousMenu_SwitchBoot3dsxTargetTitle },
|
||||
{ "Change the menu combo", METHOD, .method = MiscellaneousMenu_ChangeMenuCombo },
|
||||
{ "Start InputRedirection", METHOD, .method = &MiscellaneousMenu_InputRedirection },
|
||||
}
|
||||
};
|
||||
|
||||
void MiscellaneousMenu_SwitchBoot3dsxTargetTitle(void)
|
||||
{
|
||||
Result res;
|
||||
u64 titleId = 0;
|
||||
char failureReason[64];
|
||||
|
||||
if(HBLDR_3DSX_TID == HBLDR_DEFAULT_3DSX_TID)
|
||||
{
|
||||
u32 pidList[0x40];
|
||||
s32 processAmount;
|
||||
|
||||
res = svcGetProcessList(&processAmount, pidList, 0x40);
|
||||
if(R_SUCCEEDED(res))
|
||||
{
|
||||
for(s32 i = 0; i < processAmount && (u32)(titleId >> 32) != 0x00040010 && (u32)(titleId >> 32) != 0x00040000; i++)
|
||||
{
|
||||
Handle processHandle;
|
||||
Result res = svcOpenProcess(&processHandle, pidList[i]);
|
||||
if(R_FAILED(res))
|
||||
continue;
|
||||
|
||||
svcGetProcessInfo((s64 *)&titleId, processHandle, 0x10001);
|
||||
svcCloseHandle(processHandle);
|
||||
}
|
||||
}
|
||||
|
||||
if(R_SUCCEEDED(res) && ((u32)(titleId >> 32) == 0x00040010 || (u32)(titleId >> 32) == 0x00040000))
|
||||
{
|
||||
HBLDR_3DSX_TID = titleId;
|
||||
miscellaneousMenu.items[0].title = "Switch the hb. title to hblauncher_loader";
|
||||
}
|
||||
else if(R_FAILED(res))
|
||||
sprintf(failureReason, "%08x", (u32)res);
|
||||
else
|
||||
{
|
||||
res = -1;
|
||||
strcpy(failureReason, "no suitable process found");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
res = 0;
|
||||
HBLDR_3DSX_TID = HBLDR_DEFAULT_3DSX_TID;
|
||||
miscellaneousMenu.items[0].title = "Switch the hb. title to the current app.";
|
||||
}
|
||||
|
||||
Draw_Lock();
|
||||
Draw_ClearFramebuffer();
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
do
|
||||
{
|
||||
Draw_Lock();
|
||||
Draw_DrawString(10, 10, COLOR_TITLE, "Miscellaneous options menu");
|
||||
|
||||
if(R_SUCCEEDED(res))
|
||||
Draw_DrawString(10, 30, COLOR_WHITE, "Operation succeeded.");
|
||||
else
|
||||
Draw_DrawFormattedString(10, 30, COLOR_WHITE, "Operation failed (%s).", failureReason);
|
||||
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
}
|
||||
while(!(waitInput() & BUTTON_B) && !terminationRequest);
|
||||
}
|
||||
|
||||
static void MiscellaneousMenu_ConvertComboToString(char *out, u32 combo)
|
||||
{
|
||||
static const char *keys[] = { "A", "B", "Select", "Start", "Right", "Left", "Up", "Down", "R", "L", "X", "Y" };
|
||||
for(s32 i = 11; i >= 0; i--)
|
||||
{
|
||||
if(combo & (1 << i))
|
||||
{
|
||||
strcpy(out, keys[i]);
|
||||
out += strlen(keys[i]);
|
||||
*out++ = '+';
|
||||
}
|
||||
}
|
||||
|
||||
out[-1] = 0;
|
||||
}
|
||||
void MiscellaneousMenu_ChangeMenuCombo(void)
|
||||
{
|
||||
char comboStrOrig[64], comboStr[64];
|
||||
u32 posY;
|
||||
|
||||
Draw_Lock();
|
||||
Draw_ClearFramebuffer();
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
|
||||
MiscellaneousMenu_ConvertComboToString(comboStrOrig, menuCombo);
|
||||
|
||||
Draw_Lock();
|
||||
Draw_DrawString(10, 10, COLOR_TITLE, "Miscellaneous options menu");
|
||||
|
||||
posY = Draw_DrawFormattedString(10, 30, COLOR_WHITE, "The current menu combo is: %s", comboStrOrig);
|
||||
posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, "Please enter the new combo:");
|
||||
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
|
||||
menuCombo = waitInput();
|
||||
MiscellaneousMenu_ConvertComboToString(comboStr, menuCombo);
|
||||
|
||||
do
|
||||
{
|
||||
Draw_Lock();
|
||||
Draw_DrawString(10, 10, COLOR_TITLE, "Miscellaneous options menu");
|
||||
|
||||
posY = Draw_DrawFormattedString(10, 30, COLOR_WHITE, "The current menu combo is: %s", comboStrOrig);
|
||||
posY = Draw_DrawFormattedString(10, posY + SPACING_Y, COLOR_WHITE, "Please enter the new combo: %s", comboStr) + SPACING_Y;
|
||||
|
||||
posY = Draw_DrawString(10, posY + SPACING_Y, COLOR_WHITE, "Successfully changed the menu combo.");
|
||||
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
}
|
||||
while(!(waitInput() & BUTTON_B) && !terminationRequest);
|
||||
}
|
||||
|
||||
void MiscellaneousMenu_InputRedirection(void)
|
||||
{
|
||||
static MyThread *inputRedirectionThread = NULL;
|
||||
bool done = false;
|
||||
|
||||
Result res;
|
||||
char buf[65];
|
||||
bool wasEnabled = inputRedirectionEnabled;
|
||||
bool cantStart;
|
||||
|
||||
if(wasEnabled)
|
||||
{
|
||||
res = InputRedirection_DoOrUndoPatches();
|
||||
inputRedirectionEnabled = false;
|
||||
res = MyThread_Join(inputRedirectionThread, 5 * 1000 * 1000 * 1000LL);
|
||||
svcCloseHandle(inputRedirectionThreadStartedEvent);
|
||||
|
||||
if(res != 0)
|
||||
sprintf(buf, "Failed to stop InputRedirection (0x%08x).", (u32)res);
|
||||
else
|
||||
miscellaneousMenu.items[2].title = "Start InputRedirection";
|
||||
}
|
||||
else
|
||||
{
|
||||
Handle dummy;
|
||||
s64 dummyInfo;
|
||||
bool isN3DS = svcGetSystemInfo(&dummyInfo, 0x10001, 0) == 0;
|
||||
|
||||
res = OpenProcessByName("socket", &dummy);
|
||||
cantStart = R_FAILED(res);
|
||||
svcCloseHandle(dummy);
|
||||
|
||||
if(!cantStart && isN3DS)
|
||||
{
|
||||
res = OpenProcessByName("ir", &dummy);
|
||||
cantStart = R_FAILED(res);
|
||||
svcCloseHandle(dummy);
|
||||
}
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
Draw_Lock();
|
||||
Draw_DrawString(10, 10, COLOR_TITLE, "Miscellaneous options menu");
|
||||
|
||||
if(!wasEnabled && cantStart)
|
||||
Draw_DrawString(10, 30, COLOR_WHITE, "Can't start the debugger before the system has fi-\nnished loading.");
|
||||
else if(!wasEnabled)
|
||||
{
|
||||
Draw_DrawString(10, 30, COLOR_WHITE, "Starting InputRedirection...");
|
||||
if(!done)
|
||||
{
|
||||
res = InputRedirection_DoOrUndoPatches();
|
||||
if(R_SUCCEEDED(res))
|
||||
{
|
||||
res = svcCreateEvent(&inputRedirectionThreadStartedEvent, RESET_STICKY);
|
||||
if(R_SUCCEEDED(res))
|
||||
{
|
||||
inputRedirectionThread = inputRedirectionCreateThread();
|
||||
res = svcWaitSynchronization(inputRedirectionThreadStartedEvent, 10 * 1000 * 1000 * 1000LL);
|
||||
if(res == 0)
|
||||
res = (Result)inputRedirectionStartResult;
|
||||
|
||||
if(res != 0)
|
||||
InputRedirection_DoOrUndoPatches();
|
||||
}
|
||||
}
|
||||
|
||||
if(res != 0)
|
||||
sprintf(buf, "Starting InputRedirection... failed (0x%08x).", (u32)res);
|
||||
else
|
||||
miscellaneousMenu.items[2].title = "Stop InputRedirection";
|
||||
|
||||
done = true;
|
||||
}
|
||||
|
||||
if(res == 0)
|
||||
Draw_DrawString(10, 30, COLOR_WHITE, "Starting InputRedirection... OK.");
|
||||
else
|
||||
Draw_DrawString(10, 30, COLOR_WHITE, buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(res == 0)
|
||||
Draw_DrawString(10, 30, COLOR_WHITE, "InputRedirection stopped successfully.");
|
||||
else
|
||||
Draw_DrawString(10, 30, COLOR_WHITE, buf);
|
||||
}
|
||||
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
}
|
||||
while(!(waitInput() & BUTTON_B) && !terminationRequest);
|
||||
}
|
||||
74
sysmodules/rosalina/source/menus/n3ds.c
Normal file
74
sysmodules/rosalina/source/menus/n3ds.c
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 <3ds.h>
|
||||
#include "fmt.h"
|
||||
#include "menus/n3ds.h"
|
||||
#include "memory.h"
|
||||
#include "menu.h"
|
||||
|
||||
static char clkRateBuf[128 + 1];
|
||||
|
||||
Menu N3DSMenu = {
|
||||
"New 3DS menu",
|
||||
.nbItems = 2,
|
||||
{
|
||||
{ "Enable L2 cache", METHOD, .method = &N3DSMenu_EnableDisableL2Cache },
|
||||
{ clkRateBuf, METHOD, .method = &N3DSMenu_ChangeClockRate }
|
||||
}
|
||||
};
|
||||
|
||||
static s64 clkRate = 0, higherClkRate = 0, L2CacheEnabled = 0;
|
||||
|
||||
void N3DSMenu_UpdateStatus(void)
|
||||
{
|
||||
svcGetSystemInfo(&clkRate, 0x10001, 0);
|
||||
svcGetSystemInfo(&higherClkRate, 0x10001, 1);
|
||||
svcGetSystemInfo(&L2CacheEnabled, 0x10001, 2);
|
||||
|
||||
N3DSMenu.items[0].title = L2CacheEnabled ? "Disable L2 cache" : "Enable L2 cache";
|
||||
sprintf(clkRateBuf, "Set clock rate to %uMHz", clkRate != 268 ? 268 : (u32)higherClkRate);
|
||||
}
|
||||
|
||||
void N3DSMenu_ChangeClockRate(void)
|
||||
{
|
||||
N3DSMenu_UpdateStatus();
|
||||
|
||||
s64 newBitMask = (L2CacheEnabled << 1) | ((clkRate != 268 ? 1 : 0) ^ 1);
|
||||
svcKernelSetState(10, (u32)newBitMask);
|
||||
|
||||
N3DSMenu_UpdateStatus();
|
||||
}
|
||||
|
||||
void N3DSMenu_EnableDisableL2Cache(void)
|
||||
{
|
||||
N3DSMenu_UpdateStatus();
|
||||
|
||||
s64 newBitMask = ((L2CacheEnabled ^ 1) << 1) | (clkRate != 268 ? 1 : 0);
|
||||
svcKernelSetState(10, (u32)newBitMask);
|
||||
|
||||
N3DSMenu_UpdateStatus();
|
||||
}
|
||||
229
sysmodules/rosalina/source/menus/process_list.c
Normal file
229
sysmodules/rosalina/source/menus/process_list.c
Normal file
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* 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 <3ds.h>
|
||||
#include "menus/process_list.h"
|
||||
#include "memory.h"
|
||||
#include "draw.h"
|
||||
#include "menu.h"
|
||||
#include "utils.h"
|
||||
#include "fmt.h"
|
||||
#include "gdb/server.h"
|
||||
#include "minisoc.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
typedef struct ProcessInfo
|
||||
{
|
||||
u32 pid;
|
||||
u64 titleId;
|
||||
char name[8];
|
||||
bool isZombie;
|
||||
} ProcessInfo;
|
||||
|
||||
static ProcessInfo infos[0x40] = {0}, infosPrev[0x40] = {0};
|
||||
extern GDBServer gdbServer;
|
||||
|
||||
static inline int ProcessListMenu_FormatInfoLine(char *out, const ProcessInfo *info)
|
||||
{
|
||||
const char *checkbox;
|
||||
u32 id;
|
||||
for(id = 0; id < MAX_DEBUG && (!(gdbServer.ctxs[id].flags & GDB_FLAG_SELECTED) || gdbServer.ctxs[id].pid != info->pid); id++);
|
||||
checkbox = !gdbServer.super.running ? "" : (id < MAX_DEBUG ? "(x) " : "( ) ");
|
||||
|
||||
char commentBuf[23 + 1] = { 0 }; // exactly the size of "Remote: 255.255.255.255"
|
||||
memset(commentBuf, ' ', 23);
|
||||
|
||||
if(info->isZombie)
|
||||
memcpy(commentBuf, "Zombie", 7);
|
||||
|
||||
else if(gdbServer.super.running && id < MAX_DEBUG)
|
||||
{
|
||||
if(gdbServer.ctxs[id].state >= GDB_STATE_CONNECTED && gdbServer.ctxs[id].state < GDB_STATE_CLOSING)
|
||||
{
|
||||
u8 *addr = (u8 *)&gdbServer.ctxs[id].super.addr_in.sin_addr;
|
||||
checkbox = "(A) ";
|
||||
sprintf(commentBuf, "Remote: %hhu.%hhu.%hhu.%hhu", addr[0], addr[1], addr[2], addr[3]);
|
||||
}
|
||||
else
|
||||
{
|
||||
checkbox = "(W) ";
|
||||
sprintf(commentBuf, "Port: %d", GDB_PORT_BASE + id);
|
||||
}
|
||||
}
|
||||
|
||||
return sprintf(out, "%s%-4u %-8.8s %s", checkbox, info->pid, info->name, commentBuf); // Theoritically PIDs are 32-bit ints, but we'll only justify 4 digits
|
||||
}
|
||||
|
||||
static inline void ProcessListMenu_HandleSelected(const ProcessInfo *info)
|
||||
{
|
||||
if(!gdbServer.super.running || info->isZombie)
|
||||
return;
|
||||
|
||||
u32 id;
|
||||
for(id = 0; id < MAX_DEBUG && (!(gdbServer.ctxs[id].flags & GDB_FLAG_SELECTED) || gdbServer.ctxs[id].pid != info->pid); id++);
|
||||
|
||||
GDBContext *ctx = &gdbServer.ctxs[id];
|
||||
|
||||
if(id < MAX_DEBUG)
|
||||
{
|
||||
if(ctx->flags & GDB_FLAG_USED)
|
||||
{
|
||||
RecursiveLock_Lock(&ctx->lock);
|
||||
ctx->super.should_close = true;
|
||||
RecursiveLock_Unlock(&ctx->lock);
|
||||
|
||||
while(ctx->super.should_close)
|
||||
svcSleepThread(12 * 1000 * 1000LL);
|
||||
}
|
||||
else
|
||||
{
|
||||
RecursiveLock_Lock(&ctx->lock);
|
||||
ctx->flags &= ~GDB_FLAG_SELECTED;
|
||||
RecursiveLock_Unlock(&ctx->lock);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(id = 0; id < MAX_DEBUG && gdbServer.ctxs[id].flags & GDB_FLAG_SELECTED; id++);
|
||||
if(id < MAX_DEBUG)
|
||||
{
|
||||
ctx = &gdbServer.ctxs[id];
|
||||
RecursiveLock_Lock(&ctx->lock);
|
||||
ctx->pid = info->pid;
|
||||
ctx->flags |= GDB_FLAG_SELECTED;
|
||||
RecursiveLock_Unlock(&ctx->lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s32 ProcessListMenu_FetchInfo(void)
|
||||
{
|
||||
u32 pidList[0x40];
|
||||
s32 processAmount;
|
||||
|
||||
svcGetProcessList(&processAmount, pidList, 0x40);
|
||||
|
||||
for(s32 i = 0; i < processAmount; i++)
|
||||
{
|
||||
Handle processHandle;
|
||||
Result res = svcOpenProcess(&processHandle, pidList[i]);
|
||||
if(R_FAILED(res))
|
||||
continue;
|
||||
|
||||
infos[i].pid = pidList[i];
|
||||
svcGetProcessInfo((s64 *)&infos[i].name, processHandle, 0x10000);
|
||||
svcGetProcessInfo((s64 *)&infos[i].titleId, processHandle, 0x10001);
|
||||
infos[i].isZombie = svcWaitSynchronization(processHandle, 0) == 0;
|
||||
svcCloseHandle(processHandle);
|
||||
}
|
||||
|
||||
return processAmount;
|
||||
}
|
||||
|
||||
void RosalinaMenu_ProcessList(void)
|
||||
{
|
||||
s32 processAmount = ProcessListMenu_FetchInfo();
|
||||
s32 selected = 0, page = 0, pagePrev = 0;
|
||||
nfds_t nfdsPrev;
|
||||
|
||||
do
|
||||
{
|
||||
nfdsPrev = gdbServer.super.nfds;
|
||||
memcpy(infosPrev, infos, sizeof(infos));
|
||||
|
||||
Draw_Lock();
|
||||
if(page != pagePrev)
|
||||
Draw_ClearFramebuffer();
|
||||
Draw_DrawString(10, 10, COLOR_TITLE, "Process list");
|
||||
|
||||
if(gdbServer.super.running)
|
||||
{
|
||||
char ipBuffer[17];
|
||||
u32 ip = gethostid();
|
||||
u8 *addr = (u8 *)&ip;
|
||||
int n = sprintf(ipBuffer, "%hhu.%hhu.%hhu.%hhu", addr[0], addr[1], addr[2], addr[3]);
|
||||
Draw_DrawString(SCREEN_BOT_WIDTH - 10 - SPACING_X * n, 10, COLOR_WHITE, ipBuffer);
|
||||
}
|
||||
|
||||
|
||||
for(s32 i = 0; i < PROCESSES_PER_MENU_PAGE && page * PROCESSES_PER_MENU_PAGE + i < processAmount; i++)
|
||||
{
|
||||
char buf[65] = {0};
|
||||
ProcessListMenu_FormatInfoLine(buf, &infos[page * PROCESSES_PER_MENU_PAGE + i]);
|
||||
|
||||
Draw_DrawString(30, 30 + i * SPACING_Y, COLOR_WHITE, buf);
|
||||
Draw_DrawCharacter(10, 30 + i * SPACING_Y, COLOR_TITLE, page * PROCESSES_PER_MENU_PAGE + i == selected ? '>' : ' ');
|
||||
}
|
||||
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
|
||||
if(terminationRequest)
|
||||
break;
|
||||
|
||||
u32 pressed;
|
||||
do
|
||||
{
|
||||
pressed = waitInputWithTimeout(50);
|
||||
if(pressed != 0 || nfdsPrev != gdbServer.super.nfds)
|
||||
break;
|
||||
processAmount = ProcessListMenu_FetchInfo();
|
||||
if(memcmp(infos, infosPrev, sizeof(infos)) != 0)
|
||||
break;
|
||||
}
|
||||
while(pressed == 0 && !terminationRequest);
|
||||
|
||||
if(pressed & BUTTON_B)
|
||||
break;
|
||||
else if(pressed & BUTTON_A)
|
||||
ProcessListMenu_HandleSelected(&infos[selected]);
|
||||
else if(pressed & BUTTON_DOWN)
|
||||
selected++;
|
||||
else if(pressed & BUTTON_UP)
|
||||
selected--;
|
||||
else if(pressed & BUTTON_LEFT)
|
||||
selected -= PROCESSES_PER_MENU_PAGE;
|
||||
else if(pressed & BUTTON_RIGHT)
|
||||
{
|
||||
if(selected + PROCESSES_PER_MENU_PAGE < processAmount)
|
||||
selected += PROCESSES_PER_MENU_PAGE;
|
||||
else if((processAmount - 1) / PROCESSES_PER_MENU_PAGE == page)
|
||||
selected %= PROCESSES_PER_MENU_PAGE;
|
||||
else
|
||||
selected = processAmount - 1;
|
||||
}
|
||||
|
||||
if(selected < 0)
|
||||
selected = processAmount - 1;
|
||||
else if(selected >= processAmount)
|
||||
selected = 0;
|
||||
|
||||
pagePrev = page;
|
||||
page = selected / PROCESSES_PER_MENU_PAGE;
|
||||
}
|
||||
while(!terminationRequest);
|
||||
}
|
||||
183
sysmodules/rosalina/source/menus/process_patches.c
Normal file
183
sysmodules/rosalina/source/menus/process_patches.c
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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 <3ds.h>
|
||||
#include "csvc.h"
|
||||
#include "menus/process_patches.h"
|
||||
#include "memory.h"
|
||||
#include "draw.h"
|
||||
#include "hbloader.h"
|
||||
#include "fmt.h"
|
||||
#include "utils.h"
|
||||
|
||||
Menu processPatchesMenu = {
|
||||
"Process patches menu",
|
||||
.nbItems = 2,
|
||||
{
|
||||
{ "Patch SM for the service checks", METHOD, .method = &ProcessPatchesMenu_PatchUnpatchSM },
|
||||
{ "Patch FS for the archive checks", METHOD, .method = &ProcessPatchesMenu_PatchUnpatchFS },
|
||||
}
|
||||
};
|
||||
|
||||
static Result ProcessPatchesMenu_DoPatchUnpatchSM(u32 textTotalRoundedSize)
|
||||
{
|
||||
static bool patched = false;
|
||||
static u32 *off;
|
||||
static u32 origData[7];
|
||||
|
||||
if(patched)
|
||||
{
|
||||
memcpy(off, &origData, sizeof(origData));
|
||||
patched = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
for(off = (u32 *)0x00100000; off < (u32 *)(0x00100000 + textTotalRoundedSize) - 6 &&
|
||||
(off[0] != 0xE1A01006 || off[1] != 0xE1A00005 || off[3] != 0xE3500000 || off[6] != 0xE2850004);
|
||||
off++);
|
||||
|
||||
if(off >= (u32 *)(0x00100000 + textTotalRoundedSize) - 6)
|
||||
return -1;
|
||||
|
||||
memcpy(&origData, off, sizeof(origData));
|
||||
|
||||
off[2] = off[3] = off[4] = off[5] = 0xE320F000; // nop
|
||||
patched = true;
|
||||
}
|
||||
|
||||
processPatchesMenu.items[0].title = patched ? "Unpatch SM for the service checks" : "Patch SM for the service checks";
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Result ProcessPatchesMenu_DoPatchUnpatchFS(u32 textTotalRoundedSize)
|
||||
{
|
||||
static bool patched = false;
|
||||
static u16 *off;
|
||||
static const u16 origData[2] = {
|
||||
0x4618, // mov r0, r3
|
||||
0x3481, // adds r4, #0x81
|
||||
};
|
||||
|
||||
if(patched)
|
||||
{
|
||||
memcpy(off, &origData, sizeof(origData));
|
||||
patched = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
off = (u16 *)memsearch((u8 *)0x00100000, &origData, textTotalRoundedSize, sizeof(origData));
|
||||
if(off == NULL)
|
||||
return -1;
|
||||
|
||||
off[0] = 0x2001; // mov r0, #1
|
||||
off[1] = 0x4770; // bx lr
|
||||
|
||||
patched = true;
|
||||
}
|
||||
|
||||
processPatchesMenu.items[1].title = patched ? "Unpatch FS for the archive checks" : "Patch FS for the archive checks";
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result OpenProcessByName(const char *name, Handle *h)
|
||||
{
|
||||
u32 pidList[0x40];
|
||||
s32 processCount;
|
||||
svcGetProcessList(&processCount, pidList, 0x40);
|
||||
Handle dstProcessHandle = 0;
|
||||
|
||||
for(s32 i = 0; i < processCount; i++)
|
||||
{
|
||||
Handle processHandle;
|
||||
Result res = svcOpenProcess(&processHandle, pidList[i]);
|
||||
if(R_FAILED(res))
|
||||
continue;
|
||||
|
||||
char procName[8] = {0};
|
||||
svcGetProcessInfo((s64 *)procName, processHandle, 0x10000);
|
||||
if(strncmp(procName, name, 8) == 0)
|
||||
dstProcessHandle = processHandle;
|
||||
else
|
||||
svcCloseHandle(processHandle);
|
||||
}
|
||||
|
||||
if(dstProcessHandle == 0)
|
||||
return -1;
|
||||
|
||||
*h = dstProcessHandle;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 ProcessPatchesMenu_PatchUnpatchProcessByName(const char *name, Result (*func)(u32 size))
|
||||
{
|
||||
Result res;
|
||||
Handle processHandle;
|
||||
OpenProcessByName(name, &processHandle);
|
||||
|
||||
s64 textTotalRoundedSize = 0, startAddress = 0;
|
||||
svcGetProcessInfo(&textTotalRoundedSize, processHandle, 0x10002); // only patch .text
|
||||
svcGetProcessInfo(&startAddress, processHandle, 0x10005);
|
||||
if(R_FAILED(res = svcMapProcessMemoryEx(processHandle, 0x00100000, (u32) startAddress, textTotalRoundedSize)))
|
||||
return res;
|
||||
|
||||
res = func(textTotalRoundedSize);
|
||||
|
||||
svcUnmapProcessMemory(processHandle, 0x00100000, textTotalRoundedSize);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void ProcessPatchesMenu_PatchUnpatchProcess(const char *processName, Result (*func)(u32 size))
|
||||
{
|
||||
Draw_Lock();
|
||||
Draw_ClearFramebuffer();
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
|
||||
Result res = ProcessPatchesMenu_PatchUnpatchProcessByName(processName, func);
|
||||
|
||||
do
|
||||
{
|
||||
Draw_Lock();
|
||||
Draw_DrawString(10, 10, COLOR_TITLE, "Process patches menu");
|
||||
if(R_SUCCEEDED(res))
|
||||
Draw_DrawString(10, 30, COLOR_WHITE, "Operation succeeded.");
|
||||
else
|
||||
Draw_DrawFormattedString(10, 30, COLOR_WHITE, "Operation failed (0x%08x).", res);
|
||||
Draw_FlushFramebuffer();
|
||||
Draw_Unlock();
|
||||
}
|
||||
while(!(waitInput() & BUTTON_B) && !terminationRequest);
|
||||
}
|
||||
|
||||
void ProcessPatchesMenu_PatchUnpatchSM(void)
|
||||
{
|
||||
ProcessPatchesMenu_PatchUnpatchProcess("sm", &ProcessPatchesMenu_DoPatchUnpatchSM);
|
||||
}
|
||||
|
||||
void ProcessPatchesMenu_PatchUnpatchFS(void)
|
||||
{
|
||||
ProcessPatchesMenu_PatchUnpatchProcess("fs", &ProcessPatchesMenu_DoPatchUnpatchFS);
|
||||
}
|
||||
434
sysmodules/rosalina/source/minisoc.c
Normal file
434
sysmodules/rosalina/source/minisoc.c
Normal file
@@ -0,0 +1,434 @@
|
||||
/*
|
||||
* 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 "minisoc.h"
|
||||
#include <sys/socket.h>
|
||||
#include <3ds/ipc.h>
|
||||
#include <3ds/os.h>
|
||||
#include <3ds/synchronization.h>
|
||||
#include "memory.h"
|
||||
|
||||
s32 _net_convert_error(s32 sock_retval);
|
||||
|
||||
static Result SOCU_Initialize(Handle memhandle, u32 memsize)
|
||||
{
|
||||
Result ret = 0;
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(0x1,1,4); // 0x10044
|
||||
cmdbuf[1] = memsize;
|
||||
cmdbuf[2] = IPC_Desc_CurProcessHandle();
|
||||
cmdbuf[4] = IPC_Desc_SharedHandles(1);
|
||||
cmdbuf[5] = memhandle;
|
||||
|
||||
ret = svcSendSyncRequest(SOCU_handle);
|
||||
if(ret != 0)
|
||||
return ret;
|
||||
|
||||
return cmdbuf[1];
|
||||
}
|
||||
|
||||
static Result SOCU_Shutdown(void)
|
||||
{
|
||||
Result ret = 0;
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(0x19,0,0); // 0x190000
|
||||
|
||||
ret = svcSendSyncRequest(SOCU_handle);
|
||||
if(ret != 0)
|
||||
return ret;
|
||||
|
||||
return cmdbuf[1];
|
||||
}
|
||||
|
||||
static s32 miniSocRefCount = 0;
|
||||
static u32 socContextAddr = 0x08000000;
|
||||
static u32 socContextSize = 0x60000;
|
||||
// SOCU_handle from ctrulib
|
||||
// socMemhandle from ctrulib
|
||||
|
||||
Result miniSocInit()
|
||||
{
|
||||
if(AtomicPostIncrement(&miniSocRefCount))
|
||||
return 0;
|
||||
|
||||
u32 tmp = 0;
|
||||
Result ret = 0;
|
||||
|
||||
ret = svcControlMemory(&tmp, socContextAddr, 0, socContextSize, MEMOP_ALLOC, MEMPERM_READ | MEMPERM_WRITE);
|
||||
if(ret != 0) goto cleanup;
|
||||
|
||||
socContextAddr = tmp;
|
||||
|
||||
ret = svcCreateMemoryBlock(&socMemhandle, (u32)socContextAddr, socContextSize, 0, 3);
|
||||
if(ret != 0) goto cleanup;
|
||||
|
||||
ret = srvGetServiceHandle(&SOCU_handle, "soc:U");
|
||||
if(ret != 0) goto cleanup;
|
||||
|
||||
ret = SOCU_Initialize(socMemhandle, socContextSize);
|
||||
if(ret != 0) goto cleanup;
|
||||
|
||||
svcKernelSetState(0x10000, 2);
|
||||
return 0;
|
||||
|
||||
cleanup:
|
||||
AtomicDecrement(&miniSocRefCount);
|
||||
|
||||
if(socMemhandle != 0)
|
||||
{
|
||||
svcCloseHandle(socMemhandle);
|
||||
socMemhandle = 0;
|
||||
}
|
||||
|
||||
if(SOCU_handle != 0)
|
||||
{
|
||||
SOCU_Shutdown();
|
||||
svcCloseHandle(SOCU_handle);
|
||||
SOCU_handle = 0;
|
||||
}
|
||||
|
||||
if(tmp != 0)
|
||||
svcControlMemory(&tmp, socContextAddr, socContextAddr, socContextSize, MEMOP_FREE, MEMPERM_DONTCARE);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Result miniSocExit(void)
|
||||
{
|
||||
if(AtomicDecrement(&miniSocRefCount))
|
||||
return 0;
|
||||
|
||||
Result ret = 0;
|
||||
u32 tmp;
|
||||
|
||||
svcCloseHandle(socMemhandle);
|
||||
socMemhandle = 0;
|
||||
|
||||
ret = SOCU_Shutdown();
|
||||
|
||||
svcCloseHandle(SOCU_handle);
|
||||
SOCU_handle = 0;
|
||||
|
||||
svcControlMemory(&tmp, socContextAddr, socContextAddr, socContextSize, MEMOP_FREE, MEMPERM_DONTCARE);
|
||||
if(ret == 0)
|
||||
svcKernelSetState(0x10000, 2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int socSocket(int domain, int type, int protocol)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
|
||||
// The protocol on the 3DS *must* be 0 to work
|
||||
// To that end, when appropriate, we will make the change for the user
|
||||
if (domain == AF_INET
|
||||
&& type == SOCK_STREAM
|
||||
&& protocol == IPPROTO_TCP) {
|
||||
protocol = 0; // TCP is the only option, so 0 will work as expected
|
||||
}
|
||||
if (domain == AF_INET
|
||||
&& type == SOCK_DGRAM
|
||||
&& protocol == IPPROTO_UDP) {
|
||||
protocol = 0; // UDP is the only option, so 0 will work as expected
|
||||
}
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(0x2,3,2); // 0x200C2
|
||||
cmdbuf[1] = domain;
|
||||
cmdbuf[2] = type;
|
||||
cmdbuf[3] = protocol;
|
||||
cmdbuf[4] = IPC_Desc_CurProcessHandle();
|
||||
|
||||
ret = svcSendSyncRequest(SOCU_handle);
|
||||
if(ret != 0)
|
||||
{
|
||||
//errno = SYNC_ERROR;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = (int)cmdbuf[1];
|
||||
if(ret == 0) ret = cmdbuf[2];
|
||||
if(ret < 0)
|
||||
{
|
||||
//if(cmdbuf[1] == 0)errno = _net_convert_error(ret);
|
||||
//if(cmdbuf[1] != 0)errno = SYNC_ERROR;
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
return cmdbuf[2];
|
||||
}
|
||||
|
||||
int socBind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
|
||||
{
|
||||
Result ret = 0;
|
||||
socklen_t tmp_addrlen = 0;
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
u8 tmpaddr[0x1c];
|
||||
|
||||
memset(tmpaddr, 0, 0x1c);
|
||||
|
||||
if(addr->sa_family == AF_INET)
|
||||
tmp_addrlen = 8;
|
||||
else
|
||||
tmp_addrlen = 0x1c;
|
||||
|
||||
if(addrlen < tmp_addrlen)
|
||||
{
|
||||
//errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
tmpaddr[0] = tmp_addrlen;
|
||||
tmpaddr[1] = addr->sa_family;
|
||||
memcpy(&tmpaddr[2], &addr->sa_data, tmp_addrlen-2);
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(0x5,2,4); // 0x50084
|
||||
cmdbuf[1] = (u32)sockfd;
|
||||
cmdbuf[2] = (u32)tmp_addrlen;
|
||||
cmdbuf[3] = IPC_Desc_CurProcessHandle();
|
||||
cmdbuf[5] = IPC_Desc_StaticBuffer(tmp_addrlen,0);
|
||||
cmdbuf[6] = (u32)tmpaddr;
|
||||
|
||||
ret = svcSendSyncRequest(SOCU_handle);
|
||||
if(ret != 0) {
|
||||
//errno = SYNC_ERROR;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = (int)cmdbuf[1];
|
||||
if(ret == 0)
|
||||
ret = _net_convert_error(cmdbuf[2]);
|
||||
|
||||
if(ret < 0) {
|
||||
//errno = -ret;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int socListen(int sockfd, int max_connections)
|
||||
{
|
||||
Result ret = 0;
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(0x3,2,2); // 0x30082
|
||||
cmdbuf[1] = (u32)sockfd;
|
||||
cmdbuf[2] = (u32)max_connections;
|
||||
cmdbuf[3] = IPC_Desc_CurProcessHandle();
|
||||
|
||||
ret = svcSendSyncRequest(SOCU_handle);
|
||||
if(ret != 0)
|
||||
{
|
||||
//errno = SYNC_ERROR;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = (int)cmdbuf[1];
|
||||
if(ret == 0)
|
||||
ret = _net_convert_error(cmdbuf[2]);
|
||||
|
||||
if(ret < 0) {
|
||||
//errno = -ret;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int socAccept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
|
||||
{
|
||||
Result ret = 0;
|
||||
int tmp_addrlen = 0x1c;
|
||||
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
u8 tmpaddr[0x1c];
|
||||
u32 saved_threadstorage[2];
|
||||
|
||||
memset(tmpaddr, 0, 0x1c);
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(0x4,2,2); // 0x40082
|
||||
cmdbuf[1] = (u32)sockfd;
|
||||
cmdbuf[2] = (u32)tmp_addrlen;
|
||||
cmdbuf[3] = IPC_Desc_CurProcessHandle();
|
||||
|
||||
u32 *staticbufs = getThreadStaticBuffers();
|
||||
saved_threadstorage[0] = staticbufs[0];
|
||||
saved_threadstorage[1] = staticbufs[1];
|
||||
|
||||
staticbufs[0] = IPC_Desc_StaticBuffer(tmp_addrlen,0);
|
||||
staticbufs[1] = (u32)tmpaddr;
|
||||
|
||||
ret = svcSendSyncRequest(SOCU_handle);
|
||||
|
||||
staticbufs[0] = saved_threadstorage[0];
|
||||
staticbufs[1] = saved_threadstorage[1];
|
||||
|
||||
if(ret != 0)
|
||||
return ret;
|
||||
|
||||
ret = (int)cmdbuf[1];
|
||||
if(ret == 0)
|
||||
ret = _net_convert_error(cmdbuf[2]);
|
||||
|
||||
if(ret < 0)
|
||||
//errno = -ret;
|
||||
|
||||
if(ret >= 0 && addr != NULL)
|
||||
{
|
||||
addr->sa_family = tmpaddr[1];
|
||||
if(*addrlen > tmpaddr[0])
|
||||
*addrlen = tmpaddr[0];
|
||||
memcpy(addr->sa_data, &tmpaddr[2], *addrlen - 2);
|
||||
}
|
||||
|
||||
if(ret < 0)
|
||||
return -1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int socPoll(struct pollfd *fds, nfds_t nfds, int timeout)
|
||||
{
|
||||
int ret = 0;
|
||||
nfds_t i;
|
||||
u32 size = sizeof(struct pollfd)*nfds;
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
u32 saved_threadstorage[2];
|
||||
|
||||
if(nfds == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for(i = 0; i < nfds; ++i) {
|
||||
fds[i].revents = 0;
|
||||
}
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(0x14,2,4); // 0x140084
|
||||
cmdbuf[1] = (u32)nfds;
|
||||
cmdbuf[2] = (u32)timeout;
|
||||
cmdbuf[3] = IPC_Desc_CurProcessHandle();
|
||||
cmdbuf[5] = IPC_Desc_StaticBuffer(size,10);
|
||||
cmdbuf[6] = (u32)fds;
|
||||
|
||||
u32 * staticbufs = getThreadStaticBuffers();
|
||||
saved_threadstorage[0] = staticbufs[0];
|
||||
saved_threadstorage[1] = staticbufs[1];
|
||||
|
||||
staticbufs[0] = IPC_Desc_StaticBuffer(size,0);
|
||||
staticbufs[1] = (u32)fds;
|
||||
|
||||
ret = svcSendSyncRequest(SOCU_handle);
|
||||
|
||||
staticbufs[0] = saved_threadstorage[0];
|
||||
staticbufs[1] = saved_threadstorage[1];
|
||||
|
||||
if(ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = (int)cmdbuf[1];
|
||||
if(ret == 0)
|
||||
ret = _net_convert_error(cmdbuf[2]);
|
||||
|
||||
if(ret < 0) {
|
||||
//errno = -ret;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int socClose(int sockfd)
|
||||
{
|
||||
int ret = 0;
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(0xB,1,2); // 0xB0042
|
||||
cmdbuf[1] = (u32)sockfd;
|
||||
cmdbuf[2] = IPC_Desc_CurProcessHandle();
|
||||
|
||||
ret = svcSendSyncRequest(SOCU_handle);
|
||||
if(ret != 0) {
|
||||
//errno = SYNC_ERROR;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = (int)cmdbuf[1];
|
||||
if(ret == 0)
|
||||
ret =_net_convert_error(cmdbuf[2]);
|
||||
|
||||
if(ret < 0) {
|
||||
//errno = -ret;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int socSetsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen)
|
||||
{
|
||||
int ret = 0;
|
||||
u32 *cmdbuf = getThreadCommandBuffer();
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(0x12,4,4); // 0x120104
|
||||
cmdbuf[1] = (u32)sockfd;
|
||||
cmdbuf[2] = (u32)level;
|
||||
cmdbuf[3] = (u32)optname;
|
||||
cmdbuf[4] = (u32)optlen;
|
||||
cmdbuf[5] = IPC_Desc_CurProcessHandle();
|
||||
cmdbuf[7] = IPC_Desc_StaticBuffer(optlen,9);
|
||||
cmdbuf[8] = (u32)optval;
|
||||
|
||||
ret = svcSendSyncRequest(SOCU_handle);
|
||||
if(ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = (int)cmdbuf[1];
|
||||
if(ret == 0)
|
||||
ret = _net_convert_error(cmdbuf[2]);
|
||||
|
||||
if(ret < 0) {
|
||||
//errno = -ret;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t soc_recv(int sockfd, void *buf, size_t len, int flags)
|
||||
{
|
||||
return soc_recvfrom(sockfd, buf, len, flags, NULL, 0);
|
||||
}
|
||||
|
||||
ssize_t soc_send(int sockfd, const void *buf, size_t len, int flags)
|
||||
{
|
||||
return soc_sendto(sockfd, buf, len, flags, NULL, 0);
|
||||
}
|
||||
108
sysmodules/rosalina/source/services.c
Normal file
108
sysmodules/rosalina/source/services.c
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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 <3ds.h>
|
||||
#include <string.h>
|
||||
#include "fsreg.h"
|
||||
#include "services.h"
|
||||
|
||||
static Handle *srvHandlePtr;
|
||||
static int srvRefCount;
|
||||
static RecursiveLock initLock;
|
||||
static int initLockinit = 0;
|
||||
|
||||
Result srvSysInit(void)
|
||||
{
|
||||
Result rc = 0;
|
||||
|
||||
if (!initLockinit)
|
||||
RecursiveLock_Init(&initLock);
|
||||
|
||||
RecursiveLock_Lock(&initLock);
|
||||
|
||||
if (srvRefCount > 0)
|
||||
{
|
||||
RecursiveLock_Unlock(&initLock);
|
||||
return MAKERESULT(RL_INFO, RS_NOP, 25, RD_ALREADY_INITIALIZED);
|
||||
}
|
||||
|
||||
srvHandlePtr = srvGetSessionHandle();
|
||||
|
||||
while(1)
|
||||
{
|
||||
rc = svcConnectToPort(srvHandlePtr, "srv:");
|
||||
if (R_LEVEL(rc) != RL_PERMANENT ||
|
||||
R_SUMMARY(rc) != RS_NOTFOUND ||
|
||||
R_DESCRIPTION(rc) != RD_NOT_FOUND
|
||||
) break;
|
||||
svcSleepThread(500 * 1000LL);
|
||||
}
|
||||
|
||||
if(R_SUCCEEDED(rc))
|
||||
{
|
||||
rc = srvRegisterClient();
|
||||
srvRefCount++;
|
||||
}
|
||||
|
||||
RecursiveLock_Unlock(&initLock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result srvSysExit(void)
|
||||
{
|
||||
Result rc = 0;
|
||||
RecursiveLock_Lock(&initLock);
|
||||
|
||||
if(srvRefCount > 1)
|
||||
{
|
||||
srvRefCount--;
|
||||
RecursiveLock_Unlock(&initLock);
|
||||
return MAKERESULT(RL_INFO, RS_NOP, 25, RD_BUSY);
|
||||
}
|
||||
|
||||
if(srvHandlePtr != 0)
|
||||
svcCloseHandle(*srvHandlePtr);
|
||||
else
|
||||
svcBreak(USERBREAK_ASSERT);
|
||||
|
||||
srvHandlePtr = 0;
|
||||
srvRefCount--;
|
||||
|
||||
RecursiveLock_Unlock(&initLock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void fsSysInit(void)
|
||||
{
|
||||
if(R_FAILED(fsregSetupPermissions()))
|
||||
svcBreak(USERBREAK_ASSERT);
|
||||
|
||||
Handle *fsHandlePtr = fsGetSessionHandle();
|
||||
srvGetServiceHandle(fsHandlePtr, "fs:USER");
|
||||
|
||||
FSUSER_InitializeWithSdkVersion(*fsHandlePtr, SDK_VERSION);
|
||||
FSUSER_SetPriority(0);
|
||||
}
|
||||
297
sysmodules/rosalina/source/sock_util.c
Normal file
297
sysmodules/rosalina/source/sock_util.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 <sys/socket.h>
|
||||
#include <3ds/result.h>
|
||||
#include <3ds/svc.h>
|
||||
#include <3ds/synchronization.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "memory.h"
|
||||
#include "minisoc.h"
|
||||
#include "sock_util.h"
|
||||
|
||||
extern Handle terminationRequestEvent;
|
||||
extern bool terminationRequest;
|
||||
|
||||
// soc's poll function is odd, and doesn't like -1 as fd.
|
||||
// so this compacts everything together
|
||||
|
||||
static void compact(struct sock_server *serv)
|
||||
{
|
||||
int new_fds[MAX_CTXS];
|
||||
struct sock_ctx *new_ctxs[MAX_CTXS];
|
||||
nfds_t n = 0;
|
||||
|
||||
for(nfds_t i = 0; i < serv->nfds; i++)
|
||||
{
|
||||
if(serv->poll_fds[i].fd != -1)
|
||||
{
|
||||
new_fds[n] = serv->poll_fds[i].fd;
|
||||
new_ctxs[n] = serv->ctx_ptrs[i];
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
for(nfds_t i = 0; i < n; i++)
|
||||
{
|
||||
serv->poll_fds[i].fd = new_fds[i];
|
||||
serv->ctx_ptrs[i] = new_ctxs[i];
|
||||
serv->ctx_ptrs[i]->i = i;
|
||||
}
|
||||
serv->nfds = n;
|
||||
serv->compact_needed = false;
|
||||
}
|
||||
|
||||
static struct sock_ctx *server_alloc_server_ctx(struct sock_server *serv)
|
||||
{
|
||||
for(int i = 0; i < MAX_PORTS; i++)
|
||||
{
|
||||
if(serv->serv_ctxs[i].type == SOCK_NONE)
|
||||
return &serv->serv_ctxs[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void server_close_ctx(struct sock_server *serv, struct sock_ctx *ctx)
|
||||
{
|
||||
serv->compact_needed = true;
|
||||
|
||||
Handle sock = serv->poll_fds[ctx->i].fd;
|
||||
if(ctx->type == SOCK_CLIENT)
|
||||
{
|
||||
serv->close_cb(ctx);
|
||||
serv->free(serv, ctx);
|
||||
ctx->serv->n--;
|
||||
}
|
||||
|
||||
socClose(sock);
|
||||
ctx->should_close = false;
|
||||
|
||||
serv->poll_fds[ctx->i].fd = -1;
|
||||
serv->poll_fds[ctx->i].events = 0;
|
||||
serv->poll_fds[ctx->i].revents = 0;
|
||||
|
||||
ctx->type = SOCK_NONE;
|
||||
}
|
||||
|
||||
Result server_init(struct sock_server *serv)
|
||||
{
|
||||
Result ret = 0;
|
||||
|
||||
ret = miniSocInit();
|
||||
if(R_FAILED(ret))
|
||||
return ret;
|
||||
memset(serv, 0, sizeof(struct sock_server));
|
||||
|
||||
for(int i = 0; i < MAX_PORTS; i++)
|
||||
serv->serv_ctxs[i].type = SOCK_NONE;
|
||||
|
||||
for(int i = 0; i < MAX_CTXS; i++)
|
||||
serv->ctx_ptrs[i] = NULL;
|
||||
|
||||
ret = svcCreateEvent(&serv->started_event, RESET_STICKY);
|
||||
if(R_FAILED(ret))
|
||||
return ret;
|
||||
return svcCreateEvent(&serv->shall_terminate_event, RESET_STICKY);
|
||||
}
|
||||
|
||||
void server_bind(struct sock_server *serv, u16 port)
|
||||
{
|
||||
int server_sockfd;
|
||||
Handle handles[2] = { terminationRequestEvent, serv->shall_terminate_event };
|
||||
s32 idx = -1;
|
||||
server_sockfd = socSocket(AF_INET, SOCK_STREAM, 0);
|
||||
int res;
|
||||
|
||||
while(server_sockfd == -1)
|
||||
{
|
||||
if(svcWaitSynchronizationN(&idx, handles, 2, false, 100 * 1000 * 1000LL) == 0)
|
||||
return;
|
||||
|
||||
server_sockfd = socSocket(AF_INET, SOCK_STREAM, 0);
|
||||
}
|
||||
|
||||
if(server_sockfd != -1)
|
||||
{
|
||||
struct sockaddr_in saddr;
|
||||
saddr.sin_family = AF_INET;
|
||||
saddr.sin_port = htons(port);
|
||||
saddr.sin_addr.s_addr = gethostid();
|
||||
|
||||
res = socBind(server_sockfd, (struct sockaddr*)&saddr, sizeof(struct sockaddr_in));
|
||||
|
||||
if(res == 0)
|
||||
{
|
||||
res = socListen(server_sockfd, 2);
|
||||
if(res == 0)
|
||||
{
|
||||
int idx = serv->nfds;
|
||||
serv->nfds++;
|
||||
serv->poll_fds[idx].fd = server_sockfd;
|
||||
serv->poll_fds[idx].events = POLLIN;
|
||||
|
||||
struct sock_ctx *new_ctx = server_alloc_server_ctx(serv);
|
||||
memcpy(&new_ctx->addr_in, &saddr, sizeof(struct sockaddr_in));
|
||||
new_ctx->type = SOCK_SERVER;
|
||||
new_ctx->sockfd = server_sockfd;
|
||||
new_ctx->n = 0;
|
||||
new_ctx->i = idx;
|
||||
serv->ctx_ptrs[idx] = new_ctx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void server_run(struct sock_server *serv)
|
||||
{
|
||||
struct pollfd *fds = serv->poll_fds;
|
||||
Handle handles[2] = { terminationRequestEvent, serv->shall_terminate_event };
|
||||
|
||||
serv->running = true;
|
||||
svcSignalEvent(serv->started_event);
|
||||
|
||||
while(serv->running && !terminationRequest)
|
||||
{
|
||||
s32 idx = -1;
|
||||
if(svcWaitSynchronizationN(&idx, handles, 2, false, 0LL) == 0)
|
||||
goto abort_connections;
|
||||
|
||||
if(serv->nfds == 0)
|
||||
{
|
||||
if(svcWaitSynchronizationN(&idx, handles, 2, false, 12 * 1000 * 1000LL) == 0)
|
||||
goto abort_connections;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
for(nfds_t i = 0; i < serv->nfds; i++)
|
||||
fds[i].revents = 0;
|
||||
int pollres = socPoll(fds, serv->nfds, 50);
|
||||
|
||||
for(nfds_t i = 0; pollres > 0 && i < serv->nfds; i++)
|
||||
{
|
||||
struct sock_ctx *curr_ctx = serv->ctx_ptrs[i];
|
||||
|
||||
if((fds[i].revents & POLLHUP) || curr_ctx->should_close)
|
||||
server_close_ctx(serv, curr_ctx);
|
||||
|
||||
else if(fds[i].revents & POLLIN)
|
||||
{
|
||||
if(curr_ctx->type == SOCK_SERVER) // Listening socket?
|
||||
{
|
||||
struct sockaddr_in saddr;
|
||||
socklen_t len = sizeof(struct sockaddr_in);
|
||||
int client_sockfd = socAccept(fds[i].fd, (struct sockaddr *)&saddr, &len);
|
||||
|
||||
if(svcWaitSynchronizationN(&idx, handles, 2, false, 0LL) == 0)
|
||||
goto abort_connections;
|
||||
|
||||
if(client_sockfd < 0 || curr_ctx->n == serv->clients_per_server || serv->nfds == MAX_CTXS)
|
||||
socClose(client_sockfd);
|
||||
|
||||
else
|
||||
{
|
||||
struct sock_ctx *new_ctx = serv->alloc(serv, ntohs(curr_ctx->addr_in.sin_port));
|
||||
if(new_ctx == NULL)
|
||||
socClose(client_sockfd);
|
||||
else
|
||||
{
|
||||
fds[serv->nfds].fd = client_sockfd;
|
||||
fds[serv->nfds].events = POLLIN;
|
||||
|
||||
int new_idx = serv->nfds;
|
||||
serv->nfds++;
|
||||
curr_ctx->n++;
|
||||
|
||||
new_ctx->type = SOCK_CLIENT;
|
||||
new_ctx->sockfd = client_sockfd;
|
||||
new_ctx->serv = curr_ctx;
|
||||
new_ctx->i = new_idx;
|
||||
new_ctx->n = 0;
|
||||
|
||||
serv->ctx_ptrs[new_idx] = new_ctx;
|
||||
|
||||
if(serv->accept_cb(new_ctx) == -1)
|
||||
server_close_ctx(serv, new_ctx);
|
||||
|
||||
memcpy(&new_ctx->addr_in, &saddr, sizeof(struct sockaddr_in));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(serv->data_cb(curr_ctx) == -1)
|
||||
server_close_ctx(serv, curr_ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(serv->compact_needed)
|
||||
compact(serv);
|
||||
}
|
||||
|
||||
// Clean up.
|
||||
for(unsigned int i = 0; i < serv->nfds; i++)
|
||||
{
|
||||
if(fds[i].fd != -1)
|
||||
socClose(fds[i].fd);
|
||||
}
|
||||
|
||||
serv->running = false;
|
||||
svcClearEvent(serv->started_event);
|
||||
return;
|
||||
|
||||
abort_connections:
|
||||
for(unsigned int i = 0; i < serv->nfds; i++)
|
||||
{
|
||||
struct linger linger;
|
||||
linger.l_onoff = 1;
|
||||
linger.l_linger = 0;
|
||||
|
||||
socSetsockopt(fds[i].fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(struct linger));
|
||||
socClose(fds[i].fd);
|
||||
}
|
||||
|
||||
serv->running = false;
|
||||
svcClearEvent(serv->started_event);
|
||||
}
|
||||
|
||||
void server_finalize(struct sock_server *serv)
|
||||
{
|
||||
for(nfds_t i = 0; i < MAX_CTXS; i++)
|
||||
{
|
||||
if(serv->ctx_ptrs[i] != NULL)
|
||||
server_close_ctx(serv, serv->ctx_ptrs[i]);
|
||||
}
|
||||
|
||||
miniSocExit();
|
||||
|
||||
svcClearEvent(serv->shall_terminate_event);
|
||||
svcCloseHandle(serv->shall_terminate_event);
|
||||
svcClearEvent(serv->started_event);
|
||||
svcCloseHandle(serv->started_event);
|
||||
}
|
||||
Reference in New Issue
Block a user