Since PR #1316, we use sign-extend for all address generation, including PC, to match the ISA specification for modifiable XLEN. However, when we set a breakpoint using remote GDB, our address is not sign-extended. This causes the breakpoint to be set at the wrong address, as specified in Issue #1463. This PR fixes the issue by sign-extending the address when setting a breakpoint. This also matches the RISC-V ISA Specification that "must sign-extend results to fill the entire widest supported XLEN in the destination register." Change-Id: I9b493bf8ad5b1ef45a9728bb40fc5e38250fe9c3 Signed-off-by: Yangyu Chen <cyy@cyyself.name>
493 lines
14 KiB
C++
493 lines
14 KiB
C++
/*
|
|
* 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.
|
|
*/
|
|
|
|
#ifndef __REMOTE_GDB_HH__
|
|
#define __REMOTE_GDB_HH__
|
|
|
|
|
|
#include <cstdint>
|
|
#include <exception>
|
|
#include <map>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "arch/generic/pcstate.hh"
|
|
#include "base/cprintf.hh"
|
|
#include "base/pollevent.hh"
|
|
#include "base/socket.hh"
|
|
#include "base/types.hh"
|
|
#include "cpu/pc_event.hh"
|
|
#include "gdbremote/signals.hh"
|
|
#include "sim/debug.hh"
|
|
#include "sim/eventq.hh"
|
|
|
|
/*
|
|
* This file implements a client for the GDB remote serial protocol as
|
|
* described in this official documentation:
|
|
*
|
|
* https://sourceware.org/gdb/current/onlinedocs/gdb/Remote-Protocol.html
|
|
*/
|
|
|
|
namespace gem5
|
|
{
|
|
|
|
class System;
|
|
class ThreadContext;
|
|
|
|
class BaseRemoteGDB;
|
|
class HardBreakpoint;
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @ingroup api_remote_gdb
|
|
*/
|
|
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).
|
|
*
|
|
* @ingroup api_remote_gdb
|
|
*/
|
|
virtual size_t size() const = 0;
|
|
|
|
/**
|
|
* Fill the raw buffer from the registers in the ThreadContext.
|
|
*
|
|
* @ingroup api_remote_gdb
|
|
*/
|
|
virtual void getRegs(ThreadContext*) = 0;
|
|
|
|
/**
|
|
* Set the ThreadContext's registers from the values
|
|
* in the raw buffer.
|
|
*
|
|
* @ingroup api_remote_gdb
|
|
*/
|
|
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.
|
|
*
|
|
* @ingroup api_remote_gdb
|
|
*/
|
|
virtual const std::string name() const = 0;
|
|
|
|
/**
|
|
* @ingroup api_remote_gdb
|
|
*/
|
|
BaseGdbRegCache(BaseRemoteGDB *g) : gdb(g)
|
|
{}
|
|
virtual ~BaseGdbRegCache()
|
|
{}
|
|
|
|
protected:
|
|
BaseRemoteGDB *gdb;
|
|
};
|
|
|
|
class BaseRemoteGDB
|
|
{
|
|
friend class HardBreakpoint;
|
|
public:
|
|
|
|
/**
|
|
* @ingroup api_remote_gdb
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* Interface to other parts of the simulator.
|
|
*/
|
|
BaseRemoteGDB(System *system, ListenSocketConfig _listen_config);
|
|
virtual ~BaseRemoteGDB();
|
|
|
|
std::string name();
|
|
|
|
void listen();
|
|
void connect();
|
|
|
|
const ListenSocket &hostSocket() const;
|
|
|
|
void attach(int fd);
|
|
void detach();
|
|
bool isAttached() { return attached; }
|
|
|
|
void addThreadContext(ThreadContext *_tc);
|
|
void replaceThreadContext(ThreadContext *_tc);
|
|
bool selectThreadContext(ContextID id);
|
|
|
|
void trap(ContextID id, GDBSignal sig,const std::string& stopReason="");
|
|
bool sendMessage(std::string message);
|
|
//schedule a trap event with these properties
|
|
void scheduleTrapEvent(ContextID id,GDBSignal type, int delta,
|
|
std::string stopReason);
|
|
/** @} */ // end of api_remote_gdb
|
|
|
|
template <class GDBStub, class ...Args>
|
|
static BaseRemoteGDB *
|
|
build(ListenSocketConfig listen_config, Args... args)
|
|
{
|
|
if (listen_config)
|
|
return new GDBStub(args..., listen_config);
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
protected:
|
|
/*
|
|
* Connection to the external GDB.
|
|
*/
|
|
|
|
/*
|
|
* Asynchronous socket events and event handlers.
|
|
*
|
|
* These events occur asynchronously and are handled asynchronously
|
|
* to main simulation loop - therefore they *shall not* interact with
|
|
* rest of gem5.
|
|
*
|
|
* The only thing they do is to schedule a synchronous event at instruction
|
|
* boundary to deal with the request.
|
|
*/
|
|
void incomingData(int revent);
|
|
void incomingConnection(int revent);
|
|
|
|
template <void (BaseRemoteGDB::*F)(int revent)>
|
|
class SocketEvent : public PollEvent
|
|
{
|
|
protected:
|
|
BaseRemoteGDB *gdb;
|
|
|
|
public:
|
|
SocketEvent(BaseRemoteGDB *gdb, int fd, int e) :
|
|
PollEvent(fd, e), gdb(gdb)
|
|
{}
|
|
|
|
void process(int revent) { (gdb->*F)(revent); }
|
|
};
|
|
|
|
typedef SocketEvent<&BaseRemoteGDB::incomingConnection>
|
|
IncomingConnectionEvent;
|
|
typedef SocketEvent<&BaseRemoteGDB::incomingData>
|
|
IncomingDataEvent;
|
|
|
|
friend IncomingConnectionEvent;
|
|
friend IncomingDataEvent;
|
|
|
|
IncomingConnectionEvent *incomingConnectionEvent;
|
|
IncomingDataEvent *incomingDataEvent;
|
|
|
|
ListenSocketPtr listener;
|
|
|
|
// The socket commands come in through.
|
|
int fd;
|
|
|
|
// Transfer data to/from GDB.
|
|
uint8_t getbyte();
|
|
bool try_getbyte(uint8_t* c,int timeout=-1);//return true if successful
|
|
void putbyte(uint8_t b);
|
|
|
|
void recv(std::vector<char> &bp);
|
|
void send(const char *data);
|
|
void send(const std::string &data) { send(data.c_str()); }
|
|
|
|
template <typename ...Args>
|
|
void
|
|
send(const char *format, const Args &...args)
|
|
{
|
|
send(csprintf(format, args...));
|
|
}
|
|
|
|
/*
|
|
* Process commands from remote GDB. If simulation has been
|
|
* stopped because of some kind of fault (as segmentation violation,
|
|
* or SW trap), 'signum' is the signal value reported back to GDB
|
|
* in "S" packet (this is done in trap()).
|
|
*/
|
|
void processCommands(GDBSignal sig=GDBSignal::ZERO);
|
|
|
|
/*
|
|
* Simulator side debugger state.
|
|
*/
|
|
bool attached = false;
|
|
bool threadSwitching = false;
|
|
|
|
System *sys;
|
|
|
|
std::map<ContextID, ThreadContext *> threads;
|
|
ThreadContext *tc = nullptr;
|
|
|
|
BaseGdbRegCache *regCachePtr = nullptr;
|
|
|
|
MemberEventWrapper<&BaseRemoteGDB::connect> connectEvent;
|
|
MemberEventWrapper<&BaseRemoteGDB::detach> disconnectEvent;
|
|
|
|
class TrapEvent : public Event
|
|
{
|
|
protected:
|
|
GDBSignal _type;
|
|
ContextID _id;
|
|
std::string _stopReason;
|
|
BaseRemoteGDB *gdb;
|
|
|
|
public:
|
|
TrapEvent(BaseRemoteGDB *g) : gdb(g)
|
|
{}
|
|
|
|
void type(GDBSignal t) { _type = t; }
|
|
void stopReason(std::string s) {_stopReason = s; }
|
|
void id(ContextID id) { _id = id; }
|
|
void process() { gdb->trap(_id, _type,_stopReason); }
|
|
} trapEvent;
|
|
|
|
/*
|
|
* The interface to the simulated system.
|
|
*/
|
|
virtual bool readBlob(Addr vaddr, size_t size, char *data);
|
|
virtual bool writeBlob(Addr vaddr, size_t size, const char *data);
|
|
bool read(Addr vaddr, size_t size, char *data);
|
|
bool write(Addr vaddr, 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();
|
|
MemberEventWrapper<&BaseRemoteGDB::singleStep> singleStepEvent;
|
|
|
|
void clearSingleStep();
|
|
void setSingleStep();
|
|
|
|
/// Schedule an event which will be triggered "delta" instructions later.
|
|
void scheduleInstCommitEvent(Event *ev, int delta,ThreadContext* _tc);
|
|
void scheduleInstCommitEvent(Event *ev, int delta){
|
|
scheduleInstCommitEvent(ev, delta,tc);
|
|
};
|
|
/// Deschedule an instruction count based event.
|
|
void descheduleInstCommitEvent(Event *ev);
|
|
|
|
// Breakpoints.
|
|
virtual void insertSoftBreak(Addr addr, size_t kind);
|
|
virtual void removeSoftBreak(Addr addr, size_t kind);
|
|
virtual void insertHardBreak(Addr addr, size_t kind);
|
|
virtual void removeHardBreak(Addr addr, size_t kind);
|
|
|
|
void sendTPacket(GDBSignal sig, ContextID id,
|
|
const std::string& stopReason);
|
|
void sendSPacket(GDBSignal sig);
|
|
//The OPacket allow to send string to be displayed by the remote GDB
|
|
void sendOPacket(const std::string message);
|
|
/*
|
|
* GDB commands.
|
|
*/
|
|
struct GdbCommand
|
|
{
|
|
public:
|
|
struct Context
|
|
{
|
|
const GdbCommand *cmd;
|
|
char cmdByte;
|
|
GDBSignal 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) {}
|
|
};
|
|
|
|
static std::map<char, GdbCommand> commandMap;
|
|
|
|
struct GdbMultiLetterCommand
|
|
{
|
|
public:
|
|
struct Context
|
|
{
|
|
const GdbMultiLetterCommand *cmd;
|
|
std::string cmdTxt;
|
|
GDBSignal type;
|
|
char *data;
|
|
int len;
|
|
};
|
|
|
|
typedef bool (BaseRemoteGDB::*Func)(Context &ctx);
|
|
|
|
const char * const name;
|
|
const Func func;
|
|
|
|
GdbMultiLetterCommand(const char *_name, Func _func) :
|
|
name(_name), func(_func) {}
|
|
};
|
|
|
|
|
|
static std::map<std::string, GdbMultiLetterCommand> multiLetterMap;
|
|
|
|
bool cmdUnsupported(GdbCommand::Context &ctx);
|
|
|
|
bool cmdSignal(GdbCommand::Context &ctx);
|
|
bool cmdCont(GdbCommand::Context &ctx);
|
|
bool cmdAsyncCont(GdbCommand::Context &ctx);
|
|
bool cmdDetach(GdbCommand::Context &ctx);
|
|
bool cmdRegR(GdbCommand::Context &ctx);
|
|
bool cmdRegW(GdbCommand::Context &ctx);
|
|
bool cmdSetThread(GdbCommand::Context &ctx);
|
|
bool cmdIsThreadAlive(GdbCommand::Context &ctx);
|
|
bool cmdMemR(GdbCommand::Context &ctx);
|
|
bool cmdMemW(GdbCommand::Context &ctx);
|
|
bool cmdQueryVar(GdbCommand::Context &ctx);
|
|
bool cmdStep(GdbCommand::Context &ctx);
|
|
bool cmdAsyncStep(GdbCommand::Context &ctx);
|
|
bool cmdClrHwBkpt(GdbCommand::Context &ctx);
|
|
bool cmdSetHwBkpt(GdbCommand::Context &ctx);
|
|
bool cmdDumpPageTable(GdbCommand::Context &ctx);
|
|
bool cmdMultiLetter(GdbCommand::Context &ctx);
|
|
|
|
//Multi letter command
|
|
bool cmdMultiUnsupported(GdbMultiLetterCommand::Context &ctx);
|
|
|
|
bool cmdReplyEmpty(GdbMultiLetterCommand::Context &ctx);
|
|
bool cmdVKill(GdbMultiLetterCommand::Context &ctx);
|
|
|
|
struct QuerySetCommand
|
|
{
|
|
struct Context
|
|
{
|
|
const std::string &name;
|
|
std::vector<std::string> args;
|
|
|
|
Context(const std::string &_name) : name(_name) {}
|
|
};
|
|
|
|
using Func = bool (BaseRemoteGDB::*)(Context &ctx);
|
|
|
|
const char * const argSep;
|
|
const Func func;
|
|
|
|
QuerySetCommand(Func _func, const char *_argSep=nullptr) :
|
|
argSep(_argSep), func(_func)
|
|
{}
|
|
};
|
|
|
|
static std::map<std::string, QuerySetCommand> queryMap;
|
|
|
|
bool queryC(QuerySetCommand::Context &ctx);
|
|
bool querySupported(QuerySetCommand::Context &ctx);
|
|
bool queryXfer(QuerySetCommand::Context &ctx);
|
|
bool querySymbol(QuerySetCommand::Context &ctx);
|
|
bool queryRcmd(QuerySetCommand::Context &ctx);
|
|
bool queryAttached(QuerySetCommand::Context &ctx);
|
|
|
|
size_t threadInfoIdx = 0;
|
|
bool queryFThreadInfo(QuerySetCommand::Context &ctx);
|
|
bool querySThreadInfo(QuerySetCommand::Context &ctx);
|
|
|
|
protected:
|
|
ThreadContext *context() { return tc; }
|
|
System *system() { return sys; }
|
|
|
|
void encodeBinaryData(const std::string &unencoded,
|
|
std::string &encoded) const;
|
|
|
|
void encodeXferResponse(const std::string &unencoded,
|
|
std::string &encoded, size_t offset, size_t unencoded_length) const;
|
|
|
|
// checkBpKind checks if a kind of breakpoint is legal. This function should
|
|
// be implemented by subclasses by arch. The "kind" is considered to be
|
|
// breakpoint size in some arch.
|
|
virtual bool checkBpKind(size_t kind);
|
|
|
|
virtual BaseGdbRegCache *gdbRegs() = 0;
|
|
|
|
virtual bool acc(Addr addr, size_t len) = 0;
|
|
|
|
virtual std::vector<std::string> availableFeatures() const;
|
|
|
|
/**
|
|
* Get an XML target description.
|
|
*
|
|
* @param[in] annex the XML filename
|
|
* @param[out] output set to the decoded XML
|
|
* @return true if the given annex was found
|
|
*/
|
|
virtual bool getXferFeaturesRead(const std::string &annex,
|
|
std::string &output);
|
|
};
|
|
|
|
template <class T>
|
|
inline T
|
|
BaseRemoteGDB::read(Addr addr)
|
|
{
|
|
T temp;
|
|
read(addr, sizeof(T), (char *)&temp);
|
|
return temp;
|
|
}
|
|
|
|
template <class T>
|
|
inline void
|
|
BaseRemoteGDB::write(Addr addr, T data)
|
|
{
|
|
write(addr, sizeof(T), (const char *)&data);
|
|
}
|
|
|
|
} // namespace gem5
|
|
|
|
#endif /* __REMOTE_GDB_H__ */
|