diff options
29 files changed, 3210 insertions, 49 deletions
@@ -1,4 +1,4 @@ -build +build*/  bin  obj  *.depend diff --git a/CMakeLists.txt b/CMakeLists.txt index 57d82ef..598b3bd 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 b59530d..2fd7c2d 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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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 + @@ -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 35854d1..0451894 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 c1ecd7c..ad1a3f0 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 fe7b143..ff24e58 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 0fa9684..eb5f81e 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 b1ee455..ae45d3d 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 67e6b33..b40dce9 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 e83a0c7..3939aef 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 0000000..45f1c1b --- /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 0000000..057502f --- /dev/null +++ b/src/debug/GdbCmds.cpp @@ -0,0 +1,924 @@ + +#include <stdio.h> +#include <assert.h> + +#include "../CRC32.h" +#include "../Platform.h" +#include "hexutil.h" + +#include "GdbProto.h" + +using Platform::Log; +using Platform::LogLevel; + +namespace Gdb +{ + +enum class GdbSignal : int +{ +	INT  = 2, +	TRAP = 5, +	EMT  = 7, // "emulation trap" +	SEGV = 11, +	ILL  = 4 +}; + +// 12: llvm::MachO::CPU_TYPE_ARM +// 5: llvm::MachO::CPU_SUBTYPE_ARM_V4T +// 7: llvm::MachO::CPU_SUBTYPE_ARM_V5TEJ +const char* TARGET_INFO_ARM7 = "cputype:12;cpusubtype:5;triple:arm-none-eabi;ostype:none;vendor:none;endian:little;ptrsize:4;"; +const char* TARGET_INFO_ARM9 = "cputype:12;cpusubtype:7;triple:arm-none-eabi;ostype:none;vendor:none;endian:little;ptrsize:4;"; + + +#define TARGET_XML__CORE_REGS \ +	"<reg name=\"r0\" bitsize=\"32\" type=\"uint32\"/>" \ +	"<reg name=\"r1\" bitsize=\"32\" type=\"uint32\"/>" \ +	"<reg name=\"r2\" bitsize=\"32\" type=\"uint32\"/>" \ +	"<reg name=\"r3\" bitsize=\"32\" type=\"uint32\"/>" \ +	"<reg name=\"r4\" bitsize=\"32\" type=\"uint32\"/>" \ +	"<reg name=\"r5\" bitsize=\"32\" type=\"uint32\"/>" \ +	"<reg name=\"r6\" bitsize=\"32\" type=\"uint32\"/>" \ +	"<reg name=\"r7\" bitsize=\"32\" type=\"uint32\"/>" \ +	"<reg name=\"r8\" bitsize=\"32\" type=\"uint32\"/>" \ +	"<reg name=\"r9\" bitsize=\"32\" type=\"uint32\"/>" \ +	"<reg name=\"r10\" bitsize=\"32\" type=\"uint32\"/>" \ +	"<reg name=\"r11\" bitsize=\"32\" type=\"uint32\"/>" \ +	"<reg name=\"r12\" bitsize=\"32\" type=\"uint32\"/>" \ +	"<reg name=\"sp\" bitsize=\"32\" type=\"data_ptr\"/>" \ +	"<reg name=\"lr\" bitsize=\"32\" type=\"code_ptr\"/>" \ +	"<reg name=\"pc\" bitsize=\"32\" type=\"code_ptr\"/>" \ +	/* 16 regs */ \ + +#define TARGET_XML__MODE_REGS \ +	"<reg name=\"cpsr\" bitsize=\"32\" regnum=\"25\"/>" \ +	"<reg name=\"sp_usr\" bitsize=\"32\" regnum=\"26\" type=\"data_ptr\"/>" \ +	"<reg name=\"lr_usr\" bitsize=\"32\" regnum=\"27\" type=\"code_ptr\"/>" \ +	"<reg name=\"r8_fiq\" bitsize=\"32\" type=\"uint32\" regnum=\"28\"/>" \ +	"<reg name=\"r9_fiq\" bitsize=\"32\" type=\"uint32\" regnum=\"29\"/>" \ +	"<reg name=\"r10_fiq\" bitsize=\"32\" type=\"uint32\" regnum=\"30\"/>" \ +	"<reg name=\"r11_fiq\" bitsize=\"32\" type=\"uint32\" regnum=\"31\"/>" \ +	"<reg name=\"r12_fiq\" bitsize=\"32\" type=\"uint32\" regnum=\"32\"/>" \ +	"<reg name=\"sp_fiq\" bitsize=\"32\" regnum=\"33\" type=\"data_ptr\"/>" \ +	"<reg name=\"lr_fiq\" bitsize=\"32\" regnum=\"34\" type=\"code_ptr\"/>" \ +	"<reg name=\"sp_irq\" bitsize=\"32\" regnum=\"35\" type=\"data_ptr\"/>" \ +	"<reg name=\"lr_irq\" bitsize=\"32\" regnum=\"36\" type=\"code_ptr\"/>" \ +	"<reg name=\"sp_svc\" bitsize=\"32\" regnum=\"37\" type=\"data_ptr\"/>" \ +	"<reg name=\"lr_svc\" bitsize=\"32\" regnum=\"38\" type=\"code_ptr\"/>" \ +	"<reg name=\"sp_abt\" bitsize=\"32\" regnum=\"39\" type=\"data_ptr\"/>" \ +	"<reg name=\"lr_abt\" bitsize=\"32\" regnum=\"40\" type=\"code_ptr\"/>" \ +	"<reg name=\"sp_und\" bitsize=\"32\" regnum=\"41\" type=\"data_ptr\"/>" \ +	"<reg name=\"lr_und\" bitsize=\"32\" regnum=\"42\" type=\"code_ptr\"/>" \ +	"<reg name=\"spsr_fiq\" bitsize=\"32\" regnum=\"43\"/>" \ +	"<reg name=\"spsr_irq\" bitsize=\"32\" regnum=\"44\"/>" \ +	"<reg name=\"spsr_svc\" bitsize=\"32\" regnum=\"45\"/>" \ +	"<reg name=\"spsr_abt\" bitsize=\"32\" regnum=\"46\"/>" \ +	"<reg name=\"spsr_und\" bitsize=\"32\" regnum=\"47\"/>" \ +	/* 23 regs */ \ + + +const char* TARGET_XML_ARM7 = +	"<target version=\"1.0\">" +	"<architecture>armv4t</architecture>" +	"<osabi>none</osabi>" +	"<feature name=\"org.gnu.gdb.arm.core\">" +	TARGET_XML__CORE_REGS +	TARGET_XML__MODE_REGS +	// 39 regs total +	"</feature>" +	"</target>"; + + +const char* TARGET_XML_ARM9 = +	"<target version=\"1.0\">" +	"<architecture>armv5te</architecture>" +	"<osabi>none</osabi>" +	"<feature name=\"org.gnu.gdb.arm.core\">" +	TARGET_XML__CORE_REGS +	TARGET_XML__MODE_REGS +	// 39 regs total +	"</feature>" +	"</target>"; +	// TODO: CP15? + + +static int DoQResponse(GdbStub* stub, const u8* query, const char* data, const size_t len) +{ +	size_t qaddr, qlen; + +	Log(LogLevel::Debug, "[GDB qresp] query='%s'\n", query); +	if (sscanf((const char*)query, "%zx,%zx", &qaddr, &qlen) != 2) +	{ +		return stub->RespStr("E01"); +	} +	else if (qaddr >  len) +	{ +		return stub->RespStr("E01"); +	} +	else if (qaddr == len) +	{ +		return stub->RespStr("l"); +	} + +	size_t bleft = len - qaddr; +	size_t outlen = qlen; +	if (outlen > bleft) outlen = bleft; +	Log(LogLevel::Debug, "[GDB qresp] qaddr=%zu qlen=%zu left=%zu outlen=%zu\n", +			qaddr, qlen, bleft, outlen); + +	return stub->RespC("m", 1, (const u8*)&data[qaddr], outlen); +} + +__attribute__((__aligned__(4))) +static u8 tempdatabuf[1024]; + +ExecResult GdbStub::Handle_g(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	u8* regstrbuf = tempdatabuf; + +	for (size_t i = 0; i < GDB_ARCH_N_REG; ++i) +	{ +		u32 v = stub->Cb->ReadReg(static_cast<Register>(i)); +		hexfmt32(®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<Register>(i), v); +	} + +	stub->RespStr("OK"); + +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_m(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	u32 addr = 0, llen = 0, end; + +	if (sscanf((const char*)cmd, "%08X,%08X", &addr, &llen) != 2) +	{ +		stub->RespStr("E01"); +		return ExecResult::Ok; +	} +	else if (llen > (GDBPROTO_BUFFER_CAPACITY/2)) +	{ +		stub->RespStr("E02"); +		return ExecResult::Ok; +	} +	end = addr + llen; + +	u8* datastr = tempdatabuf; +	u8* dataptr = datastr; + +	// pre-align: byte +	if ((addr & 1)) +	{ +		if ((end-addr) >= 1) +		{ +			u32 v = stub->Cb->ReadMem(addr, 8); +			hexfmt8(dataptr, v&0xff); +			++addr; +			dataptr += 2; +		} +		else goto end; +	} + +	// pre-align: short +	if ((addr & 2)) +	{ +		if ((end-addr) >= 2) +		{ +			u32 v = stub->Cb->ReadMem(addr, 16); +			hexfmt16(dataptr, v&0xffff); +			addr += 2; +			dataptr += 4; +		} +		else if ((end-addr) == 1) +		{ // last byte +			u32 v = stub->Cb->ReadMem(addr, 8); +			hexfmt8(dataptr, v&0xff); +			++addr; +			dataptr += 2; +		} +		else goto end; +	} + +	// main loop: 4-byte chunks +	while (addr < end) +	{ +		if (end - addr < 4) break; // post-align stuff + +		u32 v = stub->Cb->ReadMem(addr, 32); +		hexfmt32(dataptr, v); +		addr += 4; +		dataptr += 8; +	} + +	// post-align: short +	if ((end-addr) & 2) +	{ +		u32 v = stub->Cb->ReadMem(addr, 16); +		hexfmt16(dataptr, v&0xffff); +		addr += 2; +		dataptr += 4; +	} + +	// post-align: byte +	if ((end-addr) == 1) +	{ +		u32 v = stub->Cb->ReadMem(addr, 8); +		hexfmt8(dataptr, v&0xff); +		++addr; +		dataptr += 2; +	} + +end: +	assert(addr == end); + +	stub->Resp(datastr, llen*2); + +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_M(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	u32 addr, llen, end; +	int inoff; + +	if (sscanf((const char*)cmd, "%08X,%08X:%n", &addr, &llen, &inoff) != 2) +	{ +		stub->RespStr("E01"); +		return ExecResult::Ok; +	} +	else if (llen > (GDBPROTO_BUFFER_CAPACITY/2)) +	{ +		stub->RespStr("E02"); +		return ExecResult::Ok; +	} +	end = addr + llen; + +	const u8* dataptr = cmd + inoff; + +	// pre-align: byte +	if ((addr & 1)) +	{ +		if ((end-addr) >= 1) +		{ +			u8 v = unhex8(dataptr); +			stub->Cb->WriteMem(addr, 8, v); +			++addr; +			dataptr += 2; +		} +		else goto end; +	} + +	// pre-align: short +	if ((addr & 2)) +	{ +		if ((end-addr) >= 2) +		{ +			u16 v = unhex16(dataptr); +			stub->Cb->WriteMem(addr, 16, v); +			addr += 2; +			dataptr += 4; +		} +		else if ((end-addr) == 1) +		{ // last byte +			u8 v = unhex8(dataptr); +			stub->Cb->WriteMem(addr, 8, v); +			++addr; +			dataptr += 2; +		} +		else goto end; +	} + +	// main loop: 4-byte chunks +	while (addr < end) +	{ +		if (end - addr < 4) break; // post-align stuff + +		u32 v = unhex32(dataptr); +		stub->Cb->WriteMem(addr, 32, v); +		addr += 4; +		dataptr += 8; +	} + +	// post-align: short +	if ((end-addr) & 2) +	{ +		u16 v = unhex16(dataptr); +		stub->Cb->WriteMem(addr, 16, v); +		addr += 2; +		dataptr += 4; +	} + +	// post-align: byte +	if ((end-addr) == 1) +	{ +		u8 v = unhex8(dataptr); +		stub->Cb->WriteMem(addr, 8, v); +		++addr; +		dataptr += 2; +	} + +end: +	assert(addr == end); + +	stub->RespStr("OK"); + +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_X(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	u32 addr, llen, end; +	int inoff; + +	if (sscanf((const char*)cmd, "%08X,%08X:%n", &addr, &llen, &inoff) != 2) +	{ +		stub->RespStr("E01"); +		return ExecResult::Ok; +	} +	else if (llen > (GDBPROTO_BUFFER_CAPACITY/2)) +	{ +		stub->RespStr("E02"); +		return ExecResult::Ok; +	} +	end = addr + llen; + +	const u8* dataptr = cmd + inoff; + +	// pre-align: byte +	if ((addr & 1)) +	{ +		if ((end-addr) >= 1) +		{ +			u8 v = *dataptr; +			stub->Cb->WriteMem(addr, 8, v); +			++addr; +			dataptr += 1; +		} +		else goto end; +	} + +	// pre-align: short +	if ((addr & 2)) +	{ +		if ((end-addr) >= 2) +		{ +			u16 v = dataptr[0] | ((u16)dataptr[1] << 8); +			stub->Cb->WriteMem(addr, 16, v); +			addr += 2; +			dataptr += 2; +		} +		else if ((end-addr) == 1) +		{ // last byte +			u8 v = *dataptr; +			stub->Cb->WriteMem(addr, 8, v); +			++addr; +			dataptr += 1; +		} +		else goto end; +	} + +	// main loop: 4-byte chunks +	while (addr < end) +	{ +		if (end - addr < 4) break; // post-align stuff + +		u32 v = dataptr[0] | ((u32)dataptr[1] << 8) +			| ((u32)dataptr[2] << 16) | ((u32)dataptr[3] << 24); +		stub->Cb->WriteMem(addr, 32, v); +		addr += 4; +		dataptr += 4; +	} + +	// post-align: short +	if ((end-addr) & 2) +	{ +		u16 v = dataptr[0] | ((u16)dataptr[1] << 8); +		stub->Cb->WriteMem(addr, 16, v); +		addr += 2; +		dataptr += 2; +	} + +	// post-align: byte +	if ((end-addr) == 1) +	{ +		u8 v = unhex8(dataptr); +		stub->Cb->WriteMem(addr, 8, v); +		++addr; +		dataptr += 1; +	} + +end: +	assert(addr == end); + +	stub->RespStr("OK"); + +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_c(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	u32 addr = ~(u32)0; + +	if (len > 0) +	{ +		if (len <= 8) +		{ +			if (sscanf((const char*)cmd, "%08X", &addr) != 1) +			{ +				stub->RespStr("E01"); +			} // else: ok +		} +		else +		{ +			stub->RespStr("E01"); +		} +	} // else: continue at current + +	if (~addr) +	{ +		stub->Cb->WriteReg(Register::pc, addr); +	} + +	return ExecResult::Continue; +} + +ExecResult GdbStub::Handle_s(GdbStub* stub, const u8* cmd, ssize_t len) { +	u32 addr = ~(u32)0; + +	if (len > 0) +	{ +		if (len <= 8) +		{ +			if (sscanf((const char*)cmd, "%08X", &addr) != 1) { +				stub->RespStr("E01"); +				return ExecResult::Ok; +			} // else: ok +		} +		else +		{ +			stub->RespStr("E01"); +			return ExecResult::Ok; +		} +	} // else: continue at current + +	if (~addr != 0) +	{ +		stub->Cb->WriteReg(Register::pc, addr); +	} + +	return ExecResult::Step; +} + +ExecResult GdbStub::Handle_p(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	int reg; +	if (sscanf((const char*)cmd, "%x", ®) != 1 || reg < 0 || reg >= GDB_ARCH_N_REG) +	{ +		stub->RespStr("E01"); +		return ExecResult::Ok; +	} + +	u32 v = stub->Cb->ReadReg(static_cast<Register>(reg)); +	hexfmt32(tempdatabuf, v); +	stub->Resp(tempdatabuf, 4*2); + +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_P(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	int reg, dataoff; + +	if (sscanf((const char*)cmd, "%x=%n", ®, &dataoff) != 1 || reg < 0 +			|| reg >= GDB_ARCH_N_REG || dataoff + 4*2 > len) +	{ +		stub->RespStr("E01"); +		return ExecResult::Ok; +	} + +	u32 v = unhex32(&cmd[dataoff]); +	stub->Cb->WriteReg(static_cast<Register>(reg), v); + +	stub->RespStr("OK"); + +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_H(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	u8 operation = cmd[0]; +	u32 thread_id; +	sscanf((const char*)&cmd[1], "%u", &thread_id); + +	(void)operation; +	if (thread_id <= 1) +	{ +		stub->RespStr("OK"); +	} +	else +	{ +		stub->RespStr("E01"); +	} + +	return ExecResult::Ok; +} + + +ExecResult GdbStub::Handle_Question(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	// "request reason for target halt" (which must also halt) + +	TgtStatus st = stub->Stat; +	u32 arg = ~(u32)0; +	int typ = 0; + +	switch (st) +	{ +	case TgtStatus::None: // no target! +		stub->RespStr("W00"); +		break; + +	case TgtStatus::Running: // will break very soon due to retval +	case TgtStatus::BreakReq: +		stub->RespFmt("S%02X", GdbSignal::INT); +		break; + +	case TgtStatus::SingleStep: +		stub->RespFmt("S%02X", GdbSignal::TRAP); +		break; + +	case TgtStatus::Bkpt: +		arg = stub->CurBkpt; +		typ = 1; +		goto bkpt_rest; +	case TgtStatus::Watchpt: +		arg = stub->CurWatchpt; +		typ = 2; +	bkpt_rest: +		if (!~arg) +		{ +			stub->RespFmt("S%02X", GdbSignal::TRAP); +		} +		else +		{ +			switch (typ) +			{ +			case 1: +				stub->RespFmt("S%02X", GdbSignal::TRAP); +				//stub->RespFmt("T%02Xhwbreak:"/*"%08X"*/";", GdbSignal::TRAP/*, arg*/); +				break; +			case 2: +				stub->RespFmt("S%02X", GdbSignal::TRAP); +				//stub->RespFmt("T%02Xwatch:"/*"%08X"*/";", GdbSignal::TRAP/*, arg*/); +				break; +			default: +				stub->RespFmt("S%02X", GdbSignal::TRAP); +				break; +			} +		} +		break; +	case TgtStatus::BkptInsn: +		stub->RespFmt("T%02Xswbreak:%08X;", GdbSignal::TRAP, +				stub->Cb->ReadReg(Register::pc)); +		break; + +		// these three should technically be a SIGBUS but gdb etc don't really +		// like that (plus it sounds confusing) +	case TgtStatus::FaultData: +	case TgtStatus::FaultIAcc: +		stub->RespFmt("S%02X", GdbSignal::SEGV); +		break; +	case TgtStatus::FaultInsn: +		stub->RespFmt("S%02X", GdbSignal::ILL); +		break; +	default: break; +	} + +	return ExecResult::InitialBreak; +} + +ExecResult GdbStub::Handle_Exclamation(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	stub->RespStr("OK"); +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_D(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	stub->RespStr("OK"); +	return ExecResult::Detached; +} + +ExecResult GdbStub::Handle_r(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	stub->Cb->ResetGdb(); +	return ExecResult::Ok; +} +ExecResult GdbStub::Handle_R(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	stub->Cb->ResetGdb(); +	return ExecResult::Ok; +} +ExecResult GdbStub::Handle_k(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	return ExecResult::Detached; +} + + +ExecResult GdbStub::Handle_z(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	int typ; +	u32 addr, kind; + +	if (sscanf((const char*)cmd, "%d,%x,%u", &typ, &addr, &kind) != 3) +	{ +		stub->RespStr("E01"); +		return ExecResult::Ok; +	} + +	switch (typ) +	{ +	case 0: case 1: // remove breakpoint (we cheat & always insert a hardware breakpoint) +		stub->DelBkpt(addr, kind); +		break; +	case 2: case 3: case 4: // watchpoint. currently not distinguishing between reads & writes oops +		stub->DelWatchpt(addr, kind, typ); +		break; +	default: +		stub->RespStr("E02"); +		return ExecResult::Ok; +	} + +	stub->RespStr("OK"); +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_Z(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	int typ; +	u32 addr, kind; + +	if (sscanf((const char*)cmd, "%d,%x,%u", &typ, &addr, &kind) != 3) +	{ +		stub->RespStr("E01"); +		return ExecResult::Ok; +	} + +	switch (typ) +	{ +	case 0: case 1: // insert breakpoint (we cheat & always insert a hardware breakpoint) +		stub->AddBkpt(addr, kind); +		break; +	case 2: case 3: case 4: // watchpoint. currently not distinguishing between reads & writes oops +		stub->AddWatchpt(addr, kind, typ); +		break; +	default: +		stub->RespStr("E02"); +		return ExecResult::Ok; +	} + +	stub->RespStr("OK"); +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_q_HostInfo(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	const char* resp = ""; + +	switch (stub->Cb->GetCPU()) +	{ +	case 7: resp = TARGET_INFO_ARM7; break; +	case 9: resp = TARGET_INFO_ARM9; break; +	default: break; +	} + +	stub->RespStr(resp); +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_q_Rcmd(GdbStub* stub, const u8* cmd, ssize_t len) +{ + +	memset(tempdatabuf, 0, sizeof tempdatabuf); +	for (ssize_t i = 0; i < len/2; ++i) +	{ +		tempdatabuf[i] = unhex8(&cmd[i*2]); +	} + +	int r = stub->Cb->RemoteCmd(tempdatabuf, len/2); + +	if (r) stub->RespFmt("E%02X", r&0xff); +	else stub->RespStr("OK"); + +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_q_Supported(GdbStub* stub, +		const u8* cmd, ssize_t len) { +	// TODO: support Xfer:memory-map:read:: +	//       but NWRAM is super annoying with that +	stub->RespFmt("PacketSize=%X;qXfer:features:read+;swbreak-;hwbreak+;QStartNoAckMode+", GDBPROTO_BUFFER_CAPACITY-1); +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_q_CRC(GdbStub* stub, +		const u8* cmd, ssize_t llen) +{ +	static u8 crcbuf[128]; + +	u32 addr, len; +	if (sscanf((const char*)cmd, "%x,%x", &addr, &len) != 2) +	{ +		stub->RespStr("E01"); +		return ExecResult::Ok; +	} + +	u32 val = 0; // start at 0 +	u32 caddr = addr; +	u32 realend = addr + len; + +	for (; caddr < addr + len; ) +	{ +		// calc partial CRC in 128-byte chunks +		u32 end = caddr + sizeof(crcbuf)/sizeof(crcbuf[0]); +		if (end > realend) end = realend; +		u32 clen = end - caddr; + +		for (size_t i = 0; caddr < end; ++caddr, ++i) +		{ +			crcbuf[i] = stub->Cb->ReadMem(caddr, 8); +		} + +		val = CRC32(crcbuf, clen, val); +	} + +	stub->RespFmt("C%x", val); + +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_q_C(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	stub->RespStr("QC1"); // current thread ID is 1 +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_q_fThreadInfo(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	stub->RespStr("m1"); // one thread +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_q_sThreadInfo(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	stub->RespStr("l"); // end of thread list +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_q_features(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	const char* resp; + +	Log(LogLevel::Debug, "[GDB] CPU type = %d\n", stub->Cb->GetCPU()); +	switch (stub->Cb->GetCPU()) +	{ +	case 7: resp = TARGET_XML_ARM7; break; +	case 9: resp = TARGET_XML_ARM9; break; +	default: resp = ""; break; +	} + +	DoQResponse(stub, cmd, resp, strlen(resp)); +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_q_Attached(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	stub->RespStr("1"); // always "attach to a process" +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_v_Attach(GdbStub* stub, const u8* cmd, ssize_t len) +{ + +	TgtStatus st = stub->Stat; + +	if (st == TgtStatus::None) +	{ +		// no target +		stub->RespStr("E01"); +		return ExecResult::Ok; +	} + +	stub->RespStr("T05thread:1;"); + +	if (st == TgtStatus::Running) return ExecResult::MustBreak; +	else return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_v_Kill(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	TgtStatus st = stub->Stat; + +	stub->Cb->ResetGdb(); + +	stub->RespStr("OK"); + +	return (st != TgtStatus::Running && st != TgtStatus::None) ? ExecResult::Detached : ExecResult::Ok; +} + +ExecResult GdbStub::Handle_v_Run(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	TgtStatus st = stub->Stat; + +	stub->Cb->ResetGdb(); + +	// TODO: handle cmdline for homebrew? + +	return (st != TgtStatus::Running && st != TgtStatus::None) ? ExecResult::Continue : ExecResult::Ok; +} + +ExecResult GdbStub::Handle_v_Stopped(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	TgtStatus st = stub->Stat; + +	static bool notified = true; + +	// not sure if i understand this correctly +	if (st != TgtStatus::Running) +	{ +		if (notified) stub->RespStr("OK"); +		else stub->RespStr("W00"); + +		notified = !notified; +	} +	else stub->RespStr("OK"); + +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_v_MustReplyEmpty(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	stub->Resp(NULL, 0); +	return ExecResult::Ok; +} + +ExecResult GdbStub::Handle_v_Cont(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	if (len < 1) +	{ +		stub->RespStr("E01"); +		return ExecResult::Ok; +	} + +	switch (cmd[0]) +	{ +	case 'c': +		stub->RespStr("OK"); +		return ExecResult::Continue; +	case 's': +		stub->RespStr("OK"); +		return ExecResult::Step; +	case 't': +		stub->RespStr("OK"); +		return ExecResult::MustBreak; +	default: +		stub->RespStr("E01"); +		return ExecResult::Ok; +	} +} + +ExecResult GdbStub::Handle_v_ContQuery(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	stub->RespStr("vCont;c;s;t"); +	return ExecResult::Ok; +} + + +ExecResult GdbStub::Handle_Q_StartNoAckMode(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	stub->NoAck = true; +	stub->RespStr("OK"); +	return ExecResult::Ok; +} + +} + diff --git a/src/debug/GdbCmds.h b/src/debug/GdbCmds.h new file mode 100644 index 0000000..4f30060 --- /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 0000000..dc80364 --- /dev/null +++ b/src/debug/GdbProto.cpp @@ -0,0 +1,389 @@ + +#ifdef _WIN32 +#include <WS2tcpip.h> +#include <winsock.h> +#include <winsock2.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <errno.h> + +#ifndef _WIN32 +#include <poll.h> +#include <sys/select.h> +#include <sys/socket.h> +#endif + +#include "../Platform.h" +#include "hexutil.h" + +#include "GdbProto.h" + + +using Platform::Log; +using Platform::LogLevel; + +namespace Gdb +{ + +/* + * TODO commands to support: + * m M g G c s p P H + * ? D r + * qC qfThreadInfo qsThreadInfo + * z0 Z0 z1 Z1 z4 Z4 + * qCRC + * vAttach;addr + * vKill;pid + * qRcmd? qSupported? + */ +u8 Cmdbuf[GDBPROTO_BUFFER_CAPACITY]; +ssize_t Cmdlen; + +namespace Proto +{ + +u8 PacketBuf[GDBPROTO_BUFFER_CAPACITY]; +u8 RespBuf[GDBPROTO_BUFFER_CAPACITY+5]; + +ReadResult MsgRecv(int connfd, u8 cmd_dest[/*static GDBPROTO_BUFFER_CAPACITY*/]) +{ +	static ssize_t dataoff = 0; + +	ssize_t recv_total = dataoff; +	ssize_t cksumoff = -1; +	u8 sum = 0; + +	bool first = true; + +	//printf("--- dataoff=%zd\n", dataoff); +	if (dataoff != 0) { +		printf("--- got preexisting: %s\n", PacketBuf); + +		ssize_t datastart = 0; +		while (true) +		{ +			if (PacketBuf[datastart] == '\x04') return ReadResult::Eof; +			else if (PacketBuf[datastart] == '+' || PacketBuf[datastart] == '-') +			{ +				/*if (PacketBuf[datastart] == '+') SendAck(connfd); +				else SendNak(connfd);*/ +				++datastart; +				continue; +			} +			else if (PacketBuf[datastart] == '$') +			{ +				++datastart; +				break; +			} +			else +			{ +				__builtin_trap(); +				return ReadResult::Wut; +			} +		} +		printf("--- datastart=%zd\n", datastart); + +		for (ssize_t i = datastart; i < dataoff; ++i) +		{ +			if (PacketBuf[i] == '#') +			{ +				cksumoff = i + 1; +				printf("--- cksumoff=%zd\n", cksumoff); +				break; +			} + +			sum += PacketBuf[i]; +		} + +		if (cksumoff >= 0) +		{ +			recv_total = dataoff - datastart + 1; +			dataoff = cksumoff + 2 - datastart + 1; +			cksumoff -= datastart - 1; + +			memmove(&PacketBuf[1], &PacketBuf[datastart], recv_total); +			PacketBuf[0] = '$'; +			PacketBuf[recv_total] = 0; + +			printf("=== cksumoff=%zi recv_total=%zi datastart=%zi dataoff=%zi\n==> %s\n", +					cksumoff, recv_total, datastart, dataoff, PacketBuf); +			//break; +		} +	} + +	while (cksumoff < 0) +	{ +		u8* pkt = &PacketBuf[dataoff]; +		ssize_t n, blehoff = 0; + +		memset(pkt, 0, sizeof(PacketBuf) - dataoff); +		int flag = 0; +#if MOCKTEST +		static bool FIRST = false; +		if (FIRST) { +			printf("%s", "[==>] TEST DONE\n"); +			__builtin_trap(); +		} +		FIRST = true; + +		const char* testinp1 = "+$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77"; +		const char* testinp2 = "+$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386#77---+$vMustReplyEmpty#3a"; + +		const char* testinp = testinp1; + +		n = strlen(testinp); +		memcpy(pkt, testinp, strlen(testinp)); +#else +#ifndef _WIN32 +		if (first) flag |= MSG_DONTWAIT; +		n = recv(connfd, pkt, sizeof(PacketBuf) - dataoff, flag); +#else +		// fuck windows +		n = recv(connfd, (char*)pkt, sizeof(PacketBuf) - dataoff, flag); +#endif +#endif + +		if (n <= 0) +		{ +			if (first) return ReadResult::NoPacket; +			else +			{ +				Log(LogLevel::Debug, "[GDB] recv() error %zi, errno=%d (%s)\n", n, errno, strerror(errno)); +				return ReadResult::Eof; +			} +		} + +		Log(LogLevel::Debug, "[GDB] recv() %zd bytes: '%s' (%02x)\n", n, pkt, pkt[0]); +		first = false; + +		do +		{ +			if (dataoff == 0) +			{ +				if (pkt[blehoff] == '\x04') return ReadResult::Eof; +				else if (pkt[blehoff] == '\x03') return ReadResult::Break; +				else if (pkt[blehoff] != '$') +				{ +					++blehoff; +					--n; +				} +				else break; + +				if (n == 0) goto next_outer; +			} +		} +		while (true); + +		if (blehoff > 0) +		{ +			memmove(pkt, &pkt[blehoff], n - blehoff + 1); +			n -= blehoff - 1; // ??? +		} + +		recv_total += n; + +		Log(LogLevel::Debug, "[GDB] recv() after skipping: n=%zd, recv_total=%zd\n", n, recv_total); + +		for (ssize_t i = (dataoff == 0) ? 1 : 0; i < n; ++i) +		{ +			u8 v = pkt[i]; +			if (v == '#') +			{ +				cksumoff = dataoff + i + 1; +				break; +			} + +			sum += pkt[i]; +		} + +		if (cksumoff < 0) +		{ +			// oops, need more data +			dataoff += n; +		} + +	next_outer:; +	} + +	u8 ck = (hex2nyb(PacketBuf[cksumoff+0]) << 4) +		| hex2nyb(PacketBuf[cksumoff+1]); + +	Log(LogLevel::Debug, "[GDB] got pkt, checksum: %02x vs %02x\n", ck, sum); + +	if (ck != sum) +	{ +		//__builtin_trap(); +		return ReadResult::CksumErr; +	} + +	if (cksumoff + 2 > recv_total) +	{ +		Log(LogLevel::Error, "[GDB] BIG MISTAKE: %zi > %zi which shouldn't happen!\n", cksumoff + 2, recv_total); +		//__builtin_trap(); +		return ReadResult::Wut; +	} +	else +	{ +		Cmdlen = cksumoff - 2; +		memcpy(Cmdbuf, &PacketBuf[1], Cmdlen); +		Cmdbuf[Cmdlen] = 0; + +		if (cksumoff + 2 < recv_total) { +			// huh, we have the start of the next packet +			dataoff = recv_total - (cksumoff + 2); +			memmove(PacketBuf, &PacketBuf[cksumoff + 2], (size_t)dataoff); +			PacketBuf[dataoff] = 0; +			Log(LogLevel::Debug, "[GDB] got more: cksumoff=%zd, recvtotal=%zd, remain=%zd\n==> %s\n", cksumoff, recv_total, dataoff, PacketBuf); +		} +		else dataoff = 0; +	} + +	return ReadResult::CmdRecvd; +} + +int SendAck(int connfd) +{ +	Log(LogLevel::Debug, "[GDB] send ack\n"); +	u8 v = '+'; +#if MOCKTEST +	return 1; +#endif + +#ifdef _WIN32 +	// fuck windows +	return send(connfd, (const char*)&v, 1, 0); +#else +	return send(connfd, &v, 1, 0); +#endif +} + +int SendNak(int connfd) +{ +	Log(LogLevel::Debug, "[GDB] send nak\n"); +	u8 v = '-'; +#if MOCKTEST +	return 1; +#endif + +#ifdef _WIN32 +	// fuck windows +	return send(connfd, (const char*)&v, 1, 0); +#else +	return send(connfd, &v, 1, 0); +#endif +} + +int WaitAckBlocking(int connfd, u8* ackp, int to_ms) +{ +#if MOCKTEST +	*ackp = '+'; +	return 0; +#endif + +#ifdef _WIN32 +	fd_set infd, outfd, errfd; +	FD_ZERO(&infd); FD_ZERO(&outfd); FD_ZERO(&errfd); +	FD_SET(connfd, &infd); + +	struct timeval to; +	to.tv_sec = to_ms / 1000; +	to.tv_usec = (to_ms % 1000) * 1000; + +	int r = select(connfd+1, &infd, &outfd, &errfd, &to); + +	if (FD_ISSET(connfd, &errfd)) return -1; +	else if (FD_ISSET(connfd, &infd)) +	{ +		r = recv(connfd, (char*)ackp, 1, 0); +		if (r < 0) return r; +		return 0; +	} + +	return -1; +#else +	struct pollfd pfd; + +	pfd.fd = connfd; +	pfd.events = POLLIN; +	pfd.revents = 0; + +	ssize_t r = (ssize_t)poll(&pfd, 1, to_ms); +	if (r < 0) return r; +	if (r == 0) return -1; + +	if (pfd.revents & (POLLHUP|POLLERR)) return -69; + +	r = recv(connfd, ackp, 1, 0); +	if (r < 0) return r; + +	return (r == 1) ? 0 : -1; +#endif +} + +int Resp(int connfd, const u8* data1, size_t len1, const u8* data2, size_t len2, bool noack) +{ +	u8 cksum = 0; +	int tries = 0; + +	size_t totallen = len1 + len2; + +	if (totallen >= GDBPROTO_BUFFER_CAPACITY) +	{ +		Log(LogLevel::Error, "[GDB] packet with len %zu can't fit in buffer!\n", totallen); +		return -42; +	} + +	RespBuf[0] = '$'; +	for (size_t i = 0; i < len1; ++i) +	{ +		cksum += data1[i]; +		RespBuf[i+1] = data1[i]; +	} +	for (size_t i = 0; i < len2; ++i) +	{ +		cksum += data2[i]; +		RespBuf[len1+i+1] = data2[i]; +	} +	RespBuf[totallen+1] = '#'; +	hexfmt8(&RespBuf[totallen+2], cksum); +	RespBuf[totallen+4] = 0; + +	do +	{ +		ssize_t r; +		u8 ack; + +		Log(LogLevel::Debug, "[GDB] send resp: '%s'\n", RespBuf); +#if MOCKTEST +		r = totallen+4; +#else +#ifdef _WIN32 +		r = send(connfd, (const char*)RespBuf, totallen+4, 0); +#else +		r = send(connfd, RespBuf, totallen+4, 0); +#endif +#endif +		if (r < 0) return r; + +		if (noack) break; + +		r = WaitAckBlocking(connfd, &ack, 2000); +		//Log(LogLevel::Debug, "[GDB] got ack: '%c'\n", ack); +		if (r == 0 && ack == '+') break; + +		++tries; +	} +	while (tries < 3); + +	return 0; +} + +} + +} + diff --git a/src/debug/GdbProto.h b/src/debug/GdbProto.h new file mode 100644 index 0000000..ef8bdec --- /dev/null +++ b/src/debug/GdbProto.h @@ -0,0 +1,40 @@ + +#ifndef GDBPROTO_H_ +#define GDBPROTO_H_ + +#include <string.h> +#include <sys/types.h> + +#include "GdbStub.h" /* class GdbStub */ + + +#define MOCKTEST 0 + + +namespace Gdb { + +constexpr int GDBPROTO_BUFFER_CAPACITY = 1024+128; + +extern u8 Cmdbuf[GDBPROTO_BUFFER_CAPACITY]; +extern ssize_t Cmdlen; + +namespace Proto { + +extern u8 PacketBuf[GDBPROTO_BUFFER_CAPACITY]; +extern u8 RespBuf[GDBPROTO_BUFFER_CAPACITY+5]; + +Gdb::ReadResult MsgRecv(int connfd, u8 cmd_dest[/*static GDBPROTO_BUFFER_CAPACITY*/]); + +int SendAck(int connfd); +int SendNak(int connfd); + +int Resp(int connfd, const u8* data1, size_t len1, const u8* data2, size_t len2, bool noack); + +int WaitAckBlocking(int connfd, u8* ackp, int to_ms); + +} + +} + +#endif + diff --git a/src/debug/GdbStub.cpp b/src/debug/GdbStub.cpp new file mode 100644 index 0000000..d756ce4 --- /dev/null +++ b/src/debug/GdbStub.cpp @@ -0,0 +1,693 @@ + +#ifdef _WIN32 +#include <WS2tcpip.h> +#include <winsock.h> +#include <winsock2.h> +#endif + +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <sys/types.h> + +#ifndef _WIN32 +#include <sys/socket.h> +#include <fcntl.h> +#include <poll.h> +#include <signal.h> +#include <arpa/inet.h> +#include <netinet/tcp.h> +#endif + + +#include "../Platform.h" +#include "GdbProto.h" + +using Platform::Log; +using Platform::LogLevel; + +static int SocketSetBlocking(int fd, bool block) +{ +#if MOCKTEST +	return 0; +#endif + +	if (fd < 0) return -1; + +#ifdef _WIN32 +	unsigned long mode = block ? 0 : 1; +	return ioctlsocket(fd, FIONBIO, &mode); +#else +	int flags = fcntl(fd, F_GETFL, 0); +	if (flags == -1) return -1; +	flags = block ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK); +	return fcntl(fd, F_SETFL, flags); +#endif +} + +namespace Gdb +{ + +GdbStub::GdbStub(StubCallbacks* cb, int port) +	: Cb(cb), Port(port) +	, SockFd(0), ConnFd(0) +	, Stat(TgtStatus::None), CurBkpt(0), CurWatchpt(0), StatFlag(false), NoAck(false) +	, ServerSA((void*)new struct sockaddr_in()) +	, ClientSA((void*)new struct sockaddr_in()) +{ } + +bool GdbStub::Init() +{ +	Log(LogLevel::Info, "[GDB] initializing GDB stub for core %d on port %d\n", +		Cb->GetCPU(), Port); + +#if MOCKTEST +	SockFd = 0; +	return true; +#endif + +#ifndef _WIN32 +	/*void* fn = SIG_IGN; +	struct sigaction act = { 0 }; +	act.sa_flags = SA_SIGINFO; +	act.sa_sigaction = (sighandler_t)fn; +	if (sigaction(SIGPIPE, &act, NULL) == -1) { +		Log(LogLevel::Warn, "[GDB] couldn't ignore SIGPIPE, stuff may fail on GDB disconnect.\n"); +	}*/ +	signal(SIGPIPE, SIG_IGN); +#else +	WSADATA wsa; +	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) +	{ +		Log(LogLevel::Error, "[GDB] winsock could not be initialized (%d).\n", WSAGetLastError()); +		return false; +	} +#endif + +	int r; +	struct sockaddr_in* server = (struct sockaddr_in*)ServerSA; +	struct sockaddr_in* client = (struct sockaddr_in*)ClientSA; + +	int typ = SOCK_STREAM; +#ifdef __linux__ +	typ |= SOCK_NONBLOCK; +#endif +	SockFd = socket(AF_INET, SOCK_STREAM, 0); +	if (SockFd < 0) +	{ +		Log(LogLevel::Error, "[GDB] err: can't create a socket fd\n"); +		goto err; +	} +#ifndef __linux__ +	SocketSetBlocking(SockFd, false); +#endif + +	server->sin_family = AF_INET; +	server->sin_addr.s_addr = htonl(INADDR_ANY); +	server->sin_port = htons(Port); + +	r = bind(SockFd, (const sockaddr*)server, sizeof(*server)); +	if (r < 0) +	{ +		Log(LogLevel::Error, "[GDB] err: can't bind to address <any> and port %d\n", Port); +		goto err; +	} + +	r = listen(SockFd, 5); +	if (r < 0) +	{ +		Log(LogLevel::Error, "[GDB] err: can't listen to SockFd\n"); +		goto err; +	} + +	return true; + +err: +	if (SockFd != 0) +	{ +#ifdef _WIN32 +		closesocket(SockFd); +#else +		close(SockFd); +#endif +		SockFd = 0; +	} + +	return false; +} + +void GdbStub::Close() +{ +	Disconnect(); +	if (SockFd > 0) close(SockFd); +	SockFd = 0; +} + +void GdbStub::Disconnect() +{ +	if (ConnFd > 0) close(ConnFd); +	ConnFd = 0; +} + +GdbStub::~GdbStub() +{ +	Close(); +	delete (struct sockaddr_in*)ServerSA; +	delete (struct sockaddr_in*)ClientSA; +} + +SubcmdHandler GdbStub::Handlers_v[] = { +	{ .MainCmd = 'v', .SubStr = "Attach;"       , .Handler = GdbStub::Handle_v_Attach }, +	{ .MainCmd = 'v', .SubStr = "Kill;"         , .Handler = GdbStub::Handle_v_Kill }, +	{ .MainCmd = 'v', .SubStr = "Run"           , .Handler = GdbStub::Handle_v_Run }, +	{ .MainCmd = 'v', .SubStr = "Stopped"       , .Handler = GdbStub::Handle_v_Stopped }, +	{ .MainCmd = 'v', .SubStr = "MustReplyEmpty", .Handler = GdbStub::Handle_v_MustReplyEmpty }, +	{ .MainCmd = 'v', .SubStr = "Cont?"         , .Handler = GdbStub::Handle_v_ContQuery }, +	{ .MainCmd = 'v', .SubStr = "Cont"          , .Handler = GdbStub::Handle_v_Cont }, + +	{ .MainCmd = 'v', .SubStr = NULL, .Handler = NULL } +}; + +SubcmdHandler GdbStub::Handlers_q[] = { +	{ .MainCmd = 'q', .SubStr = "HostInfo"   , .Handler = GdbStub::Handle_q_HostInfo }, +	{ .MainCmd = 'q', .SubStr = "ProcessInfo", .Handler = GdbStub::Handle_q_HostInfo }, +	{ .MainCmd = 'q', .SubStr = "Rcmd,"      , .Handler = GdbStub::Handle_q_Rcmd }, +	{ .MainCmd = 'q', .SubStr = "Supported:" , .Handler = GdbStub::Handle_q_Supported }, +	{ .MainCmd = 'q', .SubStr = "CRC:"       , .Handler = GdbStub::Handle_q_CRC }, +	{ .MainCmd = 'q', .SubStr = "C"          , .Handler = GdbStub::Handle_q_C }, +	{ .MainCmd = 'q', .SubStr = "fThreadInfo", .Handler = GdbStub::Handle_q_fThreadInfo }, +	{ .MainCmd = 'q', .SubStr = "sThreadInfo", .Handler = GdbStub::Handle_q_sThreadInfo }, +	{ .MainCmd = 'q', .SubStr = "Attached"   , .Handler = GdbStub::Handle_q_Attached }, +	{ .MainCmd = 'q', .SubStr = "Xfer:features:read:target.xml:", .Handler = GdbStub::Handle_q_features }, + +	{ .MainCmd = 'q', .SubStr = NULL, .Handler = NULL }, +}; + +SubcmdHandler GdbStub::Handlers_Q[] = { +	{ .MainCmd = 'Q', .SubStr = "StartNoAckMode", .Handler = GdbStub::Handle_Q_StartNoAckMode }, + +	{ .MainCmd = 'Q', .SubStr = NULL, .Handler = NULL }, +}; + +ExecResult GdbStub::Handle_q(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	return stub->SubcmdExec(cmd, len, Handlers_q); +} + +ExecResult GdbStub::Handle_v(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	return stub->SubcmdExec(cmd, len, Handlers_v); +} + +ExecResult GdbStub::Handle_Q(GdbStub* stub, const u8* cmd, ssize_t len) +{ +	return stub->SubcmdExec(cmd, len, Handlers_Q); +} + +CmdHandler GdbStub::Handlers_top[] = { +	{ .Cmd = 'g', .Handler = GdbStub::Handle_g }, +	{ .Cmd = 'G', .Handler = GdbStub::Handle_G }, +	{ .Cmd = 'm', .Handler = GdbStub::Handle_m }, +	{ .Cmd = 'M', .Handler = GdbStub::Handle_M }, +	{ .Cmd = 'X', .Handler = GdbStub::Handle_X }, +	{ .Cmd = 'c', .Handler = GdbStub::Handle_c }, +	{ .Cmd = 's', .Handler = GdbStub::Handle_s }, +	{ .Cmd = 'p', .Handler = GdbStub::Handle_p }, +	{ .Cmd = 'P', .Handler = GdbStub::Handle_P }, +	{ .Cmd = 'H', .Handler = GdbStub::Handle_H }, +	{ .Cmd = 'T', .Handler = GdbStub::Handle_H }, + +	{ .Cmd = '?', .Handler = GdbStub::Handle_Question }, +	{ .Cmd = '!', .Handler = GdbStub::Handle_Exclamation }, +	{ .Cmd = 'D', .Handler = GdbStub::Handle_D }, +	{ .Cmd = 'r', .Handler = GdbStub::Handle_r }, +	{ .Cmd = 'R', .Handler = GdbStub::Handle_R }, +	{ .Cmd = 'k', .Handler = GdbStub::Handle_k }, + +	{ .Cmd = 'z', .Handler = GdbStub::Handle_z }, +	{ .Cmd = 'Z', .Handler = GdbStub::Handle_Z }, + +	{ .Cmd = 'q', .Handler = GdbStub::Handle_q }, +	{ .Cmd = 'v', .Handler = GdbStub::Handle_v }, +	{ .Cmd = 'Q', .Handler = GdbStub::Handle_Q }, + +	{ .Cmd = 0, .Handler = NULL } +}; + + +StubState GdbStub::HandlePacket() +{ +	ExecResult r = CmdExec(Handlers_top); + +	if (r == ExecResult::MustBreak) +	{ +		if (Stat == TgtStatus::None || Stat == TgtStatus::Running) +			Stat = TgtStatus::BreakReq; +		return StubState::Break; +	} +	else if (r == ExecResult::InitialBreak) +	{ +		Stat = TgtStatus::BreakReq; +		return StubState::Attach; +	/*} +	else if (r == ExecResult::Detached) +	{ +		Stat = TgtStatus::None; +		return StubState::Disconnect;*/ +	} +	else if (r == ExecResult::Continue) +	{ +		Stat = TgtStatus::Running; +		return StubState::Continue; +	} +	else if (r == ExecResult::Step) +	{ +		return StubState::Step; +	} +	else if (r == ExecResult::Ok || r == ExecResult::UnkCmd) +	{ +		return StubState::None; +	} +	else +	{ +		Stat = TgtStatus::None; +		return StubState::Disconnect; +	} +} + +StubState GdbStub::Poll(bool wait) +{ +	int r; + +	if (ConnFd <= 0) +	{ +		SocketSetBlocking(SockFd, wait); + +		// not yet connected, so let's wait for one +		// nonblocking only done in part of read_packet(), so that it can still +		// quickly handle partly-received packets +		struct sockaddr_in* client = (struct sockaddr_in*)ClientSA; +		socklen_t len = sizeof(*client); +#if MOCKTEST +		ConnFd = 0; +#else +#ifdef __linux__ +		ConnFd = accept4(SockFd, (struct sockaddr*)client, &len, /*SOCK_NONBLOCK|*/SOCK_CLOEXEC); +#else +		ConnFd = accept(SockFd, (struct sockaddr*)client, &len); +#endif +#endif + +		if (ConnFd < 0) return StubState::NoConn; + +		u8 a; +		if (Proto::WaitAckBlocking(ConnFd, &a, 1000) < 0) +		{ +			Log(LogLevel::Error, "[GDB] inital handshake: didn't receive inital ack!\n"); +			close(ConnFd); +			ConnFd = 0; +			return StubState::Disconnect; +		} + +		if (a != '+') +		{ +			Log(LogLevel::Error, "[GDB] inital handshake: unexpected character '%c'!\n", a); +		} +		SendAck(); + +		Stat = TgtStatus::Running; // on connected +		StatFlag = false; +	} + +	if (StatFlag) +	{ +		StatFlag = false; +		//Log(LogLevel::Debug, "[GDB] STAT FLAG WAS TRUE\n"); + +		Handle_Question(this, NULL, 0); // ugly hack but it should work +	} + +#if MOCKTEST +	// nothing... +#else +#ifndef _WIN32 +	struct pollfd pfd; +	pfd.fd = ConnFd; +	pfd.events = POLLIN; +	pfd.revents = 0; + +	r = poll(&pfd, 1, wait ? -1 : 0); + +	if (r == 0) return StubState::None; // nothing is happening + +	if (pfd.revents & (POLLHUP|POLLERR|POLLNVAL)) +	{ +		// oopsie, something happened +		Disconnect(); +		return StubState::Disconnect; +	} +#else +	fd_set infd, outfd, errfd; +	FD_ZERO(&infd); FD_ZERO(&outfd); FD_ZERO(&errfd); +	FD_SET(ConnFd, &infd); + +	struct timeval to; +	if (wait) +	{ +		to.tv_sec = ~(time_t)0; +		to.tv_usec = ~(long)0; +	} +	else +	{ +		to.tv_sec = 0; +		to.tv_usec = 0; +	} + +	r = select(ConnFd+1, &infd, &outfd, &errfd, &to); + +	if (FD_ISSET(ConnFd, &errfd)) +	{ +		Disconnect(); +		return StubState::Disconnect; +	} +	else if (!FD_ISSET(ConnFd, &infd)) +	{ +		return StubState::None; +	} +#endif +#endif + +	ReadResult res = Proto::MsgRecv(ConnFd, Cmdbuf); + +	switch (res) +	{ +	case ReadResult::NoPacket: +		return StubState::None; +	case ReadResult::Break: +		return StubState::Break; +	case ReadResult::Wut: +		Log(LogLevel::Info, "[GDB] WUT\n"); +	case_gdbp_eof: +	case ReadResult::Eof: +		Log(LogLevel::Info, "[GDB] EOF!\n"); +		close(ConnFd); +		ConnFd = 0; +		return StubState::Disconnect; +	case ReadResult::CksumErr: +		Log(LogLevel::Info, "[GDB] checksum err!\n"); +		if (SendNak() < 0) { +			Log(LogLevel::Error, "[GDB] send nak after cksum fail errored!\n"); +			goto case_gdbp_eof; +		} +		return StubState::None; +	case ReadResult::CmdRecvd: +		/*if (SendAck() < 0) { +			Log(LogLevel::Error, "[GDB] send packet ack failed!\n"); +			goto case_gdbp_eof; +		}*/ +		break; +	} + +	return HandlePacket(); +} + +ExecResult GdbStub::SubcmdExec(const u8* cmd, ssize_t len, const SubcmdHandler* handlers) +{ +	//Log(LogLevel::Debug, "[GDB] subcommand in: '%s'\n", cmd); + +	for (size_t i = 0; handlers[i].Handler != NULL; ++i) { +		// check if prefix matches +		if (!strncmp((const char*)cmd, handlers[i].SubStr, strlen(handlers[i].SubStr))) +		{ +			if (SendAck() < 0) +			{ +				Log(LogLevel::Error, "[GDB] send packet ack failed!\n"); +				return ExecResult::NetErr; +			} +			return handlers[i].Handler(this, &cmd[strlen(handlers[i].SubStr)], len-strlen(handlers[i].SubStr)); +		} +	} + +	Log(LogLevel::Info, "[GDB] unknown subcommand '%s'!\n", cmd); +	/*if (SendNak() < 0) +	{ +		Log(LogLevel::Error, "[GDB] send nak after cksum fail errored!\n"); +		return ExecResult::NetErr; +	}*/ +	//Resp("E99"); +	Resp(NULL, 0); +	return ExecResult::UnkCmd; +} + +ExecResult GdbStub::CmdExec(const CmdHandler* handlers) +{ +	Log(LogLevel::Debug, "[GDB] command in: '%s'\n", Cmdbuf); + +	for (size_t i = 0; handlers[i].Handler != NULL; ++i) +	{ +		if (handlers[i].Cmd == Cmdbuf[0]) +		{ +			if (SendAck() < 0) +			{ +				Log(LogLevel::Error, "[GDB] send packet ack failed!\n"); +				return ExecResult::NetErr; +			} +			return handlers[i].Handler(this, &Cmdbuf[1], Cmdlen-1); +		} +	} + +	Log(LogLevel::Info, "[GDB] unknown command '%c'!\n", Cmdbuf[0]); +	/*if (SendNak() < 0) +	{ +		Log(LogLevel::Error, "[GDB] send nak after cksum fail errored!\n"); +		return ExecResult::NetErr; +	}*/ +	//RespStr("E99"); +	Resp(NULL, 0); +	return ExecResult::UnkCmd; +} + + +void GdbStub::SignalStatus(TgtStatus stat, u32 arg) +{ +	//Log(LogLevel::Debug, "[GDB] SIGNAL STATUS %d!\n", stat); + +	this->Stat = stat; +	StatFlag = true; + +	if (stat == TgtStatus::Bkpt) CurBkpt = arg; +	else if (stat == TgtStatus::Watchpt) CurWatchpt = arg; +} + + +StubState GdbStub::Enter(bool stay, TgtStatus stat, u32 arg, bool wait_for_conn) +{ +	if (stat != TgtStatus::NoEvent) SignalStatus(stat, arg); + +	StubState st; +	bool do_next = true; +	do +	{ +		bool was_conn = ConnFd > 0; +		st = Poll(wait_for_conn); +		bool has_conn = ConnFd > 0; + +		if (has_conn && !was_conn) stay = true; + +		switch (st) +		{ +		case StubState::Break: +			Log(LogLevel::Info, "[GDB] break execution\n"); +			SignalStatus(TgtStatus::BreakReq, ~(u32)0); +			break; +		case StubState::Continue: +			Log(LogLevel::Info, "[GDB] continue execution\n"); +			do_next = false; +			break; +		case StubState::Step: +			Log(LogLevel::Info, "[GDB] single-step\n"); +			do_next = false; +			break; +		case StubState::Disconnect: +			Log(LogLevel::Info, "[GDB] disconnect\n"); +			SignalStatus(TgtStatus::None, ~(u32)0); +			do_next = false; +			break; +		default: break; +		} +	} +	while (do_next && stay); + +	if (st != StubState::None && st != StubState::NoConn) +	{ +		Log(LogLevel::Debug, "[GDB] enter exit: %d\n", st); +	} +	return st; +} + +void GdbStub::AddBkpt(u32 addr, int kind) +{ +	BpWp np; +	np.addr = addr ^ (addr & 1); // clear lowest bit to not break on thumb mode weirdnesses +	np.len = 0; +	np.kind = kind; + +	{ +		// already in the map +		auto search = BpList.find(np.addr); +		if (search != BpList.end()) return; +	} + +	BpList.insert({np.addr, np}); + +	Log(LogLevel::Debug, "[GDB] added bkpt:\n"); +	size_t i = 0; +	for (auto search = BpList.begin(); search != BpList.end(); ++search, ++i) +	{ +		Log(LogLevel::Debug, "\t[%zu]: addr=%08x, kind=%d\n", i, search->first, search->second.kind); +	} +} +void GdbStub::AddWatchpt(u32 addr, u32 len, int kind) +{ +	BpWp np; +	np.addr = addr; +	np.len = len; +	np.kind = kind; + +	for (auto search = WpList.begin(); search != WpList.end(); ++search) +	{ +		if (search->addr > addr) +		{ +			WpList.insert(search, np); +			return; +		} +		else if (search->addr == addr && search->kind == kind) +		{ +			if (search->len < len) search->len = len; +			return; +		} +	} + +	WpList.push_back(np); +} + +void GdbStub::DelBkpt(u32 addr, int kind) +{ +	addr = addr ^ (addr & 1); + +	auto search = BpList.find(addr); +	if (search != BpList.end()) +	{ +		BpList.erase(search); +	} +} +void GdbStub::DelWatchpt(u32 addr, u32 len, int kind) +{ +	(void)len; (void)kind; + +	for (auto search = WpList.begin(); search != WpList.end(); ++search) +	{ +		if (search->addr == addr && search->kind == kind) +		{ +			WpList.erase(search); +			return; +		} +		else if (search->addr > addr) return; +	} +} + +void GdbStub::DelAllBpWp() +{ +	BpList.erase(BpList.begin(), BpList.end()); +	WpList.erase(WpList.begin(), WpList.end()); +} + +StubState GdbStub::CheckBkpt(u32 addr, bool enter, bool stay) +{ +	addr ^= (addr & 1); // clear lowest bit to not break on thumb mode weirdnesses + +	auto search = BpList.find(addr); +	if (search == BpList.end()) return StubState::CheckNoHit; + +	if (enter) +	{ +		StubState r = Enter(stay, TgtStatus::Bkpt, addr); +		Log(LogLevel::Debug, "[GDB] ENTER st=%d\n", r); +		return r; +	} +	else +	{ +		SignalStatus(TgtStatus::Bkpt, addr); +		return StubState::None; +	} +} +StubState GdbStub::CheckWatchpt(u32 addr, int kind, bool enter, bool stay) +{ +	for (auto search = WpList.begin(); search != WpList.end(); ++search) +	{ +		if (search->addr > addr) break; + +		if (addr >= search->addr && addr < search->addr + search->len && search->kind == kind) +		{ +			if (enter) return Enter(stay, TgtStatus::Watchpt, addr); +			else +			{ +				SignalStatus(TgtStatus::Watchpt, addr); +				return StubState::None; +			} +		} +	} + +	return StubState::CheckNoHit; +} + +int GdbStub::SendAck() +{ +	if (NoAck) return 1; +	return Proto::SendAck(ConnFd); +} +int GdbStub::SendNak() +{ +	if (NoAck) return 1; +	return Proto::SendNak(ConnFd); +} + +int GdbStub::Resp(const u8* data1, size_t len1, const u8* data2, size_t len2) +{ +	return Proto::Resp(ConnFd, data1, len1, data2, len2, NoAck); +} +int GdbStub::RespC(const char* data1, size_t len1, const u8* data2, size_t len2) +{ +	return Proto::Resp(ConnFd, (const u8*)data1, len1, data2, len2, NoAck); +} +#if defined(__GCC__) || defined(__clang__) +__attribute__((__format__(printf, 2/*includes implicit this*/, 3))) +#endif +int GdbStub::RespFmt(const char* fmt, ...) +{ +	va_list args; +	va_start(args, fmt); +	int r = vsnprintf((char*)&Proto::RespBuf[1], sizeof(Proto::RespBuf)-5, fmt, args); +	va_end(args); + +	if (r < 0) return r; + +	if ((size_t)r >= sizeof(Proto::RespBuf)-5) +	{ +		Log(LogLevel::Error, "[GDB] truncated response in send_fmt()! (lost %zd bytes)\n", +				(ssize_t)r - (ssize_t)(sizeof(Proto::RespBuf)-5)); +		r = sizeof(Proto::RespBuf)-5; +	} + +	return Resp(&Proto::RespBuf[1], r); +} + +int GdbStub::RespStr(const char* str) +{ +	return Resp((const u8*)str, strlen(str)); +} + +} + diff --git a/src/debug/GdbStub.h b/src/debug/GdbStub.h new file mode 100644 index 0000000..b3bdab7 --- /dev/null +++ b/src/debug/GdbStub.h @@ -0,0 +1,184 @@ + +#ifndef GDBSTUB_H_ +#define GDBSTUB_H_ + +#include <stddef.h> +#include <map> +#include <vector> + +#include "../types.h" + +#include "GdbArch.h" + +namespace Gdb +{ + +enum class TgtStatus +{ +	NoEvent, + +	None, +	Running, +	SingleStep, +	BreakReq, // "break" command from gdb client +	Bkpt, +	Watchpt, +	BkptInsn, // "bkpt" instruction +	FaultData, // data abort +	FaultIAcc, // instruction fetch abort +	FaultInsn, // illegal instruction +}; + +class StubCallbacks +{ +public: +	StubCallbacks(){} +	virtual ~StubCallbacks(){}; + +	virtual int GetCPU() const = 0; // 7 or 9 (currently, maybe also xtensa in the future?) + +	// 0..14: as usual +	// 15: pc *pipeline-corrected* +	// 16: cpsr +	virtual u32  ReadReg (Register reg) = 0; +	virtual void WriteReg(Register reg, u32 value) = 0; + +	virtual u32  ReadMem (u32 addr, int len) = 0; +	virtual void WriteMem(u32 addr, int len, u32 value) = 0; + +	virtual void ResetGdb() = 0; +	virtual int RemoteCmd(const u8* cmd, size_t len) = 0; +}; + +enum class StubState +{ +	NoConn, +	None, +	Break, +	Continue, +	Step, +	Disconnect, +	Attach, +	CheckNoHit +}; + +enum class ReadResult +{ +	NoPacket, +	Eof, +	CksumErr, +	CmdRecvd, +	Wut, +	Break +}; + +enum class ExecResult +{ +	Ok, +	UnkCmd, +	NetErr, +	InitialBreak, +	MustBreak, +	Detached, +	Step, +	Continue +}; + +class GdbStub; + +typedef ExecResult (*GdbProtoCmd)(GdbStub* stub, const u8* cmd, ssize_t len); + +struct SubcmdHandler +{ +	char MainCmd; +	const char* SubStr; +	GdbProtoCmd Handler; +}; + +struct CmdHandler +{ +	char Cmd; +	GdbProtoCmd Handler; +}; + +class GdbStub +{ +public: +	struct BpWp +	{ +	public: +		u32 addr, len; +		int kind; +	}; + +	GdbStub(StubCallbacks* cb, int port); +	~GdbStub(); + +	bool Init(); +	void Close(); + +	StubState Poll(bool wait = false); +	void SignalStatus(TgtStatus stat, u32 arg); +	StubState Enter(bool stay, TgtStatus stat=TgtStatus::NoEvent, u32 arg=~(u32)0u, bool wait_for_conn=false); + +	// kind: 2=thumb, 3=thumb2 (not relevant), 4=arm +	void AddBkpt(u32 addr, int kind); +	void DelBkpt(u32 addr, int kind); +	// kind: 2=read, 3=write, 4=rdwr +	void AddWatchpt(u32 addr, u32 len, int kind); +	void DelWatchpt(u32 addr, u32 len, int kind); + +	void DelAllBpWp(); + +	StubState CheckBkpt(u32 addr, bool enter, bool stay); +	StubState CheckWatchpt(u32 addr, int kind, bool enter, bool stay); + +#include "GdbCmds.h" + +	Gdb::ExecResult SubcmdExec(const u8* cmd, ssize_t len, const SubcmdHandler* handlers); +	Gdb::ExecResult CmdExec(const CmdHandler* handlers); + +public: +	int SendAck(); +	int SendNak(); + +	int Resp(const u8* data1, size_t len1, const u8* data2 = NULL, size_t len2 = 0); +	int RespC(const char* data1, size_t len1, const u8* data2 = NULL, size_t len2 = 0); +#if defined(__GCC__) || defined(__clang__) +	__attribute__((__format__(printf, 2, 3))) +#endif +	int RespFmt(const char* fmt, ...); + +	int RespStr(const char* str); + +private: +	void Disconnect(); +	StubState HandlePacket(); + +private: +	StubCallbacks* Cb; + +	//struct sockaddr_in server, client; +	void *ServerSA, *ClientSA; +	int Port; +	int SockFd; +	int ConnFd; + +	TgtStatus Stat; +	u32 CurBkpt, CurWatchpt; +	bool StatFlag; +	bool NoAck; + +	std::map<u32, BpWp> BpList; +	std::vector<BpWp> WpList; + +	static SubcmdHandler Handlers_v[]; +	static SubcmdHandler Handlers_q[]; +	static SubcmdHandler Handlers_Q[]; +	static CmdHandler Handlers_top[]; +}; + +} + +#endif + diff --git a/src/debug/gdb_test/.gitignore b/src/debug/gdb_test/.gitignore new file mode 100644 index 0000000..218500b --- /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 0000000..e835795 --- /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 0000000..afdfa2c --- /dev/null +++ b/src/debug/gdb_test/main.cpp @@ -0,0 +1,124 @@ + +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> + +#include "GdbStub.h" +#include "Platform.h" + +class Debug : public Gdb::StubCallbacks +{ +public: +	Debug(){} +	~Debug(){} + +	int GetCPU() const override { return 9; } + +	u32 ReadReg(Gdb::Register reg) override +	{ +		printf("[==>] read reg %d\n", (int)reg); +		if (reg == Gdb::Register::pc) return 0x000000df; // cpsr: irq,fiq disabled, arm, sys mode +		else return 0x69420; +	} +	void WriteReg(Gdb::Register reg, u32 value) override +	{ +		printf("[==>] write reg %d: 0x%08x\n", (int)reg, value); +	} + +	u32 ReadMem(u32 addr, int len) override +	{ +		static const u32 words[] = { +			0xeafffffe, +			0xe0211002, +			0xe12fff1e, +			0 +		}; + +		printf("[==>] read mem 0x%08x (size %u)\n", addr, len); + +		// $: b $ (arm) +		return words[(addr>>2)&3] & ((1uLL<<len)-1); +	} +	void WriteMem(u32 addr, int len, u32 value) override +	{ +		printf("[==>] write addr 0x%08x (size %u): 0x%08x\n", addr, len, value); +	} + +	void ResetGdb() override +	{ +		printf("[==>] RESET!!!\n"); +	} +	int RemoteCmd(const u8* cmd, size_t len) override +	{ +		printf("[==>] Rcmd: %s\n", cmd); +		return 0; +	} +}; + +int main(int argc, char** argv) { +	Debug debug; + +	Gdb::GdbStub stub(&debug, (argc > 1) ? atoi(argv[1]) : 3333); +	if (!stub.Init()) return 1; + +	do +	{ +		while (true) +		{ +			Gdb::StubState s = stub.Poll(); + +			if (s == Gdb::StubState::None || s == Gdb::StubState::NoConn) +			{ +				struct timespec ts; +				ts.tv_sec = 0; +				ts.tv_nsec = 1000*1000; // 1 ms +				nanosleep(&ts, NULL); +				continue; +			} + +			switch (s) +			{ +			case Gdb::StubState::Attach: +				printf("[==>] attached\n"); +				break; +			case Gdb::StubState::Break: +				printf("[==>] break execution\n"); +				stub.SignalStatus(Gdb::TgtStatus::BreakReq, ~(u32)0); +				break; +			case Gdb::StubState::Continue: +				printf("[==>] continue execution\n"); +				// TODO: send signal status on SIGSTOP? eh. +				break; +			case Gdb::StubState::Step: +				printf("[==>] single-step\n"); +				stub.SignalStatus(Gdb::TgtStatus::SingleStep, ~(u32)0); +				break; +			case Gdb::StubState::Disconnect: +				printf("[==>] disconnect\n"); +				stub.SignalStatus(Gdb::TgtStatus::None, ~(u32)0); +				break; +			} + +			if (s == Gdb::StubState::Disconnect) break; +		} +	} +	while (false); + +	stub.Close(); +	return 0; +} + +namespace Platform +{ +void Log(LogLevel level, const char* fmt, ...) +{ +    if (fmt == nullptr) return; + +    va_list args; +    va_start(args, fmt); +    vprintf(fmt, args); +    va_end(args); +} +} + diff --git a/src/debug/hexutil.h b/src/debug/hexutil.h new file mode 100644 index 0000000..9eb4ad2 --- /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 <stdint.h> + +inline static uint8_t hex2nyb(uint8_t v) +{ +	if (v >= '0' && v <= '9') return v - '0'; +	else if (v >= 'A' && v <= 'F') return v - 'A' + 0xa; +	else if (v >= 'a' && v <= 'f') return v - 'a' + 0xa; +	else +	{ +		__builtin_trap(); +		return 0xcc; +	} +} +inline static uint8_t nyb2hex(uint8_t v) +{ +	v &= 0xf; +	if (v >= 0xa) return v - 0xa + 'a'; +	else return v - 0x0 + '0'; +} + +inline static void hexfmt8(uint8_t* dst, uint8_t v) +{ +	dst[0] = nyb2hex(v>>4); +	dst[1] = nyb2hex(v>>0); +} +inline static uint8_t unhex8(const uint8_t* src) +{ +	return (hex2nyb(src[0]) << 4) | hex2nyb(src[1]); +} + +inline static void hexfmt16(uint8_t* dst, uint16_t v) +{ +	dst[0] = nyb2hex(v>> 4); +	dst[1] = nyb2hex(v>> 0); +	dst[2] = nyb2hex(v>>12); +	dst[3] = nyb2hex(v>> 8); +} +inline static uint16_t unhex16(const uint8_t* src) +{ +	return unhex8(&src[0*2]) | ((uint16_t)unhex8(&src[1*2]) << 8); +} + +inline static void hexfmt32(uint8_t* dst, uint32_t v) +{ +	for (size_t i = 0; i < 4; ++i, v >>= 8) +	{ +		dst[2*i+0] = nyb2hex(v>>4); +		dst[2*i+1] = nyb2hex(v>>0); +	} +} +inline static uint32_t unhex32(const uint8_t* src) +{ +	uint32_t v = 0; +	for (size_t i = 0; i < 4; ++i) +	{ +		v |= (uint32_t)unhex8(&src[i*2]) << (i*8); +	} +	return v; +} + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 2426103..3923f37 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 898e4a1..da08c28 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 504c068..b1d9532 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 0bdbb5c..571f36a 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 @@ -232,6 +253,13 @@ void EmuSettingsDialog::done(int r)              || 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              || bios7Path != Config::BIOS7Path @@ -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 6a79626..2ebfd2f 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 b434bbe..74bc086 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.ui +++ b/src/frontend/qt_sdl/EmuSettingsDialog.ui @@ -26,7 +26,7 @@     <item>      <widget class="QTabWidget" name="tabWidget">       <property name="currentIndex"> -      <number>0</number> +      <number>5</number>       </property>       <widget class="QWidget" name="tab">        <attribute name="title"> @@ -568,6 +568,101 @@         </item>        </layout>       </widget> +     <widget class="QWidget" name="tab_6"> +      <attribute name="title"> +       <string>Devtools</string> +      </attribute> +      <layout class="QGridLayout" name="gridLayout_3"> +       <item row="1" column="0"> +        <widget class="QLabel" name="label_16"> +         <property name="text"> +          <string>ARM9 port</string> +         </property> +        </widget> +       </item> +       <item row="5" column="0"> +        <spacer name="verticalSpacer_4"> +         <property name="orientation"> +          <enum>Qt::Vertical</enum> +         </property> +         <property name="sizeHint" stdset="0"> +          <size> +           <width>20</width> +           <height>40</height> +          </size> +         </property> +        </spacer> +       </item> +       <item row="2" column="0"> +        <widget class="QLabel" name="label_17"> +         <property name="text"> +          <string>ARM7 port</string> +         </property> +        </widget> +       </item> +       <item row="0" column="0" colspan="3"> +        <widget class="QCheckBox" name="cbGdbEnabled"> +         <property name="text"> +          <string>Enable GDB stub</string> +         </property> +        </widget> +       </item> +       <item row="4" column="0" colspan="7"> +        <widget class="QLabel" name="label_18"> +         <property name="text"> +          <string>Note: melonDS must be restarted in order for these changes to have effect</string> +         </property> +        </widget> +       </item> +       <item row="3" column="0" colspan="7"> +        <widget class="QLabel" name="label_19"> +         <property name="text"> +          <string>Note: GDB stub cannot be used together with the JIT recompiler</string> +         </property> +        </widget> +       </item> +       <item row="1" column="6"> +        <widget class="QCheckBox" name="cbGdbBOSA9"> +         <property name="text"> +          <string>Break on startup</string> +         </property> +        </widget> +       </item> +       <item row="1" column="1" colspan="5"> +        <widget class="QSpinBox" name="intGdbPortA9"> +         <property name="minimum"> +          <number>1000</number> +         </property> +         <property name="maximum"> +          <number>65535</number> +         </property> +         <property name="value"> +          <number>3333</number> +         </property> +        </widget> +       </item> +       <item row="2" column="1" colspan="5"> +        <widget class="QSpinBox" name="intGdbPortA7"> +         <property name="minimum"> +          <number>1000</number> +         </property> +         <property name="maximum"> +          <number>65535</number> +         </property> +         <property name="value"> +          <number>3334</number> +         </property> +        </widget> +       </item> +       <item row="2" column="6"> +        <widget class="QCheckBox" name="cbGdbBOSA7"> +         <property name="text"> +          <string>Break on startup</string> +         </property> +        </widget> +       </item> +      </layout> +     </widget>      </widget>     </item>     <item> @@ -590,7 +685,6 @@    </customwidget>   </customwidgets>   <tabstops> -  <tabstop>tabWidget</tabstop>    <tabstop>cbxConsoleType</tabstop>    <tabstop>chkDirectBoot</tabstop>    <tabstop>chkExternalBIOS</tabstop> @@ -639,8 +733,8 @@     <slot>accept()</slot>     <hints>      <hint type="sourcelabel"> -     <x>257</x> -     <y>349</y> +     <x>266</x> +     <y>379</y>      </hint>      <hint type="destinationlabel">       <x>157</x> @@ -655,8 +749,8 @@     <slot>reject()</slot>     <hints>      <hint type="sourcelabel"> -     <x>325</x> -     <y>349</y> +     <x>334</x> +     <y>379</y>      </hint>      <hint type="destinationlabel">       <x>286</x> diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index 7f6e1d5..2fa0b18 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;  |