mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2025-07-27 00:00:07 -06:00
GDB stub (#1583)
* gdbstub beginnings * gdbstub: finish gdb impl things, next up is integration with melonDS * holy fuck the gdbstub works * gdb breakpoints work, but there's a mysterious crash on continue * fix memory corruption that sometimes happened, and make resetting the console thru gdb work * remove some gdb debug printing * fix things in gdbstub * separate option for enabling gdbstub * add mode-dependent CPU registers * C++ize the GDBstub code * add gdbstub config in emu settings dialog * make sure gdb is disabled when jit is enabled * Remove unnecessary compiler flags, mark ARMJIT assembly code as no-execute-stack This hardens the binary a little bit against common exploitation methods * add option to wait for debugger attach on startup * only insert GNU stack notes on linux * disable gdbstub enable checkbox when jit is enabled * fix non-linux incompatibilities * enable gdbstub by default * fix issues with gdbstub settings disable stuff * format stuff * update gdb test code * Fix segfault when calling StubCallbacks->GetCPU() C++ overrides are hard. Please I'm just a lowly C programmer. * fix packet size not being sent correctly Thanks to @GlowingUmbreon on Github for troubleshooting this * fix select(2) calls (i should read docs more properly) * fix GDB command sequencing/parsing issue (hopefully) * [GDB] implement no-ack mode * fix sending ack on handshake * get lldb to work
This commit is contained in:
389
src/debug/GdbProto.cpp
Normal file
389
src/debug/GdbProto.cpp
Normal file
@ -0,0 +1,389 @@
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <WS2tcpip.h>
|
||||
#include <winsock.h>
|
||||
#include <winsock2.h>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <poll.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include "../Platform.h"
|
||||
#include "hexutil.h"
|
||||
|
||||
#include "GdbProto.h"
|
||||
|
||||
|
||||
using Platform::Log;
|
||||
using Platform::LogLevel;
|
||||
|
||||
namespace Gdb
|
||||
{
|
||||
|
||||
/*
|
||||
* TODO commands to support:
|
||||
* m M g G c s p P H
|
||||
* ? D r
|
||||
* qC qfThreadInfo qsThreadInfo
|
||||
* z0 Z0 z1 Z1 z4 Z4
|
||||
* qCRC
|
||||
* vAttach;addr
|
||||
* vKill;pid
|
||||
* qRcmd? qSupported?
|
||||
*/
|
||||
u8 Cmdbuf[GDBPROTO_BUFFER_CAPACITY];
|
||||
ssize_t Cmdlen;
|
||||
|
||||
namespace Proto
|
||||
{
|
||||
|
||||
u8 PacketBuf[GDBPROTO_BUFFER_CAPACITY];
|
||||
u8 RespBuf[GDBPROTO_BUFFER_CAPACITY+5];
|
||||
|
||||
ReadResult MsgRecv(int connfd, u8 cmd_dest[/*static GDBPROTO_BUFFER_CAPACITY*/])
|
||||
{
|
||||
static ssize_t dataoff = 0;
|
||||
|
||||
ssize_t recv_total = dataoff;
|
||||
ssize_t cksumoff = -1;
|
||||
u8 sum = 0;
|
||||
|
||||
bool first = true;
|
||||
|
||||
//printf("--- dataoff=%zd\n", dataoff);
|
||||
if (dataoff != 0) {
|
||||
printf("--- got preexisting: %s\n", PacketBuf);
|
||||
|
||||
ssize_t datastart = 0;
|
||||
while (true)
|
||||
{
|
||||
if (PacketBuf[datastart] == '\x04') return ReadResult::Eof;
|
||||
else if (PacketBuf[datastart] == '+' || PacketBuf[datastart] == '-')
|
||||
{
|
||||
/*if (PacketBuf[datastart] == '+') SendAck(connfd);
|
||||
else SendNak(connfd);*/
|
||||
++datastart;
|
||||
continue;
|
||||
}
|
||||
else if (PacketBuf[datastart] == '$')
|
||||
{
|
||||
++datastart;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
__builtin_trap();
|
||||
return ReadResult::Wut;
|
||||
}
|
||||
}
|
||||
printf("--- datastart=%zd\n", datastart);
|
||||
|
||||
for (ssize_t i = datastart; i < dataoff; ++i)
|
||||
{
|
||||
if (PacketBuf[i] == '#')
|
||||
{
|
||||
cksumoff = i + 1;
|
||||
printf("--- cksumoff=%zd\n", cksumoff);
|
||||
break;
|
||||
}
|
||||
|
||||
sum += PacketBuf[i];
|
||||
}
|
||||
|
||||
if (cksumoff >= 0)
|
||||
{
|
||||
recv_total = dataoff - datastart + 1;
|
||||
dataoff = cksumoff + 2 - datastart + 1;
|
||||
cksumoff -= datastart - 1;
|
||||
|
||||
memmove(&PacketBuf[1], &PacketBuf[datastart], recv_total);
|
||||
PacketBuf[0] = '$';
|
||||
PacketBuf[recv_total] = 0;
|
||||
|
||||
printf("=== cksumoff=%zi recv_total=%zi datastart=%zi dataoff=%zi\n==> %s\n",
|
||||
cksumoff, recv_total, datastart, dataoff, PacketBuf);
|
||||
//break;
|
||||
}
|
||||
}
|
||||
|
||||
while (cksumoff < 0)
|
||||
{
|
||||
u8* pkt = &PacketBuf[dataoff];
|
||||
ssize_t n, blehoff = 0;
|
||||
|
||||
memset(pkt, 0, sizeof(PacketBuf) - dataoff);
|
||||
int flag = 0;
|
||||
#if MOCKTEST
|
||||
static bool FIRST = false;
|
||||
if (FIRST) {
|
||||
printf("%s", "[==>] TEST DONE\n");
|
||||
__builtin_trap();
|
||||
}
|
||||
FIRST = true;
|
||||
|
||||
const char* testinp1 = "+$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77";
|
||||
const char* testinp2 = "+$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77---+$vMustReplyEmpty#3a";
|
||||
|
||||
const char* testinp = testinp1;
|
||||
|
||||
n = strlen(testinp);
|
||||
memcpy(pkt, testinp, strlen(testinp));
|
||||
#else
|
||||
#ifndef _WIN32
|
||||
if (first) flag |= MSG_DONTWAIT;
|
||||
n = recv(connfd, pkt, sizeof(PacketBuf) - dataoff, flag);
|
||||
#else
|
||||
// fuck windows
|
||||
n = recv(connfd, (char*)pkt, sizeof(PacketBuf) - dataoff, flag);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (n <= 0)
|
||||
{
|
||||
if (first) return ReadResult::NoPacket;
|
||||
else
|
||||
{
|
||||
Log(LogLevel::Debug, "[GDB] recv() error %zi, errno=%d (%s)\n", n, errno, strerror(errno));
|
||||
return ReadResult::Eof;
|
||||
}
|
||||
}
|
||||
|
||||
Log(LogLevel::Debug, "[GDB] recv() %zd bytes: '%s' (%02x)\n", n, pkt, pkt[0]);
|
||||
first = false;
|
||||
|
||||
do
|
||||
{
|
||||
if (dataoff == 0)
|
||||
{
|
||||
if (pkt[blehoff] == '\x04') return ReadResult::Eof;
|
||||
else if (pkt[blehoff] == '\x03') return ReadResult::Break;
|
||||
else if (pkt[blehoff] != '$')
|
||||
{
|
||||
++blehoff;
|
||||
--n;
|
||||
}
|
||||
else break;
|
||||
|
||||
if (n == 0) goto next_outer;
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
|
||||
if (blehoff > 0)
|
||||
{
|
||||
memmove(pkt, &pkt[blehoff], n - blehoff + 1);
|
||||
n -= blehoff - 1; // ???
|
||||
}
|
||||
|
||||
recv_total += n;
|
||||
|
||||
Log(LogLevel::Debug, "[GDB] recv() after skipping: n=%zd, recv_total=%zd\n", n, recv_total);
|
||||
|
||||
for (ssize_t i = (dataoff == 0) ? 1 : 0; i < n; ++i)
|
||||
{
|
||||
u8 v = pkt[i];
|
||||
if (v == '#')
|
||||
{
|
||||
cksumoff = dataoff + i + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
sum += pkt[i];
|
||||
}
|
||||
|
||||
if (cksumoff < 0)
|
||||
{
|
||||
// oops, need more data
|
||||
dataoff += n;
|
||||
}
|
||||
|
||||
next_outer:;
|
||||
}
|
||||
|
||||
u8 ck = (hex2nyb(PacketBuf[cksumoff+0]) << 4)
|
||||
| hex2nyb(PacketBuf[cksumoff+1]);
|
||||
|
||||
Log(LogLevel::Debug, "[GDB] got pkt, checksum: %02x vs %02x\n", ck, sum);
|
||||
|
||||
if (ck != sum)
|
||||
{
|
||||
//__builtin_trap();
|
||||
return ReadResult::CksumErr;
|
||||
}
|
||||
|
||||
if (cksumoff + 2 > recv_total)
|
||||
{
|
||||
Log(LogLevel::Error, "[GDB] BIG MISTAKE: %zi > %zi which shouldn't happen!\n", cksumoff + 2, recv_total);
|
||||
//__builtin_trap();
|
||||
return ReadResult::Wut;
|
||||
}
|
||||
else
|
||||
{
|
||||
Cmdlen = cksumoff - 2;
|
||||
memcpy(Cmdbuf, &PacketBuf[1], Cmdlen);
|
||||
Cmdbuf[Cmdlen] = 0;
|
||||
|
||||
if (cksumoff + 2 < recv_total) {
|
||||
// huh, we have the start of the next packet
|
||||
dataoff = recv_total - (cksumoff + 2);
|
||||
memmove(PacketBuf, &PacketBuf[cksumoff + 2], (size_t)dataoff);
|
||||
PacketBuf[dataoff] = 0;
|
||||
Log(LogLevel::Debug, "[GDB] got more: cksumoff=%zd, recvtotal=%zd, remain=%zd\n==> %s\n", cksumoff, recv_total, dataoff, PacketBuf);
|
||||
}
|
||||
else dataoff = 0;
|
||||
}
|
||||
|
||||
return ReadResult::CmdRecvd;
|
||||
}
|
||||
|
||||
int SendAck(int connfd)
|
||||
{
|
||||
Log(LogLevel::Debug, "[GDB] send ack\n");
|
||||
u8 v = '+';
|
||||
#if MOCKTEST
|
||||
return 1;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
// fuck windows
|
||||
return send(connfd, (const char*)&v, 1, 0);
|
||||
#else
|
||||
return send(connfd, &v, 1, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
int SendNak(int connfd)
|
||||
{
|
||||
Log(LogLevel::Debug, "[GDB] send nak\n");
|
||||
u8 v = '-';
|
||||
#if MOCKTEST
|
||||
return 1;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
// fuck windows
|
||||
return send(connfd, (const char*)&v, 1, 0);
|
||||
#else
|
||||
return send(connfd, &v, 1, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
int WaitAckBlocking(int connfd, u8* ackp, int to_ms)
|
||||
{
|
||||
#if MOCKTEST
|
||||
*ackp = '+';
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
fd_set infd, outfd, errfd;
|
||||
FD_ZERO(&infd); FD_ZERO(&outfd); FD_ZERO(&errfd);
|
||||
FD_SET(connfd, &infd);
|
||||
|
||||
struct timeval to;
|
||||
to.tv_sec = to_ms / 1000;
|
||||
to.tv_usec = (to_ms % 1000) * 1000;
|
||||
|
||||
int r = select(connfd+1, &infd, &outfd, &errfd, &to);
|
||||
|
||||
if (FD_ISSET(connfd, &errfd)) return -1;
|
||||
else if (FD_ISSET(connfd, &infd))
|
||||
{
|
||||
r = recv(connfd, (char*)ackp, 1, 0);
|
||||
if (r < 0) return r;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
#else
|
||||
struct pollfd pfd;
|
||||
|
||||
pfd.fd = connfd;
|
||||
pfd.events = POLLIN;
|
||||
pfd.revents = 0;
|
||||
|
||||
ssize_t r = (ssize_t)poll(&pfd, 1, to_ms);
|
||||
if (r < 0) return r;
|
||||
if (r == 0) return -1;
|
||||
|
||||
if (pfd.revents & (POLLHUP|POLLERR)) return -69;
|
||||
|
||||
r = recv(connfd, ackp, 1, 0);
|
||||
if (r < 0) return r;
|
||||
|
||||
return (r == 1) ? 0 : -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
int Resp(int connfd, const u8* data1, size_t len1, const u8* data2, size_t len2, bool noack)
|
||||
{
|
||||
u8 cksum = 0;
|
||||
int tries = 0;
|
||||
|
||||
size_t totallen = len1 + len2;
|
||||
|
||||
if (totallen >= GDBPROTO_BUFFER_CAPACITY)
|
||||
{
|
||||
Log(LogLevel::Error, "[GDB] packet with len %zu can't fit in buffer!\n", totallen);
|
||||
return -42;
|
||||
}
|
||||
|
||||
RespBuf[0] = '$';
|
||||
for (size_t i = 0; i < len1; ++i)
|
||||
{
|
||||
cksum += data1[i];
|
||||
RespBuf[i+1] = data1[i];
|
||||
}
|
||||
for (size_t i = 0; i < len2; ++i)
|
||||
{
|
||||
cksum += data2[i];
|
||||
RespBuf[len1+i+1] = data2[i];
|
||||
}
|
||||
RespBuf[totallen+1] = '#';
|
||||
hexfmt8(&RespBuf[totallen+2], cksum);
|
||||
RespBuf[totallen+4] = 0;
|
||||
|
||||
do
|
||||
{
|
||||
ssize_t r;
|
||||
u8 ack;
|
||||
|
||||
Log(LogLevel::Debug, "[GDB] send resp: '%s'\n", RespBuf);
|
||||
#if MOCKTEST
|
||||
r = totallen+4;
|
||||
#else
|
||||
#ifdef _WIN32
|
||||
r = send(connfd, (const char*)RespBuf, totallen+4, 0);
|
||||
#else
|
||||
r = send(connfd, RespBuf, totallen+4, 0);
|
||||
#endif
|
||||
#endif
|
||||
if (r < 0) return r;
|
||||
|
||||
if (noack) break;
|
||||
|
||||
r = WaitAckBlocking(connfd, &ack, 2000);
|
||||
//Log(LogLevel::Debug, "[GDB] got ack: '%c'\n", ack);
|
||||
if (r == 0 && ack == '+') break;
|
||||
|
||||
++tries;
|
||||
}
|
||||
while (tries < 3);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user