thermosphere: rewrite gdb/mem

This commit is contained in:
TuxSH 2020-01-24 00:52:55 +00:00
parent bd36796d5f
commit 58d52675cd
6 changed files with 95 additions and 245 deletions

View file

@ -15,7 +15,8 @@
// 512+24 is the ideal size as IDA will try to read exactly 0x100 bytes at a time. Add 4 to this, for $#<checksum>, see below. // 512+24 is the ideal size as IDA will try to read exactly 0x100 bytes at a time. Add 4 to this, for $#<checksum>, see below.
// IDA seems to want additional bytes as well. // IDA seems to want additional bytes as well.
// 1024 is fine enough to put all regs in the 'T' stop reply packets // 1024 is fine enough to put all regs in the 'T' stop reply packets
#define GDB_BUF_LEN 2048 #define GDB_BUF_LEN 0x800
#define GDB_WORK_BUF_LEN 0x2000
#define GDB_HANDLER(name) GDB_Handle##name #define GDB_HANDLER(name) GDB_Handle##name
#define GDB_QUERY_HANDLER(name) GDB_HANDLER(Query##name) #define GDB_QUERY_HANDLER(name) GDB_HANDLER(Query##name)
@ -97,6 +98,7 @@ typedef struct GDBContext
char *commandData, *commandEnd; char *commandData, *commandEnd;
int latestSentPacketSize; int latestSentPacketSize;
char buffer[GDB_BUF_LEN + 4]; char buffer[GDB_BUF_LEN + 4];
char *workBuffer;
} GDBContext; } GDBContext;
typedef int (*GDBCommandHandler)(GDBContext *ctx); typedef int (*GDBCommandHandler)(GDBContext *ctx);

View file

