X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;ds=sidebyside;f=src%2Fbase%2Fremote_gdb.cc;h=9a1f7bf2bbacbdc55b2b6015b47af99c3e3e5b5e;hb=78e5caef696b017b15447c2a12311e94961cfdb4;hp=0d3b73b1eeb091e1a1f89ebb9c00d95a06b8ffb4;hpb=4b732e43a61bd87db39a0316105a32dcde9b0fb8;p=gem5.git diff --git a/src/base/remote_gdb.cc b/src/base/remote_gdb.cc index 0d3b73b1e..9a1f7bf2b 100644 --- a/src/base/remote_gdb.cc +++ b/src/base/remote_gdb.cc @@ -1,4 +1,17 @@ /* + * Copyright (c) 2018 ARM Limited + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Copyright 2015 LabWare + * Copyright 2014 Google, Inc. * Copyright (c) 2002-2005 The Regents of The University of Michigan * All rights reserved. * @@ -26,11 +39,12 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Authors: Nathan Binkert + * Boris Shingarov */ /* - * Copyright (c) 1990, 1993 - * The Regents of the University of California. All rights reserved. + * Copyright (c) 1990, 1993 The Regents of the University of California + * All rights reserved * * This software was developed by the Computer Systems Engineering group * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and @@ -38,8 +52,8 @@ * * All advertising materials mentioning features or use of this software * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Lawrence Berkeley Laboratories. + * This product includes software developed by the University of + * California, Lawrence Berkeley Laboratories. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -51,8 +65,8 @@ * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. + * This product includes software developed by the University of + * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. @@ -69,7 +83,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * @(#)kgdb_stub.c 8.4 (Berkeley) 1/12/94 + * @(#)kgdb_stub.c 8.4 (Berkeley) 1/12/94 */ /*- @@ -89,8 +103,8 @@ * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: - * This product includes software developed by the NetBSD - * Foundation, Inc. and its contributors. + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. @@ -116,521 +130,483 @@ * "Stub" to allow remote cpu to debug over a serial line using gdb. */ +#include "base/remote_gdb.hh" + #include +#include +#include +#include +#include +#include #include -#include #include "arch/vtophys.hh" #include "base/intmath.hh" -#include "base/kgdb.h" -#include "base/remote_gdb.hh" #include "base/socket.hh" #include "base/trace.hh" -#include "config/full_system.hh" -#include "cpu/thread_context.hh" +#include "config/the_isa.hh" +#include "cpu/base.hh" #include "cpu/static_inst.hh" -#include "mem/physical.hh" +#include "cpu/thread_context.hh" +#include "debug/GDBAll.hh" +#include "mem/fs_translating_port_proxy.hh" #include "mem/port.hh" +#include "mem/se_translating_port_proxy.hh" +#include "sim/full_system.hh" #include "sim/system.hh" using namespace std; using namespace TheISA; -#ifndef NDEBUG -vector debuggers; -int current_debugger = -1; +static const char GDBStart = '$'; +static const char GDBEnd = '#'; +static const char GDBGoodP = '+'; +static const char GDBBadP = '-'; -void -debugger() +vector debuggers; + +class HardBreakpoint : public PCEvent { - if (current_debugger >= 0 && current_debugger < debuggers.size()) { - RemoteGDB *gdb = debuggers[current_debugger]; - if (!gdb->isattached()) - gdb->listener->accept(); - if (gdb->isattached()) - gdb->trap(ALPHA_KENTRY_IF); + private: + BaseRemoteGDB *gdb; + + public: + int refcount; + + public: + HardBreakpoint(BaseRemoteGDB *_gdb, PCEventScope *s, Addr pc) + : PCEvent(s, "HardBreakpoint Event", pc), + gdb(_gdb), refcount(0) + { + DPRINTF(GDBMisc, "creating hardware breakpoint at %#x\n", evpc); } -} -#endif -/////////////////////////////////////////////////////////// -// -// -// + const std::string name() const override { return gdb->name() + ".hwbkpt"; } -GDBListener::Event::Event(GDBListener *l, int fd, int e) - : PollEvent(fd, e), listener(l) -{} + void + process(ThreadContext *tc) override + { + DPRINTF(GDBMisc, "handling hardware breakpoint at %#x\n", pc()); -void -GDBListener::Event::process(int revent) + if (tc == gdb->tc) + gdb->trap(SIGTRAP); + } +}; + +namespace { + +// Exception to throw when the connection to the client is broken. +struct BadClient { - listener->accept(); -} + const char *warning; + BadClient(const char *_warning=NULL) : warning(_warning) + {} +}; -GDBListener::GDBListener(RemoteGDB *g, int p) - : event(NULL), gdb(g), port(p) +// Exception to throw when an error needs to be reported to the client. +struct CmdError { - assert(!gdb->listener); - gdb->listener = this; -} + string error; + CmdError(std::string _error) : error(_error) + {} +}; + +// Exception to throw when something isn't supported. +class Unsupported {}; -GDBListener::~GDBListener() +// Convert a hex digit into an integer. +// This returns -1 if the argument passed is no valid hex digit. +int +digit2i(char c) { - if (event) - delete event; + 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); } -string -GDBListener::name() +// Convert the low 4 bits of an integer into an hex digit. +char +i2digit(int n) { - return gdb->name() + ".listener"; + return ("0123456789abcdef"[n & 0x0f]); } +// Convert a byte array into an hex string. void -GDBListener::listen() +mem2hex(char *vdst, const char *vsrc, int len) { - while (!listener.listen(port, true)) { - DPRINTF(GDBMisc, "Can't bind port %d\n", port); - port++; - } + char *dst = vdst; + const char *src = vsrc; - event = new Event(this, listener.getfd(), POLLIN); - pollQueue.schedule(event); + while (len--) { + *dst++ = i2digit(*src >> 4); + *dst++ = i2digit(*src++); + } + *dst = '\0'; +} -#ifndef NDEBUG - gdb->number = debuggers.size(); - debuggers.push_back(gdb); -#endif +// 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; -#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 + 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; } -void -GDBListener::accept() +// 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) { - if (!listener.islistening()) - panic("GDBListener::accept(): cannot accept if we're not listening!"); - - int sfd = listener.accept(true); + const char *src = *srcp; + Addr r = 0; + int nibble; - if (sfd != -1) { - if (gdb->isattached()) - close(sfd); - else - gdb->attach(sfd); + while ((nibble = digit2i(*src)) >= 0) { + r *= 16; + r += nibble; + src++; } + *srcp = src; + return r; } -/////////////////////////////////////////////////////////// -// -// -// -int digit2i(char); -char i2digit(int); -void mem2hex(void *, const void *, int); -const char *hex2mem(void *, const char *, int); -Addr hex2i(const char **); - -RemoteGDB::Event::Event(RemoteGDB *g, int fd, int e) - : PollEvent(fd, e), gdb(g) -{} +enum GdbBreakpointType { + GdbSoftBp = '0', + GdbHardBp = '1', + GdbWriteWp = '2', + GdbReadWp = '3', + GdbAccWp = '4', +}; -void -RemoteGDB::Event::process(int revent) +#ifndef NDEBUG +const char * +break_type(char c) { - if (revent & POLLIN) - gdb->trap(ALPHA_KENTRY_IF); - else if (revent & POLLNVAL) - gdb->detach(); + 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 hardBreakMap; + } -RemoteGDB::RemoteGDB(System *_system, ThreadContext *c) - : event(NULL), listener(NULL), number(-1), fd(-1), - active(false), attached(false), - system(_system), pmem(_system->physmem), context(c) +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) { - memset(gdbregs, 0, sizeof(gdbregs)); + debuggers.push_back(this); } -RemoteGDB::~RemoteGDB() +BaseRemoteGDB::~BaseRemoteGDB() { - if (event) - delete event; + delete connectEvent; + delete dataEvent; } string -RemoteGDB::name() +BaseRemoteGDB::name() { - return system->name() + ".remote_gdb"; + return sys->name() + ".remote_gdb"; } -bool -RemoteGDB::isattached() -{ return attached; } - void -RemoteGDB::attach(int f) +BaseRemoteGDB::listen() { - fd = f; + if (ListenSocket::allDisabled()) { + warn_once("Sockets disabled, not accepting gdb connections"); + return; + } - event = new Event(this, fd, POLLIN); - pollQueue.schedule(event); + while (!listener.listen(_port, true)) { + DPRINTF(GDBMisc, "Can't bind port %d\n", _port); + _port++; + } - attached = true; - DPRINTFN("remote gdb attached\n"); + connectEvent = new ConnectEvent(this, listener.getfd(), POLLIN); + pollQueue.schedule(connectEvent); + + ccprintf(cerr, "%d: %s: listening for remote gdb on port %d\n", + curTick(), name(), _port); } void -RemoteGDB::detach() +BaseRemoteGDB::connect() { - attached = false; - close(fd); - fd = -1; + panic_if(!listener.islistening(), + "Cannot accept GDB connections if we're not listening!"); - pollQueue.remove(event); - DPRINTFN("remote gdb detached\n"); -} + int sfd = listener.accept(true); -const char * -gdb_command(char cmd) -{ - switch (cmd) { - case KGDB_SIGNAL: return "KGDB_SIGNAL"; - case KGDB_SET_BAUD: return "KGDB_SET_BAUD"; - case KGDB_SET_BREAK: return "KGDB_SET_BREAK"; - case KGDB_CONT: return "KGDB_CONT"; - case KGDB_ASYNC_CONT: return "KGDB_ASYNC_CONT"; - case KGDB_DEBUG: return "KGDB_DEBUG"; - case KGDB_DETACH: return "KGDB_DETACH"; - case KGDB_REG_R: return "KGDB_REG_R"; - case KGDB_REG_W: return "KGDB_REG_W"; - case KGDB_SET_THREAD: return "KGDB_SET_THREAD"; - case KGDB_CYCLE_STEP: return "KGDB_CYCLE_STEP"; - case KGDB_SIG_CYCLE_STEP: return "KGDB_SIG_CYCLE_STEP"; - case KGDB_KILL: return "KGDB_KILL"; - case KGDB_MEM_W: return "KGDB_MEM_W"; - case KGDB_MEM_R: return "KGDB_MEM_R"; - case KGDB_SET_REG: return "KGDB_SET_REG"; - case KGDB_READ_REG: return "KGDB_READ_REG"; - case KGDB_QUERY_VAR: return "KGDB_QUERY_VAR"; - case KGDB_SET_VAR: return "KGDB_SET_VAR"; - case KGDB_RESET: return "KGDB_RESET"; - case KGDB_STEP: return "KGDB_STEP"; - case KGDB_ASYNC_STEP: return "KGDB_ASYNC_STEP"; - case KGDB_THREAD_ALIVE: return "KGDB_THREAD_ALIVE"; - case KGDB_TARGET_EXIT: return "KGDB_TARGET_EXIT"; - case KGDB_BINARY_DLOAD: return "KGDB_BINARY_DLOAD"; - case KGDB_CLR_HW_BKPT: return "KGDB_CLR_HW_BKPT"; - case KGDB_SET_HW_BKPT: return "KGDB_SET_HW_BKPT"; - case KGDB_START: return "KGDB_START"; - case KGDB_END: return "KGDB_END"; - case KGDB_GOODP: return "KGDB_GOODP"; - case KGDB_BADP: return "KGDB_BADP"; - default: return "KGDB_UNKNOWN"; + if (sfd != -1) { + if (isAttached()) + close(sfd); + else + attach(sfd); } } -/////////////////////////////////////////////////////////// -// RemoteGDB::acc -// -// Determine if the mapping at va..(va+len) is valid. -// -bool -RemoteGDB::acc(Addr va, size_t len) -{ - Addr last_va; - - va = TheISA::TruncPage(va); - last_va = TheISA::RoundPage(va + len); - - do { - if (TheISA::IsK0Seg(va)) { - if (va < (TheISA::K0SegBase + pmem->size())) { - DPRINTF(GDBAcc, "acc: Mapping is valid K0SEG <= " - "%#x < K0SEG + size\n", va); - return true; - } else { - DPRINTF(GDBAcc, "acc: Mapping invalid %#x > K0SEG + size\n", - va); - return false; - } - } - - /** - * This code says that all accesses to palcode (instruction and data) - * are valid since there isn't a va->pa mapping because palcode is - * accessed physically. At some point this should probably be cleaned up - * but there is no easy way to do it. - */ - - if (AlphaISA::PcPAL(va) || va < 0x10000) - return true; - - Addr ptbr = context->readMiscReg(AlphaISA::IPR_PALtemp20); - TheISA::PageTableEntry pte = TheISA::kernel_pte_lookup(context->getPhysPort(), ptbr, va); - if (!pte.valid()) { - DPRINTF(GDBAcc, "acc: %#x pte is invalid\n", va); - return false; - } - va += TheISA::PageBytes; - } while (va < last_va); - - DPRINTF(GDBAcc, "acc: %#x mapping is valid\n", va); - return true; -} - -/////////////////////////////////////////////////////////// -// RemoteGDB::signal -// -// Translate a trap number into a Unix-compatible signal number. -// (GDB only understands Unix signal numbers.) -// int -RemoteGDB::signal(int type) +BaseRemoteGDB::port() const { - switch (type) { - case ALPHA_KENTRY_INT: - return (SIGTRAP); - - case ALPHA_KENTRY_UNA: - return (SIGBUS); - - case ALPHA_KENTRY_ARITH: - return (SIGFPE); - - case ALPHA_KENTRY_IF: - return (SIGILL); - - case ALPHA_KENTRY_MM: - return (SIGSEGV); - - default: - panic("unknown signal type"); - return 0; - } + panic_if(!listener.islistening(), + "Remote GDB port is unknown until listen() has been called.\n"); + return _port; } -/////////////////////////////////////////////////////////// -// RemoteGDB::getregs -// -// Translate the kernel debugger register format into -// the GDB register format. void -RemoteGDB::getregs() +BaseRemoteGDB::attach(int f) { - memset(gdbregs, 0, sizeof(gdbregs)); + fd = f; - gdbregs[KGDB_REG_PC] = context->readPC(); + dataEvent = new DataEvent(this, fd, POLLIN); + pollQueue.schedule(dataEvent); - // @todo: Currently this is very Alpha specific. - if (AlphaISA::PcPAL(gdbregs[KGDB_REG_PC])) { - for (int i = 0; i < TheISA::NumIntArchRegs; ++i) { - gdbregs[i] = context->readIntReg(AlphaISA::reg_redir[i]); - } - } else { - for (int i = 0; i < TheISA::NumIntArchRegs; ++i) { - gdbregs[i] = context->readIntReg(i); - } - } - -#ifdef KGDB_FP_REGS - for (int i = 0; i < TheISA::NumFloatArchRegs; ++i) { - gdbregs[i + KGDB_REG_F0] = context->readFloatRegBits(i); - } -#endif + attached = true; + DPRINTFN("remote gdb attached\n"); } -/////////////////////////////////////////////////////////// -// RemoteGDB::setregs -// -// Translate the GDB register format into the kernel -// debugger register format. -// void -RemoteGDB::setregs() +BaseRemoteGDB::detach() { - // @todo: Currently this is very Alpha specific. - if (AlphaISA::PcPAL(gdbregs[KGDB_REG_PC])) { - for (int i = 0; i < TheISA::NumIntArchRegs; ++i) { - context->setIntReg(AlphaISA::reg_redir[i], gdbregs[i]); - } - } else { - for (int i = 0; i < TheISA::NumIntArchRegs; ++i) { - context->setIntReg(i, gdbregs[i]); - } - } + attached = false; + active = false; + clearSingleStep(); + close(fd); + fd = -1; -#ifdef KGDB_FP_REGS - for (int i = 0; i < TheISA::NumFloatArchRegs; ++i) { - context->setFloatRegBits(i, gdbregs[i + KGDB_REG_F0]); - } -#endif - context->setPC(gdbregs[KGDB_REG_PC]); + pollQueue.remove(dataEvent); + DPRINTFN("remote gdb detached\n"); } -void -RemoteGDB::setTempBreakpoint(TempBreakpoint &bkpt, Addr addr) +// 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) { - DPRINTF(GDBMisc, "setTempBreakpoint: addr=%#x\n", addr); - bkpt.address = addr; - insertHardBreak(addr, 4); -} + if (!attached) + return false; -void -RemoteGDB::clearTempBreakpoint(TempBreakpoint &bkpt) -{ - DPRINTF(GDBMisc, "setTempBreakpoint: addr=%#x\n", - bkpt.address); + DPRINTF(GDBMisc, "trap: PC=%s\n", tc->pcState()); + clearSingleStep(); - removeHardBreak(bkpt.address, 4); - bkpt.address = 0; -} + /* + * 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()); + } -void -RemoteGDB::clearSingleStep() -{ - DPRINTF(GDBMisc, "clearSingleStep bt_addr=%#x nt_addr=%#x\n", - takenBkpt.address, notTakenBkpt.address); + // Stick frame regs into our reg cache. + regCachePtr = gdbRegs(); + regCachePtr->getRegs(tc); + + GdbCommand::Context cmdCtx; + cmdCtx.type = type; + std::vector data; + + for (;;) { + try { + recv(data); + if (data.size() == 1) + throw BadClient(); + cmdCtx.cmd_byte = data[0]; + cmdCtx.data = data.data() + 1; + // One for sentinel, one for cmd_byte. + cmdCtx.len = data.size() - 2; + + 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; - if (takenBkpt.address != 0) - clearTempBreakpoint(takenBkpt); + } 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."); + } + } - if (notTakenBkpt.address != 0) - clearTempBreakpoint(notTakenBkpt); + return true; } void -RemoteGDB::setSingleStep() +BaseRemoteGDB::incomingData(int revent) { - Addr pc = context->readPC(); - Addr npc, bpc; - bool set_bt = false; - - npc = pc + sizeof(MachInst); - - // User was stopped at pc, e.g. the instruction at pc was not - // executed. - MachInst inst = read(pc); - StaticInstPtr si(inst); - if (si->hasBranchTarget(pc, context, bpc)) { - // Don't bother setting a breakpoint on the taken branch if it - // is the same as the next pc - if (bpc != npc) - set_bt = true; + if (trapEvent.scheduled()) { + warn("GDB trap event has already been scheduled!"); + return; } - DPRINTF(GDBMisc, "setSingleStep bt_addr=%#x nt_addr=%#x\n", - takenBkpt.address, notTakenBkpt.address); - - setTempBreakpoint(notTakenBkpt, npc); - - if (set_bt) - setTempBreakpoint(takenBkpt, bpc); + if (revent & POLLIN) { + trapEvent.type(SIGILL); + scheduleInstCommitEvent(&trapEvent, 0); + } else if (revent & POLLNVAL) { + descheduleInstCommitEvent(&trapEvent); + detach(); + } } -///////////////////////// -// -// - uint8_t -RemoteGDB::getbyte() +BaseRemoteGDB::getbyte() { uint8_t b; - ::read(fd, &b, 1); - return b; -} + if (::read(fd, &b, sizeof(b)) == sizeof(b)) + return b; -void -RemoteGDB::putbyte(uint8_t b) -{ - ::write(fd, &b, 1); + throw BadClient("Couldn't read data from debugger."); } -// Send a packet to gdb void -RemoteGDB::send(const char *bp) +BaseRemoteGDB::putbyte(uint8_t b) { - const char *p; - uint8_t csum, c; - - DPRINTF(GDBSend, "send: %s\n", bp); + if (::write(fd, &b, sizeof(b)) == sizeof(b)) + return; - do { - p = bp; - putbyte(KGDB_START); - for (csum = 0; (c = *p); p++) { - putbyte(c); - csum += c; - } - putbyte(KGDB_END); - putbyte(i2digit(csum >> 4)); - putbyte(i2digit(csum)); - } while ((c = getbyte() & 0x7f) == KGDB_BADP); + throw BadClient("Couldn't write data to the debugger."); } // Receive a packet from gdb -int -RemoteGDB::recv(char *bp, int maxlen) +void +BaseRemoteGDB::recv(std::vector& bp) { - char *p; - int c, csum; - int len; + uint8_t c; + int csum; + bp.resize(0); do { - p = bp; - csum = len = 0; - while ((c = getbyte()) != KGDB_START) - ; - - while ((c = getbyte()) != KGDB_END && len < maxlen) { + csum = 0; + // Find the beginning of a packet + while ((c = getbyte()) != GDBStart); + + // Read until you find the end of the data in the packet, and keep + // track of the check sum. + while (true) { + c = getbyte(); + if (c == GDBEnd) + break; c &= 0x7f; csum += c; - *p++ = c; - len++; + bp.push_back(c); } - csum &= 0xff; - *p = '\0'; - if (len >= maxlen) { - putbyte(KGDB_BADP); - continue; - } + // Mask the check sum. + csum &= 0xff; + // Bring in the checksum. If the check sum matches, csum will be 0. csum -= digit2i(getbyte()) * 16; csum -= digit2i(getbyte()); + // If the check sum was correct if (csum == 0) { - putbyte(KGDB_GOODP); + // Report that the packet was received correctly + putbyte(GDBGoodP); // Sequence present? - if (bp[2] == ':') { + if (bp.size() > 2 && bp[2] == ':') { putbyte(bp[0]); putbyte(bp[1]); - len -= 3; - bcopy(bp + 3, bp, len); + auto begin = std::begin(bp); + bp.erase(begin, std::next(begin, 3)); } break; } - putbyte(KGDB_BADP); + // Otherwise, report that there was a mistake. + putbyte(GDBBadP); } while (1); + // Sentinel. + bp.push_back('\0'); + DPRINTF(GDBRecv, "recv: %s\n", bp.data()); +} - DPRINTF(GDBRecv, "recv: %s: %s\n", gdb_command(*bp), bp); +// Send a packet to gdb +void +BaseRemoteGDB::send(const char *bp) +{ + const char *p; + uint8_t csum, c; + + DPRINTF(GDBSend, "send: %s\n", bp); - return (len); + 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 -RemoteGDB::read(Addr vaddr, size_t size, char *data) +BaseRemoteGDB::read(Addr vaddr, size_t size, char *data) { static Addr lastaddr = 0; static size_t lastsize = 0; @@ -642,9 +618,8 @@ RemoteGDB::read(Addr vaddr, size_t size, char *data) DPRINTF(GDBRead, "read: addr=%#x, size=%d", vaddr, size); - VirtualPort *vp = context->getVirtPort(context); - vp->readBlob(vaddr, (uint8_t*)data, size); - context->delVirtPort(vp); + PortProxy &proxy = tc->getVirtProxy(); + proxy.readBlob(vaddr, data, size); #if TRACING_ON if (DTRACE(GDBRead)) { @@ -662,7 +637,7 @@ RemoteGDB::read(Addr vaddr, size_t size, char *data) // Write bytes to kernel address space for debugger. bool -RemoteGDB::write(Addr vaddr, size_t size, const char *data) +BaseRemoteGDB::write(Addr vaddr, size_t size, const char *data) { static Addr lastaddr = 0; static size_t lastsize = 0; @@ -681,500 +656,478 @@ RemoteGDB::write(Addr vaddr, size_t size, const char *data) } else DPRINTFNR("\n"); } - VirtualPort *vp = context->getVirtPort(context); - vp->writeBlob(vaddr, (uint8_t*)data, size); - context->delVirtPort(vp); - -#ifdef IMB - alpha_pal_imb(); -#endif + PortProxy &proxy = tc->getVirtProxy(); + proxy.writeBlob(vaddr, data, size); return true; } - -PCEventQueue *RemoteGDB::getPcEventQueue() +void +BaseRemoteGDB::singleStep() { - return &system->pcEventQueue; + if (!singleStepEvent.scheduled()) + scheduleInstCommitEvent(&singleStepEvent, 1); + trap(SIGTRAP); } - -RemoteGDB::HardBreakpoint::HardBreakpoint(RemoteGDB *_gdb, Addr pc) - : PCEvent(_gdb->getPcEventQueue(), "HardBreakpoint Event", pc), - gdb(_gdb), refcount(0) +void +BaseRemoteGDB::clearSingleStep() { - DPRINTF(GDBMisc, "creating hardware breakpoint at %#x\n", evpc); + descheduleInstCommitEvent(&singleStepEvent); } void -RemoteGDB::HardBreakpoint::process(ThreadContext *tc) +BaseRemoteGDB::setSingleStep() { - DPRINTF(GDBMisc, "handling hardware breakpoint at %#x\n", pc()); - - if (tc == gdb->context) - gdb->trap(ALPHA_KENTRY_INT); + if (!singleStepEvent.scheduled()) + scheduleInstCommitEvent(&singleStepEvent, 1); } -bool -RemoteGDB::insertSoftBreak(Addr addr, size_t len) +void +BaseRemoteGDB::insertSoftBreak(Addr addr, size_t len) { - if (len != sizeof(MachInst)) - panic("invalid length\n"); + if (!checkBpLen(len)) + throw BadClient("Invalid breakpoint length\n"); return insertHardBreak(addr, len); } -bool -RemoteGDB::removeSoftBreak(Addr addr, size_t len) +void +BaseRemoteGDB::removeSoftBreak(Addr addr, size_t len) { - if (len != sizeof(MachInst)) - panic("invalid length\n"); + if (!checkBpLen(len)) + throw BadClient("Invalid breakpoint length.\n"); return removeHardBreak(addr, len); } -bool -RemoteGDB::insertHardBreak(Addr addr, size_t len) +void +BaseRemoteGDB::insertHardBreak(Addr addr, size_t len) { - if (len != sizeof(MachInst)) - panic("invalid length\n"); + if (!checkBpLen(len)) + throw BadClient("Invalid breakpoint length\n"); - DPRINTF(GDBMisc, "inserting hardware breakpoint at %#x\n", addr); + DPRINTF(GDBMisc, "Inserting hardware breakpoint at %#x\n", addr); HardBreakpoint *&bkpt = hardBreakMap[addr]; if (bkpt == 0) - bkpt = new HardBreakpoint(this, addr); + bkpt = new HardBreakpoint(this, sys, addr); bkpt->refcount++; - - return true; } -bool -RemoteGDB::removeHardBreak(Addr addr, size_t len) +void +BaseRemoteGDB::removeHardBreak(Addr addr, size_t len) { - if (len != sizeof(MachInst)) - panic("invalid length\n"); + if (!checkBpLen(len)) + throw BadClient("Invalid breakpoint length\n"); - DPRINTF(GDBMisc, "removing hardware breakpoint at %#x\n", addr); + 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()) - return false; + throw CmdError("E0C"); HardBreakpoint *hbp = (*i).second; if (--hbp->refcount == 0) { delete hbp; hardBreakMap.erase(i); } - - return true; } -const char * -break_type(char c) +void +BaseRemoteGDB::clearTempBreakpoint(Addr &bkpt) { - switch(c) { - case '0': return "software breakpoint"; - case '1': return "hardware breakpoint"; - case '2': return "write watchpoint"; - case '3': return "read watchpoint"; - case '4': return "access watchpoint"; - default: return "unknown breakpoint/watchpoint"; - } + DPRINTF(GDBMisc, "setTempBreakpoint: addr=%#x\n", bkpt); + removeHardBreak(bkpt, sizeof(TheISA::MachInst)); + bkpt = 0; } -// 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 -RemoteGDB::trap(int type) +void +BaseRemoteGDB::setTempBreakpoint(Addr bkpt) { - uint64_t val; - size_t datalen, len; - char data[KGDB_BUFLEN + 1]; - char buffer[sizeof(gdbregs) * 2 + 256]; - char temp[KGDB_BUFLEN]; - const char *p; - char command, subcmd; - string var; - bool ret; - - if (!attached) - return false; - - DPRINTF(GDBMisc, "trap: PC=%#x NPC=%#x\n", - context->readPC(), context->readNextPC()); - - 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. - snprintf((char *)buffer, sizeof(buffer), "S%02x", signal(type)); - send(buffer); - - // Stick frame regs into our reg cache. - getregs(); - - for (;;) { - datalen = recv(data, sizeof(data)); - data[sizeof(data) - 1] = 0; // Sentinel - command = data[0]; - subcmd = 0; - p = data + 1; - switch (command) { - - case KGDB_SIGNAL: - // if this command came from a running gdb, answer it -- - // the other guy has no way of knowing if we're in or out - // of this loop when he issues a "remote-signal". - snprintf((char *)buffer, sizeof(buffer), "S%02x", signal(type)); - send(buffer); - continue; - - case KGDB_REG_R: - if (2 * sizeof(gdbregs) > sizeof(buffer)) - panic("buffer too small"); - - mem2hex(buffer, gdbregs, sizeof(gdbregs)); - send(buffer); - continue; - - case KGDB_REG_W: - p = hex2mem(gdbregs, p, sizeof(gdbregs)); - if (p == NULL || *p != '\0') - send("E01"); - else { - setregs(); - send("OK"); - } - continue; - -#if 0 - case KGDB_SET_REG: - val = hex2i(&p); - if (*p++ != '=') { - send("E01"); - continue; - } - if (val < 0 && val >= KGDB_NUMREGS) { - send("E01"); - continue; - } - - gdbregs[val] = hex2i(&p); - setregs(); - send("OK"); - - continue; -#endif - - case KGDB_MEM_R: - val = hex2i(&p); - if (*p++ != ',') { - send("E02"); - continue; - } - len = hex2i(&p); - if (*p != '\0') { - send("E03"); - continue; - } - if (len > sizeof(buffer)) { - send("E04"); - continue; - } - if (!acc(val, len)) { - send("E05"); - continue; - } - - if (read(val, (size_t)len, (char *)buffer)) { - mem2hex(temp, buffer, len); - send(temp); - } else { - send("E05"); - } - continue; + DPRINTF(GDBMisc, "setTempBreakpoint: addr=%#x\n", bkpt); + insertHardBreak(bkpt, sizeof(TheISA::MachInst)); +} - case KGDB_MEM_W: - val = hex2i(&p); - if (*p++ != ',') { - send("E06"); - continue; - } - len = hex2i(&p); - if (*p++ != ':') { - send("E07"); - continue; - } - if (len > datalen - (p - data)) { - send("E08"); - continue; - } - p = hex2mem(buffer, p, sizeof(buffer)); - if (p == NULL) { - send("E09"); - continue; - } - if (!acc(val, len)) { - send("E0A"); - continue; - } - if (write(val, (size_t)len, (char *)buffer)) - send("OK"); - else - send("E0B"); - continue; - - case KGDB_SET_THREAD: - subcmd = *p++; - val = hex2i(&p); - if (val == 0) - send("OK"); - else - send("E01"); - continue; - - case KGDB_DETACH: - case KGDB_KILL: - active = false; - clearSingleStep(); - detach(); - goto out; - - case KGDB_ASYNC_CONT: - subcmd = hex2i(&p); - if (*p++ == ';') { - val = hex2i(&p); - context->setPC(val); - context->setNextPC(val + sizeof(MachInst)); - } - clearSingleStep(); - goto out; - - case KGDB_CONT: - if (p - data < datalen) { - val = hex2i(&p); - context->setPC(val); - context->setNextPC(val + sizeof(MachInst)); - } - clearSingleStep(); - goto out; - - case KGDB_ASYNC_STEP: - subcmd = hex2i(&p); - if (*p++ == ';') { - val = hex2i(&p); - context->setPC(val); - context->setNextPC(val + sizeof(MachInst)); - } - setSingleStep(); - goto out; - - case KGDB_STEP: - if (p - data < datalen) { - val = hex2i(&p); - context->setPC(val); - context->setNextPC(val + sizeof(MachInst)); - } - setSingleStep(); - goto out; +void +BaseRemoteGDB::scheduleInstCommitEvent(Event *ev, int delta) +{ + // Here "ticks" aren't simulator ticks which measure time, they're + // instructions committed by the CPU. + tc->scheduleInstCountEvent(ev, tc->getCurrentInstCount() + delta); +} - case KGDB_CLR_HW_BKPT: - subcmd = *p++; - if (*p++ != ',') send("E0D"); - val = hex2i(&p); - if (*p++ != ',') send("E0D"); - len = hex2i(&p); +void +BaseRemoteGDB::descheduleInstCommitEvent(Event *ev) +{ + if (ev->scheduled()) + tc->descheduleInstCountEvent(ev); +} - DPRINTF(GDBMisc, "clear %s, addr=%#x, len=%d\n", - break_type(subcmd), val, len); +std::map BaseRemoteGDB::command_map = { + // last signal + { '?', { "KGDB_SIGNAL", &BaseRemoteGDB::cmd_signal } }, + // set baud (deprecated) + { 'b', { "KGDB_SET_BAUD", &BaseRemoteGDB::cmd_unsupported } }, + // set breakpoint (deprecated) + { 'B', { "KGDB_SET_BREAK", &BaseRemoteGDB::cmd_unsupported } }, + // resume + { 'c', { "KGDB_CONT", &BaseRemoteGDB::cmd_cont } }, + // continue with signal + { 'C', { "KGDB_ASYNC_CONT", &BaseRemoteGDB::cmd_async_cont } }, + // toggle debug flags (deprecated) + { 'd', { "KGDB_DEBUG", &BaseRemoteGDB::cmd_unsupported } }, + // detach remote gdb + { 'D', { "KGDB_DETACH", &BaseRemoteGDB::cmd_detach } }, + // read general registers + { 'g', { "KGDB_REG_R", &BaseRemoteGDB::cmd_reg_r } }, + // write general registers + { 'G', { "KGDB_REG_W", &BaseRemoteGDB::cmd_reg_w } }, + // set thread + { 'H', { "KGDB_SET_THREAD", &BaseRemoteGDB::cmd_set_thread } }, + // step a single cycle + { 'i', { "KGDB_CYCLE_STEP", &BaseRemoteGDB::cmd_unsupported } }, + // signal then cycle step + { 'I', { "KGDB_SIG_CYCLE_STEP", &BaseRemoteGDB::cmd_unsupported } }, + // kill program + { 'k', { "KGDB_KILL", &BaseRemoteGDB::cmd_detach } }, + // read memory + { 'm', { "KGDB_MEM_R", &BaseRemoteGDB::cmd_mem_r } }, + // write memory + { 'M', { "KGDB_MEM_W", &BaseRemoteGDB::cmd_mem_w } }, + // read register + { 'p', { "KGDB_READ_REG", &BaseRemoteGDB::cmd_unsupported } }, + // write register + { 'P', { "KGDB_SET_REG", &BaseRemoteGDB::cmd_unsupported } }, + // query variable + { 'q', { "KGDB_QUERY_VAR", &BaseRemoteGDB::cmd_query_var } }, + // set variable + { 'Q', { "KGDB_SET_VAR", &BaseRemoteGDB::cmd_unsupported } }, + // reset system (deprecated) + { 'r', { "KGDB_RESET", &BaseRemoteGDB::cmd_unsupported } }, + // step + { 's', { "KGDB_STEP", &BaseRemoteGDB::cmd_step } }, + // signal and step + { 'S', { "KGDB_ASYNC_STEP", &BaseRemoteGDB::cmd_async_step } }, + // find out if the thread is alive + { 'T', { "KGDB_THREAD_ALIVE", &BaseRemoteGDB::cmd_unsupported } }, + // target exited + { 'W', { "KGDB_TARGET_EXIT", &BaseRemoteGDB::cmd_unsupported } }, + // write memory + { 'X', { "KGDB_BINARY_DLOAD", &BaseRemoteGDB::cmd_unsupported } }, + // remove breakpoint or watchpoint + { 'z', { "KGDB_CLR_HW_BKPT", &BaseRemoteGDB::cmd_clr_hw_bkpt } }, + // insert breakpoint or watchpoint + { 'Z', { "KGDB_SET_HW_BKPT", &BaseRemoteGDB::cmd_set_hw_bkpt } }, +}; - ret = false; +bool +BaseRemoteGDB::checkBpLen(size_t len) +{ + return len == sizeof(MachInst); +} - switch (subcmd) { - case '0': // software breakpoint - ret = removeSoftBreak(val, len); - break; +bool +BaseRemoteGDB::cmd_unsupported(GdbCommand::Context &ctx) +{ + DPRINTF(GDBMisc, "Unsupported command: %s\n", ctx.cmd->name); + DDUMP(GDBMisc, ctx.data, ctx.len); + throw Unsupported(); +} - case '1': // hardware breakpoint - ret = removeHardBreak(val, len); - break; - case '2': // write watchpoint - case '3': // read watchpoint - case '4': // access watchpoint - default: // unknown - send(""); - break; - } +bool +BaseRemoteGDB::cmd_signal(GdbCommand::Context &ctx) +{ + send(csprintf("S%02x", ctx.type).c_str()); + return true; +} - send(ret ? "OK" : "E0C"); - continue; +bool +BaseRemoteGDB::cmd_cont(GdbCommand::Context &ctx) +{ + const char *p = ctx.data; + if (ctx.len) { + Addr newPc = hex2i(&p); + tc->pcState(newPc); + } + clearSingleStep(); + return false; +} - case KGDB_SET_HW_BKPT: - subcmd = *p++; - if (*p++ != ',') send("E0D"); - val = hex2i(&p); - if (*p++ != ',') send("E0D"); - len = hex2i(&p); +bool +BaseRemoteGDB::cmd_async_cont(GdbCommand::Context &ctx) +{ + const char *p = ctx.data; + hex2i(&p); + if (*p++ == ';') { + Addr newPc = hex2i(&p); + tc->pcState(newPc); + } + clearSingleStep(); + return false; +} - DPRINTF(GDBMisc, "set %s, addr=%#x, len=%d\n", - break_type(subcmd), val, len); +bool +BaseRemoteGDB::cmd_detach(GdbCommand::Context &ctx) +{ + detach(); + return false; +} - ret = false; +bool +BaseRemoteGDB::cmd_reg_r(GdbCommand::Context &ctx) +{ + char buf[2 * regCachePtr->size() + 1]; + buf[2 * regCachePtr->size()] = '\0'; + mem2hex(buf, regCachePtr->data(), regCachePtr->size()); + send(buf); + return true; +} - switch (subcmd) { - case '0': // software breakpoint - ret = insertSoftBreak(val, len); - break; +bool +BaseRemoteGDB::cmd_reg_w(GdbCommand::Context &ctx) +{ + const char *p = ctx.data; + p = hex2mem(regCachePtr->data(), p, regCachePtr->size()); + if (p == NULL || *p != '\0') + throw CmdError("E01"); - case '1': // hardware breakpoint - ret = insertHardBreak(val, len); - break; + regCachePtr->setRegs(tc); + send("OK"); - case '2': // write watchpoint - case '3': // read watchpoint - case '4': // access watchpoint - default: // unknown - send(""); - break; - } + return true; +} - send(ret ? "OK" : "E0C"); - continue; - - case KGDB_QUERY_VAR: - var = string(p, datalen - 1); - if (var == "C") - send("QC0"); - else - send(""); - continue; - - case KGDB_SET_BAUD: - case KGDB_SET_BREAK: - case KGDB_DEBUG: - case KGDB_CYCLE_STEP: - case KGDB_SIG_CYCLE_STEP: - case KGDB_READ_REG: - case KGDB_SET_VAR: - case KGDB_RESET: - case KGDB_THREAD_ALIVE: - case KGDB_TARGET_EXIT: - case KGDB_BINARY_DLOAD: - // Unsupported command - DPRINTF(GDBMisc, "Unsupported command: %s\n", - gdb_command(command)); - DDUMP(GDBMisc, (uint8_t *)data, datalen); - send(""); - continue; +bool +BaseRemoteGDB::cmd_set_thread(GdbCommand::Context &ctx) +{ + const char *p = ctx.data + 1; // Ignore the subcommand byte. + if (hex2i(&p) != 0) + throw CmdError("E01"); + send("OK"); + return true; +} - default: - // Unknown command. - DPRINTF(GDBMisc, "Unknown command: %c(%#x)\n", - command, command); - send(""); - continue; +bool +BaseRemoteGDB::cmd_mem_r(GdbCommand::Context &ctx) +{ + const char *p = ctx.data; + Addr addr = hex2i(&p); + if (*p++ != ',') + throw CmdError("E02"); + size_t len = hex2i(&p); + if (*p != '\0') + throw CmdError("E03"); + if (!acc(addr, len)) + throw CmdError("E05"); + + char buf[len]; + if (!read(addr, len, buf)) + throw CmdError("E05"); + + char temp[2 * len + 1]; + temp[2 * len] = '\0'; + mem2hex(temp, buf, len); + send(temp); + return true; +} +bool +BaseRemoteGDB::cmd_mem_w(GdbCommand::Context &ctx) +{ + const char *p = ctx.data; + Addr addr = hex2i(&p); + if (*p++ != ',') + throw CmdError("E06"); + size_t len = hex2i(&p); + if (*p++ != ':') + throw CmdError("E07"); + if (len * 2 > ctx.len - (p - ctx.data)) + throw CmdError("E08"); + char buf[len]; + p = (char *)hex2mem(buf, p, len); + if (p == NULL) + throw CmdError("E09"); + if (!acc(addr, len)) + throw CmdError("E0A"); + if (!write(addr, len, buf)) + throw CmdError("E0B"); + send("OK"); + return true; +} +bool +BaseRemoteGDB::cmd_query_var(GdbCommand::Context &ctx) +{ + string s(ctx.data, ctx.len - 1); + string xfer_read_prefix = "Xfer:features:read:"; + if (s.rfind("Supported:", 0) == 0) { + std::ostringstream oss; + // This reply field mandatory. We can receive arbitrarily + // long packets, so we could choose it to be arbitrarily large. + // This is just an arbitrary filler value that seems to work. + oss << "PacketSize=1024"; + for (const auto& feature : availableFeatures()) + oss << ';' << feature; + send(oss.str().c_str()); + } else if (s.rfind(xfer_read_prefix, 0) == 0) { + size_t offset, length; + auto value_string = s.substr(xfer_read_prefix.length()); + auto colon_pos = value_string.find(':'); + auto comma_pos = value_string.find(','); + if (colon_pos == std::string::npos || comma_pos == std::string::npos) + throw CmdError("E00"); + std::string annex; + if (!getXferFeaturesRead(value_string.substr(0, colon_pos), annex)) + throw CmdError("E00"); + try { + offset = std::stoull( + value_string.substr(colon_pos + 1, comma_pos), NULL, 16); + length = std::stoull( + value_string.substr(comma_pos + 1), NULL, 16); + } catch (std::invalid_argument& e) { + throw CmdError("E00"); + } catch (std::out_of_range& e) { + throw CmdError("E00"); } + std::string encoded; + encodeXferResponse(annex, encoded, offset, length); + send(encoded.c_str()); + } else if (s == "C") { + send("QC0"); + } else { + throw Unsupported(); } - - out: return true; } -// Convert a hex digit into an integer. -// This returns -1 if the argument passed is no valid hex digit. -int -digit2i(char c) +std::vector +BaseRemoteGDB::availableFeatures() const { - 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 {}; +}; - return (c - 'A' + 10); - else - return (-1); +bool +BaseRemoteGDB::getXferFeaturesRead( + const std::string &annex, std::string &output) +{ + return false; } -// Convert the low 4 bits of an integer into an hex digit. -char -i2digit(int n) +void +BaseRemoteGDB::encodeBinaryData( + const std::string &unencoded, std::string &encoded) const { - return ("0123456789abcdef"[n & 0x0f]); + for (const char& c : unencoded) { + if (c == '$' || c == '#' || c == '}' || c == '*') { + encoded += '}'; + encoded += c ^ 0x20; + } else { + encoded += c; + } + } } -// Convert a byte array into an hex string. void -mem2hex(void *vdst, const void *vsrc, int len) +BaseRemoteGDB::encodeXferResponse(const std::string &unencoded, + std::string &encoded, size_t offset, size_t unencoded_length) const { - char *dst = (char *)vdst; - const char *src = (const char *)vsrc; + if (offset + unencoded_length < unencoded.length()) + encoded += 'm'; + else + encoded += 'l'; + encodeBinaryData(unencoded.substr(offset, unencoded_length), encoded); +} - while (len--) { - *dst++ = i2digit(*src >> 4); - *dst++ = i2digit(*src++); +bool +BaseRemoteGDB::cmd_async_step(GdbCommand::Context &ctx) +{ + const char *p = ctx.data; + hex2i(&p); // Ignore the subcommand byte. + if (*p++ == ';') { + Addr newPc = hex2i(&p); + tc->pcState(newPc); } - *dst = '\0'; + setSingleStep(); + return false; } -// 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(void *vdst, const char *src, int maxlen) +bool +BaseRemoteGDB::cmd_step(GdbCommand::Context &ctx) { - char *dst = (char *)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; + if (ctx.len) { + const char *p = ctx.data; + Addr newPc = hex2i(&p); + tc->pcState(newPc); } - return (src); + setSingleStep(); + return false; } -// 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) +bool +BaseRemoteGDB::cmd_clr_hw_bkpt(GdbCommand::Context &ctx) { - const char *src = *srcp; - Addr r = 0; - int nibble; - - while ((nibble = digit2i(*src)) >= 0) { - r *= 16; - r += nibble; - src++; + const char *p = ctx.data; + char subcmd = *p++; + if (*p++ != ',') + throw CmdError("E0D"); + Addr addr = hex2i(&p); + if (*p++ != ',') + throw CmdError("E0D"); + size_t len = hex2i(&p); + + DPRINTF(GDBMisc, "clear %s, addr=%#x, len=%d\n", + break_type(subcmd), addr, len); + + switch (subcmd) { + case GdbSoftBp: + removeSoftBreak(addr, len); + break; + case GdbHardBp: + removeHardBreak(addr, len); + break; + case GdbWriteWp: + case GdbReadWp: + case GdbAccWp: + default: // unknown + throw Unsupported(); } - *srcp = src; - return (r); + send("OK"); + + return true; } +bool +BaseRemoteGDB::cmd_set_hw_bkpt(GdbCommand::Context &ctx) +{ + const char *p = ctx.data; + char subcmd = *p++; + if (*p++ != ',') + throw CmdError("E0D"); + Addr addr = hex2i(&p); + if (*p++ != ',') + throw CmdError("E0D"); + size_t len = hex2i(&p); + + DPRINTF(GDBMisc, "set %s, addr=%#x, len=%d\n", + break_type(subcmd), addr, len); + + switch (subcmd) { + case GdbSoftBp: + insertSoftBreak(addr, len); + break; + case GdbHardBp: + insertHardBreak(addr, len); + break; + case GdbWriteWp: + case GdbReadWp: + case GdbAccWp: + default: // unknown + throw Unsupported(); + } + send("OK"); + + return true; +}