#include #include #include "../CRC32.h" #include "../Platform.h" #include "hexutil.h" #include "GdbProto.h" using namespace melonDS; 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 \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ /* 16 regs */ \ #define TARGET_XML__MODE_REGS \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ "" \ /* 23 regs */ \ const char* TARGET_XML_ARM7 = "" "armv4t" "none" "" TARGET_XML__CORE_REGS TARGET_XML__MODE_REGS // 39 regs total "" ""; const char* TARGET_XML_ARM9 = "" "armv5te" "none" "" TARGET_XML__CORE_REGS TARGET_XML__MODE_REGS // 39 regs total "" ""; // 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(i)); hexfmt32(®strbuf[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(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", ®) != 1 || reg < 0 || reg >= GDB_ARCH_N_REG) { stub->RespStr("E01"); return ExecResult::Ok; } u32 v = stub->Cb->ReadReg(static_cast(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", ®, &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(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; } }