sim, arch, base: Refactor the base remote GDB class.
Fold the GDBListener class into the main BaseRemoteGDB class, move around a bunch of functions, convert a lot of internal functions to be private, move some functions into the .cc, make some functions non-virtual which didn't really need to be overridden. Change-Id: Id0832b730b0fdfb2eababa5067e72c66de1c147d Reviewed-on: https://gem5-review.googlesource.com/7422 Reviewed-by: Jason Lowe-Power <jason@lowepower.com> Reviewed-by: Gabe Black <gabeblack@google.com> Maintainer: Gabe Black <gabeblack@google.com>
This commit is contained in:
@@ -144,9 +144,11 @@
|
||||
using namespace std;
|
||||
using namespace AlphaISA;
|
||||
|
||||
RemoteGDB::RemoteGDB(System *_system, ThreadContext *tc)
|
||||
: BaseRemoteGDB(_system, tc)
|
||||
RemoteGDB::RemoteGDB(System *_system, ThreadContext *tc, int _port)
|
||||
: BaseRemoteGDB(_system, tc, _port)
|
||||
{
|
||||
warn_once("Breakpoints do not work in Alpha PAL mode.\n"
|
||||
" See PCEventQueue::doService() in cpu/pc_event.cc.\n");
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -165,7 +167,7 @@ RemoteGDB::acc(Addr va, size_t len)
|
||||
|
||||
do {
|
||||
if (IsK0Seg(va)) {
|
||||
if (va < (K0SegBase + system->memSize())) {
|
||||
if (va < (K0SegBase + system()->memSize())) {
|
||||
DPRINTF(GDBAcc, "acc: Mapping is valid K0SEG <= "
|
||||
"%#x < K0SEG + size\n", va);
|
||||
return true;
|
||||
@@ -187,9 +189,9 @@ RemoteGDB::acc(Addr va, size_t len)
|
||||
if (PcPAL(va) || va < 0x10000)
|
||||
return true;
|
||||
|
||||
Addr ptbr = context->readMiscRegNoEffect(IPR_PALtemp20);
|
||||
Addr ptbr = context()->readMiscRegNoEffect(IPR_PALtemp20);
|
||||
PageTableEntry pte =
|
||||
kernel_pte_lookup(context->getPhysProxy(), ptbr, va);
|
||||
kernel_pte_lookup(context()->getPhysProxy(), ptbr, va);
|
||||
if (!pte.valid()) {
|
||||
DPRINTF(GDBAcc, "acc: %#x pte is invalid\n", va);
|
||||
return false;
|
||||
@@ -247,31 +249,10 @@ RemoteGDB::AlphaGdbRegCache::setRegs(ThreadContext *context) const
|
||||
context->pcState(r.pc);
|
||||
}
|
||||
|
||||
// Write bytes to kernel address space for debugger.
|
||||
bool
|
||||
RemoteGDB::write(Addr vaddr, size_t size, const char *data)
|
||||
|
||||
BaseGdbRegCache*
|
||||
RemoteGDB::gdbRegs()
|
||||
{
|
||||
if (BaseRemoteGDB::write(vaddr, size, data)) {
|
||||
#ifdef IMB
|
||||
alpha_pal_imb();
|
||||
#endif
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
RemoteGDB::insertHardBreak(Addr addr, size_t len)
|
||||
{
|
||||
warn_once("Breakpoints do not work in Alpha PAL mode.\n"
|
||||
" See PCEventQueue::doService() in cpu/pc_event.cc.\n");
|
||||
BaseRemoteGDB::insertHardBreak(addr, len);
|
||||
}
|
||||
|
||||
RemoteGDB::BaseGdbRegCache*
|
||||
RemoteGDB::gdbRegs() {
|
||||
return new AlphaGdbRegCache(this);
|
||||
return new AlphaGdbRegCache(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -51,9 +51,6 @@ class RemoteGDB : public BaseRemoteGDB
|
||||
protected:
|
||||
// Machine memory
|
||||
bool acc(Addr addr, size_t len) override;
|
||||
bool write(Addr addr, size_t size, const char *data) override;
|
||||
|
||||
void insertHardBreak(Addr addr, size_t len) override;
|
||||
|
||||
class AlphaGdbRegCache : public BaseGdbRegCache
|
||||
{
|
||||
@@ -70,11 +67,15 @@ class RemoteGDB : public BaseRemoteGDB
|
||||
size_t size() const { return sizeof(r); }
|
||||
void getRegs(ThreadContext*);
|
||||
void setRegs(ThreadContext*) const;
|
||||
const std::string name() const { return gdb->name() + ".AlphaGdbRegCache"; }
|
||||
const std::string
|
||||
name() const
|
||||
{
|
||||
return gdb->name() + ".AlphaGdbRegCache";
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
RemoteGDB(System *system, ThreadContext *context);
|
||||
RemoteGDB(System *system, ThreadContext *context, int _port);
|
||||
BaseGdbRegCache *gdbRegs() override;
|
||||
};
|
||||
|
||||
|
||||
@@ -164,8 +164,8 @@
|
||||
using namespace std;
|
||||
using namespace ArmISA;
|
||||
|
||||
RemoteGDB::RemoteGDB(System *_system, ThreadContext *tc)
|
||||
: BaseRemoteGDB(_system, tc), regCache32(this), regCache64(this)
|
||||
RemoteGDB::RemoteGDB(System *_system, ThreadContext *tc, int _port)
|
||||
: BaseRemoteGDB(_system, tc, _port), regCache32(this), regCache64(this)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ RemoteGDB::acc(Addr va, size_t len)
|
||||
{
|
||||
if (FullSystem) {
|
||||
for (ChunkGenerator gen(va, len, PageBytes); !gen.done(); gen.next()) {
|
||||
if (!virtvalid(context, gen.addr())) {
|
||||
if (!virtvalid(context(), gen.addr())) {
|
||||
DPRINTF(GDBAcc, "acc: %#x mapping is invalid\n", va);
|
||||
return false;
|
||||
}
|
||||
@@ -189,7 +189,7 @@ RemoteGDB::acc(Addr va, size_t len)
|
||||
TlbEntry entry;
|
||||
//Check to make sure the first byte is mapped into the processes address
|
||||
//space.
|
||||
if (context->getProcessPtr()->pTable->lookup(va, entry))
|
||||
if (context()->getProcessPtr()->pTable->lookup(va, entry))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@@ -301,10 +301,10 @@ RemoteGDB::AArch32GdbRegCache::setRegs(ThreadContext *context) const
|
||||
context->setMiscRegNoEffect(MISCREG_CPSR, r.cpsr);
|
||||
}
|
||||
|
||||
RemoteGDB::BaseGdbRegCache*
|
||||
BaseGdbRegCache*
|
||||
RemoteGDB::gdbRegs()
|
||||
{
|
||||
if (inAArch64(context))
|
||||
if (inAArch64(context()))
|
||||
return ®Cache64;
|
||||
else
|
||||
return ®Cache32;
|
||||
|
||||
@@ -115,7 +115,7 @@ class RemoteGDB : public BaseRemoteGDB
|
||||
AArch64GdbRegCache regCache64;
|
||||
|
||||
public:
|
||||
RemoteGDB(System *_system, ThreadContext *tc);
|
||||
RemoteGDB(System *_system, ThreadContext *tc, int _port);
|
||||
BaseGdbRegCache *gdbRegs();
|
||||
};
|
||||
} // namespace ArmISA
|
||||
|
||||
@@ -151,8 +151,8 @@
|
||||
using namespace std;
|
||||
using namespace MipsISA;
|
||||
|
||||
RemoteGDB::RemoteGDB(System *_system, ThreadContext *tc)
|
||||
: BaseRemoteGDB(_system, tc), regCache(this)
|
||||
RemoteGDB::RemoteGDB(System *_system, ThreadContext *tc, int _port)
|
||||
: BaseRemoteGDB(_system, tc, _port), regCache(this)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ RemoteGDB::acc(Addr va, size_t len)
|
||||
if (FullSystem)
|
||||
panic("acc not implemented for MIPS FS!");
|
||||
else
|
||||
return context->getProcessPtr()->pTable->lookup(va, entry);
|
||||
return context()->getProcessPtr()->pTable->lookup(va, entry);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -205,7 +205,8 @@ RemoteGDB::MipsGdbRegCache::setRegs(ThreadContext *context) const
|
||||
context->setFloatRegBits(FLOATREG_FIR, r.fir);
|
||||
}
|
||||
|
||||
RemoteGDB::BaseGdbRegCache*
|
||||
RemoteGDB::gdbRegs() {
|
||||
BaseGdbRegCache*
|
||||
RemoteGDB::gdbRegs()
|
||||
{
|
||||
return ®Cache;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ class RemoteGDB : public BaseRemoteGDB
|
||||
MipsGdbRegCache regCache;
|
||||
|
||||
public:
|
||||
RemoteGDB(System *_system, ThreadContext *tc);
|
||||
RemoteGDB(System *_system, ThreadContext *tc, int _port);
|
||||
BaseGdbRegCache *gdbRegs();
|
||||
};
|
||||
|
||||
|
||||
@@ -151,8 +151,8 @@
|
||||
using namespace std;
|
||||
using namespace PowerISA;
|
||||
|
||||
RemoteGDB::RemoteGDB(System *_system, ThreadContext *tc)
|
||||
: BaseRemoteGDB(_system, tc), regCache(this)
|
||||
RemoteGDB::RemoteGDB(System *_system, ThreadContext *tc, int _port)
|
||||
: BaseRemoteGDB(_system, tc, _port), regCache(this)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ RemoteGDB::acc(Addr va, size_t len)
|
||||
if (FullSystem)
|
||||
panic("acc not implemented for POWER FS!");
|
||||
else
|
||||
return context->getProcessPtr()->pTable->lookup(va, entry);
|
||||
return context()->getProcessPtr()->pTable->lookup(va, entry);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -216,8 +216,9 @@ RemoteGDB::PowerGdbRegCache::setRegs(ThreadContext *context) const
|
||||
context->setIntReg(INTREG_XER, betoh(r.xer));
|
||||
}
|
||||
|
||||
RemoteGDB::BaseGdbRegCache*
|
||||
RemoteGDB::gdbRegs() {
|
||||
BaseGdbRegCache*
|
||||
RemoteGDB::gdbRegs()
|
||||
{
|
||||
return ®Cache;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ class RemoteGDB : public BaseRemoteGDB
|
||||
PowerGdbRegCache regCache;
|
||||
|
||||
public:
|
||||
RemoteGDB(System *_system, ThreadContext *tc);
|
||||
RemoteGDB(System *_system, ThreadContext *tc, int _port);
|
||||
BaseGdbRegCache *gdbRegs();
|
||||
};
|
||||
|
||||
|
||||
@@ -148,8 +148,8 @@
|
||||
using namespace std;
|
||||
using namespace RiscvISA;
|
||||
|
||||
RemoteGDB::RemoteGDB(System *_system, ThreadContext *tc)
|
||||
: BaseRemoteGDB(_system, tc), regCache(this)
|
||||
RemoteGDB::RemoteGDB(System *_system, ThreadContext *tc, int _port)
|
||||
: BaseRemoteGDB(_system, tc, _port), regCache(this)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ RemoteGDB::acc(Addr va, size_t len)
|
||||
if (FullSystem)
|
||||
panic("acc not implemented for RISCV FS!");
|
||||
else
|
||||
return context->getProcessPtr()->pTable->lookup(va, entry);
|
||||
return context()->getProcessPtr()->pTable->lookup(va, entry);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -199,7 +199,8 @@ RemoteGDB::RiscvGdbRegCache::setRegs(ThreadContext *context) const
|
||||
context->setMiscReg(i, r.csr[i - ExplicitCSRs]);
|
||||
}
|
||||
|
||||
RemoteGDB::BaseGdbRegCache*
|
||||
RemoteGDB::gdbRegs() {
|
||||
BaseGdbRegCache*
|
||||
RemoteGDB::gdbRegs()
|
||||
{
|
||||
return ®Cache;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ class RemoteGDB : public BaseRemoteGDB
|
||||
RiscvGdbRegCache regCache;
|
||||
|
||||
public:
|
||||
RemoteGDB(System *_system, ThreadContext *tc);
|
||||
RemoteGDB(System *_system, ThreadContext *tc, int _port);
|
||||
BaseGdbRegCache *gdbRegs();
|
||||
};
|
||||
|
||||
|
||||
@@ -147,8 +147,8 @@
|
||||
using namespace std;
|
||||
using namespace SparcISA;
|
||||
|
||||
RemoteGDB::RemoteGDB(System *_system, ThreadContext *c)
|
||||
: BaseRemoteGDB(_system, c), regCache32(this), regCache64(this)
|
||||
RemoteGDB::RemoteGDB(System *_system, ThreadContext *c, int _port)
|
||||
: BaseRemoteGDB(_system, c, _port), regCache32(this), regCache64(this)
|
||||
{}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
@@ -170,7 +170,7 @@ RemoteGDB::acc(Addr va, size_t len)
|
||||
TlbEntry entry;
|
||||
// Check to make sure the first byte is mapped into the processes
|
||||
// address space.
|
||||
if (context->getProcessPtr()->pTable->lookup(va, entry))
|
||||
if (context()->getProcessPtr()->pTable->lookup(va, entry))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@@ -244,10 +244,10 @@ RemoteGDB::SPARC64GdbRegCache::setRegs(ThreadContext *context) const
|
||||
}
|
||||
|
||||
|
||||
RemoteGDB::BaseGdbRegCache*
|
||||
BaseGdbRegCache*
|
||||
RemoteGDB::gdbRegs()
|
||||
{
|
||||
PSTATE pstate = context->readMiscReg(MISCREG_PSTATE);
|
||||
PSTATE pstate = context()->readMiscReg(MISCREG_PSTATE);
|
||||
if (pstate.am) {
|
||||
return ®Cache32;
|
||||
} else {
|
||||
|
||||
@@ -69,7 +69,11 @@ class RemoteGDB : public BaseRemoteGDB
|
||||
size_t size() const { return sizeof(r); }
|
||||
void getRegs(ThreadContext*);
|
||||
void setRegs(ThreadContext*) const;
|
||||
const std::string name() const { return gdb->name() + ".SPARCGdbRegCache"; }
|
||||
const std::string
|
||||
name() const
|
||||
{
|
||||
return gdb->name() + ".SPARCGdbRegCache";
|
||||
}
|
||||
};
|
||||
|
||||
class SPARC64GdbRegCache : public BaseGdbRegCache
|
||||
@@ -91,14 +95,18 @@ class RemoteGDB : public BaseRemoteGDB
|
||||
size_t size() const { return sizeof(r); }
|
||||
void getRegs(ThreadContext*);
|
||||
void setRegs(ThreadContext*) const;
|
||||
const std::string name() const { return gdb->name() + ".SPARC64GdbRegCache"; }
|
||||
const std::string
|
||||
name() const
|
||||
{
|
||||
return gdb->name() + ".SPARC64GdbRegCache";
|
||||
}
|
||||
};
|
||||
|
||||
SPARCGdbRegCache regCache32;
|
||||
SPARC64GdbRegCache regCache64;
|
||||
|
||||
public:
|
||||
RemoteGDB(System *_system, ThreadContext *tc);
|
||||
RemoteGDB(System *_system, ThreadContext *tc, int _port);
|
||||
BaseGdbRegCache *gdbRegs();
|
||||
};
|
||||
} // namespace SparcISA
|
||||
|
||||
@@ -64,8 +64,8 @@
|
||||
using namespace std;
|
||||
using namespace X86ISA;
|
||||
|
||||
RemoteGDB::RemoteGDB(System *_system, ThreadContext *c) :
|
||||
BaseRemoteGDB(_system, c), regCache32(this), regCache64(this)
|
||||
RemoteGDB::RemoteGDB(System *_system, ThreadContext *c, int _port) :
|
||||
BaseRemoteGDB(_system, c, _port), regCache32(this), regCache64(this)
|
||||
{}
|
||||
|
||||
bool
|
||||
@@ -73,9 +73,9 @@ RemoteGDB::acc(Addr va, size_t len)
|
||||
{
|
||||
if (FullSystem) {
|
||||
Walker *walker = dynamic_cast<TLB *>(
|
||||
context->getDTBPtr())->getWalker();
|
||||
context()->getDTBPtr())->getWalker();
|
||||
unsigned logBytes;
|
||||
Fault fault = walker->startFunctional(context, va, logBytes,
|
||||
Fault fault = walker->startFunctional(context(), va, logBytes,
|
||||
BaseTLB::Read);
|
||||
if (fault != NoFault)
|
||||
return false;
|
||||
@@ -84,19 +84,19 @@ RemoteGDB::acc(Addr va, size_t len)
|
||||
if ((va & ~mask(logBytes)) == (endVa & ~mask(logBytes)))
|
||||
return true;
|
||||
|
||||
fault = walker->startFunctional(context, endVa, logBytes,
|
||||
fault = walker->startFunctional(context(), endVa, logBytes,
|
||||
BaseTLB::Read);
|
||||
return fault == NoFault;
|
||||
} else {
|
||||
TlbEntry entry;
|
||||
return context->getProcessPtr()->pTable->lookup(va, entry);
|
||||
return context()->getProcessPtr()->pTable->lookup(va, entry);
|
||||
}
|
||||
}
|
||||
|
||||
RemoteGDB::BaseGdbRegCache*
|
||||
BaseGdbRegCache*
|
||||
RemoteGDB::gdbRegs()
|
||||
{
|
||||
HandyM5Reg m5reg = context->readMiscRegNoEffect(MISCREG_M5_REG);
|
||||
HandyM5Reg m5reg = context()->readMiscRegNoEffect(MISCREG_M5_REG);
|
||||
if (m5reg.submode == SixtyFourBitMode)
|
||||
return ®Cache64;
|
||||
else
|
||||
|
||||
@@ -143,7 +143,7 @@ class RemoteGDB : public BaseRemoteGDB
|
||||
AMD64GdbRegCache regCache64;
|
||||
|
||||
public:
|
||||
RemoteGDB(System *system, ThreadContext *context);
|
||||
RemoteGDB(System *system, ThreadContext *context, int _port);
|
||||
BaseGdbRegCache *gdbRegs();
|
||||
};
|
||||
} // namespace X86ISA
|
||||
|
||||
@@ -154,179 +154,236 @@ static const char GDBBadP = '-';
|
||||
|
||||
static const int GDBPacketBufLen = 1024;
|
||||
|
||||
#ifndef NDEBUG
|
||||
vector<BaseRemoteGDB *> debuggers;
|
||||
|
||||
void
|
||||
debugger()
|
||||
class HardBreakpoint : public PCEvent
|
||||
{
|
||||
static int current_debugger = -1;
|
||||
if (current_debugger >= 0 && current_debugger < (int)debuggers.size()) {
|
||||
BaseRemoteGDB *gdb = debuggers[current_debugger];
|
||||
if (!gdb->isattached())
|
||||
gdb->listener->accept();
|
||||
if (gdb->isattached())
|
||||
gdb->trap(SIGILL);
|
||||
private:
|
||||
BaseRemoteGDB *gdb;
|
||||
|
||||
public:
|
||||
int refcount;
|
||||
|
||||
public:
|
||||
HardBreakpoint(BaseRemoteGDB *_gdb, PCEventQueue *q, Addr pc)
|
||||
: PCEvent(q, "HardBreakpoint Event", pc),
|
||||
gdb(_gdb), refcount(0)
|
||||
{
|
||||
DPRINTF(GDBMisc, "creating hardware breakpoint at %#x\n", evpc);
|
||||
}
|
||||
|
||||
const std::string name() const { return gdb->name() + ".hwbkpt"; }
|
||||
|
||||
void
|
||||
process(ThreadContext *tc) override
|
||||
{
|
||||
DPRINTF(GDBMisc, "handling hardware breakpoint at %#x\n", pc());
|
||||
|
||||
if (tc == gdb->tc)
|
||||
gdb->trap(SIGTRAP);
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
// Exception to throw when the connection to the client is broken.
|
||||
struct BadClient
|
||||
{
|
||||
const char *warning;
|
||||
BadClient(const char *_warning=NULL) : warning(_warning)
|
||||
{}
|
||||
};
|
||||
|
||||
// Exception to throw when an error needs to be reported to the client.
|
||||
struct CmdError
|
||||
{
|
||||
string error;
|
||||
CmdError(std::string _error) : error(_error)
|
||||
{}
|
||||
};
|
||||
|
||||
// Exception to throw when something isn't supported.
|
||||
class Unsupported {};
|
||||
|
||||
// Convert a hex digit into an integer.
|
||||
// This returns -1 if the argument passed is no valid hex digit.
|
||||
int
|
||||
digit2i(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return (c - '0');
|
||||
else if (c >= 'a' && c <= 'f')
|
||||
return (c - 'a' + 10);
|
||||
else if (c >= 'A' && c <= 'F')
|
||||
return (c - 'A' + 10);
|
||||
else
|
||||
return (-1);
|
||||
}
|
||||
|
||||
// Convert the low 4 bits of an integer into an hex digit.
|
||||
char
|
||||
i2digit(int n)
|
||||
{
|
||||
return ("0123456789abcdef"[n & 0x0f]);
|
||||
}
|
||||
|
||||
// Convert a byte array into an hex string.
|
||||
void
|
||||
mem2hex(char *vdst, const char *vsrc, int len)
|
||||
{
|
||||
char *dst = vdst;
|
||||
const char *src = vsrc;
|
||||
|
||||
while (len--) {
|
||||
*dst++ = i2digit(*src >> 4);
|
||||
*dst++ = i2digit(*src++);
|
||||
}
|
||||
*dst = '\0';
|
||||
}
|
||||
|
||||
// Convert an hex string into a byte array.
|
||||
// This returns a pointer to the character following the last valid
|
||||
// hex digit. If the string ends in the middle of a byte, NULL is
|
||||
// returned.
|
||||
const char *
|
||||
hex2mem(char *vdst, const char *src, int maxlen)
|
||||
{
|
||||
char *dst = vdst;
|
||||
int msb, lsb;
|
||||
|
||||
while (*src && maxlen--) {
|
||||
msb = digit2i(*src++);
|
||||
if (msb < 0)
|
||||
return (src - 1);
|
||||
lsb = digit2i(*src++);
|
||||
if (lsb < 0)
|
||||
return (NULL);
|
||||
*dst++ = (msb << 4) | lsb;
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
// Convert an hex string into an integer.
|
||||
// This returns a pointer to the character following the last valid
|
||||
// hex digit.
|
||||
Addr
|
||||
hex2i(const char **srcp)
|
||||
{
|
||||
const char *src = *srcp;
|
||||
Addr r = 0;
|
||||
int nibble;
|
||||
|
||||
while ((nibble = digit2i(*src)) >= 0) {
|
||||
r *= 16;
|
||||
r += nibble;
|
||||
src++;
|
||||
}
|
||||
*srcp = src;
|
||||
return r;
|
||||
}
|
||||
|
||||
enum GdbBreakpointType {
|
||||
GdbSoftBp = '0',
|
||||
GdbHardBp = '1',
|
||||
GdbWriteWp = '2',
|
||||
GdbReadWp = '3',
|
||||
GdbAccWp = '4',
|
||||
};
|
||||
|
||||
const char *
|
||||
break_type(char c)
|
||||
{
|
||||
switch(c) {
|
||||
case GdbSoftBp: return "software breakpoint";
|
||||
case GdbHardBp: return "hardware breakpoint";
|
||||
case GdbWriteWp: return "write watchpoint";
|
||||
case GdbReadWp: return "read watchpoint";
|
||||
case GdbAccWp: return "access watchpoint";
|
||||
default: return "unknown breakpoint/watchpoint";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
//
|
||||
//
|
||||
//
|
||||
std::map<Addr, HardBreakpoint *> hardBreakMap;
|
||||
|
||||
GDBListener::InputEvent::InputEvent(GDBListener *l, int fd, int e)
|
||||
: PollEvent(fd, e), listener(l)
|
||||
{}
|
||||
|
||||
void
|
||||
GDBListener::InputEvent::process(int revent)
|
||||
EventQueue *
|
||||
getComInstEventQueue(ThreadContext *tc)
|
||||
{
|
||||
listener->accept();
|
||||
return tc->getCpuPtr()->comInstEventQueue[tc->threadId()];
|
||||
}
|
||||
|
||||
GDBListener::GDBListener(BaseRemoteGDB *g, int p)
|
||||
: inputEvent(NULL), gdb(g), port(p)
|
||||
{
|
||||
assert(!gdb->listener);
|
||||
gdb->listener = this;
|
||||
}
|
||||
|
||||
GDBListener::~GDBListener()
|
||||
BaseRemoteGDB::BaseRemoteGDB(System *_system, ThreadContext *c, int _port) :
|
||||
connectEvent(nullptr), dataEvent(nullptr), _port(_port), fd(-1),
|
||||
active(false), attached(false), sys(_system), tc(c),
|
||||
trapEvent(this), singleStepEvent(*this)
|
||||
{
|
||||
delete inputEvent;
|
||||
debuggers.push_back(this);
|
||||
}
|
||||
|
||||
BaseRemoteGDB::~BaseRemoteGDB()
|
||||
{
|
||||
delete connectEvent;
|
||||
delete dataEvent;
|
||||
}
|
||||
|
||||
string
|
||||
GDBListener::name()
|
||||
BaseRemoteGDB::name()
|
||||
{
|
||||
return gdb->name() + ".listener";
|
||||
return sys->name() + ".remote_gdb";
|
||||
}
|
||||
|
||||
void
|
||||
GDBListener::listen()
|
||||
BaseRemoteGDB::listen()
|
||||
{
|
||||
if (ListenSocket::allDisabled()) {
|
||||
warn_once("Sockets disabled, not accepting gdb connections");
|
||||
return;
|
||||
}
|
||||
|
||||
while (!listener.listen(port, true)) {
|
||||
DPRINTF(GDBMisc, "Can't bind port %d\n", port);
|
||||
port++;
|
||||
while (!listener.listen(_port, true)) {
|
||||
DPRINTF(GDBMisc, "Can't bind port %d\n", _port);
|
||||
_port++;
|
||||
}
|
||||
|
||||
inputEvent = new InputEvent(this, listener.getfd(), POLLIN);
|
||||
pollQueue.schedule(inputEvent);
|
||||
connectEvent = new ConnectEvent(this, listener.getfd(), POLLIN);
|
||||
pollQueue.schedule(connectEvent);
|
||||
|
||||
#ifndef NDEBUG
|
||||
gdb->number = debuggers.size();
|
||||
debuggers.push_back(gdb);
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
ccprintf(cerr, "%d: %s: listening for remote gdb #%d on port %d\n",
|
||||
curTick(), name(), gdb->number, port);
|
||||
#else
|
||||
ccprintf(cerr, "%d: %s: listening for remote gdb on port %d\n",
|
||||
curTick(), name(), port);
|
||||
#endif
|
||||
curTick(), name(), _port);
|
||||
}
|
||||
|
||||
void
|
||||
GDBListener::accept()
|
||||
BaseRemoteGDB::connect()
|
||||
{
|
||||
if (!listener.islistening())
|
||||
panic("GDBListener::accept(): cannot accept if we're not listening!");
|
||||
panic_if(!listener.islistening(),
|
||||
"Cannot accept GDB connections if we're not listening!");
|
||||
|
||||
int sfd = listener.accept(true);
|
||||
|
||||
if (sfd != -1) {
|
||||
if (gdb->isattached())
|
||||
if (isAttached())
|
||||
close(sfd);
|
||||
else
|
||||
gdb->attach(sfd);
|
||||
attach(sfd);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
GDBListener::getPort() const
|
||||
BaseRemoteGDB::port() const
|
||||
{
|
||||
panic_if(!listener.islistening(),
|
||||
"Remote GDB port is unknown until GDBListener::listen() has "
|
||||
"been called.\n");
|
||||
|
||||
return port;
|
||||
"Remote GDB port is unknown until listen() has been called.\n");
|
||||
return _port;
|
||||
}
|
||||
|
||||
BaseRemoteGDB::InputEvent::InputEvent(BaseRemoteGDB *g, int fd, int e)
|
||||
: PollEvent(fd, e), gdb(g)
|
||||
{}
|
||||
|
||||
void
|
||||
BaseRemoteGDB::InputEvent::process(int revent)
|
||||
{
|
||||
if (gdb->trapEvent.scheduled()) {
|
||||
warn("GDB trap event has already been scheduled! "
|
||||
"Ignoring this input event.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (revent & POLLIN) {
|
||||
gdb->trapEvent.type(SIGILL);
|
||||
gdb->scheduleInstCommitEvent(&gdb->trapEvent, 0);
|
||||
} else if (revent & POLLNVAL) {
|
||||
gdb->descheduleInstCommitEvent(&gdb->trapEvent);
|
||||
gdb->detach();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BaseRemoteGDB::TrapEvent::process()
|
||||
{
|
||||
gdb->trap(_type);
|
||||
}
|
||||
|
||||
void
|
||||
BaseRemoteGDB::processSingleStepEvent()
|
||||
{
|
||||
if (!singleStepEvent.scheduled())
|
||||
scheduleInstCommitEvent(&singleStepEvent, 1);
|
||||
trap(SIGTRAP);
|
||||
}
|
||||
|
||||
BaseRemoteGDB::BaseRemoteGDB(System *_system, ThreadContext *c) :
|
||||
inputEvent(NULL), trapEvent(this), listener(NULL), number(-1),
|
||||
fd(-1), active(false), attached(false), system(_system),
|
||||
context(c),
|
||||
singleStepEvent([this]{ processSingleStepEvent(); }, name())
|
||||
{
|
||||
}
|
||||
|
||||
BaseRemoteGDB::~BaseRemoteGDB()
|
||||
{
|
||||
if (inputEvent)
|
||||
delete inputEvent;
|
||||
}
|
||||
|
||||
string
|
||||
BaseRemoteGDB::name()
|
||||
{
|
||||
return system->name() + ".remote_gdb";
|
||||
}
|
||||
|
||||
bool
|
||||
BaseRemoteGDB::isattached()
|
||||
{ return attached; }
|
||||
|
||||
void
|
||||
BaseRemoteGDB::attach(int f)
|
||||
{
|
||||
fd = f;
|
||||
|
||||
inputEvent = new InputEvent(this, fd, POLLIN);
|
||||
pollQueue.schedule(inputEvent);
|
||||
dataEvent = new DataEvent(this, fd, POLLIN);
|
||||
pollQueue.schedule(dataEvent);
|
||||
|
||||
attached = true;
|
||||
DPRINTFN("remote gdb attached\n");
|
||||
@@ -341,13 +398,106 @@ BaseRemoteGDB::detach()
|
||||
close(fd);
|
||||
fd = -1;
|
||||
|
||||
pollQueue.remove(inputEvent);
|
||||
pollQueue.remove(dataEvent);
|
||||
DPRINTFN("remote gdb detached\n");
|
||||
}
|
||||
|
||||
/////////////////////////
|
||||
//
|
||||
//
|
||||
// This function does all command processing for interfacing to a
|
||||
// remote gdb. Note that the error codes are ignored by gdb at
|
||||
// present, but might eventually become meaningful. (XXX) It might
|
||||
// makes sense to use POSIX errno values, because that is what the
|
||||
// gdb/remote.c functions want to return.
|
||||
bool
|
||||
BaseRemoteGDB::trap(int type)
|
||||
{
|
||||
|
||||
if (!attached)
|
||||
return false;
|
||||
|
||||
DPRINTF(GDBMisc, "trap: PC=%s\n", tc->pcState());
|
||||
|
||||
clearSingleStep();
|
||||
|
||||
/*
|
||||
* The first entry to this function is normally through
|
||||
* a breakpoint trap in kgdb_connect(), in which case we
|
||||
* must advance past the breakpoint because gdb will not.
|
||||
*
|
||||
* On the first entry here, we expect that gdb is not yet
|
||||
* listening to us, so just enter the interaction loop.
|
||||
* After the debugger is "active" (connected) it will be
|
||||
* waiting for a "signaled" message from us.
|
||||
*/
|
||||
if (!active) {
|
||||
active = true;
|
||||
} else {
|
||||
// Tell remote host that an exception has occurred.
|
||||
send(csprintf("S%02x", type).c_str());
|
||||
}
|
||||
|
||||
// Stick frame regs into our reg cache.
|
||||
regCachePtr = gdbRegs();
|
||||
regCachePtr->getRegs(tc);
|
||||
|
||||
char data[GDBPacketBufLen + 1];
|
||||
GdbCommand::Context cmdCtx;
|
||||
cmdCtx.type = type;
|
||||
cmdCtx.data = &data[1];
|
||||
|
||||
for (;;) {
|
||||
try {
|
||||
size_t datalen = recv(data, sizeof(data));
|
||||
if (datalen < 1)
|
||||
throw BadClient();
|
||||
|
||||
data[datalen] = 0; // Sentinel
|
||||
cmdCtx.cmd_byte = data[0];
|
||||
cmdCtx.len = datalen - 1;
|
||||
|
||||
auto cmdIt = command_map.find(cmdCtx.cmd_byte);
|
||||
if (cmdIt == command_map.end()) {
|
||||
DPRINTF(GDBMisc, "Unknown command: %c(%#x)\n",
|
||||
cmdCtx.cmd_byte, cmdCtx.cmd_byte);
|
||||
throw Unsupported();
|
||||
}
|
||||
cmdCtx.cmd = &(cmdIt->second);
|
||||
|
||||
if (!(this->*(cmdCtx.cmd->func))(cmdCtx))
|
||||
break;
|
||||
|
||||
} catch (BadClient &e) {
|
||||
if (e.warning)
|
||||
warn(e.warning);
|
||||
detach();
|
||||
break;
|
||||
} catch (Unsupported &e) {
|
||||
send("");
|
||||
} catch (CmdError &e) {
|
||||
send(e.error.c_str());
|
||||
} catch (...) {
|
||||
panic("Unrecognzied GDB exception.");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
BaseRemoteGDB::incomingData(int revent)
|
||||
{
|
||||
if (trapEvent.scheduled()) {
|
||||
warn("GDB trap event has already been scheduled!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (revent & POLLIN) {
|
||||
trapEvent.type(SIGILL);
|
||||
scheduleInstCommitEvent(&trapEvent, 0);
|
||||
} else if (revent & POLLNVAL) {
|
||||
descheduleInstCommitEvent(&trapEvent);
|
||||
detach();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t
|
||||
BaseRemoteGDB::getbyte()
|
||||
@@ -368,35 +518,6 @@ BaseRemoteGDB::putbyte(uint8_t b)
|
||||
throw BadClient("Couldn't write data to the debugger.");
|
||||
}
|
||||
|
||||
// Send a packet to gdb
|
||||
void
|
||||
BaseRemoteGDB::send(const char *bp)
|
||||
{
|
||||
const char *p;
|
||||
uint8_t csum, c;
|
||||
|
||||
DPRINTF(GDBSend, "send: %s\n", bp);
|
||||
|
||||
do {
|
||||
p = bp;
|
||||
// Start sending a packet
|
||||
putbyte(GDBStart);
|
||||
// Send the contents, and also keep a check sum.
|
||||
for (csum = 0; (c = *p); p++) {
|
||||
putbyte(c);
|
||||
csum += c;
|
||||
}
|
||||
// Send the ending character.
|
||||
putbyte(GDBEnd);
|
||||
// Send the checksum.
|
||||
putbyte(i2digit(csum >> 4));
|
||||
putbyte(i2digit(csum));
|
||||
// Try transmitting over and over again until the other end doesn't
|
||||
// send an error back.
|
||||
c = getbyte();
|
||||
} while ((c & 0x7f) == GDBBadP);
|
||||
}
|
||||
|
||||
// Receive a packet from gdb
|
||||
int
|
||||
BaseRemoteGDB::recv(char *bp, int maxlen)
|
||||
@@ -460,6 +581,35 @@ BaseRemoteGDB::recv(char *bp, int maxlen)
|
||||
return len;
|
||||
}
|
||||
|
||||
// Send a packet to gdb
|
||||
void
|
||||
BaseRemoteGDB::send(const char *bp)
|
||||
{
|
||||
const char *p;
|
||||
uint8_t csum, c;
|
||||
|
||||
DPRINTF(GDBSend, "send: %s\n", bp);
|
||||
|
||||
do {
|
||||
p = bp;
|
||||
// Start sending a packet
|
||||
putbyte(GDBStart);
|
||||
// Send the contents, and also keep a check sum.
|
||||
for (csum = 0; (c = *p); p++) {
|
||||
putbyte(c);
|
||||
csum += c;
|
||||
}
|
||||
// Send the ending character.
|
||||
putbyte(GDBEnd);
|
||||
// Send the checksum.
|
||||
putbyte(i2digit(csum >> 4));
|
||||
putbyte(i2digit(csum));
|
||||
// Try transmitting over and over again until the other end doesn't
|
||||
// send an error back.
|
||||
c = getbyte();
|
||||
} while ((c & 0x7f) == GDBBadP);
|
||||
}
|
||||
|
||||
// Read bytes from kernel address space for debugger.
|
||||
bool
|
||||
BaseRemoteGDB::read(Addr vaddr, size_t size, char *data)
|
||||
@@ -475,10 +625,10 @@ BaseRemoteGDB::read(Addr vaddr, size_t size, char *data)
|
||||
DPRINTF(GDBRead, "read: addr=%#x, size=%d", vaddr, size);
|
||||
|
||||
if (FullSystem) {
|
||||
FSTranslatingPortProxy &proxy = context->getVirtProxy();
|
||||
FSTranslatingPortProxy &proxy = tc->getVirtProxy();
|
||||
proxy.readBlob(vaddr, (uint8_t*)data, size);
|
||||
} else {
|
||||
SETranslatingPortProxy &proxy = context->getMemProxy();
|
||||
SETranslatingPortProxy &proxy = tc->getMemProxy();
|
||||
proxy.readBlob(vaddr, (uint8_t*)data, size);
|
||||
}
|
||||
|
||||
@@ -518,16 +668,24 @@ BaseRemoteGDB::write(Addr vaddr, size_t size, const char *data)
|
||||
DPRINTFNR("\n");
|
||||
}
|
||||
if (FullSystem) {
|
||||
FSTranslatingPortProxy &proxy = context->getVirtProxy();
|
||||
FSTranslatingPortProxy &proxy = tc->getVirtProxy();
|
||||
proxy.writeBlob(vaddr, (uint8_t*)data, size);
|
||||
} else {
|
||||
SETranslatingPortProxy &proxy = context->getMemProxy();
|
||||
SETranslatingPortProxy &proxy = tc->getMemProxy();
|
||||
proxy.writeBlob(vaddr, (uint8_t*)data, size);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
BaseRemoteGDB::singleStep()
|
||||
{
|
||||
if (!singleStepEvent.scheduled())
|
||||
scheduleInstCommitEvent(&singleStepEvent, 1);
|
||||
trap(SIGTRAP);
|
||||
}
|
||||
|
||||
void
|
||||
BaseRemoteGDB::clearSingleStep()
|
||||
{
|
||||
@@ -541,56 +699,6 @@ BaseRemoteGDB::setSingleStep()
|
||||
scheduleInstCommitEvent(&singleStepEvent, 1);
|
||||
}
|
||||
|
||||
PCEventQueue *BaseRemoteGDB::getPcEventQueue()
|
||||
{
|
||||
return &system->pcEventQueue;
|
||||
}
|
||||
|
||||
EventQueue *
|
||||
BaseRemoteGDB::getComInstEventQueue()
|
||||
{
|
||||
BaseCPU *cpu = context->getCpuPtr();
|
||||
return cpu->comInstEventQueue[context->threadId()];
|
||||
}
|
||||
|
||||
void
|
||||
BaseRemoteGDB::scheduleInstCommitEvent(Event *ev, int delta)
|
||||
{
|
||||
EventQueue *eq = getComInstEventQueue();
|
||||
// Here "ticks" aren't simulator ticks which measure time, they're
|
||||
// instructions committed by the CPU.
|
||||
eq->schedule(ev, eq->getCurTick() + delta);
|
||||
}
|
||||
|
||||
void
|
||||
BaseRemoteGDB::descheduleInstCommitEvent(Event *ev)
|
||||
{
|
||||
if (ev->scheduled())
|
||||
getComInstEventQueue()->deschedule(ev);
|
||||
}
|
||||
|
||||
bool
|
||||
BaseRemoteGDB::checkBpLen(size_t len)
|
||||
{
|
||||
return len == sizeof(MachInst);
|
||||
}
|
||||
|
||||
BaseRemoteGDB::HardBreakpoint::HardBreakpoint(BaseRemoteGDB *_gdb, Addr pc)
|
||||
: PCEvent(_gdb->getPcEventQueue(), "HardBreakpoint Event", pc),
|
||||
gdb(_gdb), refcount(0)
|
||||
{
|
||||
DPRINTF(GDBMisc, "creating hardware breakpoint at %#x\n", evpc);
|
||||
}
|
||||
|
||||
void
|
||||
BaseRemoteGDB::HardBreakpoint::process(ThreadContext *tc)
|
||||
{
|
||||
DPRINTF(GDBMisc, "handling hardware breakpoint at %#x\n", pc());
|
||||
|
||||
if (tc == gdb->context)
|
||||
gdb->trap(SIGTRAP);
|
||||
}
|
||||
|
||||
void
|
||||
BaseRemoteGDB::insertSoftBreak(Addr addr, size_t len)
|
||||
{
|
||||
@@ -619,7 +727,7 @@ BaseRemoteGDB::insertHardBreak(Addr addr, size_t len)
|
||||
|
||||
HardBreakpoint *&bkpt = hardBreakMap[addr];
|
||||
if (bkpt == 0)
|
||||
bkpt = new HardBreakpoint(this, addr);
|
||||
bkpt = new HardBreakpoint(this, &sys->pcEventQueue, addr);
|
||||
|
||||
bkpt->refcount++;
|
||||
}
|
||||
@@ -632,7 +740,7 @@ BaseRemoteGDB::removeHardBreak(Addr addr, size_t len)
|
||||
|
||||
DPRINTF(GDBMisc, "Removing hardware breakpoint at %#x\n", addr);
|
||||
|
||||
break_iter_t i = hardBreakMap.find(addr);
|
||||
auto i = hardBreakMap.find(addr);
|
||||
if (i == hardBreakMap.end())
|
||||
throw CmdError("E0C");
|
||||
|
||||
@@ -643,13 +751,6 @@ BaseRemoteGDB::removeHardBreak(Addr addr, size_t len)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BaseRemoteGDB::setTempBreakpoint(Addr bkpt)
|
||||
{
|
||||
DPRINTF(GDBMisc, "setTempBreakpoint: addr=%#x\n", bkpt);
|
||||
insertHardBreak(bkpt, sizeof(TheISA::MachInst));
|
||||
}
|
||||
|
||||
void
|
||||
BaseRemoteGDB::clearTempBreakpoint(Addr &bkpt)
|
||||
{
|
||||
@@ -658,28 +759,30 @@ BaseRemoteGDB::clearTempBreakpoint(Addr &bkpt)
|
||||
bkpt = 0;
|
||||
}
|
||||
|
||||
enum GdbBreakpointType {
|
||||
GdbSoftBp = '0',
|
||||
GdbHardBp = '1',
|
||||
GdbWriteWp = '2',
|
||||
GdbReadWp = '3',
|
||||
GdbAccWp = '4',
|
||||
};
|
||||
|
||||
const char *
|
||||
BaseRemoteGDB::break_type(char c)
|
||||
void
|
||||
BaseRemoteGDB::setTempBreakpoint(Addr bkpt)
|
||||
{
|
||||
switch(c) {
|
||||
case GdbSoftBp: return "software breakpoint";
|
||||
case GdbHardBp: return "hardware breakpoint";
|
||||
case GdbWriteWp: return "write watchpoint";
|
||||
case GdbReadWp: return "read watchpoint";
|
||||
case GdbAccWp: return "access watchpoint";
|
||||
default: return "unknown breakpoint/watchpoint";
|
||||
}
|
||||
DPRINTF(GDBMisc, "setTempBreakpoint: addr=%#x\n", bkpt);
|
||||
insertHardBreak(bkpt, sizeof(TheISA::MachInst));
|
||||
}
|
||||
|
||||
std::map<char, GdbCommand> BaseRemoteGDB::command_map = {
|
||||
void
|
||||
BaseRemoteGDB::scheduleInstCommitEvent(Event *ev, int delta)
|
||||
{
|
||||
EventQueue *eq = getComInstEventQueue(tc);
|
||||
// Here "ticks" aren't simulator ticks which measure time, they're
|
||||
// instructions committed by the CPU.
|
||||
eq->schedule(ev, eq->getCurTick() + delta);
|
||||
}
|
||||
|
||||
void
|
||||
BaseRemoteGDB::descheduleInstCommitEvent(Event *ev)
|
||||
{
|
||||
if (ev->scheduled())
|
||||
getComInstEventQueue(tc)->deschedule(ev);
|
||||
}
|
||||
|
||||
std::map<char, BaseRemoteGDB::GdbCommand> BaseRemoteGDB::command_map = {
|
||||
// last signal
|
||||
{ '?', { "KGDB_SIGNAL", &BaseRemoteGDB::cmd_signal } },
|
||||
// set baud (deprecated)
|
||||
@@ -736,6 +839,11 @@ std::map<char, GdbCommand> BaseRemoteGDB::command_map = {
|
||||
{ 'Z', { "KGDB_SET_HW_BKPT", &BaseRemoteGDB::cmd_set_hw_bkpt } },
|
||||
};
|
||||
|
||||
bool
|
||||
BaseRemoteGDB::checkBpLen(size_t len)
|
||||
{
|
||||
return len == sizeof(MachInst);
|
||||
}
|
||||
|
||||
bool
|
||||
BaseRemoteGDB::cmd_unsupported(GdbCommand::Context &ctx)
|
||||
@@ -759,7 +867,7 @@ BaseRemoteGDB::cmd_cont(GdbCommand::Context &ctx)
|
||||
const char *p = ctx.data;
|
||||
if (ctx.len) {
|
||||
Addr newPc = hex2i(&p);
|
||||
context->pcState(newPc);
|
||||
tc->pcState(newPc);
|
||||
}
|
||||
clearSingleStep();
|
||||
return false;
|
||||
@@ -772,7 +880,7 @@ BaseRemoteGDB::cmd_async_cont(GdbCommand::Context &ctx)
|
||||
hex2i(&p);
|
||||
if (*p++ == ';') {
|
||||
Addr newPc = hex2i(&p);
|
||||
context->pcState(newPc);
|
||||
tc->pcState(newPc);
|
||||
}
|
||||
clearSingleStep();
|
||||
return false;
|
||||
@@ -803,7 +911,7 @@ BaseRemoteGDB::cmd_reg_w(GdbCommand::Context &ctx)
|
||||
if (p == NULL || *p != '\0')
|
||||
throw CmdError("E01");
|
||||
|
||||
regCachePtr->setRegs(context);
|
||||
regCachePtr->setRegs(tc);
|
||||
send("OK");
|
||||
|
||||
return true;
|
||||
@@ -883,7 +991,7 @@ BaseRemoteGDB::cmd_async_step(GdbCommand::Context &ctx)
|
||||
hex2i(&p); // Ignore the subcommand byte.
|
||||
if (*p++ == ';') {
|
||||
Addr newPc = hex2i(&p);
|
||||
context->pcState(newPc);
|
||||
tc->pcState(newPc);
|
||||
}
|
||||
setSingleStep();
|
||||
return false;
|
||||
@@ -895,7 +1003,7 @@ BaseRemoteGDB::cmd_step(GdbCommand::Context &ctx)
|
||||
if (ctx.len) {
|
||||
const char *p = ctx.data;
|
||||
Addr newPc = hex2i(&p);
|
||||
context->pcState(newPc);
|
||||
tc->pcState(newPc);
|
||||
}
|
||||
setSingleStep();
|
||||
return false;
|
||||
@@ -966,161 +1074,3 @@ BaseRemoteGDB::cmd_set_hw_bkpt(GdbCommand::Context &ctx)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// This function does all command processing for interfacing to a
|
||||
// remote gdb. Note that the error codes are ignored by gdb at
|
||||
// present, but might eventually become meaningful. (XXX) It might
|
||||
// makes sense to use POSIX errno values, because that is what the
|
||||
// gdb/remote.c functions want to return.
|
||||
bool
|
||||
BaseRemoteGDB::trap(int type)
|
||||
{
|
||||
|
||||
if (!attached)
|
||||
return false;
|
||||
|
||||
DPRINTF(GDBMisc, "trap: PC=%s\n", context->pcState());
|
||||
|
||||
clearSingleStep();
|
||||
|
||||
/*
|
||||
* The first entry to this function is normally through
|
||||
* a breakpoint trap in kgdb_connect(), in which case we
|
||||
* must advance past the breakpoint because gdb will not.
|
||||
*
|
||||
* On the first entry here, we expect that gdb is not yet
|
||||
* listening to us, so just enter the interaction loop.
|
||||
* After the debugger is "active" (connected) it will be
|
||||
* waiting for a "signaled" message from us.
|
||||
*/
|
||||
if (!active) {
|
||||
active = true;
|
||||
} else {
|
||||
// Tell remote host that an exception has occurred.
|
||||
send(csprintf("S%02x", type).c_str());
|
||||
}
|
||||
|
||||
// Stick frame regs into our reg cache.
|
||||
regCachePtr = gdbRegs();
|
||||
regCachePtr->getRegs(context);
|
||||
|
||||
char data[GDBPacketBufLen + 1];
|
||||
GdbCommand::Context cmdCtx;
|
||||
cmdCtx.type = type;
|
||||
cmdCtx.data = &data[1];
|
||||
|
||||
for (;;) {
|
||||
try {
|
||||
size_t datalen = recv(data, sizeof(data));
|
||||
if (datalen < 1)
|
||||
throw BadClient();
|
||||
|
||||
data[datalen] = 0; // Sentinel
|
||||
cmdCtx.cmd_byte = data[0];
|
||||
cmdCtx.len = datalen - 1;
|
||||
|
||||
auto cmdIt = command_map.find(cmdCtx.cmd_byte);
|
||||
if (cmdIt == command_map.end()) {
|
||||
DPRINTF(GDBMisc, "Unknown command: %c(%#x)\n",
|
||||
cmdCtx.cmd_byte, cmdCtx.cmd_byte);
|
||||
throw Unsupported();
|
||||
}
|
||||
cmdCtx.cmd = &(cmdIt->second);
|
||||
|
||||
if (!(this->*(cmdCtx.cmd->func))(cmdCtx))
|
||||
break;
|
||||
|
||||
} catch (BadClient &e) {
|
||||
if (e.warning)
|
||||
warn(e.warning);
|
||||
detach();
|
||||
break;
|
||||
} catch (Unsupported &e) {
|
||||
send("");
|
||||
} catch (CmdError &e) {
|
||||
send(e.error.c_str());
|
||||
} catch (...) {
|
||||
panic("Unrecognzied GDB exception.");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Convert a hex digit into an integer.
|
||||
// This returns -1 if the argument passed is no valid hex digit.
|
||||
int
|
||||
BaseRemoteGDB::digit2i(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return (c - '0');
|
||||
else if (c >= 'a' && c <= 'f')
|
||||
return (c - 'a' + 10);
|
||||
else if (c >= 'A' && c <= 'F')
|
||||
return (c - 'A' + 10);
|
||||
else
|
||||
return (-1);
|
||||
}
|
||||
|
||||
// Convert the low 4 bits of an integer into an hex digit.
|
||||
char
|
||||
BaseRemoteGDB::i2digit(int n)
|
||||
{
|
||||
return ("0123456789abcdef"[n & 0x0f]);
|
||||
}
|
||||
|
||||
// Convert a byte array into an hex string.
|
||||
void
|
||||
BaseRemoteGDB::mem2hex(char *vdst, const char *vsrc, int len)
|
||||
{
|
||||
char *dst = vdst;
|
||||
const char *src = vsrc;
|
||||
|
||||
while (len--) {
|
||||
*dst++ = i2digit(*src >> 4);
|
||||
*dst++ = i2digit(*src++);
|
||||
}
|
||||
*dst = '\0';
|
||||
}
|
||||
|
||||
// Convert an hex string into a byte array.
|
||||
// This returns a pointer to the character following the last valid
|
||||
// hex digit. If the string ends in the middle of a byte, NULL is
|
||||
// returned.
|
||||
const char *
|
||||
BaseRemoteGDB::hex2mem(char *vdst, const char *src, int maxlen)
|
||||
{
|
||||
char *dst = vdst;
|
||||
int msb, lsb;
|
||||
|
||||
while (*src && maxlen--) {
|
||||
msb = digit2i(*src++);
|
||||
if (msb < 0)
|
||||
return (src - 1);
|
||||
lsb = digit2i(*src++);
|
||||
if (lsb < 0)
|
||||
return (NULL);
|
||||
*dst++ = (msb << 4) | lsb;
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
// Convert an hex string into an integer.
|
||||
// This returns a pointer to the character following the last valid
|
||||
// hex digit.
|
||||
Addr
|
||||
BaseRemoteGDB::hex2i(const char **srcp)
|
||||
{
|
||||
const char *src = *srcp;
|
||||
Addr r = 0;
|
||||
int nibble;
|
||||
|
||||
while ((nibble = digit2i(*src)) >= 0) {
|
||||
r *= 16;
|
||||
r += nibble;
|
||||
src++;
|
||||
}
|
||||
*srcp = src;
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -49,68 +49,210 @@
|
||||
class System;
|
||||
class ThreadContext;
|
||||
|
||||
class GDBListener;
|
||||
|
||||
class BaseRemoteGDB;
|
||||
class HardBreakpoint;
|
||||
|
||||
struct GdbCommand
|
||||
/**
|
||||
* Concrete subclasses of this abstract class represent how the
|
||||
* register values are transmitted on the wire. Usually each
|
||||
* architecture should define one subclass, but there can be more
|
||||
* if there is more than one possible wire format. For example,
|
||||
* ARM defines both AArch32GdbRegCache and AArch64GdbRegCache.
|
||||
*/
|
||||
class BaseGdbRegCache
|
||||
{
|
||||
public:
|
||||
struct Context
|
||||
{
|
||||
const GdbCommand *cmd;
|
||||
char cmd_byte;
|
||||
int type;
|
||||
char *data;
|
||||
int len;
|
||||
};
|
||||
|
||||
typedef bool (BaseRemoteGDB::*Func)(Context &ctx);
|
||||
/**
|
||||
* Return the pointer to the raw bytes buffer containing the
|
||||
* register values. Each byte of this buffer is literally
|
||||
* encoded as two hex digits in the g or G RSP packet.
|
||||
*/
|
||||
virtual char *data() const = 0;
|
||||
|
||||
const char * const name;
|
||||
const Func func;
|
||||
/**
|
||||
* Return the size of the raw buffer, in bytes
|
||||
* (i.e., half of the number of digits in the g/G packet).
|
||||
*/
|
||||
virtual size_t size() const = 0;
|
||||
|
||||
GdbCommand(const char *_name, Func _func) : name(_name), func(_func)
|
||||
/**
|
||||
* Fill the raw buffer from the registers in the ThreadContext.
|
||||
*/
|
||||
virtual void getRegs(ThreadContext*) = 0;
|
||||
|
||||
/**
|
||||
* Set the ThreadContext's registers from the values
|
||||
* in the raw buffer.
|
||||
*/
|
||||
virtual void setRegs(ThreadContext*) const = 0;
|
||||
|
||||
/**
|
||||
* Return the name to use in places like DPRINTF.
|
||||
* Having each concrete superclass redefine this member
|
||||
* is useful in situations where the class of the regCache
|
||||
* can change on the fly.
|
||||
*/
|
||||
virtual const std::string name() const = 0;
|
||||
|
||||
BaseGdbRegCache(BaseRemoteGDB *g) : gdb(g)
|
||||
{}
|
||||
virtual ~BaseGdbRegCache()
|
||||
{}
|
||||
|
||||
protected:
|
||||
BaseRemoteGDB *gdb;
|
||||
};
|
||||
|
||||
class BaseRemoteGDB
|
||||
{
|
||||
friend class HardBreakpoint;
|
||||
public:
|
||||
|
||||
/*
|
||||
* Interface to other parts of the simulator.
|
||||
*/
|
||||
BaseRemoteGDB(System *system, ThreadContext *context, int _port);
|
||||
virtual ~BaseRemoteGDB();
|
||||
|
||||
std::string name();
|
||||
|
||||
void listen();
|
||||
void connect();
|
||||
|
||||
int port() const;
|
||||
|
||||
void attach(int fd);
|
||||
void detach();
|
||||
bool isAttached() { return attached; }
|
||||
|
||||
void replaceThreadContext(ThreadContext *_tc) { tc = _tc; }
|
||||
|
||||
bool trap(int type);
|
||||
bool breakpoint() { return trap(SIGTRAP); }
|
||||
|
||||
private:
|
||||
friend void debugger();
|
||||
friend class GDBListener;
|
||||
/*
|
||||
* Connection to the external GDB.
|
||||
*/
|
||||
void incomingData(int revent);
|
||||
void connectWrapper(int revent) { connect(); }
|
||||
|
||||
protected:
|
||||
/// Exception to throw when the connection to the client is broken.
|
||||
struct BadClient
|
||||
template <void (BaseRemoteGDB::*F)(int revent)>
|
||||
class SocketEvent : public PollEvent
|
||||
{
|
||||
const char *warning;
|
||||
BadClient(const char *_warning=NULL) : warning(_warning)
|
||||
protected:
|
||||
BaseRemoteGDB *gdb;
|
||||
|
||||
public:
|
||||
SocketEvent(BaseRemoteGDB *gdb, int fd, int e) :
|
||||
PollEvent(fd, e), gdb(gdb)
|
||||
{}
|
||||
|
||||
void process(int revent) { (gdb->*F)(revent); }
|
||||
};
|
||||
/// Exception to throw when an error needs to be reported to the client.
|
||||
struct CmdError
|
||||
|
||||
typedef SocketEvent<&BaseRemoteGDB::connectWrapper> ConnectEvent;
|
||||
typedef SocketEvent<&BaseRemoteGDB::incomingData> DataEvent;
|
||||
|
||||
friend ConnectEvent;
|
||||
friend DataEvent;
|
||||
|
||||
ConnectEvent *connectEvent;
|
||||
DataEvent *dataEvent;
|
||||
|
||||
ListenSocket listener;
|
||||
int _port;
|
||||
|
||||
// The socket commands come in through.
|
||||
int fd;
|
||||
|
||||
// Transfer data to/from GDB.
|
||||
uint8_t getbyte();
|
||||
void putbyte(uint8_t b);
|
||||
|
||||
int recv(char *data, int len);
|
||||
void send(const char *data);
|
||||
|
||||
/*
|
||||
* Simulator side debugger state.
|
||||
*/
|
||||
bool active;
|
||||
bool attached;
|
||||
|
||||
System *sys;
|
||||
ThreadContext *tc;
|
||||
|
||||
BaseGdbRegCache *regCachePtr;
|
||||
|
||||
class TrapEvent : public Event
|
||||
{
|
||||
std::string error;
|
||||
CmdError(std::string _error) : error(_error)
|
||||
protected:
|
||||
int _type;
|
||||
BaseRemoteGDB *gdb;
|
||||
|
||||
public:
|
||||
TrapEvent(BaseRemoteGDB *g) : gdb(g)
|
||||
{}
|
||||
|
||||
void type(int t) { _type = t; }
|
||||
void process() { gdb->trap(_type); }
|
||||
} trapEvent;
|
||||
|
||||
/*
|
||||
* The interface to the simulated system.
|
||||
*/
|
||||
// Machine memory.
|
||||
bool read(Addr addr, size_t size, char *data);
|
||||
bool write(Addr addr, size_t size, const char *data);
|
||||
|
||||
template <class T> T read(Addr addr);
|
||||
template <class T> void write(Addr addr, T data);
|
||||
|
||||
// Single step.
|
||||
void singleStep();
|
||||
EventWrapper<BaseRemoteGDB, &BaseRemoteGDB::singleStep> singleStepEvent;
|
||||
|
||||
void clearSingleStep();
|
||||
void setSingleStep();
|
||||
|
||||
/// Schedule an event which will be triggered "delta" instructions later.
|
||||
void scheduleInstCommitEvent(Event *ev, int delta);
|
||||
/// Deschedule an instruction count based event.
|
||||
void descheduleInstCommitEvent(Event *ev);
|
||||
|
||||
// Breakpoints.
|
||||
void insertSoftBreak(Addr addr, size_t len);
|
||||
void removeSoftBreak(Addr addr, size_t len);
|
||||
void insertHardBreak(Addr addr, size_t len);
|
||||
void removeHardBreak(Addr addr, size_t len);
|
||||
|
||||
void clearTempBreakpoint(Addr &bkpt);
|
||||
void setTempBreakpoint(Addr bkpt);
|
||||
|
||||
/*
|
||||
* GDB commands.
|
||||
*/
|
||||
struct GdbCommand
|
||||
{
|
||||
public:
|
||||
struct Context
|
||||
{
|
||||
const GdbCommand *cmd;
|
||||
char cmd_byte;
|
||||
int type;
|
||||
char *data;
|
||||
int len;
|
||||
};
|
||||
|
||||
typedef bool (BaseRemoteGDB::*Func)(Context &ctx);
|
||||
|
||||
const char * const name;
|
||||
const Func func;
|
||||
|
||||
GdbCommand(const char *_name, Func _func) : name(_name), func(_func) {}
|
||||
};
|
||||
/// Exception to throw when something isn't supported.
|
||||
class Unsupported {};
|
||||
|
||||
// Helper functions
|
||||
protected:
|
||||
int digit2i(char);
|
||||
char i2digit(int);
|
||||
Addr hex2i(const char **);
|
||||
// Address formats, break types, and gdb commands may change
|
||||
// between architectures, so they're defined as virtual
|
||||
// functions.
|
||||
virtual void mem2hex(char *, const char *, int);
|
||||
virtual const char * hex2mem(char *, const char *, int);
|
||||
virtual const char * break_type(char c);
|
||||
|
||||
protected:
|
||||
static std::map<char, GdbCommand> command_map;
|
||||
|
||||
bool cmd_unsupported(GdbCommand::Context &ctx);
|
||||
@@ -131,183 +273,15 @@ class BaseRemoteGDB
|
||||
bool cmd_set_hw_bkpt(GdbCommand::Context &ctx);
|
||||
|
||||
protected:
|
||||
class InputEvent : public PollEvent
|
||||
{
|
||||
protected:
|
||||
BaseRemoteGDB *gdb;
|
||||
ThreadContext *context() { return tc; }
|
||||
System *system() { return sys; }
|
||||
|
||||
public:
|
||||
InputEvent(BaseRemoteGDB *g, int fd, int e);
|
||||
void process(int revent);
|
||||
};
|
||||
|
||||
class TrapEvent : public Event
|
||||
{
|
||||
protected:
|
||||
int _type;
|
||||
BaseRemoteGDB *gdb;
|
||||
|
||||
public:
|
||||
TrapEvent(BaseRemoteGDB *g) : gdb(g)
|
||||
{}
|
||||
|
||||
void type(int t) { _type = t; }
|
||||
void process();
|
||||
};
|
||||
|
||||
friend class InputEvent;
|
||||
InputEvent *inputEvent;
|
||||
TrapEvent trapEvent;
|
||||
GDBListener *listener;
|
||||
int number;
|
||||
|
||||
protected:
|
||||
// The socket commands come in through
|
||||
int fd;
|
||||
|
||||
protected:
|
||||
bool active;
|
||||
bool attached;
|
||||
|
||||
System *system;
|
||||
ThreadContext *context;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Concrete subclasses of this abstract class represent how the
|
||||
* register values are transmitted on the wire. Usually each
|
||||
* architecture should define one subclass, but there can be more
|
||||
* if there is more than one possible wire format. For example,
|
||||
* ARM defines both AArch32GdbRegCache and AArch64GdbRegCache.
|
||||
*/
|
||||
class BaseGdbRegCache
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Return the pointer to the raw bytes buffer containing the
|
||||
* register values. Each byte of this buffer is literally
|
||||
* encoded as two hex digits in the g or G RSP packet.
|
||||
*/
|
||||
virtual char *data() const = 0;
|
||||
|
||||
/**
|
||||
* Return the size of the raw buffer, in bytes
|
||||
* (i.e., half of the number of digits in the g/G packet).
|
||||
*/
|
||||
virtual size_t size() const = 0;
|
||||
|
||||
/**
|
||||
* Fill the raw buffer from the registers in the ThreadContext.
|
||||
*/
|
||||
virtual void getRegs(ThreadContext*) = 0;
|
||||
|
||||
/**
|
||||
* Set the ThreadContext's registers from the values
|
||||
* in the raw buffer.
|
||||
*/
|
||||
virtual void setRegs(ThreadContext*) const = 0;
|
||||
|
||||
/**
|
||||
* Return the name to use in places like DPRINTF.
|
||||
* Having each concrete superclass redefine this member
|
||||
* is useful in situations where the class of the regCache
|
||||
* can change on the fly.
|
||||
*/
|
||||
virtual const std::string name() const = 0;
|
||||
|
||||
BaseGdbRegCache(BaseRemoteGDB *g) : gdb(g)
|
||||
{}
|
||||
virtual ~BaseGdbRegCache()
|
||||
{}
|
||||
|
||||
protected:
|
||||
BaseRemoteGDB *gdb;
|
||||
};
|
||||
|
||||
BaseGdbRegCache *regCachePtr;
|
||||
|
||||
protected:
|
||||
uint8_t getbyte();
|
||||
void putbyte(uint8_t b);
|
||||
|
||||
int recv(char *data, int len);
|
||||
void send(const char *data);
|
||||
|
||||
protected:
|
||||
// Machine memory
|
||||
virtual bool read(Addr addr, size_t size, char *data);
|
||||
virtual bool write(Addr addr, size_t size, const char *data);
|
||||
|
||||
template <class T> T read(Addr addr);
|
||||
template <class T> void write(Addr addr, T data);
|
||||
|
||||
public:
|
||||
BaseRemoteGDB(System *system, ThreadContext *context);
|
||||
virtual ~BaseRemoteGDB();
|
||||
virtual BaseGdbRegCache *gdbRegs() = 0;
|
||||
|
||||
void replaceThreadContext(ThreadContext *tc) { context = tc; }
|
||||
|
||||
void attach(int fd);
|
||||
void detach();
|
||||
bool isattached();
|
||||
|
||||
virtual bool acc(Addr addr, size_t len) = 0;
|
||||
bool trap(int type);
|
||||
virtual bool breakpoint()
|
||||
{
|
||||
return trap(SIGTRAP);
|
||||
}
|
||||
|
||||
void processSingleStepEvent();
|
||||
EventFunctionWrapper singleStepEvent;
|
||||
|
||||
void clearSingleStep();
|
||||
void setSingleStep();
|
||||
|
||||
PCEventQueue *getPcEventQueue();
|
||||
EventQueue *getComInstEventQueue();
|
||||
|
||||
/// Schedule an event which will be triggered "delta" instructions later.
|
||||
void scheduleInstCommitEvent(Event *ev, int delta);
|
||||
/// Deschedule an instruction count based event.
|
||||
void descheduleInstCommitEvent(Event *ev);
|
||||
|
||||
protected:
|
||||
// To be implemented by subclasses.
|
||||
virtual bool checkBpLen(size_t len);
|
||||
|
||||
class HardBreakpoint : public PCEvent
|
||||
{
|
||||
private:
|
||||
BaseRemoteGDB *gdb;
|
||||
virtual BaseGdbRegCache *gdbRegs() = 0;
|
||||
|
||||
public:
|
||||
int refcount;
|
||||
|
||||
public:
|
||||
HardBreakpoint(BaseRemoteGDB *_gdb, Addr addr);
|
||||
const std::string name() const { return gdb->name() + ".hwbkpt"; }
|
||||
|
||||
virtual void process(ThreadContext *tc);
|
||||
};
|
||||
friend class HardBreakpoint;
|
||||
|
||||
typedef std::map<Addr, HardBreakpoint *> break_map_t;
|
||||
typedef break_map_t::iterator break_iter_t;
|
||||
break_map_t hardBreakMap;
|
||||
|
||||
void insertSoftBreak(Addr addr, size_t len);
|
||||
void removeSoftBreak(Addr addr, size_t len);
|
||||
virtual void insertHardBreak(Addr addr, size_t len);
|
||||
void removeHardBreak(Addr addr, size_t len);
|
||||
|
||||
protected:
|
||||
void clearTempBreakpoint(Addr &bkpt);
|
||||
void setTempBreakpoint(Addr bkpt);
|
||||
|
||||
public:
|
||||
std::string name();
|
||||
virtual bool acc(Addr addr, size_t len) = 0;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
@@ -322,38 +296,8 @@ BaseRemoteGDB::read(Addr addr)
|
||||
template <class T>
|
||||
inline void
|
||||
BaseRemoteGDB::write(Addr addr, T data)
|
||||
{ write(addr, sizeof(T), (const char *)&data); }
|
||||
|
||||
class GDBListener
|
||||
{
|
||||
protected:
|
||||
class InputEvent : public PollEvent
|
||||
{
|
||||
protected:
|
||||
GDBListener *listener;
|
||||
|
||||
public:
|
||||
InputEvent(GDBListener *l, int fd, int e);
|
||||
void process(int revent);
|
||||
};
|
||||
|
||||
friend class InputEvent;
|
||||
InputEvent *inputEvent;
|
||||
|
||||
protected:
|
||||
ListenSocket listener;
|
||||
BaseRemoteGDB *gdb;
|
||||
int port;
|
||||
|
||||
public:
|
||||
GDBListener(BaseRemoteGDB *g, int p);
|
||||
~GDBListener();
|
||||
|
||||
void accept();
|
||||
void listen();
|
||||
std::string name();
|
||||
|
||||
int getPort() const;
|
||||
};
|
||||
write(addr, sizeof(T), (const char *)&data);
|
||||
}
|
||||
|
||||
#endif /* __REMOTE_GDB_H__ */
|
||||
|
||||
@@ -261,16 +261,15 @@ System::registerThreadContext(ThreadContext *tc, ContextID assigned)
|
||||
#if THE_ISA != NULL_ISA
|
||||
int port = getRemoteGDBPort();
|
||||
if (port) {
|
||||
RemoteGDB *rgdb = new RemoteGDB(this, tc);
|
||||
GDBListener *gdbl = new GDBListener(rgdb, port + id);
|
||||
gdbl->listen();
|
||||
RemoteGDB *rgdb = new RemoteGDB(this, tc, port + id);
|
||||
rgdb->listen();
|
||||
|
||||
BaseCPU *cpu = tc->getCpuPtr();
|
||||
if (cpu->waitForRemoteGDB()) {
|
||||
inform("%s: Waiting for a remote GDB connection on port %d.\n",
|
||||
cpu->name(), gdbl->getPort());
|
||||
cpu->name(), rgdb->port());
|
||||
|
||||
gdbl->accept();
|
||||
rgdb->connect();
|
||||
}
|
||||
if (remoteGDB.size() <= id) {
|
||||
remoteGDB.resize(id + 1);
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
#endif
|
||||
|
||||
class BaseRemoteGDB;
|
||||
class GDBListener;
|
||||
class KvmVM;
|
||||
class ObjectFile;
|
||||
class ThreadContext;
|
||||
@@ -491,7 +490,6 @@ class System : public MemObject
|
||||
|
||||
public:
|
||||
std::vector<BaseRemoteGDB *> remoteGDB;
|
||||
std::vector<GDBListener *> gdbListen;
|
||||
bool breakpoint();
|
||||
|
||||
public:
|
||||
|
||||
Reference in New Issue
Block a user