| /* |
| * 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, 2021 LabWare |
| * Copyright 2014 Google, Inc. |
| * Copyright (c) 2002-2005 The Regents of The University of Michigan |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer; |
| * redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution; |
| * neither the name of the copyright holders nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /* |
| * 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 |
| * contributed to Berkeley. |
| * |
| * 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. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * 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. |
| * 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. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * 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 |
| */ |
| |
| /*- |
| * Copyright (c) 2001 The NetBSD Foundation, Inc. |
| * All rights reserved. |
| * |
| * This code is derived from software contributed to The NetBSD Foundation |
| * by Jason R. Thorpe. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * 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. |
| * 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. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
| * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
| * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /* |
| * $NetBSD: kgdb_stub.c,v 1.8 2001/07/07 22:58:00 wdk Exp $ |
| * |
| * Taken from NetBSD |
| * |
| * "Stub" to allow remote cpu to debug over a serial line using gdb. |
| */ |
| |
| #include "base/remote_gdb.hh" |
| |
| #include <sys/select.h> |
| #include <sys/time.h> |
| #include <unistd.h> |
| |
| #include <cassert> |
| #include <csignal> |
| #include <cstdint> |
| #include <cstdio> |
| #include <iterator> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| |
| #include "base/cprintf.hh" |
| #include "base/intmath.hh" |
| #include "base/logging.hh" |
| #include "base/socket.hh" |
| #include "base/trace.hh" |
| #include "cpu/base.hh" |
| #include "cpu/static_inst.hh" |
| #include "cpu/thread_context.hh" |
| #include "debug/GDBAll.hh" |
| #include "mem/port.hh" |
| #include "mem/port_proxy.hh" |
| #include "mem/se_translating_port_proxy.hh" |
| #include "mem/translating_port_proxy.hh" |
| #include "sim/full_system.hh" |
| #include "sim/process.hh" |
| #include "sim/sim_events.hh" |
| #include "sim/system.hh" |
| |
| namespace gem5 |
| { |
| |
| static const char GDBStart = '$'; |
| static const char GDBEnd = '#'; |
| static const char GDBGoodP = '+'; |
| static const char GDBBadP = '-'; |
| |
| class HardBreakpoint : public PCEvent |
| { |
| 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); |
| } |
| |
| const std::string name() const override { return gdb->name() + ".hwbkpt"; } |
| |
| void |
| process(ThreadContext *tc) override |
| { |
| DPRINTF(GDBMisc, "handling hardware breakpoint at %#x\n", pc()); |
| |
| if (tc == gdb->tc) |
| gdb->trap(tc->contextId(), GDBSignal::TRAP,""); |
| } |
| }; |
| |
| 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 |
| { |
| std::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 a hex to a char |
| char |
| hex2c(char c0,char c1) |
| { |
| char temp[3] = {c0,c1,'\0'}; |
| return std::stoi(temp,0,16); |
| } |
| |
| //this function will be used in a future patch |
| //convert a encoded string to a string |
| std::string |
| hexS2string(std::string hex_in) |
| { |
| std::string out=""; |
| for (unsigned int i = 0; i + 1 < hex_in.length();i += 2){ |
| out.push_back(hex2c(hex_in[i],hex_in[i+1])); |
| } |
| return out; |
| } |
| |
| //convert a string to a hex encoded string |
| std::string |
| string2hexS(std::string in) |
| { |
| std::string out = ""; |
| for (auto ch : in){ |
| char temp[3] = " "; |
| std::snprintf(temp,3,"%02hhx",ch); |
| out.append(temp); |
| } |
| return out; |
| } |
| |
| // 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 max_len) |
| { |
| char *dst = vdst; |
| int msb, lsb; |
| |
| while (*src && max_len--) { |
| 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; |
| } |
| |
| bool |
| parseThreadId(const char **srcp, bool &all, bool &any, ContextID &tid) |
| { |
| all = any = false; |
| tid = 0; |
| const char *src = *srcp; |
| if (*src == '-') { |
| // This could be the start of -1, which means all threads. |
| src++; |
| if (*src++ != '1') |
| return false; |
| *srcp += 2; |
| all = true; |
| return true; |
| } |
| tid = hex2i(srcp); |
| // If *srcp still points to src, no characters were consumed and no thread |
| // id was found. Without this check, we can't tell the difference between |
| // zero and a parsing error. |
| if (*srcp == src) |
| return false; |
| |
| if (tid == 0) |
| any = true; |
| |
| tid--; |
| |
| return true; |
| } |
| |
| int |
| encodeThreadId(ContextID id) |
| { |
| // Thread ID 0 is reserved and means "pick any thread". |
| return id + 1; |
| } |
| |
| enum GdbBreakpointType |
| { |
| GdbSoftBp = '0', |
| GdbHardBp = '1', |
| GdbWriteWp = '2', |
| GdbReadWp = '3', |
| GdbAccWp = '4', |
| }; |
| |
| const char * |
| breakType(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"; |
| } |
| } |
| |
| std::map<Addr, HardBreakpoint *> hardBreakMap; |
| |
| } |
| |
| BaseRemoteGDB::BaseRemoteGDB(System *_system, |
| ListenSocketConfig _listen_config) : |
| incomingConnectionEvent(nullptr), incomingDataEvent(nullptr), |
| fd(-1), sys(_system), connectEvent(*this), disconnectEvent(*this), |
| trapEvent(this), singleStepEvent(*this) |
| { |
| listener = _listen_config.build(name()); |
| } |
| |
| BaseRemoteGDB::~BaseRemoteGDB() |
| { |
| delete incomingConnectionEvent; |
| delete incomingDataEvent; |
| } |
| |
| std::string |
| BaseRemoteGDB::name() |
| { |
| return sys->name() + ".remote_gdb"; |
| } |
| |
| void |
| BaseRemoteGDB::listen() |
| { |
| if (ListenSocket::allDisabled()) { |
| warn_once("Sockets disabled, not accepting gdb connections"); |
| return; |
| } |
| |
| listener->listen(); |
| |
| incomingConnectionEvent = |
| new IncomingConnectionEvent(this, listener->getfd(), POLLIN); |
| pollQueue.schedule(incomingConnectionEvent); |
| } |
| |
| void |
| BaseRemoteGDB::connect() |
| { |
| panic_if(!listener->islistening(), |
| "Can't accept GDB connections without any threads!"); |
| |
| pollQueue.remove(incomingConnectionEvent); |
| |
| int sfd = listener->accept(); |
| |
| if (sfd != -1) { |
| if (isAttached()) |
| close(sfd); |
| else |
| attach(sfd); |
| } |
| } |
| |
| const ListenSocket & |
| BaseRemoteGDB::hostSocket() const |
| { |
| panic_if(!listener->islistening(), |
| "Remote GDB socket is unknown until listen() has been called."); |
| return *listener; |
| } |
| |
| void |
| BaseRemoteGDB::attach(int f) |
| { |
| fd = f; |
| |
| attached = true; |
| DPRINTFN("remote gdb attached\n"); |
| |
| processCommands(); |
| |
| if (isAttached()) { |
| // At this point an initial communication with GDB is handled |
| // and we're ready to continue. Here we arrange IncomingDataEvent |
| // to get notified when GDB breaks in. |
| // |
| // However, GDB can decide to disconnect during that initial |
| // communication. In that case, we cannot arrange data event because |
| // the socket is already closed (not that it makes any sense, anyways). |
| // |
| // Hence the check above. |
| incomingDataEvent = new IncomingDataEvent(this, fd, POLLIN); |
| pollQueue.schedule(incomingDataEvent); |
| } |
| } |
| |
| void |
| BaseRemoteGDB::detach() |
| { |
| attached = false; |
| clearSingleStep(); |
| close(fd); |
| fd = -1; |
| |
| if (incomingDataEvent) { |
| // incomingDataEvent gets scheduled in attach() after |
| // initial communication with GDB is handled and GDB tells |
| // gem5 to continue. |
| // |
| // GDB can disconnect before that in which case `incomingDataEvent` |
| // is NULL. |
| // |
| // Hence the check above. |
| |
| pollQueue.remove(incomingDataEvent); |
| incomingDataEvent = nullptr; |
| } |
| pollQueue.schedule(incomingConnectionEvent); |
| DPRINTFN("remote gdb detached\n"); |
| } |
| |
| void |
| BaseRemoteGDB::addThreadContext(ThreadContext *_tc) |
| { |
| [[maybe_unused]] auto it_success = threads.insert({_tc->contextId(), _tc}); |
| assert(it_success.second); |
| // If no ThreadContext is current selected, select this one. |
| if (!tc) |
| assert(selectThreadContext(_tc->contextId())); |
| |
| // Now that we have a thread, we can start listening. |
| if (!listener->islistening()) |
| listen(); |
| } |
| |
| void |
| BaseRemoteGDB::replaceThreadContext(ThreadContext *_tc) |
| { |
| auto it = threads.find(_tc->contextId()); |
| panic_if(it == threads.end(), "No context with ID %d found.", |
| _tc->contextId()); |
| it->second = _tc; |
| } |
| |
| bool |
| BaseRemoteGDB::selectThreadContext(ContextID id) |
| { |
| auto it = threads.find(id); |
| if (it == threads.end()) |
| return false; |
| |
| tc = it->second; |
| // Update the register cache for the new thread context, if there is one. |
| if (regCachePtr) |
| regCachePtr->getRegs(tc); |
| 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. |
| void |
| BaseRemoteGDB::trap(ContextID id, GDBSignal sig,const std::string& stopReason) |
| { |
| if (!attached) |
| return; |
| |
| if (tc->contextId() != id) { |
| //prevent thread switch when single stepping |
| if (singleStepEvent.scheduled()){ |
| return; |
| } |
| DPRINTF(GDBMisc, "Finishing thread switch"); |
| if (!selectThreadContext(id)) |
| return; |
| } |
| |
| |
| DPRINTF(GDBMisc, "trap: PC=%s\n", tc->pcState()); |
| |
| clearSingleStep(); |
| if (stopReason=="monitor_return"){ |
| //should wnot send any Tpacket here |
| send("OK"); |
| }else if (threadSwitching) { |
| threadSwitching = false; |
| // Tell GDB the thread switch has completed. |
| send("OK"); |
| } else { |
| // Tell remote host that an exception has occurred. |
| sendTPacket(sig,id,stopReason); |
| } |
| |
| processCommands(sig); |
| } |
| |
| bool |
| BaseRemoteGDB::sendMessage(std::string message) |
| { |
| if (!attached) |
| return false; |
| DPRINTF(GDBMisc, "passing message %s\n", message); |
| sendOPacket(message); |
| return true; |
| } |
| |
| void |
| BaseRemoteGDB::incomingConnection(int revent) |
| { |
| if (connectEvent.scheduled()) { |
| warn("GDB connect event has already been scheduled!"); |
| return; |
| } |
| |
| if (revent & POLLIN) { |
| scheduleInstCommitEvent(&connectEvent, 0); |
| } |
| } |
| |
| void |
| BaseRemoteGDB::incomingData(int revent) |
| { |
| if (trapEvent.scheduled()) { |
| warn("GDB trap event has already been scheduled!"); |
| return; |
| } |
| |
| if (revent & POLLIN) { |
| scheduleTrapEvent(tc->contextId(),GDBSignal::ILL,0,""); |
| } else if (revent & POLLNVAL) { |
| descheduleInstCommitEvent(&trapEvent); |
| scheduleInstCommitEvent(&disconnectEvent, 0); |
| } |
| } |
| |
| uint8_t |
| BaseRemoteGDB::getbyte() |
| { |
| uint8_t b; |
| while (!try_getbyte(&b,-1));//no timeout |
| return b; |
| } |
| |
| bool |
| BaseRemoteGDB::try_getbyte(uint8_t* c,int timeout_ms) |
| { |
| if (!c) |
| panic("try_getbyte called with a null pointer as c"); |
| int res,retval; |
| //Allow read to fail if it was interrupted by a signal (EINTR). |
| errno = 0; |
| //preparing fd_sets |
| fd_set rfds; |
| FD_ZERO(&rfds); |
| FD_SET(fd, &rfds); |
| |
| //setting up a timeout if timeout_ms is positive |
| struct timeval tv;struct timeval* tv_ptr; |
| if (timeout_ms >= 0){ |
| tv.tv_sec = timeout_ms/1000; |
| tv.tv_usec = timeout_ms%1000; |
| tv_ptr = &tv; |
| }else{ |
| tv_ptr = NULL; |
| } |
| //Using select to check if the FD is ready to be read. |
| while(true){ |
| do { |
| errno = 0; |
| retval = ::select(fd + 1, &rfds, NULL, NULL, tv_ptr); |
| if (retval < 0 && errno != EINTR){//error |
| DPRINTF(GDBMisc,"getbyte failed errno=%i retval=%i\n", |
| errno,retval); |
| throw BadClient("Couldn't read data from debugger."); |
| } |
| //a EINTR error means that the select call was interrupted |
| //by another signal |
| }while (errno == EINTR); |
| if (retval == 0) |
| return false;//timed out |
| //reading (retval>0) |
| res = ::read(fd, c, sizeof(*c)); |
| if (res == sizeof(*c)) |
| return true;//read successfully |
| //read failed (?) retrying select |
| } |
| } |
| void |
| BaseRemoteGDB::putbyte(uint8_t b) |
| { |
| if (::write(fd, &b, sizeof(b)) == sizeof(b)) |
| return; |
| |
| throw BadClient("Couldn't write data to the debugger."); |
| } |
| |
| // Receive a packet from gdb |
| void |
| BaseRemoteGDB::recv(std::vector<char>& bp) |
| { |
| uint8_t c; |
| int csum; |
| bp.resize(0); |
| |
| do { |
| 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; |
| bp.push_back(c); |
| } |
| |
| // 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) { |
| // Report that the packet was received correctly |
| putbyte(GDBGoodP); |
| // Sequence present? |
| if (bp.size() > 2 && bp[2] == ':') { |
| putbyte(bp[0]); |
| putbyte(bp[1]); |
| auto begin = std::begin(bp); |
| bp.erase(begin, std::next(begin, 3)); |
| } |
| break; |
| } |
| // Otherwise, report that there was a mistake. |
| putbyte(GDBBadP); |
| } while (1); |
| // Sentinel. |
| bp.push_back('\0'); |
| DPRINTF(GDBRecv, "recv: %s\n", bp.data()); |
| } |
| |
| // Send a packet to gdb |
| void |
| BaseRemoteGDB::send(const char *bp) |
| { |
| const char *p; |
| uint8_t csum, c; |
| |
| DPRINTF(GDBSend, "send: %s\n", bp); |
| //removing GDBBadP that could be waiting in the buffer |
| while (try_getbyte(&c,0)); |
| 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(); |
| if ((c & 0x7f) == GDBBadP) |
| DPRINTF(GDBSend, "PacketError\n"); |
| } while ((c & 0x7f) == GDBBadP); |
| } |
| |
| void |
| BaseRemoteGDB::processCommands(GDBSignal sig) |
| { |
| // Stick frame regs into our reg cache. |
| regCachePtr = gdbRegs(); |
| regCachePtr->getRegs(tc); |
| |
| GdbCommand::Context cmd_ctx; |
| cmd_ctx.type = sig; |
| std::vector<char> data; |
| |
| for (;;) { |
| try { |
| recv(data); |
| if (data.size() == 1) |
| throw BadClient(); |
| cmd_ctx.cmdByte = data[0]; |
| cmd_ctx.data = data.data() + 1; |
| // One for sentinel, one for cmdByte. |
| cmd_ctx.len = data.size() - 2; |
| |
| auto cmd_it = commandMap.find(cmd_ctx.cmdByte); |
| if (cmd_it == commandMap.end()) { |
| DPRINTF(GDBMisc, "Unknown command: %c(%#x)\n", |
| cmd_ctx.cmdByte, cmd_ctx.cmdByte); |
| throw Unsupported(); |
| } |
| cmd_ctx.cmd = &(cmd_it->second); |
| |
| if (!(this->*(cmd_ctx.cmd->func))(cmd_ctx)) |
| break; |
| |
| } catch (BadClient &e) { |
| if (e.warning) |
| warn(e.warning); |
| detach(); |
| break; |
| } catch (Unsupported &e) { |
| send(""); |
| } catch (CmdError &e) { |
| send(e.error); |
| } catch (std::exception &e) { |
| panic("Unrecognized GDB exception: %s", e.what()); |
| } catch (...) { |
| panic("Unrecognized GDB exception."); |
| } |
| } |
| } |
| |
| bool |
| BaseRemoteGDB::readBlob(Addr vaddr, size_t size, char *data) |
| { |
| TranslatingPortProxy fs_proxy(tc); |
| SETranslatingPortProxy se_proxy(tc); |
| PortProxy &virt_proxy = FullSystem ? fs_proxy : se_proxy; |
| |
| virt_proxy.readBlob(vaddr, data, size); |
| return true; |
| } |
| |
| bool |
| BaseRemoteGDB::writeBlob(Addr vaddr, size_t size, const char *data) |
| { |
| TranslatingPortProxy fs_proxy(tc); |
| SETranslatingPortProxy se_proxy(tc); |
| PortProxy &virt_proxy = FullSystem ? fs_proxy : se_proxy; |
| |
| virt_proxy.writeBlob(vaddr, data, size); |
| return true; |
| } |
| |
| // Read bytes from kernel address space for debugger. |
| bool |
| BaseRemoteGDB::read(Addr vaddr, size_t size, char *data) |
| { |
| DPRINTF(GDBRead, "read: addr=%#x, size=%d", vaddr, size); |
| |
| bool res = readBlob(vaddr, size, data); |
| |
| if (!res) |
| return false; |
| |
| #if TRACING_ON |
| if (debug::GDBRead) { |
| if (debug::GDBExtra) { |
| char buf[1024]; |
| mem2hex(buf, data, size); |
| DPRINTFNR(": %s\n", buf); |
| } else |
| DPRINTFNR("\n"); |
| } |
| #endif |
| |
| return true; |
| } |
| |
| // Write bytes to kernel address space for debugger. |
| bool |
| BaseRemoteGDB::write(Addr vaddr, size_t size, const char *data) |
| { |
| if (debug::GDBWrite) { |
| DPRINTFN("write: addr=%#x, size=%d", vaddr, size); |
| if (debug::GDBExtra) { |
| char buf[1024]; |
| mem2hex(buf, data, size); |
| DPRINTFNR(": %s\n", buf); |
| } else |
| DPRINTFNR("\n"); |
| } |
| return writeBlob(vaddr, size, data); |
| } |
| |
| void |
| BaseRemoteGDB::singleStep() |
| { |
| if (!singleStepEvent.scheduled()) |
| scheduleInstCommitEvent(&singleStepEvent, 1); |
| trap(tc->contextId(), GDBSignal::TRAP); |
| } |
| |
| void |
| BaseRemoteGDB::clearSingleStep() |
| { |
| descheduleInstCommitEvent(&singleStepEvent); |
| } |
| |
| void |
| BaseRemoteGDB::setSingleStep() |
| { |
| if (!singleStepEvent.scheduled()) |
| scheduleInstCommitEvent(&singleStepEvent, 1); |
| } |
| |
| void |
| BaseRemoteGDB::insertSoftBreak(Addr addr, size_t kind) |
| { |
| if (!checkBpKind(kind)) |
| throw BadClient("Invalid breakpoint kind.\n"); |
| |
| return insertHardBreak(addr, kind); |
| } |
| |
| void |
| BaseRemoteGDB::removeSoftBreak(Addr addr, size_t kind) |
| { |
| if (!checkBpKind(kind)) |
| throw BadClient("Invalid breakpoint kind.\n"); |
| |
| return removeHardBreak(addr, kind); |
| } |
| |
| void |
| BaseRemoteGDB::insertHardBreak(Addr addr, size_t kind) |
| { |
| if (!checkBpKind(kind)) |
| throw BadClient("Invalid breakpoint kind.\n"); |
| |
| DPRINTF(GDBMisc, "Inserting hardware breakpoint at %#x\n", addr); |
| |
| HardBreakpoint *&bkpt = hardBreakMap[addr]; |
| if (bkpt == 0) |
| bkpt = new HardBreakpoint(this, sys, addr); |
| |
| bkpt->refcount++; |
| } |
| |
| void |
| BaseRemoteGDB::removeHardBreak(Addr addr, size_t kind) |
| { |
| if (!checkBpKind(kind)) |
| throw BadClient("Invalid breakpoint kind.\n"); |
| |
| DPRINTF(GDBMisc, "Removing hardware breakpoint at %#x\n", addr); |
| |
| auto i = hardBreakMap.find(addr); |
| if (i == hardBreakMap.end()) |
| throw CmdError("E0C"); |
| |
| HardBreakpoint *hbp = (*i).second; |
| if (--hbp->refcount == 0) { |
| delete hbp; |
| hardBreakMap.erase(i); |
| } |
| } |
| |
| void |
| BaseRemoteGDB::sendTPacket(GDBSignal sig, ContextID id, |
| const std::string& stopReason) |
| { |
| if (!stopReason.empty()){ |
| send("T%02xcore:%x;thread:%x;%s;", |
| (uint8_t)sig,id + 1,id + 1,stopReason); |
| }else{ |
| send("T%02xcore:%x;thread:%x;", |
| (uint8_t)sig,id + 1,id + 1); |
| } |
| } |
| void |
| BaseRemoteGDB::sendSPacket(GDBSignal sig){ |
| send("S%02x",(uint8_t)sig); |
| } |
| void |
| BaseRemoteGDB::sendOPacket(const std::string message){ |
| send("O" + string2hexS(message)); |
| } |
| |
| void |
| BaseRemoteGDB::scheduleTrapEvent(ContextID id,GDBSignal sig,int delta, |
| std::string stopReason){ |
| ThreadContext* _tc = threads[id]; |
| panic_if(_tc == nullptr, "Unknown context id :%i",id); |
| trapEvent.id(id); |
| trapEvent.type(sig); |
| trapEvent.stopReason(stopReason); |
| if (!trapEvent.scheduled()) |
| scheduleInstCommitEvent(&trapEvent,delta,_tc); |
| } |
| |
| void |
| BaseRemoteGDB::scheduleInstCommitEvent(Event *ev, int delta,ThreadContext* _tc) |
| { |
| if (delta == 0 && _tc->status() != ThreadContext::Active) { |
| // If delta is zero, we're just trying to wait for an instruction |
| // boundary. If the CPU is not active, assume we're already at a |
| // boundary without waiting for the CPU to eventually wake up. |
| ev->process(); |
| } else { |
| // Here "ticks" aren't simulator ticks which measure time, they're |
| // instructions committed by the CPU. |
| _tc->scheduleInstCountEvent(ev, _tc->getCurrentInstCount() + delta); |
| } |
| } |
| |
| void |
| BaseRemoteGDB::descheduleInstCommitEvent(Event *ev) |
| { |
| if (ev->scheduled()) |
| tc->descheduleInstCountEvent(ev); |
| } |
| |
| std::map<char, BaseRemoteGDB::GdbCommand> BaseRemoteGDB::commandMap = { |
| // last signal |
| { '?', { "KGDB_SIGNAL", &BaseRemoteGDB::cmdSignal } }, |
| // set baud (deprecated) |
| { 'b', { "KGDB_SET_BAUD", &BaseRemoteGDB::cmdUnsupported } }, |
| // set breakpoint (deprecated) |
| { 'B', { "KGDB_SET_BREAK", &BaseRemoteGDB::cmdUnsupported } }, |
| // resume |
| { 'c', { "KGDB_CONT", &BaseRemoteGDB::cmdCont } }, |
| // continue with signal |
| { 'C', { "KGDB_ASYNC_CONT", &BaseRemoteGDB::cmdAsyncCont } }, |
| // toggle debug flags (deprecated) |
| { 'd', { "KGDB_DEBUG", &BaseRemoteGDB::cmdUnsupported } }, |
| // detach remote gdb |
| { 'D', { "KGDB_DETACH", &BaseRemoteGDB::cmdDetach } }, |
| // read general registers |
| { 'g', { "KGDB_REG_R", &BaseRemoteGDB::cmdRegR } }, |
| // write general registers |
| { 'G', { "KGDB_REG_W", &BaseRemoteGDB::cmdRegW } }, |
| // set thread |
| { 'H', { "KGDB_SET_THREAD", &BaseRemoteGDB::cmdSetThread } }, |
| // step a single cycle |
| { 'i', { "KGDB_CYCLE_STEP", &BaseRemoteGDB::cmdUnsupported } }, |
| // signal then cycle step |
| { 'I', { "KGDB_SIG_CYCLE_STEP", &BaseRemoteGDB::cmdUnsupported } }, |
| // kill program |
| { 'k', { "KGDB_KILL", &BaseRemoteGDB::cmdDetach } }, |
| // read memory |
| { 'm', { "KGDB_MEM_R", &BaseRemoteGDB::cmdMemR } }, |
| // write memory |
| { 'M', { "KGDB_MEM_W", &BaseRemoteGDB::cmdMemW } }, |
| // read register |
| { 'p', { "KGDB_READ_REG", &BaseRemoteGDB::cmdUnsupported } }, |
| // write register |
| { 'P', { "KGDB_SET_REG", &BaseRemoteGDB::cmdUnsupported } }, |
| // query variable |
| { 'q', { "KGDB_QUERY_VAR", &BaseRemoteGDB::cmdQueryVar } }, |
| // set variable |
| { 'Q', { "KGDB_SET_VAR", &BaseRemoteGDB::cmdUnsupported } }, |
| // reset system (deprecated) |
| { 'r', { "KGDB_RESET", &BaseRemoteGDB::cmdUnsupported } }, |
| // step |
| { 's', { "KGDB_STEP", &BaseRemoteGDB::cmdStep } }, |
| // signal and step |
| { 'S', { "KGDB_ASYNC_STEP", &BaseRemoteGDB::cmdAsyncStep } }, |
| // find out if the thread is alive |
| { 'T', { "KGDB_THREAD_ALIVE", &BaseRemoteGDB::cmdIsThreadAlive } }, |
| //multi letter command |
| { 'v', { "KGDB_MULTI_LETTER", &BaseRemoteGDB::cmdMultiLetter } }, |
| // target exited |
| { 'W', { "KGDB_TARGET_EXIT", &BaseRemoteGDB::cmdUnsupported } }, |
| // write memory |
| { 'X', { "KGDB_BINARY_DLOAD", &BaseRemoteGDB::cmdUnsupported } }, |
| // remove breakpoint or watchpoint |
| { 'z', { "KGDB_CLR_HW_BKPT", &BaseRemoteGDB::cmdClrHwBkpt } }, |
| // insert breakpoint or watchpoint |
| { 'Z', { "KGDB_SET_HW_BKPT", &BaseRemoteGDB::cmdSetHwBkpt } }, |
| // non-standard RSP extension: dump page table |
| { '.', { "GET_PAGE_TABLE", &BaseRemoteGDB::cmdDumpPageTable } }, |
| }; |
| |
| bool |
| BaseRemoteGDB::checkBpKind(size_t kind) |
| { |
| return true; |
| } |
| |
| bool |
| BaseRemoteGDB::cmdUnsupported(GdbCommand::Context &ctx) |
| { |
| DPRINTF(GDBMisc, "Unsupported command: %s\n", ctx.cmd->name); |
| DDUMP(GDBMisc, ctx.data, ctx.len); |
| throw Unsupported(); |
| } |
| |
| |
| bool |
| BaseRemoteGDB::cmdSignal(GdbCommand::Context &ctx) |
| { |
| sendTPacket(ctx.type,tc->contextId(),""); |
| return true; |
| } |
| |
| bool |
| BaseRemoteGDB::cmdCont(GdbCommand::Context &ctx) |
| { |
| const char *p = ctx.data; |
| if (ctx.len) { |
| Addr new_pc = hex2i(&p); |
| tc->pcState(new_pc); |
| } |
| clearSingleStep(); |
| return false; |
| } |
| |
| bool |
| BaseRemoteGDB::cmdAsyncCont(GdbCommand::Context &ctx) |
| { |
| const char *p = ctx.data; |
| hex2i(&p); |
| if (*p++ == ';') { |
| Addr new_pc = hex2i(&p); |
| tc->pcState(new_pc); |
| } |
| clearSingleStep(); |
| return false; |
| } |
| |
| bool |
| BaseRemoteGDB::cmdDetach(GdbCommand::Context &ctx) |
| { |
| detach(); |
| return false; |
| } |
| |
| bool |
| BaseRemoteGDB::cmdRegR(GdbCommand::Context &ctx) |
| { |
| char buf[2 * regCachePtr->size() + 1]; |
| buf[2 * regCachePtr->size()] = '\0'; |
| mem2hex(buf, regCachePtr->data(), regCachePtr->size()); |
| send(buf); |
| return true; |
| } |
| |
| bool |
| BaseRemoteGDB::cmdRegW(GdbCommand::Context &ctx) |
| { |
| const char *p = ctx.data; |
| p = hex2mem(regCachePtr->data(), p, regCachePtr->size()); |
| if (p == NULL || *p != '\0') |
| throw CmdError("E01"); |
| |
| regCachePtr->setRegs(tc); |
| send("OK"); |
| |
| return true; |
| } |
| |
| bool |
| BaseRemoteGDB::cmdSetThread(GdbCommand::Context &ctx) |
| { |
| const char *p = ctx.data; |
| char subcommand = *p++; |
| int tid = 0; |
| bool all, any; |
| if (!parseThreadId(&p, all, any, tid)) |
| throw CmdError("E01"); |
| |
| if (subcommand == 'c') { |
| // We can only single step or continue all threads at once, since we |
| // stop time itself and not individual threads. |
| if (!all) |
| throw CmdError("E02"); |
| } else if (subcommand == 'g') { |
| // We don't currently support reading registers, memory, etc, from all |
| // threads at once. GDB may never ask for this, but if it does we |
| // should complain. |
| if (all) |
| throw CmdError("E03"); |
| |
| // If GDB doesn't care which thread we're using, keep using the |
| // current one, otherwise switch. |
| if (!any && tid != tc->contextId()) { |
| if (!selectThreadContext(tid)) |
| throw CmdError("E04"); |
| // Line up on an instruction boundary in the new thread. |
| threadSwitching = true; |
| scheduleTrapEvent(tid,GDBSignal::ZERO,0,""); |
| return false; |
| } |
| } else { |
| throw CmdError("E05"); |
| } |
| |
| send("OK"); |
| return true; |
| } |
| |
| bool |
| BaseRemoteGDB::cmdIsThreadAlive(GdbCommand::Context &ctx) |
| { |
| const char *p = ctx.data; |
| int tid = 0; |
| bool all, any; |
| if (!parseThreadId(&p, all, any, tid)) |
| throw CmdError("E01"); |
| if (all) |
| throw CmdError("E03"); |
| if (threads.find(tid) == threads.end()) |
| throw CmdError("E04"); |
| send("OK"); |
| return true; |
| } |
| |
| bool |
| BaseRemoteGDB::cmdMemR(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::cmdMemW(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::cmdMultiLetter(GdbCommand::Context &ctx) |
| { |
| GdbMultiLetterCommand::Context new_ctx; |
| new_ctx.type = ctx.type; |
| strtok(ctx.data,";?"); |
| char* sep = strtok(NULL,";:?"); |
| |
| int txt_len = (sep != NULL) ? (sep - ctx.data) : strlen(ctx.data); |
| DPRINTF(GDBMisc, "Multi-letter: %s , len=%i\n", ctx.data,txt_len); |
| new_ctx.cmdTxt = std::string(ctx.data,txt_len); |
| new_ctx.data = sep; |
| new_ctx.len = ctx.len - txt_len; |
| try { |
| auto cmd_it = multiLetterMap.find(new_ctx.cmdTxt); |
| if (cmd_it == multiLetterMap.end()) { |
| DPRINTF(GDBMisc, "Unknown command: %s\n", new_ctx.cmdTxt); |
| throw Unsupported(); |
| } |
| new_ctx.cmd = &(cmd_it->second); |
| |
| return (this->*(new_ctx.cmd->func))(new_ctx); |
| //catching errors: we don't need to catch anything else |
| //as it will be handled by processCommands |
| } catch (CmdError &e) { |
| send(e.error); |
| } |
| return false; |
| } |
| |
| std::map<std::string, BaseRemoteGDB::GdbMultiLetterCommand> |
| BaseRemoteGDB::multiLetterMap = { |
| { "MustReplyEmpty", { "KGDB_REPLY_EMPTY", &BaseRemoteGDB::cmdReplyEmpty}}, |
| { "Kill", { "KGDB_VKILL", &BaseRemoteGDB::cmdVKill}}, |
| }; |
| |
| |
| bool |
| BaseRemoteGDB::cmdReplyEmpty(GdbMultiLetterCommand::Context &ctx) |
| { |
| send(""); |
| return true; |
| } |
| |
| bool |
| BaseRemoteGDB::cmdVKill(GdbMultiLetterCommand::Context &ctx) |
| { |
| warn("GDB command for kill received detaching instead"); |
| detach(); |
| return false; |
| } |
| |
| bool |
| BaseRemoteGDB::cmdMultiUnsupported(GdbMultiLetterCommand::Context &ctx) |
| { |
| DPRINTF(GDBMisc, "Unsupported Multi name command : %s\n", |
| ctx.cmd->name); |
| DDUMP(GDBMisc, ctx.data, ctx.len); |
| throw Unsupported(); |
| } |
| |
| namespace { |
| |
| std::pair<std::string, std::string> |
| splitAt(std::string str, const char * const delim) |
| { |
| size_t pos = str.find_first_of(delim); |
| if (pos == std::string::npos) |
| return std::pair<std::string, std::string>(str, ""); |
| else |
| return std::pair<std::string, std::string>( |
| str.substr(0, pos), str.substr(pos + 1)); |
| } |
| |
| } // anonymous namespace |
| |
| std::map<std::string, BaseRemoteGDB::QuerySetCommand> |
| BaseRemoteGDB::queryMap = { |
| { "C", { &BaseRemoteGDB::queryC } }, |
| { "Rcmd", { &BaseRemoteGDB::queryRcmd} }, |
| { "Attached", { &BaseRemoteGDB::queryAttached} }, |
| { "Supported", { &BaseRemoteGDB::querySupported, ";" } }, |
| { "Xfer", { &BaseRemoteGDB::queryXfer } }, |
| { "Symbol", { &BaseRemoteGDB::querySymbol ,":" } }, |
| { "fThreadInfo", { &BaseRemoteGDB::queryFThreadInfo } }, |
| { "sThreadInfo", { &BaseRemoteGDB::querySThreadInfo } }, |
| }; |
| |
| bool |
| BaseRemoteGDB::queryC(QuerySetCommand::Context &ctx) |
| { |
| send("QC%x", encodeThreadId(tc->contextId())); |
| return true; |
| } |
| |
| bool |
| BaseRemoteGDB::querySupported(QuerySetCommand::Context &ctx) |
| { |
| 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()); |
| return true; |
| } |
| |
| bool |
| BaseRemoteGDB::queryXfer(QuerySetCommand::Context &ctx) |
| { |
| auto split = splitAt(ctx.args.at(0), ":"); |
| auto object = split.first; |
| |
| split = splitAt(split.second, ":"); |
| auto operation = split.first; |
| |
| // Only the "features" object and "read"ing are supported currently. |
| if (object != "features" || operation != "read") |
| throw Unsupported(); |
| |
| // Extract the annex name. |
| split = splitAt(split.second, ":"); |
| auto annex = split.first; |
| |
| // Read the contents of the annex. |
| std::string content; |
| if (!getXferFeaturesRead(annex, content)) |
| throw CmdError("E00"); |
| |
| // Extract the offset and length. |
| split = splitAt(split.second, ","); |
| auto offset_str = split.first; |
| auto length_str = split.second; |
| |
| const char *offset_ptr = offset_str.c_str(); |
| const char *length_ptr = length_str.c_str(); |
| auto offset = hex2i(&offset_ptr); |
| auto length = hex2i(&length_ptr); |
| if (offset_ptr != offset_str.c_str() + offset_str.length() || |
| length_ptr != length_str.c_str() + length_str.length()) { |
| throw CmdError("E00"); |
| } |
| |
| std::string encoded; |
| encodeXferResponse(content, encoded, offset, length); |
| send(encoded); |
| return true; |
| } |
| bool |
| BaseRemoteGDB::querySymbol(QuerySetCommand::Context &ctx) |
| { |
| //The target does not need to look up any (more) symbols. |
| send("OK"); |
| return true; |
| } |
| bool |
| BaseRemoteGDB::queryAttached(QuerySetCommand::Context &ctx) |
| { |
| std::string pid=""; |
| if (!ctx.args.empty() && !ctx.args[0].empty()){ |
| pid=ctx.args[0]; |
| } |
| DPRINTF(GDBMisc, "QAttached : pid=%s\n",pid); |
| //The remote server is attached to an existing process. |
| send("1"); |
| return true; |
| } |
| |
| class MonitorCallEvent : public GlobalSimLoopExitEvent |
| { |
| BaseRemoteGDB& gdb; |
| ContextID id; |
| public: |
| MonitorCallEvent(BaseRemoteGDB& gdb,ContextID id,const std::string &_cause, |
| int code): |
| GlobalSimLoopExitEvent(_cause,code), gdb(gdb),id(id) |
| {}; |
| void process() override{ |
| GlobalSimLoopExitEvent::process(); |
| } |
| void clean() override{ |
| //trapping now |
| //this is the only point in time when we can call trap |
| //before any breakpoint triggers |
| gdb.trap(id,GDBSignal::ZERO,"monitor_return"); |
| delete this; |
| } |
| ~MonitorCallEvent(){ |
| DPRINTF(Event,"MonitorCallEvent destructed\n");; |
| } |
| }; |
| |
| bool |
| BaseRemoteGDB::queryRcmd(QuerySetCommand::Context &ctx){ |
| std::string message=hexS2string(ctx.args[0]); |
| DPRINTF(GDBMisc, "Rcmd Query: %s => %s\n", ctx.args[0],message); |
| //Tick when = curTick(); |
| new MonitorCallEvent(*this,tc->contextId(),"GDB_MONITOR:"+ message, 0); |
| return false; |
| } |
| |
| bool |
| BaseRemoteGDB::queryFThreadInfo(QuerySetCommand::Context &ctx) |
| { |
| threadInfoIdx = 0; |
| querySThreadInfo(ctx); |
| return true; |
| } |
| |
| bool |
| BaseRemoteGDB::querySThreadInfo(QuerySetCommand::Context &ctx) |
| { |
| if (threadInfoIdx >= threads.size()) { |
| threadInfoIdx = 0; |
| send("l"); |
| } else { |
| auto it = threads.begin(); |
| std::advance(it, threadInfoIdx++); |
| send("m%x", encodeThreadId(it->second->contextId())); |
| } |
| return true; |
| } |
| |
| bool |
| BaseRemoteGDB::cmdQueryVar(GdbCommand::Context &ctx) |
| { |
| // The query command goes until the first ':', or the end of the string. |
| std::string s(ctx.data, ctx.len); |
| auto query_split = splitAt({ ctx.data, (size_t)ctx.len }, ":,"); |
| const auto &query_str = query_split.first; |
| |
| // Look up the query command, and report if it isn't found. |
| auto query_it = queryMap.find(query_str); |
| if (query_it == queryMap.end()) { |
| DPRINTF(GDBMisc, "Unknown query %s\n", s); |
| throw Unsupported(); |
| } |
| |
| // If it was found, construct a context. |
| QuerySetCommand::Context qctx(query_str); |
| |
| const auto &query = query_it->second; |
| auto remaining = std::move(query_split.second); |
| if (!query.argSep) { |
| qctx.args.emplace_back(std::move(remaining)); |
| } else { |
| while (remaining != "") { |
| auto arg_split = splitAt(remaining, query.argSep); |
| qctx.args.emplace_back(std::move(arg_split.first)); |
| remaining = std::move(arg_split.second); |
| } |
| } |
| //returning true if the query want to pursue GDB command processing |
| //false means that the command processing stop until it's trigger again. |
| return (this->*(query.func))(qctx); |
| } |
| |
| std::vector<std::string> |
| BaseRemoteGDB::availableFeatures() const |
| { |
| return {}; |
| }; |
| |
| bool |
| BaseRemoteGDB::getXferFeaturesRead( |
| const std::string &annex, std::string &output) |
| { |
| return false; |
| } |
| |
| void |
| BaseRemoteGDB::encodeBinaryData( |
| const std::string &unencoded, std::string &encoded) const |
| { |
| for (const char& c : unencoded) { |
| if (c == '$' || c == '#' || c == '}' || c == '*') { |
| encoded += '}'; |
| encoded += c ^ 0x20; |
| } else { |
| encoded += c; |
| } |
| } |
| } |
| |
| void |
| BaseRemoteGDB::encodeXferResponse(const std::string &unencoded, |
| std::string &encoded, size_t offset, size_t unencoded_length) const |
| { |
| if (offset + unencoded_length < unencoded.length()) |
| encoded += 'm'; |
| else |
| encoded += 'l'; |
| encodeBinaryData(unencoded.substr(offset, unencoded_length), encoded); |
| } |
| |
| bool |
| BaseRemoteGDB::cmdDumpPageTable(GdbCommand::Context &ctx) |
| { |
| send(tc->getProcessPtr()->pTable->externalize().c_str()); |
| return true; |
| } |
| |
| bool |
| BaseRemoteGDB::cmdAsyncStep(GdbCommand::Context &ctx) |
| { |
| const char *p = ctx.data; |
| hex2i(&p); // Ignore the subcommand byte. |
| if (*p++ == ';') { |
| Addr new_pc = hex2i(&p); |
| tc->pcState(new_pc); |
| } |
| setSingleStep(); |
| return false; |
| } |
| |
| bool |
| BaseRemoteGDB::cmdStep(GdbCommand::Context &ctx) |
| { |
| if (ctx.len) { |
| const char *p = ctx.data; |
| Addr new_pc = hex2i(&p); |
| tc->pcState(new_pc); |
| } |
| setSingleStep(); |
| return false; |
| } |
| |
| bool |
| BaseRemoteGDB::cmdClrHwBkpt(GdbCommand::Context &ctx) |
| { |
| const char *p = ctx.data; |
| char sub_cmd = *p++; |
| if (*p++ != ',') |
| throw CmdError("E0D"); |
| Addr addr = hex2i(&p); |
| if (*p++ != ',') |
| throw CmdError("E0D"); |
| size_t kind = hex2i(&p); |
| |
| DPRINTF(GDBMisc, "clear %s, addr=%#x, kind=%d\n", |
| breakType(sub_cmd), addr, kind); |
| |
| switch (sub_cmd) { |
| case GdbSoftBp: |
| removeSoftBreak(addr, kind); |
| break; |
| case GdbHardBp: |
| removeHardBreak(addr, kind); |
| break; |
| case GdbWriteWp: |
| case GdbReadWp: |
| case GdbAccWp: |
| default: // unknown |
| throw Unsupported(); |
| } |
| send("OK"); |
| |
| return true; |
| } |
| |
| bool |
| BaseRemoteGDB::cmdSetHwBkpt(GdbCommand::Context &ctx) |
| { |
| const char *p = ctx.data; |
| char sub_cmd = *p++; |
| if (*p++ != ',') |
| throw CmdError("E0D"); |
| Addr addr = hex2i(&p); |
| if (*p++ != ',') |
| throw CmdError("E0D"); |
| size_t kind = hex2i(&p); |
| |
| DPRINTF(GDBMisc, "set %s, addr=%#x, kind=%d\n", |
| breakType(sub_cmd), addr, kind); |
| |
| switch (sub_cmd) { |
| case GdbSoftBp: |
| insertSoftBreak(addr, kind); |
| break; |
| case GdbHardBp: |
| insertHardBreak(addr, kind); |
| break; |
| case GdbWriteWp: |
| case GdbReadWp: |
| case GdbAccWp: |
| default: // unknown |
| throw Unsupported(); |
| } |
| send("OK"); |
| |
| return true; |
| } |
| |
| } // namespace gem5 |