@ -262,7 +262,7 @@ int GDB_SendStopReply(GDBContext *ctx, const DebugEventInfo *info)
size_t sent = 0; size_t sent = 0;
size_t total = 0; size_t total = 0;
while (remaining > 0) { while (remaining > 0) {
u32 pending = (GDB_BUF_LEN - 1) / 2; size_t pending = (GDB_BUF_LEN - 1) / 2;
pending = pending < remaining ? pending : remaining; pending = pending < remaining ? pending : remaining;
int res = GDB_SendMemory(ctx, "O", 1, addr + sent, pending); int res = GDB_SendMemory(ctx, "O", 1, addr + sent, pending);

View file

@ -5,228 +5,70 @@
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later) * SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/ */
#include "gdb/mem.h" #include <string.h>
#include "gdb/net.h" #include "mem.h"
#include "utils.h" #include "net.h"
#include "../guest_memory.h"
#include "../pattern_utils.h"
static void *k_memcpy_no_interrupt(void *dst, const void *src, u32 len) int GDB_SendMemory(GDBContext *ctx, const char *prefix, size_t prefixLen, size_t addr, size_t len)
{ {
__asm__ volatile("cpsid aif"); char *buf = ctx->buffer + 1;
return memcpy(dst, src, len); char *membuf = ctx->workBuffer;
}
Result GDB_ReadTargetMemoryInPage(void *out, GDBContext *ctx, u32 addr, u32 len) if(prefix != NULL) {
{ memmove(buf, prefix, prefixLen);
s64 TTBCR;
svcGetSystemInfo(&TTBCR, 0x10002, 0);
if(addr < (1u << (32 - (u32)TTBCR))) // Note: UB with user-mapped MMIO (uses memcpy).
return svcReadProcessMemory(out, ctx->debug, addr, len);
else if(!ctx->enableExternalMemoryAccess)
return -1;
else if(addr >= 0x80000000 && addr < 0xB0000000)
{
if(addr >= 0x90000000 && addr < 0x98000000) // IO
{
for(u32 off = 0; off < len; )
{
if((addr + off) & 1)
{
*((u8 *)out + off) = *(vu8 *)(addr + off);
off += 1;
}
else if((addr + off) & 3)
{
*((u16 *)out + off) = *(vu16 *)(addr + off);
off += 2;
}
else
{
*((u32 *)out + off) = *(vu32 *)(addr + off);
off += 4;
}
}
}
else memcpy(out, (const void *)addr, len);
return 0;
} }
else 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_WriteTargetMemoryInPage(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(!ctx->enableExternalMemoryAccess)
return -1;
else if(addr >= 0x80000000 && addr < 0xB0000000)
{
if(addr >= 0x90000000 && addr < 0x98000000) // IO
{
for(u32 off = 0; off < len; )
{
if((addr + off) & 1)
{
*(vu8 *)(addr + off) = *((u8 *)in + off);
off += 1;
}
else if((addr + off) & 3)
{
*(vu16 *)(addr + off) = *((u16 *)in + off);
off += 2;
}
else
{
*(vu32 *)(addr + off) = *((u32 *)in + off);
off += 4;
}
}
}
else 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_WriteTargetMemoryInPage(ctx, PA_FROM_VA_PTR(in), addr, len);
svcFlushEntireDataCache();
svcInvalidateEntireInstructionCache();
return ret;
}
}
}
u32 GDB_ReadTargetMemory(void *out, GDBContext *ctx, u32 addr, u32 len)
{
Result r = 0;
u32 remaining = len, total = 0;
u8 *out8 = (u8 *)out;
do
{
u32 nb = (remaining > 0x1000 - (addr & 0xFFF)) ? 0x1000 - (addr & 0xFFF) : remaining;
r = GDB_ReadTargetMemoryInPage(out8 + total, ctx, addr, nb);
if(R_SUCCEEDED(r))
{
addr += nb;
total += nb;
remaining -= nb;
}
}
while(remaining > 0 && R_SUCCEEDED(r));
return total;
}
u32 GDB_WriteTargetMemory(GDBContext *ctx, const void *in, 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_WriteTargetMemoryInPage(ctx, (u8 *)in + total, addr, nb);
if(R_SUCCEEDED(r))
{
addr += nb;
total += nb;
remaining -= nb;
}
}
while(remaining > 0 && R_SUCCEEDED(r));
return total;
}
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; prefixLen = 0;
}
if(prefixLen + 2 * len > GDB_BUF_LEN) // gdb shouldn't send requests which responses don't fit in a packet 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; return prefix == NULL ? GDB_ReplyErrno(ctx, ENOMEM) : -1;
}
u32 total = GDB_ReadTargetMemory(membuf, ctx, addr, len); size_t total = guestReadMemory(addr, len, membuf);
if(total == 0)
if (total == 0) {
return prefix == NULL ? GDB_ReplyErrno(ctx, EFAULT) : -EFAULT; return prefix == NULL ? GDB_ReplyErrno(ctx, EFAULT) : -EFAULT;
else } else {
{
GDB_EncodeHex(buf + prefixLen, membuf, total); GDB_EncodeHex(buf + prefixLen, membuf, total);
return GDB_SendPacket(ctx, buf, prefixLen + 2 * total); return GDB_SendPacket(ctx, buf, prefixLen + 2 * total);
} }
} }
int GDB_WriteMemory(GDBContext *ctx, const void *buf, u32 addr, u32 len) int GDB_WriteMemory(GDBContext *ctx, const void *buf, uintptr_t addr, size_t len)
{ {
u32 total = GDB_WriteTargetMemory(ctx, buf, addr, len); size_t total = guestWriteMemory(addr, len, buf);
if(total != len) return total == len ? GDB_ReplyOk(ctx) : GDB_ReplyErrno(ctx, EFAULT);
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) u32 GDB_SearchMemory(bool *found, GDBContext *ctx, uintptr_t addr, size_t len, const void *pattern, size_t patternLen)
{ {
u8 buf[0x1000 + 0x1000 * ((GDB_BUF_LEN + 0xFFF) / 0x1000)]; // Note: need to ensure GDB_WORK_BUF_LEN is at least 0x1000 bytes in size
u32 maxNbPages = 1 + ((GDB_BUF_LEN + 0xFFF) / 0x1000);
u32 curAddr = addr;
s64 TTBCR; char *buf = ctx->workBuffer;
svcGetSystemInfo(&TTBCR, 0x10002, 0); size_t maxNbPages = GDB_WORK_BUF_LEN / 0x1000;
while(curAddr < addr + len) uintptr_t curAddr = addr;
{
u32 nbPages;
u32 addrBase = curAddr & ~0xFFF, addrDispl = curAddr & 0xFFF;
for(nbPages = 0; nbPages < maxNbPages; nbPages++) while (curAddr < addr + len) {
{ size_t nbPages;
if(addr >= (1u << (32 - (u32)TTBCR))) uintptr_t addrBase = curAddr & ~0xFFF;
{ size_t addrDispl = curAddr & 0xFFF;
u32 PA = svcConvertVAToPA((const void *)addr, false);
if(PA == 0 || (PA >= 0x10000000 && PA <= 0x18000000))
break;
}
if(R_FAILED(GDB_ReadTargetMemoryInPage(buf + 0x1000 * nbPages, ctx, addrBase + nbPages * 0x1000, 0x1000))) for (nbPages = 0; nbPages < maxNbPages; nbPages++) {
if (guestReadMemory(addrBase + nbPages * 0x1000, 0x1000, buf + nbPages * 0x1000) != 0x1000) {
break; break;
}
} }
u8 *pos = NULL; u8 *pos = NULL;
if(addrDispl + patternLen <= 0x1000 * nbPages) if(addrDispl + patternLen <= 0x1000 * nbPages) {
pos = memsearch(buf + addrDispl, pattern, 0x1000 * nbPages - addrDispl, patternLen); pos = memsearch(buf + addrDispl, pattern, 0x1000 * nbPages - addrDispl, patternLen);
}
if(pos != NULL) if(pos != NULL) {
{
*found = true; *found = true;
return addrBase + (pos - buf); return addrBase + (pos - buf);
} }
@ -240,92 +82,101 @@ u32 GDB_SearchMemory(bool *found, GDBContext *ctx, u32 addr, u32 len, const void
GDB_DECLARE_HANDLER(ReadMemory) GDB_DECLARE_HANDLER(ReadMemory)
{ {
u32 lst[2]; unsigned long lst[2];
if(GDB_ParseHexIntegerList(lst, ctx->commandData, 2, 0) == NULL) if (GDB_ParseHexIntegerList(lst, ctx->commandData, 2, 0) == NULL) {
return GDB_ReplyErrno(ctx, EILSEQ); return GDB_ReplyErrno(ctx, EILSEQ);
}
u32 addr = lst[0]; uintptr_t addr = lst[0];
u32 len = lst[1]; size_t len = lst[1];
return GDB_SendMemory(ctx, NULL, 0, addr, len); return GDB_SendMemory(ctx, NULL, 0, addr, len);
} }
GDB_DECLARE_HANDLER(WriteMemory) GDB_DECLARE_HANDLER(WriteMemory)
{ {
u32 lst[2]; unsigned long lst[2];
const char *dataStart = GDB_ParseHexIntegerList(lst, ctx->commandData, 2, ':'); const char *dataStart = GDB_ParseHexIntegerList(lst, ctx->commandData, 2, ':');
if(dataStart == NULL || *dataStart != ':') if (dataStart == NULL || *dataStart != ':') {
return GDB_ReplyErrno(ctx, EILSEQ); return GDB_ReplyErrno(ctx, EILSEQ);
}
dataStart++; dataStart++;
u32 addr = lst[0]; uintptr_t addr = lst[0];
u32 len = lst[1]; size_t len = lst[1];
if(dataStart + 2 * len >= ctx->buffer + 4 + GDB_BUF_LEN) if(dataStart + 2 * len >= ctx->buffer + 4 + GDB_BUF_LEN) {
// Data len field doesn't match what we got...
return GDB_ReplyErrno(ctx, ENOMEM); return GDB_ReplyErrno(ctx, ENOMEM);
}
u8 data[GDB_BUF_LEN / 2]; size_t n = GDB_DecodeHex(ctx->workBuffer, dataStart, len);
u32 n = GDB_DecodeHex(data, dataStart, len);
if(n != len) if(n != len) {
// Decoding error...
return GDB_ReplyErrno(ctx, EILSEQ); return GDB_ReplyErrno(ctx, EILSEQ);
}
return GDB_WriteMemory(ctx, data, addr, len); return GDB_WriteMemory(ctx, ctx->workBuffer, addr, len);
} }
GDB_DECLARE_HANDLER(WriteMemoryRaw) GDB_DECLARE_HANDLER(WriteMemoryRaw)
{ {
u32 lst[2]; unsigned long lst[2];
const char *dataStart = GDB_ParseHexIntegerList(lst, ctx->commandData, 2, ':'); const char *dataStart = GDB_ParseHexIntegerList(lst, ctx->commandData, 2, ':');
if(dataStart == NULL || *dataStart != ':') if (dataStart == NULL || *dataStart != ':') {
return GDB_ReplyErrno(ctx, EILSEQ); return GDB_ReplyErrno(ctx, EILSEQ);
}
dataStart++; dataStart++;
u32 addr = lst[0]; uintptr_t addr = lst[0];
u32 len = lst[1]; size_t len = lst[1];
if(dataStart + len >= ctx->buffer + 4 + GDB_BUF_LEN) if(dataStart + 2 * len >= ctx->buffer + 4 + GDB_BUF_LEN) {
// Data len field doesn't match what we got...
return GDB_ReplyErrno(ctx, ENOMEM); return GDB_ReplyErrno(ctx, ENOMEM);
}
u8 data[GDB_BUF_LEN]; // Note: could be done in place in ctx->buffer...
u32 n = GDB_UnescapeBinaryData(data, dataStart, len); size_t n = GDB_UnescapeBinaryData(ctx->workBuffer, dataStart, len);
if(n != len) if(n != len) {
// Decoding error...
return GDB_ReplyErrno(ctx, n); return GDB_ReplyErrno(ctx, n);
}
return GDB_WriteMemory(ctx, data, addr, len); return GDB_WriteMemory(ctx, ctx->workBuffer, addr, len);
} }
GDB_DECLARE_QUERY_HANDLER(SearchMemory) GDB_DECLARE_QUERY_HANDLER(SearchMemory)
{ {
u32 lst[2]; unsigned long lst[2];
u32 addr, len;
u8 pattern[GDB_BUF_LEN];
const char *patternStart; const char *patternStart;
u32 patternLen; size_t patternLen;
bool found; bool found;
u32 foundAddr; u32 foundAddr;
if(strncmp(ctx->commandData, "memory:", 7) != 0) if (strncmp(ctx->commandData, "memory:", 7) != 0) {
return GDB_ReplyErrno(ctx, EILSEQ); return GDB_ReplyErrno(ctx, EILSEQ);
}
ctx->commandData += 7; ctx->commandData += 7;
patternStart = GDB_ParseIntegerList(lst, ctx->commandData, 2, ';', ';', 16, false); patternStart = GDB_ParseIntegerList(lst, ctx->commandData, 2, ';', ';', 16, false);
if(patternStart == NULL || *patternStart != ';') if (patternStart == NULL || *patternStart != ';') {
return GDB_ReplyErrno(ctx, EILSEQ); return GDB_ReplyErrno(ctx, EILSEQ);
}
addr = lst[0]; uintptr_t addr = lst[0];
len = lst[1]; size_t len = lst[1];
patternStart++; patternStart++;
patternLen = ctx->commandEnd - patternStart; patternLen = ctx->commandEnd - patternStart;
// Unescape pattern in place
char *pattern = patternStart;
patternLen = GDB_UnescapeBinaryData(pattern, patternStart, patternLen); patternLen = GDB_UnescapeBinaryData(pattern, patternStart, patternLen);
foundAddr = GDB_SearchMemory(&found, ctx, addr, len, patternStart, patternLen); foundAddr = GDB_SearchMemory(&found, ctx, addr, len, patternStart, patternLen);
if(found) return found ? GDB_SendFormattedPacket(ctx, "1,%x", foundAddr) : GDB_SendPacket(ctx, "0", 1);
return GDB_SendFormattedPacket(ctx, "1,%x", foundAddr);
else
return GDB_SendPacket(ctx, "0", 1);
} }

View file

@ -9,14 +9,9 @@
#include "gdb.h" #include "gdb.h"
Result GDB_ReadTargetMemoryInPage(void *out, GDBContext *ctx, u32 addr, u32 len); int GDB_SendMemory(GDBContext *ctx, const char *prefix, size_t prefixLen, uintptr_t addr, size_t len);
Result GDB_WriteTargetMemoryInPage(GDBContext *ctx, const void *in, u32 addr, u32 len); int GDB_WriteMemory(GDBContext *ctx, const void *buf, uintptr_t addr, size_t len);
u32 GDB_ReadTargetMemory(void *out, GDBContext *ctx, u32 addr, u32 len); u32 GDB_SearchMemory(bool *found, GDBContext *ctx, size_t addr, size_t len, const void *pattern, size_t patternLen);
u32 GDB_WriteTargetMemory(GDBContext *ctx, const void *in, u32 addr, u32 len);
int GDB_SendMemory(GDBContext *ctx, const char *prefix, u32 prefixLen, u32 addr, u32 len);
int GDB_WriteMemory(GDBContext *ctx, const void *buf, u32 addr, u32 len);
u32 GDB_SearchMemory(bool *found, GDBContext *ctx, u32 addr, u32 len, const void *pattern, u32 patternLen);
GDB_DECLARE_HANDLER(ReadMemory); GDB_DECLARE_HANDLER(ReadMemory);
GDB_DECLARE_HANDLER(WriteMemory); GDB_DECLARE_HANDLER(WriteMemory);

View file

@ -8,8 +8,10 @@
#include "gdb/net.h" #include "gdb/net.h"
#include <stdarg.h> #include <stdarg.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include "fmt.h" #include "fmt.h"
#include "minisoc.h" #include "minisoc.h"
#include "../pattern_utils.h"
u8 GDB_ComputeChecksum(const char *packetData, size_t len) u8 GDB_ComputeChecksum(const char *packetData, size_t len)
{ {
@ -45,7 +47,7 @@ static inline u32 GDB_DecodeHexDigit(char src, bool *ok)
} }
} }
u32 GDB_DecodeHex(void *dst, const char *src, size_t len) { size_t GDB_DecodeHex(void *dst, const char *src, size_t len) {
size_t i = 0; size_t i = 0;
bool ok = true; bool ok = true;
u8 *dst8 = (u8 *)dst; u8 *dst8 = (u8 *)dst;
@ -56,7 +58,7 @@ u32 GDB_DecodeHex(void *dst, const char *src, size_t len) {
return (!ok) ? i - 1 : i; return (!ok) ? i - 1 : i;
} }
u32 GDB_EscapeBinaryData(u32 *encodedCount, void *dst, const void *src, size_t len, size_t maxLen) size_t GDB_EscapeBinaryData(size_t *encodedCount, void *dst, const void *src, size_t len, size_t maxLen)
{ {
u8 *dst8 = (u8 *)dst; u8 *dst8 = (u8 *)dst;
const u8 *src8 = (const u8 *)src; const u8 *src8 = (const u8 *)src;
@ -80,7 +82,7 @@ u32 GDB_EscapeBinaryData(u32 *encodedCount, void *dst, const void *src, size_t l
return src8 - (u8 *)src; return src8 - (u8 *)src;
} }
u32 GDB_UnescapeBinaryData(void *dst, const void *src, size_t len) size_t GDB_UnescapeBinaryData(void *dst, const void *src, size_t len)
{ {
u8 *dst8 = (u8 *)dst; u8 *dst8 = (u8 *)dst;
const u8 *src8 = (const u8 *)src; const u8 *src8 = (const u8 *)src;

View file

@ -13,9 +13,9 @@
u8 GDB_ComputeChecksum(const char *packetData, size_t len); u8 GDB_ComputeChecksum(const char *packetData, size_t len);
void GDB_EncodeHex(char *dst, const void *src, size_t len); void GDB_EncodeHex(char *dst, const void *src, size_t len);
u32 GDB_DecodeHex(void *dst, const char *src, size_t len); size_t GDB_DecodeHex(void *dst, const char *src, size_t len);
u32 GDB_EscapeBinaryData(u32 *encodedCount, void *dst, const void *src, size_t len, size_t maxLen); size_t GDB_EscapeBinaryData(size_t *encodedCount, void *dst, const void *src, size_t len, size_t maxLen);
u32 GDB_UnescapeBinaryData(void *dst, const void *src, size_t len); size_t GDB_UnescapeBinaryData(void *dst, const void *src, size_t len);
const char *GDB_ParseIntegerList(unsigned long *dst, const char *src, size_t nb, char sep, char lastSep, u32 base, bool allowPrefix); const char *GDB_ParseIntegerList(unsigned long *dst, const char *src, size_t nb, char sep, char lastSep, u32 base, bool allowPrefix);
const char *GDB_ParseHexIntegerList(unsigned long *dst, const char *src, size_t nb, char lastSep); const char *GDB_ParseHexIntegerList(unsigned long *dst, const char *src, size_t nb, char lastSep);