From 3ab752b8ca7878246c3d7f8a338a8bc3b0de26dd Mon Sep 17 00:00:00 2001 From: PoroCYon <3253268+PoroCYon@users.noreply.github.com> Date: Sun, 22 Oct 2023 15:35:31 +0200 Subject: [PATCH] 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 --- .gitignore | 2 +- CMakeLists.txt | 5 + src/ARM.cpp | 286 +++++++ src/ARM.h | 121 ++- src/ARMInterpreter.cpp | 10 + src/ARMJIT_A64/ARMJIT_Linkage.S | 5 + src/ARMJIT_x64/ARMJIT_Linkage.S | 5 + src/CMakeLists.txt | 22 +- src/NDS.cpp | 3 + src/Platform.h | 10 +- src/SPU.cpp | 10 +- src/debug/GdbArch.h | 62 ++ src/debug/GdbCmds.cpp | 924 ++++++++++++++++++++++ src/debug/GdbCmds.h | 53 ++ src/debug/GdbProto.cpp | 389 +++++++++ src/debug/GdbProto.h | 40 + src/debug/GdbStub.cpp | 693 ++++++++++++++++ src/debug/GdbStub.h | 184 +++++ src/debug/gdb_test/.gitignore | 2 + src/debug/gdb_test/Makefile | 28 + src/debug/gdb_test/main.cpp | 124 +++ src/debug/hexutil.h | 75 ++ src/frontend/qt_sdl/CMakeLists.txt | 1 + src/frontend/qt_sdl/Config.cpp | 16 + src/frontend/qt_sdl/Config.h | 6 + src/frontend/qt_sdl/EmuSettingsDialog.cpp | 64 +- src/frontend/qt_sdl/EmuSettingsDialog.h | 2 + src/frontend/qt_sdl/EmuSettingsDialog.ui | 106 ++- src/frontend/qt_sdl/Platform.cpp | 11 + 29 files changed, 3210 insertions(+), 49 deletions(-) create mode 100644 src/debug/GdbArch.h create mode 100644 src/debug/GdbCmds.cpp create mode 100644 src/debug/GdbCmds.h create mode 100644 src/debug/GdbProto.cpp create mode 100644 src/debug/GdbProto.h create mode 100644 src/debug/GdbStub.cpp create mode 100644 src/debug/GdbStub.h create mode 100644 src/debug/gdb_test/.gitignore create mode 100644 src/debug/gdb_test/Makefile create mode 100644 src/debug/gdb_test/main.cpp create mode 100644 src/debug/hexutil.h diff --git a/.gitignore b/.gitignore index e76a0944..1aa5e3a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -build +build*/ bin obj *.depend diff --git a/CMakeLists.txt b/CMakeLists.txt index 57d82eff..598b3bdb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,6 +99,11 @@ if (CCACHE) set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) endif() +option(ENABLE_GDBSTUB "Enable GDB stub" ON) +if (ENABLE_GDBSTUB) + add_definitions(-DGDBSTUB_ENABLED) +endif() + option(BUILD_QT_SDL "Build Qt/SDL frontend" ON) add_subdirectory(src) diff --git a/src/ARM.cpp b/src/ARM.cpp index b59530d1..2fd7c2d4 100644 --- a/src/ARM.cpp +++ b/src/ARM.cpp @@ -25,6 +25,7 @@ #include "AREngine.h" #include "ARMJIT.h" #include "Platform.h" +#include "GPU.h" #ifdef JIT_ENABLED #include "ARMJIT.h" @@ -34,6 +35,45 @@ using Platform::Log; using Platform::LogLevel; +#ifdef GDBSTUB_ENABLED +void ARM::GdbCheckA() +{ + if (!IsSingleStep && !BreakReq) + { // check if eg. break signal is incoming etc. + Gdb::StubState st = GdbStub.Enter(false, Gdb::TgtStatus::NoEvent, ~(u32)0u, BreakOnStartup); + BreakOnStartup = false; + IsSingleStep = st == Gdb::StubState::Step; + BreakReq = st == Gdb::StubState::Attach || st == Gdb::StubState::Break; + } +} +void ARM::GdbCheckB() +{ + if (IsSingleStep || BreakReq) + { // use else here or we single-step the same insn twice in gdb + u32 pc_real = R[15] - ((CPSR & 0x20) ? 2 : 4); + Gdb::StubState st = GdbStub.Enter(true, Gdb::TgtStatus::SingleStep, pc_real); + IsSingleStep = st == Gdb::StubState::Step; + BreakReq = st == Gdb::StubState::Attach || st == Gdb::StubState::Break; + } +} +void ARM::GdbCheckC() +{ + u32 pc_real = R[15] - ((CPSR & 0x20) ? 2 : 4); + Gdb::StubState st = GdbStub.CheckBkpt(pc_real, true, true); + if (st != Gdb::StubState::CheckNoHit) + { + IsSingleStep = st == Gdb::StubState::Step; + BreakReq = st == Gdb::StubState::Attach || st == Gdb::StubState::Break; + } + else GdbCheckB(); +} +#else +ARM::GdbCheckA() {} +ARM::GdbCheckB() {} +ARM::GdbCheckC() {} +#endif + + // instruction timing notes // // * simple instruction: 1S (code) @@ -70,9 +110,22 @@ u32 ARM::ConditionTable[16] = ARM::ARM(u32 num) +#ifdef GDBSTUB_ENABLED + : GdbStub(this, Platform::GetConfigInt(num ? Platform::GdbPortARM7 : Platform::GdbPortARM9)) +#endif { // well uh Num = num; + +#ifdef GDBSTUB_ENABLED + if (Platform::GetConfigBool(Platform::GdbEnabled) +#ifdef JIT_ENABLED + && !Platform::GetConfigBool(Platform::JIT_Enable) +#endif + ) + GdbStub.Init(); + IsSingleStep = false; +#endif } ARM::~ARM() @@ -139,6 +192,13 @@ void ARM::Reset() FastBlockLookupSize = 0; #endif +#ifdef GDBSTUB_ENABLED + IsSingleStep = false; + BreakReq = false; + BreakOnStartup = Platform::GetConfigBool( + Num ? Platform::GdbARM7BreakOnStartup : Platform::GdbARM9BreakOnStartup); +#endif + // zorp JumpTo(ExceptionBase); } @@ -571,8 +631,15 @@ void ARMv5::DataAbort() JumpTo(ExceptionBase + 0x10); } +void ARM::CheckGdbIncoming() +{ + GdbCheckA(); +} + void ARMv5::Execute() { + GdbCheckB(); + if (Halted) { if (Halted == 2) @@ -596,6 +663,8 @@ void ARMv5::Execute() { if (CPSR & 0x20) // THUMB { + GdbCheckC(); + // prefetch R[15] += 2; CurInstr = NextInstr[0]; @@ -609,6 +678,8 @@ void ARMv5::Execute() } else { + GdbCheckC(); + // prefetch R[15] += 4; CurInstr = NextInstr[0]; @@ -723,6 +794,8 @@ void ARMv5::ExecuteJIT() void ARMv4::Execute() { + GdbCheckB(); + if (Halted) { if (Halted == 2) @@ -746,6 +819,8 @@ void ARMv4::Execute() { if (CPSR & 0x20) // THUMB { + GdbCheckC(); + // prefetch R[15] += 2; CurInstr = NextInstr[0]; @@ -758,6 +833,8 @@ void ARMv4::Execute() } else { + GdbCheckC(); + // prefetch R[15] += 4; CurInstr = NextInstr[0]; @@ -916,3 +993,212 @@ void ARMv4::FillPipeline() NextInstr[1] = CodeRead32(R[15]); } } + +#ifdef GDBSTUB_ENABLED +u32 ARM::ReadReg(Gdb::Register reg) +{ + using Gdb::Register; + int r = static_cast(reg); + + if (reg < Register::pc) return R[r]; + else if (reg == Register::pc) + { + return R[r] - ((CPSR & 0x20) ? 2 : 4); + } + else if (reg == Register::cpsr) return CPSR; + else if (reg == Register::sp_usr || reg == Register::lr_usr) + { + r -= static_cast(Register::sp_usr); + if (ModeIs(0x10) || ModeIs(0x1f)) + { + return R[13 + r]; + } + else switch (CPSR & 0x1f) + { + case 0x11: return R_FIQ[5 + r]; + case 0x12: return R_IRQ[0 + r]; + case 0x13: return R_SVC[0 + r]; + case 0x17: return R_ABT[0 + r]; + case 0x1b: return R_UND[0 + r]; + } + } + else if (reg >= Register::r8_fiq && reg <= Register::lr_fiq) + { + r -= static_cast(Register::r8_fiq); + return ModeIs(0x11) ? R[ 8 + r] : R_FIQ[r]; + } + else if (reg == Register::sp_irq || reg == Register::lr_irq) + { + r -= static_cast(Register::sp_irq); + return ModeIs(0x12) ? R[13 + r] : R_IRQ[r]; + } + else if (reg == Register::sp_svc || reg == Register::lr_svc) + { + r -= static_cast(Register::sp_svc); + return ModeIs(0x13) ? R[13 + r] : R_SVC[r]; + } + else if (reg == Register::sp_abt || reg == Register::lr_abt) + { + r -= static_cast(Register::sp_abt); + return ModeIs(0x17) ? R[13 + r] : R_ABT[r]; + } + else if (reg == Register::sp_und || reg == Register::lr_und) + { + r -= static_cast(Register::sp_und); + return ModeIs(0x1b) ? R[13 + r] : R_UND[r]; + } + else if (reg == Register::spsr_fiq) return ModeIs(0x11) ? CPSR : R_FIQ[7]; + else if (reg == Register::spsr_irq) return ModeIs(0x12) ? CPSR : R_IRQ[2]; + else if (reg == Register::spsr_svc) return ModeIs(0x13) ? CPSR : R_SVC[2]; + else if (reg == Register::spsr_abt) return ModeIs(0x17) ? CPSR : R_ABT[2]; + else if (reg == Register::spsr_und) return ModeIs(0x1b) ? CPSR : R_UND[2]; + + Log(LogLevel::Warn, "GDB reg read: unknown reg no %d\n", r); + return 0xdeadbeef; +} +void ARM::WriteReg(Gdb::Register reg, u32 v) +{ + using Gdb::Register; + int r = static_cast(reg); + + if (reg < Register::pc) R[r] = v; + else if (reg == Register::pc) JumpTo(v); + else if (reg == Register::cpsr) CPSR = v; + else if (reg == Register::sp_usr || reg == Register::lr_usr) + { + r -= static_cast(Register::sp_usr); + if (ModeIs(0x10) || ModeIs(0x1f)) + { + R[13 + r] = v; + } + else switch (CPSR & 0x1f) + { + case 0x11: R_FIQ[5 + r] = v; break; + case 0x12: R_IRQ[0 + r] = v; break; + case 0x13: R_SVC[0 + r] = v; break; + case 0x17: R_ABT[0 + r] = v; break; + case 0x1b: R_UND[0 + r] = v; break; + } + } + else if (reg >= Register::r8_fiq && reg <= Register::lr_fiq) + { + r -= static_cast(Register::r8_fiq); + *(ModeIs(0x11) ? &R[ 8 + r] : &R_FIQ[r]) = v; + } + else if (reg == Register::sp_irq || reg == Register::lr_irq) + { + r -= static_cast(Register::sp_irq); + *(ModeIs(0x12) ? &R[13 + r] : &R_IRQ[r]) = v; + } + else if (reg == Register::sp_svc || reg == Register::lr_svc) + { + r -= static_cast(Register::sp_svc); + *(ModeIs(0x13) ? &R[13 + r] : &R_SVC[r]) = v; + } + else if (reg == Register::sp_abt || reg == Register::lr_abt) + { + r -= static_cast(Register::sp_abt); + *(ModeIs(0x17) ? &R[13 + r] : &R_ABT[r]) = v; + } + else if (reg == Register::sp_und || reg == Register::lr_und) + { + r -= static_cast(Register::sp_und); + *(ModeIs(0x1b) ? &R[13 + r] : &R_UND[r]) = v; + } + else if (reg == Register::spsr_fiq) + { + *(ModeIs(0x11) ? &CPSR : &R_FIQ[7]) = v; + } + else if (reg == Register::spsr_irq) + { + *(ModeIs(0x12) ? &CPSR : &R_IRQ[2]) = v; + } + else if (reg == Register::spsr_svc) + { + *(ModeIs(0x13) ? &CPSR : &R_SVC[2]) = v; + } + else if (reg == Register::spsr_abt) + { + *(ModeIs(0x17) ? &CPSR : &R_ABT[2]) = v; + } + else if (reg == Register::spsr_und) + { + *(ModeIs(0x1b) ? &CPSR : &R_UND[2]) = v; + } + else Log(LogLevel::Warn, "GDB reg write: unknown reg no %d (write 0x%08x)\n", r, v); +} +u32 ARM::ReadMem(u32 addr, int size) +{ + if (size == 8) return BusRead8(addr); + else if (size == 16) return BusRead16(addr); + else if (size == 32) return BusRead32(addr); + else return 0xfeedface; +} +void ARM::WriteMem(u32 addr, int size, u32 v) +{ + if (size == 8) BusWrite8(addr, (u8)v); + else if (size == 16) BusWrite16(addr, (u16)v); + else if (size == 32) BusWrite32(addr, v); +} + +void ARM::ResetGdb() +{ + NDS::Reset(); + GPU::StartFrame(); // need this to properly kick off the scheduler & frame output +} +int ARM::RemoteCmd(const u8* cmd, size_t len) +{ + (void)len; + + Log(LogLevel::Info, "[ARMGDB] Rcmd: \"%s\"\n", cmd); + if (!strcmp((const char*)cmd, "reset") || !strcmp((const char*)cmd, "r")) + { + Reset(); + return 0; + } + + return 1; // not implemented (yet) +} + +void ARMv5::WriteMem(u32 addr, int size, u32 v) +{ + if (addr < ITCMSize) + { + if (size == 8) *(u8*)&ITCM[addr & (ITCMPhysicalSize - 1)] = (u8)v; + else if (size == 16) *(u16*)&ITCM[addr & (ITCMPhysicalSize - 1)] = (u16)v; + else if (size == 32) *(u32*)&ITCM[addr & (ITCMPhysicalSize - 1)] = (u32)v; + else {} + return; + } + else if ((addr & DTCMMask) == DTCMBase) + { + if (size == 8) *(u8*)&DTCM[addr & (DTCMPhysicalSize - 1)] = (u8)v; + else if (size == 16) *(u16*)&DTCM[addr & (DTCMPhysicalSize - 1)] = (u16)v; + else if (size == 32) *(u32*)&DTCM[addr & (DTCMPhysicalSize - 1)] = (u32)v; + else {} + return; + } + + ARM::WriteMem(addr, size, v); +} +u32 ARMv5::ReadMem(u32 addr, int size) +{ + if (addr < ITCMSize) + { + if (size == 8) return *(u8*)&ITCM[addr & (ITCMPhysicalSize - 1)]; + else if (size == 16) return *(u16*)&ITCM[addr & (ITCMPhysicalSize - 1)]; + else if (size == 32) return *(u32*)&ITCM[addr & (ITCMPhysicalSize - 1)]; + else return 0xfeedface; + } + else if ((addr & DTCMMask) == DTCMBase) + { + if (size == 8) return *(u8*)&DTCM[addr & (DTCMPhysicalSize - 1)]; + else if (size == 16) return *(u16*)&DTCM[addr & (DTCMPhysicalSize - 1)]; + else if (size == 32) return *(u32*)&DTCM[addr & (DTCMPhysicalSize - 1)]; + else return 0xfeedface; + } + + return ARM::ReadMem(addr, size); +} +#endif + diff --git a/src/ARM.h b/src/ARM.h index 6cfb6772..8690f313 100644 --- a/src/ARM.h +++ b/src/ARM.h @@ -24,6 +24,10 @@ #include "types.h" #include "NDS.h" +#ifdef GDBSTUB_ENABLED +#include "debug/GdbStub.h" +#endif + inline u32 ROR(u32 x, u32 n) { return (x >> (n&0x1F)) | (x << ((32-n)&0x1F)); @@ -39,6 +43,9 @@ const u32 ITCMPhysicalSize = 0x8000; const u32 DTCMPhysicalSize = 0x4000; class ARM +#ifdef GDBSTUB_ENABLED + : public Gdb::StubCallbacks +#endif { public: ARM(u32 num); @@ -93,6 +100,18 @@ public: if (v) CPSR |= 0x10000000; } + inline bool ModeIs(u32 mode) + { + u32 cm = CPSR & 0x1f; + mode &= 0x1f; + + if (mode == cm) return true; + if (mode == 0x17) return cm >= 0x14 && cm <= 0x17; // abt + if (mode == 0x1b) return cm >= 0x18 && cm <= 0x1b; // und + + return false; + } + void UpdateMode(u32 oldmode, u32 newmode, bool phony = false); void TriggerIRQ(); @@ -114,6 +133,7 @@ public: virtual void AddCycles_CDI() = 0; virtual void AddCycles_CD() = 0; + void CheckGdbIncoming(); u32 Num; @@ -155,6 +175,9 @@ public: #endif static u32 ConditionTable[16]; +#ifdef GDBSTUB_ENABLED + Gdb::GdbStub GdbStub; +#endif protected: u8 (*BusRead8)(u32 addr); @@ -163,6 +186,29 @@ protected: void (*BusWrite8)(u32 addr, u8 val); void (*BusWrite16)(u32 addr, u16 val); void (*BusWrite32)(u32 addr, u32 val); + +#ifdef GDBSTUB_ENABLED + bool IsSingleStep; + bool BreakReq; + bool BreakOnStartup; + +public: + int GetCPU() const override { return Num ? 7 : 9; } + + u32 ReadReg(Gdb::Register reg) override; + void WriteReg(Gdb::Register reg, u32 v) override; + u32 ReadMem(u32 addr, int size) override; + void WriteMem(u32 addr, int size, u32 v) override; + + void ResetGdb() override; + int RemoteCmd(const u8* cmd, size_t len) override; + +protected: +#endif + + void GdbCheckA(); + void GdbCheckB(); + void GdbCheckC(); }; class ARMv5 : public ARM @@ -171,51 +217,51 @@ public: ARMv5(); ~ARMv5(); - void Reset(); + void Reset() override; - void DoSavestate(Savestate* file); + void DoSavestate(Savestate* file) override; void UpdateRegionTimings(u32 addrstart, u32 addrend); - void FillPipeline(); + void FillPipeline() override; - void JumpTo(u32 addr, bool restorecpsr = false); + void JumpTo(u32 addr, bool restorecpsr = false) override; void PrefetchAbort(); void DataAbort(); - void Execute(); + void Execute() override; #ifdef JIT_ENABLED - void ExecuteJIT(); + void ExecuteJIT() override; #endif // all code accesses are forced nonseq 32bit u32 CodeRead32(u32 addr, bool branch); - void DataRead8(u32 addr, u32* val); - void DataRead16(u32 addr, u32* val); - void DataRead32(u32 addr, u32* val); - void DataRead32S(u32 addr, u32* val); - void DataWrite8(u32 addr, u8 val); - void DataWrite16(u32 addr, u16 val); - void DataWrite32(u32 addr, u32 val); - void DataWrite32S(u32 addr, u32 val); + void DataRead8(u32 addr, u32* val) override; + void DataRead16(u32 addr, u32* val) override; + void DataRead32(u32 addr, u32* val) override; + void DataRead32S(u32 addr, u32* val) override; + void DataWrite8(u32 addr, u8 val) override; + void DataWrite16(u32 addr, u16 val) override; + void DataWrite32(u32 addr, u32 val) override; + void DataWrite32S(u32 addr, u32 val) override; - void AddCycles_C() + void AddCycles_C() override { // code only. always nonseq 32-bit for ARM9. s32 numC = (R[15] & 0x2) ? 0 : CodeCycles; Cycles += numC; } - void AddCycles_CI(s32 numI) + void AddCycles_CI(s32 numI) override { // code+internal s32 numC = (R[15] & 0x2) ? 0 : CodeCycles; Cycles += numC + numI; } - void AddCycles_CDI() + void AddCycles_CDI() override { // LDR/LDM cycles. ARM9 seems to skip the internal cycle there. // TODO: ITCM data fetches shouldn't be parallelized, they say @@ -228,7 +274,7 @@ public: // Cycles += numC + numD; } - void AddCycles_CD() + void AddCycles_CD() override { // TODO: ITCM data fetches shouldn't be parallelized, they say s32 numC = (R[15] & 0x2) ? 0 : CodeCycles; @@ -302,6 +348,11 @@ public: u8* CurICacheLine; bool (*GetMemRegion)(u32 addr, bool write, NDS::MemRegion* region); + +#ifdef GDBSTUB_ENABLED + u32 ReadMem(u32 addr, int size) override; + void WriteMem(u32 addr, int size, u32 v) override; +#endif }; class ARMv4 : public ARM @@ -309,15 +360,15 @@ class ARMv4 : public ARM public: ARMv4(); - void Reset(); + void Reset() override; - void FillPipeline(); + void FillPipeline() override; - void JumpTo(u32 addr, bool restorecpsr = false); + void JumpTo(u32 addr, bool restorecpsr = false) override; - void Execute(); + void Execute() override; #ifdef JIT_ENABLED - void ExecuteJIT(); + void ExecuteJIT() override; #endif u16 CodeRead16(u32 addr) @@ -330,14 +381,14 @@ public: return BusRead32(addr); } - void DataRead8(u32 addr, u32* val) + void DataRead8(u32 addr, u32* val) override { *val = BusRead8(addr); DataRegion = addr; DataCycles = NDS::ARM7MemTimings[addr >> 15][0]; } - void DataRead16(u32 addr, u32* val) + void DataRead16(u32 addr, u32* val) override { addr &= ~1; @@ -346,7 +397,7 @@ public: DataCycles = NDS::ARM7MemTimings[addr >> 15][0]; } - void DataRead32(u32 addr, u32* val) + void DataRead32(u32 addr, u32* val) override { addr &= ~3; @@ -355,7 +406,7 @@ public: DataCycles = NDS::ARM7MemTimings[addr >> 15][2]; } - void DataRead32S(u32 addr, u32* val) + void DataRead32S(u32 addr, u32* val) override { addr &= ~3; @@ -363,14 +414,14 @@ public: DataCycles += NDS::ARM7MemTimings[addr >> 15][3]; } - void DataWrite8(u32 addr, u8 val) + void DataWrite8(u32 addr, u8 val) override { BusWrite8(addr, val); DataRegion = addr; DataCycles = NDS::ARM7MemTimings[addr >> 15][0]; } - void DataWrite16(u32 addr, u16 val) + void DataWrite16(u32 addr, u16 val) override { addr &= ~1; @@ -379,7 +430,7 @@ public: DataCycles = NDS::ARM7MemTimings[addr >> 15][0]; } - void DataWrite32(u32 addr, u32 val) + void DataWrite32(u32 addr, u32 val) override { addr &= ~3; @@ -388,7 +439,7 @@ public: DataCycles = NDS::ARM7MemTimings[addr >> 15][2]; } - void DataWrite32S(u32 addr, u32 val) + void DataWrite32S(u32 addr, u32 val) override { addr &= ~3; @@ -397,19 +448,19 @@ public: } - void AddCycles_C() + void AddCycles_C() override { // code only. this code fetch is sequential. Cycles += NDS::ARM7MemTimings[CodeCycles][(CPSR&0x20)?1:3]; } - void AddCycles_CI(s32 num) + void AddCycles_CI(s32 num) override { // code+internal. results in a nonseq code fetch. Cycles += NDS::ARM7MemTimings[CodeCycles][(CPSR&0x20)?0:2] + num; } - void AddCycles_CDI() + void AddCycles_CDI() override { // LDR/LDM cycles. s32 numC = NDS::ARM7MemTimings[CodeCycles][(CPSR&0x20)?0:2]; @@ -436,7 +487,7 @@ public: } } - void AddCycles_CD() + void AddCycles_CD() override { // TODO: max gain should be 5c when writing to mainRAM s32 numC = NDS::ARM7MemTimings[CodeCycles][(CPSR&0x20)?0:2]; diff --git a/src/ARMInterpreter.cpp b/src/ARMInterpreter.cpp index 35854d16..0451894f 100644 --- a/src/ARMInterpreter.cpp +++ b/src/ARMInterpreter.cpp @@ -27,6 +27,10 @@ using Platform::Log; using Platform::LogLevel; +#ifdef GDBSTUB_ENABLED +#include "debug/GdbStub.h" +#endif + namespace ARMInterpreter { @@ -34,6 +38,9 @@ namespace ARMInterpreter void A_UNK(ARM* cpu) { Log(LogLevel::Warn, "undefined ARM%d instruction %08X @ %08X\n", cpu->Num?7:9, cpu->CurInstr, cpu->R[15]-8); +#ifdef GDBSTUB_ENABLED + cpu->GdbStub.Enter(true, Gdb::TgtStatus::FaultInsn, cpu->R[15]-8); +#endif //for (int i = 0; i < 16; i++) printf("R%d: %08X\n", i, cpu->R[i]); //NDS::Halt(); u32 oldcpsr = cpu->CPSR; @@ -49,6 +56,9 @@ void A_UNK(ARM* cpu) void T_UNK(ARM* cpu) { Log(LogLevel::Warn, "undefined THUMB%d instruction %04X @ %08X\n", cpu->Num?7:9, cpu->CurInstr, cpu->R[15]-4); +#ifdef GDBSTUB_ENABLED + cpu->GdbStub.Enter(true, Gdb::TgtStatus::FaultInsn, cpu->R[15]-4); +#endif //NDS::Halt(); u32 oldcpsr = cpu->CPSR; cpu->CPSR &= ~0xBF; diff --git a/src/ARMJIT_A64/ARMJIT_Linkage.S b/src/ARMJIT_A64/ARMJIT_Linkage.S index c1ecd7c8..ad1a3f0f 100644 --- a/src/ARMJIT_A64/ARMJIT_Linkage.S +++ b/src/ARMJIT_A64/ARMJIT_Linkage.S @@ -94,3 +94,8 @@ ARM_RestoreContext: mov sp, x17 br x18 + +#ifndef __APPLE__ +.section .note.GNU-stack,"",@progbits +#endif + diff --git a/src/ARMJIT_x64/ARMJIT_Linkage.S b/src/ARMJIT_x64/ARMJIT_Linkage.S index fe7b1435..ff24e58c 100644 --- a/src/ARMJIT_x64/ARMJIT_Linkage.S +++ b/src/ARMJIT_x64/ARMJIT_Linkage.S @@ -104,3 +104,8 @@ ARM_Ret: #endif ret + +#if !defined(__APPLE__) && !defined(WIN64) +.section .note.GNU-stack,"",@progbits +#endif + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0fa96848..eb5f81e0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -61,6 +61,15 @@ add_library(core STATIC tiny-AES-c/aes.c xxhash/xxhash.c) +if (ENABLE_GDBSTUB) + message(NOTICE "Enabling GDB stub") + target_sources(core PRIVATE + debug/GdbStub.cpp + debug/GdbProto.cpp + debug/GdbCmds.cpp + ) +endif() + if (ENABLE_OGLRENDERER) target_sources(core PRIVATE GPU_OpenGL.cpp @@ -131,7 +140,7 @@ if (ENABLE_JIT) endif() if (WIN32) - target_link_libraries(core PRIVATE ole32 comctl32 ws2_32) + target_link_libraries(core PRIVATE ole32 comctl32 wsock32 ws2_32) elseif(NOT APPLE) check_library_exists(rt shm_open "" NEED_LIBRT) if (NEED_LIBRT) @@ -143,3 +152,14 @@ if (ENABLE_JIT_PROFILING) target_include_directories(core PRIVATE "${VTUNE_INCLUDE_DIR}") target_link_libraries(core PRIVATE "${VTUNE_LIBRARY}") endif() + +#if(CMAKE_BUILD_TYPE MATCHES "Debug") +# set( +# CMAKE_C_FLAGS +# "${CMAKE_C_FLAGS} -fsanitize=undefined -fsanitize=address" +# ) +# target_link_options(core +# BEFORE PUBLIC -fsanitize=undefined PUBLIC -fsanitize=address +# ) +#endif() + diff --git a/src/NDS.cpp b/src/NDS.cpp index b1ee4550..ae45d3da 100644 --- a/src/NDS.cpp +++ b/src/NDS.cpp @@ -1068,6 +1068,9 @@ u32 RunFrame() bool runFrame = Running && !(CPUStop & 0x40000000); if (runFrame) { + ARM9->CheckGdbIncoming(); + ARM7->CheckGdbIncoming(); + GPU::StartFrame(); while (Running && GPU::TotalScanlines==0) diff --git a/src/Platform.h b/src/Platform.h index 67e6b335..b40dce9e 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -134,7 +134,15 @@ enum ConfigEntry AudioBitDepth, - DSi_FullBIOSBoot + DSi_FullBIOSBoot, + +#ifdef GDBSTUB_ENABLED + GdbEnabled, + GdbPortARM7, + GdbPortARM9, + GdbARM7BreakOnStartup, + GdbARM9BreakOnStartup, +#endif }; int GetConfigInt(ConfigEntry entry); diff --git a/src/SPU.cpp b/src/SPU.cpp index e83a0c71..3939aef7 100644 --- a/src/SPU.cpp +++ b/src/SPU.cpp @@ -856,9 +856,13 @@ void Mix(u32 dummy) // OutputBufferFrame can never get full because it's // transfered to OutputBuffer at the end of the frame - OutputBackbuffer[OutputBackbufferWritePosition ] = leftoutput >> 1; - OutputBackbuffer[OutputBackbufferWritePosition + 1] = rightoutput >> 1; - OutputBackbufferWritePosition += 2; + // FIXME: apparently this does happen!!! + if (OutputBackbufferWritePosition * 2 < OutputBufferSize - 1) + { + OutputBackbuffer[OutputBackbufferWritePosition ] = leftoutput >> 1; + OutputBackbuffer[OutputBackbufferWritePosition + 1] = rightoutput >> 1; + OutputBackbufferWritePosition += 2; + } NDS::ScheduleEvent(NDS::Event_SPU, true, 1024, Mix, 0); } diff --git a/src/debug/GdbArch.h b/src/debug/GdbArch.h new file mode 100644 index 00000000..45f1c1b2 --- /dev/null +++ b/src/debug/GdbArch.h @@ -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 + diff --git a/src/debug/GdbCmds.cpp b/src/debug/GdbCmds.cpp new file mode 100644 index 00000000..057502f2 --- /dev/null +++ b/src/debug/GdbCmds.cpp @@ -0,0 +1,924 @@ + +#include +#include + +#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 \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + /* 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; +} + +} + diff --git a/src/debug/GdbCmds.h b/src/debug/GdbCmds.h new file mode 100644 index 00000000..4f300606 --- /dev/null +++ b/src/debug/GdbCmds.h @@ -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); + diff --git a/src/debug/GdbProto.cpp b/src/debug/GdbProto.cpp new file mode 100644 index 00000000..dc803649 --- /dev/null +++ b/src/debug/GdbProto.cpp @@ -0,0 +1,389 @@ + +#ifdef _WIN32 +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#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; +} + +} + +} + diff --git a/src/debug/GdbProto.h b/src/debug/GdbProto.h new file mode 100644 index 00000000..ef8bdec9 --- /dev/null +++ b/src/debug/GdbProto.h @@ -0,0 +1,40 @@ + +#ifndef GDBPROTO_H_ +#define GDBPROTO_H_ + +#include +#include + +#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 + diff --git a/src/debug/GdbStub.cpp b/src/debug/GdbStub.cpp new file mode 100644 index 00000000..d756ce44 --- /dev/null +++ b/src/debug/GdbStub.cpp @@ -0,0 +1,693 @@ + +#ifdef _WIN32 +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#include +#include +#include +#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 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)); +} + +} + diff --git a/src/debug/GdbStub.h b/src/debug/GdbStub.h new file mode 100644 index 00000000..b3bdab72 --- /dev/null +++ b/src/debug/GdbStub.h @@ -0,0 +1,184 @@ + +#ifndef GDBSTUB_H_ +#define GDBSTUB_H_ + +#include +#include +#include + +#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 BpList; + std::vector WpList; + + static SubcmdHandler Handlers_v[]; + static SubcmdHandler Handlers_q[]; + static SubcmdHandler Handlers_Q[]; + static CmdHandler Handlers_top[]; +}; + +} + +#endif + diff --git a/src/debug/gdb_test/.gitignore b/src/debug/gdb_test/.gitignore new file mode 100644 index 00000000..218500b3 --- /dev/null +++ b/src/debug/gdb_test/.gitignore @@ -0,0 +1,2 @@ +obj/ +test-gdb diff --git a/src/debug/gdb_test/Makefile b/src/debug/gdb_test/Makefile new file mode 100644 index 00000000..e8357955 --- /dev/null +++ b/src/debug/gdb_test/Makefile @@ -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 + diff --git a/src/debug/gdb_test/main.cpp b/src/debug/gdb_test/main.cpp new file mode 100644 index 00000000..afdfa2cd --- /dev/null +++ b/src/debug/gdb_test/main.cpp @@ -0,0 +1,124 @@ + +#include +#include +#include +#include + +#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<] 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); +} +} + diff --git a/src/debug/hexutil.h b/src/debug/hexutil.h new file mode 100644 index 00000000..9eb4ad22 --- /dev/null +++ b/src/debug/hexutil.h @@ -0,0 +1,75 @@ + +#ifndef HEXUTIL_GDB_H_ +#define HEXUTIL_GDB_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +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 + diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 24261030..3923f379 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -139,6 +139,7 @@ else() ) target_link_libraries(melonDS PRIVATE "${X11_LIBRARIES}" "${EGL_LIBRARIES}") target_include_directories(melonDS PRIVATE "${X11_INCLUDE_DIR}") + add_compile_definitions(QAPPLICATION_CLASS=QApplication) endif() diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index 898e4a16..da08c285 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -146,6 +146,14 @@ bool DSiBatteryCharging; bool DSiFullBIOSBoot; +#ifdef GDBSTUB_ENABLED +bool GdbEnabled; +int GdbPortARM7; +int GdbPortARM9; +bool GdbARM7BreakOnStartup; +bool GdbARM9BreakOnStartup; +#endif + CameraConfig Camera[2]; @@ -337,6 +345,14 @@ ConfigEntry ConfigFile[] = {"DSiFullBIOSBoot", 1, &DSiFullBIOSBoot, false, true}, +#ifdef GDBSTUB_ENABLED + {"GdbEnabled", 1, &GdbEnabled, false, false}, + {"GdbPortARM7", 0, &GdbPortARM7, 3334, true}, + {"GdbPortARM9", 0, &GdbPortARM9, 3333, true}, + {"GdbARM7BreakOnStartup", 1, &GdbARM7BreakOnStartup, false, true}, + {"GdbARM9BreakOnStartup", 1, &GdbARM9BreakOnStartup, false, true}, +#endif + // TODO!! // we need a more elegant way to deal with this {"Camera0_InputType", 0, &Camera[0].InputType, 0, false}, diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h index 504c068d..b1d9532e 100644 --- a/src/frontend/qt_sdl/Config.h +++ b/src/frontend/qt_sdl/Config.h @@ -193,6 +193,12 @@ extern bool DSiFullBIOSBoot; extern CameraConfig Camera[2]; +extern bool GdbEnabled; +extern int GdbPortARM7; +extern int GdbPortARM9; +extern bool GdbARM7BreakOnStartup; +extern bool GdbARM9BreakOnStartup; + void Load(); void Save(); diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.cpp b/src/frontend/qt_sdl/EmuSettingsDialog.cpp index 0bdbb5c7..571f36a2 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.cpp +++ b/src/frontend/qt_sdl/EmuSettingsDialog.cpp @@ -89,7 +89,22 @@ EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new ui->spnJITMaximumBlockSize->setDisabled(true); #endif +#ifdef GDBSTUB_ENABLED + ui->cbGdbEnabled->setChecked(Config::GdbEnabled); + ui->intGdbPortA7->setValue(Config::GdbPortARM7); + ui->intGdbPortA9->setValue(Config::GdbPortARM9); + ui->cbGdbBOSA7->setChecked(Config::GdbARM7BreakOnStartup); + ui->cbGdbBOSA9->setChecked(Config::GdbARM9BreakOnStartup); +#else + ui->cbGdbEnabled->setDisabled(true); + ui->intGdbPortA7->setDisabled(true); + ui->intGdbPortA9->setDisabled(true); + ui->cbGdbBOSA7->setDisabled(true); + ui->cbGdbBOSA9->setDisabled(true); +#endif + on_chkEnableJIT_toggled(); + on_cbGdbEnabled_toggled(); on_chkExternalBIOS_toggled(); const int imgsizes[] = {256, 512, 1024, 2048, 4096, 0}; @@ -223,6 +238,12 @@ void EmuSettingsDialog::done(int r) bool dsiSDFolderSync = ui->cbDSiSDFolder->isChecked(); std::string dsiSDFolderPath = ui->txtDSiSDFolder->text().toStdString(); + bool gdbEnabled = ui->cbGdbEnabled->isChecked(); + int gdbPortA7 = ui->intGdbPortA7->value(); + int gdbPortA9 = ui->intGdbPortA9->value(); + bool gdbBOSA7 = ui->cbGdbBOSA7->isChecked(); + bool gdbBOSA9 = ui->cbGdbBOSA9->isChecked(); + if (consoleType != Config::ConsoleType || directBoot != Config::DirectBoot #ifdef JIT_ENABLED @@ -231,6 +252,13 @@ void EmuSettingsDialog::done(int r) || jitBranchOptimisations != Config::JIT_BranchOptimisations || jitLiteralOptimisations != Config::JIT_LiteralOptimisations || jitFastMemory != Config::JIT_FastMemory +#endif +#ifdef GDBSTUB_ENABLED + || gdbEnabled != Config::GdbEnabled + || gdbPortA7 != Config::GdbPortARM7 + || gdbPortA9 != Config::GdbPortARM9 + || gdbBOSA7 != Config::GdbARM7BreakOnStartup + || gdbBOSA9 != Config::GdbARM9BreakOnStartup #endif || externalBiosEnable != Config::ExternalBIOSEnable || bios9Path != Config::BIOS9Path @@ -285,13 +313,20 @@ void EmuSettingsDialog::done(int r) Config::DSiSDFolderSync = dsiSDFolderSync; Config::DSiSDFolderPath = dsiSDFolderPath; - #ifdef JIT_ENABLED +#ifdef JIT_ENABLED Config::JIT_Enable = jitEnable; Config::JIT_MaxBlockSize = jitMaxBlockSize; Config::JIT_BranchOptimisations = jitBranchOptimisations; Config::JIT_LiteralOptimisations = jitLiteralOptimisations; Config::JIT_FastMemory = jitFastMemory; - #endif +#endif +#ifdef GDBSTUB_ENABLED + Config::GdbEnabled = gdbEnabled; + Config::GdbPortARM7 = gdbPortA7; + Config::GdbPortARM9 = gdbPortA9; + Config::GdbARM7BreakOnStartup = gdbBOSA7; + Config::GdbARM9BreakOnStartup = gdbBOSA9; +#endif Config::ConsoleType = consoleType; Config::DirectBoot = directBoot; @@ -506,6 +541,31 @@ void EmuSettingsDialog::on_chkEnableJIT_toggled() ui->chkJITFastMemory->setDisabled(disabled); #endif ui->spnJITMaximumBlockSize->setDisabled(disabled); + + on_cbGdbEnabled_toggled(); +} + +void EmuSettingsDialog::on_cbGdbEnabled_toggled() +{ +#ifdef GDBSTUB_ENABLED + bool disabled = !ui->cbGdbEnabled->isChecked(); + bool jitenable = ui->chkEnableJIT->isChecked(); + + if (jitenable && !disabled) { + ui->cbGdbEnabled->setChecked(false); + disabled = true; + } +#else + bool disabled = true; + bool jitenable = true; + ui->cbGdbEnabled->setChecked(false); +#endif + + ui->cbGdbEnabled->setDisabled(jitenable); + ui->intGdbPortA7->setDisabled(disabled); + ui->intGdbPortA9->setDisabled(disabled); + ui->cbGdbBOSA7->setDisabled(disabled); + ui->cbGdbBOSA9->setDisabled(disabled); } void EmuSettingsDialog::on_chkExternalBIOS_toggled() diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.h b/src/frontend/qt_sdl/EmuSettingsDialog.h index 6a796267..2ebfd2fc 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.h +++ b/src/frontend/qt_sdl/EmuSettingsDialog.h @@ -77,6 +77,8 @@ private slots: void on_chkEnableJIT_toggled(); void on_chkExternalBIOS_toggled(); + void on_cbGdbEnabled_toggled(); + private: void verifyFirmware(); diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.ui b/src/frontend/qt_sdl/EmuSettingsDialog.ui index b434bbe2..74bc0865 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.ui +++ b/src/frontend/qt_sdl/EmuSettingsDialog.ui @@ -26,7 +26,7 @@ - 0 + 5 @@ -568,6 +568,101 @@ + + + Devtools + + + + + + ARM9 port + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + ARM7 port + + + + + + + Enable GDB stub + + + + + + + Note: melonDS must be restarted in order for these changes to have effect + + + + + + + Note: GDB stub cannot be used together with the JIT recompiler + + + + + + + Break on startup + + + + + + + 1000 + + + 65535 + + + 3333 + + + + + + + 1000 + + + 65535 + + + 3334 + + + + + + + Break on startup + + + + + @@ -590,7 +685,6 @@ - tabWidget cbxConsoleType chkDirectBoot chkExternalBIOS @@ -639,8 +733,8 @@ accept() - 257 - 349 + 266 + 379 157 @@ -655,8 +749,8 @@ reject() - 325 - 349 + 334 + 379 286 diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index 7f6e1d56..2fa0b182 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -213,6 +213,11 @@ int GetConfigInt(ConfigEntry entry) case Firm_Color: return Config::FirmwareFavouriteColour; case AudioBitDepth: return Config::AudioBitDepth; + +#ifdef GDBSTUB_ENABLED + case GdbPortARM7: return Config::GdbPortARM7; + case GdbPortARM9: return Config::GdbPortARM9; +#endif } return 0; @@ -241,6 +246,12 @@ bool GetConfigBool(ConfigEntry entry) case Firm_OverrideSettings: return Config::FirmwareOverrideSettings != 0; case DSi_FullBIOSBoot: return Config::DSiFullBIOSBoot != 0; + +#ifdef GDBSTUB_ENABLED + case GdbEnabled: return Config::GdbEnabled; + case GdbARM7BreakOnStartup: return Config::GdbARM7BreakOnStartup; + case GdbARM9BreakOnStartup: return Config::GdbARM9BreakOnStartup; +#endif } return false;