* 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:
PoroCYon
2023-10-22 15:35:31 +02:00
committed by GitHub
parent 3d58a338a1
commit 3ab752b8ca
29 changed files with 3210 additions and 49 deletions

62
src/debug/GdbArch.h Normal file
View File

@ -0,0 +1,62 @@
#ifndef GDBARCH_H_
#define GDBARCH_H_
namespace Gdb
{
enum class Register : int
{
r0,
r1,
r2,
r3,
r4,
r5,
r6,
r7,
r8,
r9,
r10,
r11,
r12,
sp,
lr,
pc,
cpsr,
sp_usr,
lr_usr,
r8_fiq,
r9_fiq,
r10_fiq,
r11_fiq,
r12_fiq,
sp_fiq,
lr_fiq,
sp_irq,
lr_irq,
sp_svc,
lr_svc,
sp_abt,
lr_abt,
sp_und,
lr_und,
spsr_fiq,
spsr_irq,
spsr_svc,
spsr_abt,
spsr_und,
COUNT
};
constexpr int GDB_ARCH_N_REG = (int)Register::COUNT;
}
#endif

924
src/debug/GdbCmds.cpp Normal file
View File

@ -0,0 +1,924 @@
#include <stdio.h>
#include <assert.h>
#include "../CRC32.h"
#include "../Platform.h"
#include "hexutil.h"
#include "GdbProto.h"
using Platform::Log;
using Platform::LogLevel;
namespace Gdb
{
enum class GdbSignal : int
{
INT = 2,
TRAP = 5,
EMT = 7, // "emulation trap"
SEGV = 11,
ILL = 4
};
// 12: llvm::MachO::CPU_TYPE_ARM
// 5: llvm::MachO::CPU_SUBTYPE_ARM_V4T
// 7: llvm::MachO::CPU_SUBTYPE_ARM_V5TEJ
const char* TARGET_INFO_ARM7 = "cputype:12;cpusubtype:5;triple:arm-none-eabi;ostype:none;vendor:none;endian:little;ptrsize:4;";
const char* TARGET_INFO_ARM9 = "cputype:12;cpusubtype:7;triple:arm-none-eabi;ostype:none;vendor:none;endian:little;ptrsize:4;";
#define TARGET_XML__CORE_REGS \
"<reg name=\"r0\" bitsize=\"32\" type=\"uint32\"/>" \
"<reg name=\"r1\" bitsize=\"32\" type=\"uint32\"/>" \
"<reg name=\"r2\" bitsize=\"32\" type=\"uint32\"/>" \
"<reg name=\"r3\" bitsize=\"32\" type=\"uint32\"/>" \
"<reg name=\"r4\" bitsize=\"32\" type=\"uint32\"/>" \
"<reg name=\"r5\" bitsize=\"32\" type=\"uint32\"/>" \
"<reg name=\"r6\" bitsize=\"32\" type=\"uint32\"/>" \
"<reg name=\"r7\" bitsize=\"32\" type=\"uint32\"/>" \
"<reg name=\"r8\" bitsize=\"32\" type=\"uint32\"/>" \
"<reg name=\"r9\" bitsize=\"32\" type=\"uint32\"/>" \
"<reg name=\"r10\" bitsize=\"32\" type=\"uint32\"/>" \
"<reg name=\"r11\" bitsize=\"32\" type=\"uint32\"/>" \
"<reg name=\"r12\" bitsize=\"32\" type=\"uint32\"/>" \
"<reg name=\"sp\" bitsize=\"32\" type=\"data_ptr\"/>" \
"<reg name=\"lr\" bitsize=\"32\" type=\"code_ptr\"/>" \
"<reg name=\"pc\" bitsize=\"32\" type=\"code_ptr\"/>" \
/* 16 regs */ \
#define TARGET_XML__MODE_REGS \
"<reg name=\"cpsr\" bitsize=\"32\" regnum=\"25\"/>" \
"<reg name=\"sp_usr\" bitsize=\"32\" regnum=\"26\" type=\"data_ptr\"/>" \
"<reg name=\"lr_usr\" bitsize=\"32\" regnum=\"27\" type=\"code_ptr\"/>" \
"<reg name=\"r8_fiq\" bitsize=\"32\" type=\"uint32\" regnum=\"28\"/>" \
"<reg name=\"r9_fiq\" bitsize=\"32\" type=\"uint32\" regnum=\"29\"/>" \
"<reg name=\"r10_fiq\" bitsize=\"32\" type=\"uint32\" regnum=\"30\"/>" \
"<reg name=\"r11_fiq\" bitsize=\"32\" type=\"uint32\" regnum=\"31\"/>" \
"<reg name=\"r12_fiq\" bitsize=\"32\" type=\"uint32\" regnum=\"32\"/>" \
"<reg name=\"sp_fiq\" bitsize=\"32\" regnum=\"33\" type=\"data_ptr\"/>" \
"<reg name=\"lr_fiq\" bitsize=\"32\" regnum=\"34\" type=\"code_ptr\"/>" \
"<reg name=\"sp_irq\" bitsize=\"32\" regnum=\"35\" type=\"data_ptr\"/>" \
"<reg name=\"lr_irq\" bitsize=\"32\" regnum=\"36\" type=\"code_ptr\"/>" \
"<reg name=\"sp_svc\" bitsize=\"32\" regnum=\"37\" type=\"data_ptr\"/>" \
"<reg name=\"lr_svc\" bitsize=\"32\" regnum=\"38\" type=\"code_ptr\"/>" \
"<reg name=\"sp_abt\" bitsize=\"32\" regnum=\"39\" type=\"data_ptr\"/>" \
"<reg name=\"lr_abt\" bitsize=\"32\" regnum=\"40\" type=\"code_ptr\"/>" \
"<reg name=\"sp_und\" bitsize=\"32\" regnum=\"41\" type=\"data_ptr\"/>" \
"<reg name=\"lr_und\" bitsize=\"32\" regnum=\"42\" type=\"code_ptr\"/>" \
"<reg name=\"spsr_fiq\" bitsize=\"32\" regnum=\"43\"/>" \
"<reg name=\"spsr_irq\" bitsize=\"32\" regnum=\"44\"/>" \
"<reg name=\"spsr_svc\" bitsize=\"32\" regnum=\"45\"/>" \
"<reg name=\"spsr_abt\" bitsize=\"32\" regnum=\"46\"/>" \
"<reg name=\"spsr_und\" bitsize=\"32\" regnum=\"47\"/>" \
/* 23 regs */ \
const char* TARGET_XML_ARM7 =
"<target version=\"1.0\">"
"<architecture>armv4t</architecture>"
"<osabi>none</osabi>"
"<feature name=\"org.gnu.gdb.arm.core\">"
TARGET_XML__CORE_REGS
TARGET_XML__MODE_REGS
// 39 regs total
"</feature>"
"</target>";
const char* TARGET_XML_ARM9 =
"<target version=\"1.0\">"
"<architecture>armv5te</architecture>"
"<osabi>none</osabi>"
"<feature name=\"org.gnu.gdb.arm.core\">"
TARGET_XML__CORE_REGS
TARGET_XML__MODE_REGS
// 39 regs total
"</feature>"
"</target>";
// TODO: CP15?
static int DoQResponse(GdbStub* stub, const u8* query, const char* data, const size_t len)
{
size_t qaddr, qlen;
Log(LogLevel::Debug, "[GDB qresp] query='%s'\n", query);
if (sscanf((const char*)query, "%zx,%zx", &qaddr, &qlen) != 2)
{
return stub->RespStr("E01");
}
else if (qaddr > len)
{
return stub->RespStr("E01");
}
else if (qaddr == len)
{
return stub->RespStr("l");
}
size_t bleft = len - qaddr;
size_t outlen = qlen;
if (outlen > bleft) outlen = bleft;
Log(LogLevel::Debug, "[GDB qresp] qaddr=%zu qlen=%zu left=%zu outlen=%zu\n",
qaddr, qlen, bleft, outlen);
return stub->RespC("m", 1, (const u8*)&data[qaddr], outlen);
}
__attribute__((__aligned__(4)))
static u8 tempdatabuf[1024];
ExecResult GdbStub::Handle_g(GdbStub* stub, const u8* cmd, ssize_t len)
{
u8* regstrbuf = tempdatabuf;
for (size_t i = 0; i < GDB_ARCH_N_REG; ++i)
{
u32 v = stub->Cb->ReadReg(static_cast<Register>(i));
hexfmt32(&regstrbuf[i*4*2], v);
}
stub->Resp(regstrbuf, GDB_ARCH_N_REG*4*2);
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_G(GdbStub* stub, const u8* cmd, ssize_t len)
{
if (len != GDB_ARCH_N_REG*4*2)
{
Log(LogLevel::Error, "[GDB] REG WRITE ERR: BAD LEN: %zd != %d!\n", len, GDB_ARCH_N_REG*4*2);
stub->RespStr("E01");
return ExecResult::Ok;
}
for (int i = 0; i < GDB_ARCH_N_REG; ++i)
{
u32 v = unhex32(&cmd[i*4*2]);
stub->Cb->WriteReg(static_cast<Register>(i), v);
}
stub->RespStr("OK");
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_m(GdbStub* stub, const u8* cmd, ssize_t len)
{
u32 addr = 0, llen = 0, end;
if (sscanf((const char*)cmd, "%08X,%08X", &addr, &llen) != 2)
{
stub->RespStr("E01");
return ExecResult::Ok;
}
else if (llen > (GDBPROTO_BUFFER_CAPACITY/2))
{
stub->RespStr("E02");
return ExecResult::Ok;
}
end = addr + llen;
u8* datastr = tempdatabuf;
u8* dataptr = datastr;
// pre-align: byte
if ((addr & 1))
{
if ((end-addr) >= 1)
{
u32 v = stub->Cb->ReadMem(addr, 8);
hexfmt8(dataptr, v&0xff);
++addr;
dataptr += 2;
}
else goto end;
}
// pre-align: short
if ((addr & 2))
{
if ((end-addr) >= 2)
{
u32 v = stub->Cb->ReadMem(addr, 16);
hexfmt16(dataptr, v&0xffff);
addr += 2;
dataptr += 4;
}
else if ((end-addr) == 1)
{ // last byte
u32 v = stub->Cb->ReadMem(addr, 8);
hexfmt8(dataptr, v&0xff);
++addr;
dataptr += 2;
}
else goto end;
}
// main loop: 4-byte chunks
while (addr < end)
{
if (end - addr < 4) break; // post-align stuff
u32 v = stub->Cb->ReadMem(addr, 32);
hexfmt32(dataptr, v);
addr += 4;
dataptr += 8;
}
// post-align: short
if ((end-addr) & 2)
{
u32 v = stub->Cb->ReadMem(addr, 16);
hexfmt16(dataptr, v&0xffff);
addr += 2;
dataptr += 4;
}
// post-align: byte
if ((end-addr) == 1)
{
u32 v = stub->Cb->ReadMem(addr, 8);
hexfmt8(dataptr, v&0xff);
++addr;
dataptr += 2;
}
end:
assert(addr == end);
stub->Resp(datastr, llen*2);
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_M(GdbStub* stub, const u8* cmd, ssize_t len)
{
u32 addr, llen, end;
int inoff;
if (sscanf((const char*)cmd, "%08X,%08X:%n", &addr, &llen, &inoff) != 2)
{
stub->RespStr("E01");
return ExecResult::Ok;
}
else if (llen > (GDBPROTO_BUFFER_CAPACITY/2))
{
stub->RespStr("E02");
return ExecResult::Ok;
}
end = addr + llen;
const u8* dataptr = cmd + inoff;
// pre-align: byte
if ((addr & 1))
{
if ((end-addr) >= 1)
{
u8 v = unhex8(dataptr);
stub->Cb->WriteMem(addr, 8, v);
++addr;
dataptr += 2;
}
else goto end;
}
// pre-align: short
if ((addr & 2))
{
if ((end-addr) >= 2)
{
u16 v = unhex16(dataptr);
stub->Cb->WriteMem(addr, 16, v);
addr += 2;
dataptr += 4;
}
else if ((end-addr) == 1)
{ // last byte
u8 v = unhex8(dataptr);
stub->Cb->WriteMem(addr, 8, v);
++addr;
dataptr += 2;
}
else goto end;
}
// main loop: 4-byte chunks
while (addr < end)
{
if (end - addr < 4) break; // post-align stuff
u32 v = unhex32(dataptr);
stub->Cb->WriteMem(addr, 32, v);
addr += 4;
dataptr += 8;
}
// post-align: short
if ((end-addr) & 2)
{
u16 v = unhex16(dataptr);
stub->Cb->WriteMem(addr, 16, v);
addr += 2;
dataptr += 4;
}
// post-align: byte
if ((end-addr) == 1)
{
u8 v = unhex8(dataptr);
stub->Cb->WriteMem(addr, 8, v);
++addr;
dataptr += 2;
}
end:
assert(addr == end);
stub->RespStr("OK");
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_X(GdbStub* stub, const u8* cmd, ssize_t len)
{
u32 addr, llen, end;
int inoff;
if (sscanf((const char*)cmd, "%08X,%08X:%n", &addr, &llen, &inoff) != 2)
{
stub->RespStr("E01");
return ExecResult::Ok;
}
else if (llen > (GDBPROTO_BUFFER_CAPACITY/2))
{
stub->RespStr("E02");
return ExecResult::Ok;
}
end = addr + llen;
const u8* dataptr = cmd + inoff;
// pre-align: byte
if ((addr & 1))
{
if ((end-addr) >= 1)
{
u8 v = *dataptr;
stub->Cb->WriteMem(addr, 8, v);
++addr;
dataptr += 1;
}
else goto end;
}
// pre-align: short
if ((addr & 2))
{
if ((end-addr) >= 2)
{
u16 v = dataptr[0] | ((u16)dataptr[1] << 8);
stub->Cb->WriteMem(addr, 16, v);
addr += 2;
dataptr += 2;
}
else if ((end-addr) == 1)
{ // last byte
u8 v = *dataptr;
stub->Cb->WriteMem(addr, 8, v);
++addr;
dataptr += 1;
}
else goto end;
}
// main loop: 4-byte chunks
while (addr < end)
{
if (end - addr < 4) break; // post-align stuff
u32 v = dataptr[0] | ((u32)dataptr[1] << 8)
| ((u32)dataptr[2] << 16) | ((u32)dataptr[3] << 24);
stub->Cb->WriteMem(addr, 32, v);
addr += 4;
dataptr += 4;
}
// post-align: short
if ((end-addr) & 2)
{
u16 v = dataptr[0] | ((u16)dataptr[1] << 8);
stub->Cb->WriteMem(addr, 16, v);
addr += 2;
dataptr += 2;
}
// post-align: byte
if ((end-addr) == 1)
{
u8 v = unhex8(dataptr);
stub->Cb->WriteMem(addr, 8, v);
++addr;
dataptr += 1;
}
end:
assert(addr == end);
stub->RespStr("OK");
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_c(GdbStub* stub, const u8* cmd, ssize_t len)
{
u32 addr = ~(u32)0;
if (len > 0)
{
if (len <= 8)
{
if (sscanf((const char*)cmd, "%08X", &addr) != 1)
{
stub->RespStr("E01");
} // else: ok
}
else
{
stub->RespStr("E01");
}
} // else: continue at current
if (~addr)
{
stub->Cb->WriteReg(Register::pc, addr);
}
return ExecResult::Continue;
}
ExecResult GdbStub::Handle_s(GdbStub* stub, const u8* cmd, ssize_t len) {
u32 addr = ~(u32)0;
if (len > 0)
{
if (len <= 8)
{
if (sscanf((const char*)cmd, "%08X", &addr) != 1) {
stub->RespStr("E01");
return ExecResult::Ok;
} // else: ok
}
else
{
stub->RespStr("E01");
return ExecResult::Ok;
}
} // else: continue at current
if (~addr != 0)
{
stub->Cb->WriteReg(Register::pc, addr);
}
return ExecResult::Step;
}
ExecResult GdbStub::Handle_p(GdbStub* stub, const u8* cmd, ssize_t len)
{
int reg;
if (sscanf((const char*)cmd, "%x", &reg) != 1 || reg < 0 || reg >= GDB_ARCH_N_REG)
{
stub->RespStr("E01");
return ExecResult::Ok;
}
u32 v = stub->Cb->ReadReg(static_cast<Register>(reg));
hexfmt32(tempdatabuf, v);
stub->Resp(tempdatabuf, 4*2);
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_P(GdbStub* stub, const u8* cmd, ssize_t len)
{
int reg, dataoff;
if (sscanf((const char*)cmd, "%x=%n", &reg, &dataoff) != 1 || reg < 0
|| reg >= GDB_ARCH_N_REG || dataoff + 4*2 > len)
{
stub->RespStr("E01");
return ExecResult::Ok;
}
u32 v = unhex32(&cmd[dataoff]);
stub->Cb->WriteReg(static_cast<Register>(reg), v);
stub->RespStr("OK");
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_H(GdbStub* stub, const u8* cmd, ssize_t len)
{
u8 operation = cmd[0];
u32 thread_id;
sscanf((const char*)&cmd[1], "%u", &thread_id);
(void)operation;
if (thread_id <= 1)
{
stub->RespStr("OK");
}
else
{
stub->RespStr("E01");
}
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_Question(GdbStub* stub, const u8* cmd, ssize_t len)
{
// "request reason for target halt" (which must also halt)
TgtStatus st = stub->Stat;
u32 arg = ~(u32)0;
int typ = 0;
switch (st)
{
case TgtStatus::None: // no target!
stub->RespStr("W00");
break;
case TgtStatus::Running: // will break very soon due to retval
case TgtStatus::BreakReq:
stub->RespFmt("S%02X", GdbSignal::INT);
break;
case TgtStatus::SingleStep:
stub->RespFmt("S%02X", GdbSignal::TRAP);
break;
case TgtStatus::Bkpt:
arg = stub->CurBkpt;
typ = 1;
goto bkpt_rest;
case TgtStatus::Watchpt:
arg = stub->CurWatchpt;
typ = 2;
bkpt_rest:
if (!~arg)
{
stub->RespFmt("S%02X", GdbSignal::TRAP);
}
else
{
switch (typ)
{
case 1:
stub->RespFmt("S%02X", GdbSignal::TRAP);
//stub->RespFmt("T%02Xhwbreak:"/*"%08X"*/";", GdbSignal::TRAP/*, arg*/);
break;
case 2:
stub->RespFmt("S%02X", GdbSignal::TRAP);
//stub->RespFmt("T%02Xwatch:"/*"%08X"*/";", GdbSignal::TRAP/*, arg*/);
break;
default:
stub->RespFmt("S%02X", GdbSignal::TRAP);
break;
}
}
break;
case TgtStatus::BkptInsn:
stub->RespFmt("T%02Xswbreak:%08X;", GdbSignal::TRAP,
stub->Cb->ReadReg(Register::pc));
break;
// these three should technically be a SIGBUS but gdb etc don't really
// like that (plus it sounds confusing)
case TgtStatus::FaultData:
case TgtStatus::FaultIAcc:
stub->RespFmt("S%02X", GdbSignal::SEGV);
break;
case TgtStatus::FaultInsn:
stub->RespFmt("S%02X", GdbSignal::ILL);
break;
default: break;
}
return ExecResult::InitialBreak;
}
ExecResult GdbStub::Handle_Exclamation(GdbStub* stub, const u8* cmd, ssize_t len)
{
stub->RespStr("OK");
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_D(GdbStub* stub, const u8* cmd, ssize_t len)
{
stub->RespStr("OK");
return ExecResult::Detached;
}
ExecResult GdbStub::Handle_r(GdbStub* stub, const u8* cmd, ssize_t len)
{
stub->Cb->ResetGdb();
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_R(GdbStub* stub, const u8* cmd, ssize_t len)
{
stub->Cb->ResetGdb();
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_k(GdbStub* stub, const u8* cmd, ssize_t len)
{
return ExecResult::Detached;
}
ExecResult GdbStub::Handle_z(GdbStub* stub, const u8* cmd, ssize_t len)
{
int typ;
u32 addr, kind;
if (sscanf((const char*)cmd, "%d,%x,%u", &typ, &addr, &kind) != 3)
{
stub->RespStr("E01");
return ExecResult::Ok;
}
switch (typ)
{
case 0: case 1: // remove breakpoint (we cheat & always insert a hardware breakpoint)
stub->DelBkpt(addr, kind);
break;
case 2: case 3: case 4: // watchpoint. currently not distinguishing between reads & writes oops
stub->DelWatchpt(addr, kind, typ);
break;
default:
stub->RespStr("E02");
return ExecResult::Ok;
}
stub->RespStr("OK");
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_Z(GdbStub* stub, const u8* cmd, ssize_t len)
{
int typ;
u32 addr, kind;
if (sscanf((const char*)cmd, "%d,%x,%u", &typ, &addr, &kind) != 3)
{
stub->RespStr("E01");
return ExecResult::Ok;
}
switch (typ)
{
case 0: case 1: // insert breakpoint (we cheat & always insert a hardware breakpoint)
stub->AddBkpt(addr, kind);
break;
case 2: case 3: case 4: // watchpoint. currently not distinguishing between reads & writes oops
stub->AddWatchpt(addr, kind, typ);
break;
default:
stub->RespStr("E02");
return ExecResult::Ok;
}
stub->RespStr("OK");
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_q_HostInfo(GdbStub* stub, const u8* cmd, ssize_t len)
{
const char* resp = "";
switch (stub->Cb->GetCPU())
{
case 7: resp = TARGET_INFO_ARM7; break;
case 9: resp = TARGET_INFO_ARM9; break;
default: break;
}
stub->RespStr(resp);
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_q_Rcmd(GdbStub* stub, const u8* cmd, ssize_t len)
{
memset(tempdatabuf, 0, sizeof tempdatabuf);
for (ssize_t i = 0; i < len/2; ++i)
{
tempdatabuf[i] = unhex8(&cmd[i*2]);
}
int r = stub->Cb->RemoteCmd(tempdatabuf, len/2);
if (r) stub->RespFmt("E%02X", r&0xff);
else stub->RespStr("OK");
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_q_Supported(GdbStub* stub,
const u8* cmd, ssize_t len) {
// TODO: support Xfer:memory-map:read::
// but NWRAM is super annoying with that
stub->RespFmt("PacketSize=%X;qXfer:features:read+;swbreak-;hwbreak+;QStartNoAckMode+", GDBPROTO_BUFFER_CAPACITY-1);
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_q_CRC(GdbStub* stub,
const u8* cmd, ssize_t llen)
{
static u8 crcbuf[128];
u32 addr, len;
if (sscanf((const char*)cmd, "%x,%x", &addr, &len) != 2)
{
stub->RespStr("E01");
return ExecResult::Ok;
}
u32 val = 0; // start at 0
u32 caddr = addr;
u32 realend = addr + len;
for (; caddr < addr + len; )
{
// calc partial CRC in 128-byte chunks
u32 end = caddr + sizeof(crcbuf)/sizeof(crcbuf[0]);
if (end > realend) end = realend;
u32 clen = end - caddr;
for (size_t i = 0; caddr < end; ++caddr, ++i)
{
crcbuf[i] = stub->Cb->ReadMem(caddr, 8);
}
val = CRC32(crcbuf, clen, val);
}
stub->RespFmt("C%x", val);
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_q_C(GdbStub* stub, const u8* cmd, ssize_t len)
{
stub->RespStr("QC1"); // current thread ID is 1
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_q_fThreadInfo(GdbStub* stub, const u8* cmd, ssize_t len)
{
stub->RespStr("m1"); // one thread
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_q_sThreadInfo(GdbStub* stub, const u8* cmd, ssize_t len)
{
stub->RespStr("l"); // end of thread list
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_q_features(GdbStub* stub, const u8* cmd, ssize_t len)
{
const char* resp;
Log(LogLevel::Debug, "[GDB] CPU type = %d\n", stub->Cb->GetCPU());
switch (stub->Cb->GetCPU())
{
case 7: resp = TARGET_XML_ARM7; break;
case 9: resp = TARGET_XML_ARM9; break;
default: resp = ""; break;
}
DoQResponse(stub, cmd, resp, strlen(resp));
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_q_Attached(GdbStub* stub, const u8* cmd, ssize_t len)
{
stub->RespStr("1"); // always "attach to a process"
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_v_Attach(GdbStub* stub, const u8* cmd, ssize_t len)
{
TgtStatus st = stub->Stat;
if (st == TgtStatus::None)
{
// no target
stub->RespStr("E01");
return ExecResult::Ok;
}
stub->RespStr("T05thread:1;");
if (st == TgtStatus::Running) return ExecResult::MustBreak;
else return ExecResult::Ok;
}
ExecResult GdbStub::Handle_v_Kill(GdbStub* stub, const u8* cmd, ssize_t len)
{
TgtStatus st = stub->Stat;
stub->Cb->ResetGdb();
stub->RespStr("OK");
return (st != TgtStatus::Running && st != TgtStatus::None) ? ExecResult::Detached : ExecResult::Ok;
}
ExecResult GdbStub::Handle_v_Run(GdbStub* stub, const u8* cmd, ssize_t len)
{
TgtStatus st = stub->Stat;
stub->Cb->ResetGdb();
// TODO: handle cmdline for homebrew?
return (st != TgtStatus::Running && st != TgtStatus::None) ? ExecResult::Continue : ExecResult::Ok;
}
ExecResult GdbStub::Handle_v_Stopped(GdbStub* stub, const u8* cmd, ssize_t len)
{
TgtStatus st = stub->Stat;
static bool notified = true;
// not sure if i understand this correctly
if (st != TgtStatus::Running)
{
if (notified) stub->RespStr("OK");
else stub->RespStr("W00");
notified = !notified;
}
else stub->RespStr("OK");
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_v_MustReplyEmpty(GdbStub* stub, const u8* cmd, ssize_t len)
{
stub->Resp(NULL, 0);
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_v_Cont(GdbStub* stub, const u8* cmd, ssize_t len)
{
if (len < 1)
{
stub->RespStr("E01");
return ExecResult::Ok;
}
switch (cmd[0])
{
case 'c':
stub->RespStr("OK");
return ExecResult::Continue;
case 's':
stub->RespStr("OK");
return ExecResult::Step;
case 't':
stub->RespStr("OK");
return ExecResult::MustBreak;
default:
stub->RespStr("E01");
return ExecResult::Ok;
}
}
ExecResult GdbStub::Handle_v_ContQuery(GdbStub* stub, const u8* cmd, ssize_t len)
{
stub->RespStr("vCont;c;s;t");
return ExecResult::Ok;
}
ExecResult GdbStub::Handle_Q_StartNoAckMode(GdbStub* stub, const u8* cmd, ssize_t len)
{
stub->NoAck = true;
stub->RespStr("OK");
return ExecResult::Ok;
}
}

53
src/debug/GdbCmds.h Normal file
View File

@ -0,0 +1,53 @@
#ifndef GDBSTUB_H_
#error "DO NOT INCLUDE THIS FILE YOURSELF!"
#endif
private:
static ExecResult Handle_g(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_G(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_m(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_M(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_X(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_c(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_s(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_p(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_P(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_H(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_Question(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_Exclamation(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_D(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_r(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_R(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_k(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_z(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_Z(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_q(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_v(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_Q(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_q_HostInfo(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_q_Rcmd(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_q_Supported(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_q_CRC(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_q_C(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_q_fThreadInfo(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_q_sThreadInfo(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_q_TStatus(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_q_features(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_q_Attached(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_v_Attach(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_v_Kill(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_v_Run(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_v_Stopped(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_v_MustReplyEmpty(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_v_Cont(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_v_ContQuery(GdbStub* stub, const u8* cmd, ssize_t len);
static ExecResult Handle_Q_StartNoAckMode(GdbStub* stub, const u8* cmd, ssize_t len);

389
src/debug/GdbProto.cpp Normal file
View 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;
}
}
}

40
src/debug/GdbProto.h Normal file
View File

@ -0,0 +1,40 @@
#ifndef GDBPROTO_H_
#define GDBPROTO_H_
#include <string.h>
#include <sys/types.h>
#include "GdbStub.h" /* class GdbStub */
#define MOCKTEST 0
namespace Gdb {
constexpr int GDBPROTO_BUFFER_CAPACITY = 1024+128;
extern u8 Cmdbuf[GDBPROTO_BUFFER_CAPACITY];
extern ssize_t Cmdlen;
namespace Proto {
extern u8 PacketBuf[GDBPROTO_BUFFER_CAPACITY];
extern u8 RespBuf[GDBPROTO_BUFFER_CAPACITY+5];
Gdb::ReadResult MsgRecv(int connfd, u8 cmd_dest[/*static GDBPROTO_BUFFER_CAPACITY*/]);
int SendAck(int connfd);
int SendNak(int connfd);
int Resp(int connfd, const u8* data1, size_t len1, const u8* data2, size_t len2, bool noack);
int WaitAckBlocking(int connfd, u8* ackp, int to_ms);
}
}
#endif

693
src/debug/GdbStub.cpp Normal file
View File

@ -0,0 +1,693 @@
#ifdef _WIN32
#include <WS2tcpip.h>
#include <winsock.h>
#include <winsock2.h>
#endif
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#ifndef _WIN32
#include <sys/socket.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#endif
#include "../Platform.h"
#include "GdbProto.h"
using Platform::Log;
using Platform::LogLevel;
static int SocketSetBlocking(int fd, bool block)
{
#if MOCKTEST
return 0;
#endif
if (fd < 0) return -1;
#ifdef _WIN32
unsigned long mode = block ? 0 : 1;
return ioctlsocket(fd, FIONBIO, &mode);
#else
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) return -1;
flags = block ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK);
return fcntl(fd, F_SETFL, flags);
#endif
}
namespace Gdb
{
GdbStub::GdbStub(StubCallbacks* cb, int port)
: Cb(cb), Port(port)
, SockFd(0), ConnFd(0)
, Stat(TgtStatus::None), CurBkpt(0), CurWatchpt(0), StatFlag(false), NoAck(false)
, ServerSA((void*)new struct sockaddr_in())
, ClientSA((void*)new struct sockaddr_in())
{ }
bool GdbStub::Init()
{
Log(LogLevel::Info, "[GDB] initializing GDB stub for core %d on port %d\n",
Cb->GetCPU(), Port);
#if MOCKTEST
SockFd = 0;
return true;
#endif
#ifndef _WIN32
/*void* fn = SIG_IGN;
struct sigaction act = { 0 };
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = (sighandler_t)fn;
if (sigaction(SIGPIPE, &act, NULL) == -1) {
Log(LogLevel::Warn, "[GDB] couldn't ignore SIGPIPE, stuff may fail on GDB disconnect.\n");
}*/
signal(SIGPIPE, SIG_IGN);
#else
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
Log(LogLevel::Error, "[GDB] winsock could not be initialized (%d).\n", WSAGetLastError());
return false;
}
#endif
int r;
struct sockaddr_in* server = (struct sockaddr_in*)ServerSA;
struct sockaddr_in* client = (struct sockaddr_in*)ClientSA;
int typ = SOCK_STREAM;
#ifdef __linux__
typ |= SOCK_NONBLOCK;
#endif
SockFd = socket(AF_INET, SOCK_STREAM, 0);
if (SockFd < 0)
{
Log(LogLevel::Error, "[GDB] err: can't create a socket fd\n");
goto err;
}
#ifndef __linux__
SocketSetBlocking(SockFd, false);
#endif
server->sin_family = AF_INET;
server->sin_addr.s_addr = htonl(INADDR_ANY);
server->sin_port = htons(Port);
r = bind(SockFd, (const sockaddr*)server, sizeof(*server));
if (r < 0)
{
Log(LogLevel::Error, "[GDB] err: can't bind to address <any> and port %d\n", Port);
goto err;
}
r = listen(SockFd, 5);
if (r < 0)
{
Log(LogLevel::Error, "[GDB] err: can't listen to SockFd\n");
goto err;
}
return true;
err:
if (SockFd != 0)
{
#ifdef _WIN32
closesocket(SockFd);
#else
close(SockFd);
#endif
SockFd = 0;
}
return false;
}
void GdbStub::Close()
{
Disconnect();
if (SockFd > 0) close(SockFd);
SockFd = 0;
}
void GdbStub::Disconnect()
{
if (ConnFd > 0) close(ConnFd);
ConnFd = 0;
}
GdbStub::~GdbStub()
{
Close();
delete (struct sockaddr_in*)ServerSA;
delete (struct sockaddr_in*)ClientSA;
}
SubcmdHandler GdbStub::Handlers_v[] = {
{ .MainCmd = 'v', .SubStr = "Attach;" , .Handler = GdbStub::Handle_v_Attach },
{ .MainCmd = 'v', .SubStr = "Kill;" , .Handler = GdbStub::Handle_v_Kill },
{ .MainCmd = 'v', .SubStr = "Run" , .Handler = GdbStub::Handle_v_Run },
{ .MainCmd = 'v', .SubStr = "Stopped" , .Handler = GdbStub::Handle_v_Stopped },
{ .MainCmd = 'v', .SubStr = "MustReplyEmpty", .Handler = GdbStub::Handle_v_MustReplyEmpty },
{ .MainCmd = 'v', .SubStr = "Cont?" , .Handler = GdbStub::Handle_v_ContQuery },
{ .MainCmd = 'v', .SubStr = "Cont" , .Handler = GdbStub::Handle_v_Cont },
{ .MainCmd = 'v', .SubStr = NULL, .Handler = NULL }
};
SubcmdHandler GdbStub::Handlers_q[] = {
{ .MainCmd = 'q', .SubStr = "HostInfo" , .Handler = GdbStub::Handle_q_HostInfo },
{ .MainCmd = 'q', .SubStr = "ProcessInfo", .Handler = GdbStub::Handle_q_HostInfo },
{ .MainCmd = 'q', .SubStr = "Rcmd," , .Handler = GdbStub::Handle_q_Rcmd },
{ .MainCmd = 'q', .SubStr = "Supported:" , .Handler = GdbStub::Handle_q_Supported },
{ .MainCmd = 'q', .SubStr = "CRC:" , .Handler = GdbStub::Handle_q_CRC },
{ .MainCmd = 'q', .SubStr = "C" , .Handler = GdbStub::Handle_q_C },
{ .MainCmd = 'q', .SubStr = "fThreadInfo", .Handler = GdbStub::Handle_q_fThreadInfo },
{ .MainCmd = 'q', .SubStr = "sThreadInfo", .Handler = GdbStub::Handle_q_sThreadInfo },
{ .MainCmd = 'q', .SubStr = "Attached" , .Handler = GdbStub::Handle_q_Attached },
{ .MainCmd = 'q', .SubStr = "Xfer:features:read:target.xml:", .Handler = GdbStub::Handle_q_features },
{ .MainCmd = 'q', .SubStr = NULL, .Handler = NULL },
};
SubcmdHandler GdbStub::Handlers_Q[] = {
{ .MainCmd = 'Q', .SubStr = "StartNoAckMode", .Handler = GdbStub::Handle_Q_StartNoAckMode },
{ .MainCmd = 'Q', .SubStr = NULL, .Handler = NULL },
};
ExecResult GdbStub::Handle_q(GdbStub* stub, const u8* cmd, ssize_t len)
{
return stub->SubcmdExec(cmd, len, Handlers_q);
}
ExecResult GdbStub::Handle_v(GdbStub* stub, const u8* cmd, ssize_t len)
{
return stub->SubcmdExec(cmd, len, Handlers_v);
}
ExecResult GdbStub::Handle_Q(GdbStub* stub, const u8* cmd, ssize_t len)
{
return stub->SubcmdExec(cmd, len, Handlers_Q);
}
CmdHandler GdbStub::Handlers_top[] = {
{ .Cmd = 'g', .Handler = GdbStub::Handle_g },
{ .Cmd = 'G', .Handler = GdbStub::Handle_G },
{ .Cmd = 'm', .Handler = GdbStub::Handle_m },
{ .Cmd = 'M', .Handler = GdbStub::Handle_M },
{ .Cmd = 'X', .Handler = GdbStub::Handle_X },
{ .Cmd = 'c', .Handler = GdbStub::Handle_c },
{ .Cmd = 's', .Handler = GdbStub::Handle_s },
{ .Cmd = 'p', .Handler = GdbStub::Handle_p },
{ .Cmd = 'P', .Handler = GdbStub::Handle_P },
{ .Cmd = 'H', .Handler = GdbStub::Handle_H },
{ .Cmd = 'T', .Handler = GdbStub::Handle_H },
{ .Cmd = '?', .Handler = GdbStub::Handle_Question },
{ .Cmd = '!', .Handler = GdbStub::Handle_Exclamation },
{ .Cmd = 'D', .Handler = GdbStub::Handle_D },
{ .Cmd = 'r', .Handler = GdbStub::Handle_r },
{ .Cmd = 'R', .Handler = GdbStub::Handle_R },
{ .Cmd = 'k', .Handler = GdbStub::Handle_k },
{ .Cmd = 'z', .Handler = GdbStub::Handle_z },
{ .Cmd = 'Z', .Handler = GdbStub::Handle_Z },
{ .Cmd = 'q', .Handler = GdbStub::Handle_q },
{ .Cmd = 'v', .Handler = GdbStub::Handle_v },
{ .Cmd = 'Q', .Handler = GdbStub::Handle_Q },
{ .Cmd = 0, .Handler = NULL }
};
StubState GdbStub::HandlePacket()
{
ExecResult r = CmdExec(Handlers_top);
if (r == ExecResult::MustBreak)
{
if (Stat == TgtStatus::None || Stat == TgtStatus::Running)
Stat = TgtStatus::BreakReq;
return StubState::Break;
}
else if (r == ExecResult::InitialBreak)
{
Stat = TgtStatus::BreakReq;
return StubState::Attach;
/*}
else if (r == ExecResult::Detached)
{
Stat = TgtStatus::None;
return StubState::Disconnect;*/
}
else if (r == ExecResult::Continue)
{
Stat = TgtStatus::Running;
return StubState::Continue;
}
else if (r == ExecResult::Step)
{
return StubState::Step;
}
else if (r == ExecResult::Ok || r == ExecResult::UnkCmd)
{
return StubState::None;
}
else
{
Stat = TgtStatus::None;
return StubState::Disconnect;
}
}
StubState GdbStub::Poll(bool wait)
{
int r;
if (ConnFd <= 0)
{
SocketSetBlocking(SockFd, wait);
// not yet connected, so let's wait for one
// nonblocking only done in part of read_packet(), so that it can still
// quickly handle partly-received packets
struct sockaddr_in* client = (struct sockaddr_in*)ClientSA;
socklen_t len = sizeof(*client);
#if MOCKTEST
ConnFd = 0;
#else
#ifdef __linux__
ConnFd = accept4(SockFd, (struct sockaddr*)client, &len, /*SOCK_NONBLOCK|*/SOCK_CLOEXEC);
#else
ConnFd = accept(SockFd, (struct sockaddr*)client, &len);
#endif
#endif
if (ConnFd < 0) return StubState::NoConn;
u8 a;
if (Proto::WaitAckBlocking(ConnFd, &a, 1000) < 0)
{
Log(LogLevel::Error, "[GDB] inital handshake: didn't receive inital ack!\n");
close(ConnFd);
ConnFd = 0;
return StubState::Disconnect;
}
if (a != '+')
{
Log(LogLevel::Error, "[GDB] inital handshake: unexpected character '%c'!\n", a);
}
SendAck();
Stat = TgtStatus::Running; // on connected
StatFlag = false;
}
if (StatFlag)
{
StatFlag = false;
//Log(LogLevel::Debug, "[GDB] STAT FLAG WAS TRUE\n");
Handle_Question(this, NULL, 0); // ugly hack but it should work
}
#if MOCKTEST
// nothing...
#else
#ifndef _WIN32
struct pollfd pfd;
pfd.fd = ConnFd;
pfd.events = POLLIN;
pfd.revents = 0;
r = poll(&pfd, 1, wait ? -1 : 0);
if (r == 0) return StubState::None; // nothing is happening
if (pfd.revents & (POLLHUP|POLLERR|POLLNVAL))
{
// oopsie, something happened
Disconnect();
return StubState::Disconnect;
}
#else
fd_set infd, outfd, errfd;
FD_ZERO(&infd); FD_ZERO(&outfd); FD_ZERO(&errfd);
FD_SET(ConnFd, &infd);
struct timeval to;
if (wait)
{
to.tv_sec = ~(time_t)0;
to.tv_usec = ~(long)0;
}
else
{
to.tv_sec = 0;
to.tv_usec = 0;
}
r = select(ConnFd+1, &infd, &outfd, &errfd, &to);
if (FD_ISSET(ConnFd, &errfd))
{
Disconnect();
return StubState::Disconnect;
}
else if (!FD_ISSET(ConnFd, &infd))
{
return StubState::None;
}
#endif
#endif
ReadResult res = Proto::MsgRecv(ConnFd, Cmdbuf);
switch (res)
{
case ReadResult::NoPacket:
return StubState::None;
case ReadResult::Break:
return StubState::Break;
case ReadResult::Wut:
Log(LogLevel::Info, "[GDB] WUT\n");
case_gdbp_eof:
case ReadResult::Eof:
Log(LogLevel::Info, "[GDB] EOF!\n");
close(ConnFd);
ConnFd = 0;
return StubState::Disconnect;
case ReadResult::CksumErr:
Log(LogLevel::Info, "[GDB] checksum err!\n");
if (SendNak() < 0) {
Log(LogLevel::Error, "[GDB] send nak after cksum fail errored!\n");
goto case_gdbp_eof;
}
return StubState::None;
case ReadResult::CmdRecvd:
/*if (SendAck() < 0) {
Log(LogLevel::Error, "[GDB] send packet ack failed!\n");
goto case_gdbp_eof;
}*/
break;
}
return HandlePacket();
}
ExecResult GdbStub::SubcmdExec(const u8* cmd, ssize_t len, const SubcmdHandler* handlers)
{
//Log(LogLevel::Debug, "[GDB] subcommand in: '%s'\n", cmd);
for (size_t i = 0; handlers[i].Handler != NULL; ++i) {
// check if prefix matches
if (!strncmp((const char*)cmd, handlers[i].SubStr, strlen(handlers[i].SubStr)))
{
if (SendAck() < 0)
{
Log(LogLevel::Error, "[GDB] send packet ack failed!\n");
return ExecResult::NetErr;
}
return handlers[i].Handler(this, &cmd[strlen(handlers[i].SubStr)], len-strlen(handlers[i].SubStr));
}
}
Log(LogLevel::Info, "[GDB] unknown subcommand '%s'!\n", cmd);
/*if (SendNak() < 0)
{
Log(LogLevel::Error, "[GDB] send nak after cksum fail errored!\n");
return ExecResult::NetErr;
}*/
//Resp("E99");
Resp(NULL, 0);
return ExecResult::UnkCmd;
}
ExecResult GdbStub::CmdExec(const CmdHandler* handlers)
{
Log(LogLevel::Debug, "[GDB] command in: '%s'\n", Cmdbuf);
for (size_t i = 0; handlers[i].Handler != NULL; ++i)
{
if (handlers[i].Cmd == Cmdbuf[0])
{
if (SendAck() < 0)
{
Log(LogLevel::Error, "[GDB] send packet ack failed!\n");
return ExecResult::NetErr;
}
return handlers[i].Handler(this, &Cmdbuf[1], Cmdlen-1);
}
}
Log(LogLevel::Info, "[GDB] unknown command '%c'!\n", Cmdbuf[0]);
/*if (SendNak() < 0)
{
Log(LogLevel::Error, "[GDB] send nak after cksum fail errored!\n");
return ExecResult::NetErr;
}*/
//RespStr("E99");
Resp(NULL, 0);
return ExecResult::UnkCmd;
}
void GdbStub::SignalStatus(TgtStatus stat, u32 arg)
{
//Log(LogLevel::Debug, "[GDB] SIGNAL STATUS %d!\n", stat);
this->Stat = stat;
StatFlag = true;
if (stat == TgtStatus::Bkpt) CurBkpt = arg;
else if (stat == TgtStatus::Watchpt) CurWatchpt = arg;
}
StubState GdbStub::Enter(bool stay, TgtStatus stat, u32 arg, bool wait_for_conn)
{
if (stat != TgtStatus::NoEvent) SignalStatus(stat, arg);
StubState st;
bool do_next = true;
do
{
bool was_conn = ConnFd > 0;
st = Poll(wait_for_conn);
bool has_conn = ConnFd > 0;
if (has_conn && !was_conn) stay = true;
switch (st)
{
case StubState::Break:
Log(LogLevel::Info, "[GDB] break execution\n");
SignalStatus(TgtStatus::BreakReq, ~(u32)0);
break;
case StubState::Continue:
Log(LogLevel::Info, "[GDB] continue execution\n");
do_next = false;
break;
case StubState::Step:
Log(LogLevel::Info, "[GDB] single-step\n");
do_next = false;
break;
case StubState::Disconnect:
Log(LogLevel::Info, "[GDB] disconnect\n");
SignalStatus(TgtStatus::None, ~(u32)0);
do_next = false;
break;
default: break;
}
}
while (do_next && stay);
if (st != StubState::None && st != StubState::NoConn)
{
Log(LogLevel::Debug, "[GDB] enter exit: %d\n", st);
}
return st;
}
void GdbStub::AddBkpt(u32 addr, int kind)
{
BpWp np;
np.addr = addr ^ (addr & 1); // clear lowest bit to not break on thumb mode weirdnesses
np.len = 0;
np.kind = kind;
{
// already in the map
auto search = BpList.find(np.addr);
if (search != BpList.end()) return;
}
BpList.insert({np.addr, np});
Log(LogLevel::Debug, "[GDB] added bkpt:\n");
size_t i = 0;
for (auto search = BpList.begin(); search != BpList.end(); ++search, ++i)
{
Log(LogLevel::Debug, "\t[%zu]: addr=%08x, kind=%d\n", i, search->first, search->second.kind);
}
}
void GdbStub::AddWatchpt(u32 addr, u32 len, int kind)
{
BpWp np;
np.addr = addr;
np.len = len;
np.kind = kind;
for (auto search = WpList.begin(); search != WpList.end(); ++search)
{
if (search->addr > addr)
{
WpList.insert(search, np);
return;
}
else if (search->addr == addr && search->kind == kind)
{
if (search->len < len) search->len = len;
return;
}
}
WpList.push_back(np);
}
void GdbStub::DelBkpt(u32 addr, int kind)
{
addr = addr ^ (addr & 1);
auto search = BpList.find(addr);
if (search != BpList.end())
{
BpList.erase(search);
}
}
void GdbStub::DelWatchpt(u32 addr, u32 len, int kind)
{
(void)len; (void)kind;
for (auto search = WpList.begin(); search != WpList.end(); ++search)
{
if (search->addr == addr && search->kind == kind)
{
WpList.erase(search);
return;
}
else if (search->addr > addr) return;
}
}
void GdbStub::DelAllBpWp()
{
BpList.erase(BpList.begin(), BpList.end());
WpList.erase(WpList.begin(), WpList.end());
}
StubState GdbStub::CheckBkpt(u32 addr, bool enter, bool stay)
{
addr ^= (addr & 1); // clear lowest bit to not break on thumb mode weirdnesses
auto search = BpList.find(addr);
if (search == BpList.end()) return StubState::CheckNoHit;
if (enter)
{
StubState r = Enter(stay, TgtStatus::Bkpt, addr);
Log(LogLevel::Debug, "[GDB] ENTER st=%d\n", r);
return r;
}
else
{
SignalStatus(TgtStatus::Bkpt, addr);
return StubState::None;
}
}
StubState GdbStub::CheckWatchpt(u32 addr, int kind, bool enter, bool stay)
{
for (auto search = WpList.begin(); search != WpList.end(); ++search)
{
if (search->addr > addr) break;
if (addr >= search->addr && addr < search->addr + search->len && search->kind == kind)
{
if (enter) return Enter(stay, TgtStatus::Watchpt, addr);
else
{
SignalStatus(TgtStatus::Watchpt, addr);
return StubState::None;
}
}
}
return StubState::CheckNoHit;
}
int GdbStub::SendAck()
{
if (NoAck) return 1;
return Proto::SendAck(ConnFd);
}
int GdbStub::SendNak()
{
if (NoAck) return 1;
return Proto::SendNak(ConnFd);
}
int GdbStub::Resp(const u8* data1, size_t len1, const u8* data2, size_t len2)
{
return Proto::Resp(ConnFd, data1, len1, data2, len2, NoAck);
}
int GdbStub::RespC(const char* data1, size_t len1, const u8* data2, size_t len2)
{
return Proto::Resp(ConnFd, (const u8*)data1, len1, data2, len2, NoAck);
}
#if defined(__GCC__) || defined(__clang__)
__attribute__((__format__(printf, 2/*includes implicit this*/, 3)))
#endif
int GdbStub::RespFmt(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
int r = vsnprintf((char*)&Proto::RespBuf[1], sizeof(Proto::RespBuf)-5, fmt, args);
va_end(args);
if (r < 0) return r;
if ((size_t)r >= sizeof(Proto::RespBuf)-5)
{
Log(LogLevel::Error, "[GDB] truncated response in send_fmt()! (lost %zd bytes)\n",
(ssize_t)r - (ssize_t)(sizeof(Proto::RespBuf)-5));
r = sizeof(Proto::RespBuf)-5;
}
return Resp(&Proto::RespBuf[1], r);
}
int GdbStub::RespStr(const char* str)
{
return Resp((const u8*)str, strlen(str));
}
}

184
src/debug/GdbStub.h Normal file
View File

@ -0,0 +1,184 @@
#ifndef GDBSTUB_H_
#define GDBSTUB_H_
#include <stddef.h>
#include <map>
#include <vector>
#include "../types.h"
#include "GdbArch.h"
namespace Gdb
{
enum class TgtStatus
{
NoEvent,
None,
Running,
SingleStep,
BreakReq, // "break" command from gdb client
Bkpt,
Watchpt,
BkptInsn, // "bkpt" instruction
FaultData, // data abort
FaultIAcc, // instruction fetch abort
FaultInsn, // illegal instruction
};
class StubCallbacks
{
public:
StubCallbacks(){}
virtual ~StubCallbacks(){};
virtual int GetCPU() const = 0; // 7 or 9 (currently, maybe also xtensa in the future?)
// 0..14: as usual
// 15: pc *pipeline-corrected*
// 16: cpsr
virtual u32 ReadReg (Register reg) = 0;
virtual void WriteReg(Register reg, u32 value) = 0;
virtual u32 ReadMem (u32 addr, int len) = 0;
virtual void WriteMem(u32 addr, int len, u32 value) = 0;
virtual void ResetGdb() = 0;
virtual int RemoteCmd(const u8* cmd, size_t len) = 0;
};
enum class StubState
{
NoConn,
None,
Break,
Continue,
Step,
Disconnect,
Attach,
CheckNoHit
};
enum class ReadResult
{
NoPacket,
Eof,
CksumErr,
CmdRecvd,
Wut,
Break
};
enum class ExecResult
{
Ok,
UnkCmd,
NetErr,
InitialBreak,
MustBreak,
Detached,
Step,
Continue
};
class GdbStub;
typedef ExecResult (*GdbProtoCmd)(GdbStub* stub, const u8* cmd, ssize_t len);
struct SubcmdHandler
{
char MainCmd;
const char* SubStr;
GdbProtoCmd Handler;
};
struct CmdHandler
{
char Cmd;
GdbProtoCmd Handler;
};
class GdbStub
{
public:
struct BpWp
{
public:
u32 addr, len;
int kind;
};
GdbStub(StubCallbacks* cb, int port);
~GdbStub();
bool Init();
void Close();
StubState Poll(bool wait = false);
void SignalStatus(TgtStatus stat, u32 arg);
StubState Enter(bool stay, TgtStatus stat=TgtStatus::NoEvent, u32 arg=~(u32)0u, bool wait_for_conn=false);
// kind: 2=thumb, 3=thumb2 (not relevant), 4=arm
void AddBkpt(u32 addr, int kind);
void DelBkpt(u32 addr, int kind);
// kind: 2=read, 3=write, 4=rdwr
void AddWatchpt(u32 addr, u32 len, int kind);
void DelWatchpt(u32 addr, u32 len, int kind);
void DelAllBpWp();
StubState CheckBkpt(u32 addr, bool enter, bool stay);
StubState CheckWatchpt(u32 addr, int kind, bool enter, bool stay);
#include "GdbCmds.h"
Gdb::ExecResult SubcmdExec(const u8* cmd, ssize_t len, const SubcmdHandler* handlers);
Gdb::ExecResult CmdExec(const CmdHandler* handlers);
public:
int SendAck();
int SendNak();
int Resp(const u8* data1, size_t len1, const u8* data2 = NULL, size_t len2 = 0);
int RespC(const char* data1, size_t len1, const u8* data2 = NULL, size_t len2 = 0);
#if defined(__GCC__) || defined(__clang__)
__attribute__((__format__(printf, 2, 3)))
#endif
int RespFmt(const char* fmt, ...);
int RespStr(const char* str);
private:
void Disconnect();
StubState HandlePacket();
private:
StubCallbacks* Cb;
//struct sockaddr_in server, client;
void *ServerSA, *ClientSA;
int Port;
int SockFd;
int ConnFd;
TgtStatus Stat;
u32 CurBkpt, CurWatchpt;
bool StatFlag;
bool NoAck;
std::map<u32, BpWp> BpList;
std::vector<BpWp> WpList;
static SubcmdHandler Handlers_v[];
static SubcmdHandler Handlers_q[];
static SubcmdHandler Handlers_Q[];
static CmdHandler Handlers_top[];
};
}
#endif

2
src/debug/gdb_test/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
obj/
test-gdb

View File

@ -0,0 +1,28 @@
default: all
all: test-gdb
CPPFLAGS += -Werror=implicit-function-declaration -Werror=int-conversion \
-Werror=return-type -Werror=uninitialized \
-I../ -I../../ -Og -g -Wall \
-Wno-switch -Wno-pointer-sign
obj/:
@mkdir -vp "$@"
test-gdb: obj/GdbProto.o obj/GdbStub.o obj/GdbCmds.o obj/main.o obj/CRC32.o
$(CXX) $(CPPFLAGS) $(LDFLAGS) -o "$@" $^
obj/Gdb%.o: ../Gdb%.cpp obj/
$(CXX) $(CPPFLAGS) -c -o "$@" "$<"
obj/main.o: main.cpp obj/
$(CXX) $(CPPFLAGS) -c -o "$@" "$<"
obj/CRC32.o: ../../CRC32.cpp obj/
$(CXX) $(CPPFLAGS) -c -o "$@" "$<"
clean:
@$(RM) -rv obj/ test-gdb

124
src/debug/gdb_test/main.cpp Normal file
View File

@ -0,0 +1,124 @@
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "GdbStub.h"
#include "Platform.h"
class Debug : public Gdb::StubCallbacks
{
public:
Debug(){}
~Debug(){}
int GetCPU() const override { return 9; }
u32 ReadReg(Gdb::Register reg) override
{
printf("[==>] read reg %d\n", (int)reg);
if (reg == Gdb::Register::pc) return 0x000000df; // cpsr: irq,fiq disabled, arm, sys mode
else return 0x69420;
}
void WriteReg(Gdb::Register reg, u32 value) override
{
printf("[==>] write reg %d: 0x%08x\n", (int)reg, value);
}
u32 ReadMem(u32 addr, int len) override
{
static const u32 words[] = {
0xeafffffe,
0xe0211002,
0xe12fff1e,
0
};
printf("[==>] read mem 0x%08x (size %u)\n", addr, len);
// $: b $ (arm)
return words[(addr>>2)&3] & ((1uLL<<len)-1);
}
void WriteMem(u32 addr, int len, u32 value) override
{
printf("[==>] write addr 0x%08x (size %u): 0x%08x\n", addr, len, value);
}
void ResetGdb() override
{
printf("[==>] RESET!!!\n");
}
int RemoteCmd(const u8* cmd, size_t len) override
{
printf("[==>] Rcmd: %s\n", cmd);
return 0;
}
};
int main(int argc, char** argv) {
Debug debug;
Gdb::GdbStub stub(&debug, (argc > 1) ? atoi(argv[1]) : 3333);
if (!stub.Init()) return 1;
do
{
while (true)
{
Gdb::StubState s = stub.Poll();
if (s == Gdb::StubState::None || s == Gdb::StubState::NoConn)
{
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 1000*1000; // 1 ms
nanosleep(&ts, NULL);
continue;
}
switch (s)
{
case Gdb::StubState::Attach:
printf("[==>] attached\n");
break;
case Gdb::StubState::Break:
printf("[==>] break execution\n");
stub.SignalStatus(Gdb::TgtStatus::BreakReq, ~(u32)0);
break;
case Gdb::StubState::Continue:
printf("[==>] continue execution\n");
// TODO: send signal status on SIGSTOP? eh.
break;
case Gdb::StubState::Step:
printf("[==>] single-step\n");
stub.SignalStatus(Gdb::TgtStatus::SingleStep, ~(u32)0);
break;
case Gdb::StubState::Disconnect:
printf("[==>] disconnect\n");
stub.SignalStatus(Gdb::TgtStatus::None, ~(u32)0);
break;
}
if (s == Gdb::StubState::Disconnect) break;
}
}
while (false);
stub.Close();
return 0;
}
namespace Platform
{
void Log(LogLevel level, const char* fmt, ...)
{
if (fmt == nullptr) return;
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
}

75
src/debug/hexutil.h Normal file
View File

@ -0,0 +1,75 @@
#ifndef HEXUTIL_GDB_H_
#define HEXUTIL_GDB_H_
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdint.h>
inline static uint8_t hex2nyb(uint8_t v)
{
if (v >= '0' && v <= '9') return v - '0';
else if (v >= 'A' && v <= 'F') return v - 'A' + 0xa;
else if (v >= 'a' && v <= 'f') return v - 'a' + 0xa;
else
{
__builtin_trap();
return 0xcc;
}
}
inline static uint8_t nyb2hex(uint8_t v)
{
v &= 0xf;
if (v >= 0xa) return v - 0xa + 'a';
else return v - 0x0 + '0';
}
inline static void hexfmt8(uint8_t* dst, uint8_t v)
{
dst[0] = nyb2hex(v>>4);
dst[1] = nyb2hex(v>>0);
}
inline static uint8_t unhex8(const uint8_t* src)
{
return (hex2nyb(src[0]) << 4) | hex2nyb(src[1]);
}
inline static void hexfmt16(uint8_t* dst, uint16_t v)
{
dst[0] = nyb2hex(v>> 4);
dst[1] = nyb2hex(v>> 0);
dst[2] = nyb2hex(v>>12);
dst[3] = nyb2hex(v>> 8);
}
inline static uint16_t unhex16(const uint8_t* src)
{
return unhex8(&src[0*2]) | ((uint16_t)unhex8(&src[1*2]) << 8);
}
inline static void hexfmt32(uint8_t* dst, uint32_t v)
{
for (size_t i = 0; i < 4; ++i, v >>= 8)
{
dst[2*i+0] = nyb2hex(v>>4);
dst[2*i+1] = nyb2hex(v>>0);
}
}
inline static uint32_t unhex32(const uint8_t* src)
{
uint32_t v = 0;
for (size_t i = 0; i < 4; ++i)
{
v |= (uint32_t)unhex8(&src[i*2]) << (i*8);
}
return v;
}
#ifdef __cplusplus
}
#endif
#endif