884 lines
19 KiB
C++
884 lines
19 KiB
C++
/*
|
||
Copyright 2021 flyinghead
|
||
|
||
This file is part of Flycast.
|
||
|
||
Flycast is free software: you can redistribute it and/or modify
|
||
it under the terms of the GNU General Public License as published by
|
||
the Free Software Foundation, either version 2 of the License, or
|
||
(at your option) any later version.
|
||
|
||
Flycast is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
GNU General Public License for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||
*/
|
||
#include "types.h"
|
||
|
||
#ifdef GDB_SERVER
|
||
#include "gdb_server.h"
|
||
#include "debug_agent.h"
|
||
#include "network/net_platform.h"
|
||
#include <stdexcept>
|
||
#include <thread>
|
||
#include <chrono>
|
||
#include <mutex>
|
||
|
||
#define SERVER_PORT 3263
|
||
|
||
namespace debugger {
|
||
|
||
static void emuEventCallback(Event event);
|
||
|
||
class GdbServer
|
||
{
|
||
public:
|
||
struct Error : public std::runtime_error {
|
||
Error(const char *reason) : std::runtime_error(reason) {}
|
||
};
|
||
|
||
void init()
|
||
{
|
||
if (VALID(serverSocket))
|
||
return;
|
||
|
||
serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||
if (!VALID(serverSocket))
|
||
throw Error("gdb: Cannot create server socket");
|
||
|
||
int option = 1;
|
||
setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&option, sizeof(option));
|
||
|
||
struct sockaddr_in serveraddr;
|
||
memset(&serveraddr, 0, sizeof(serveraddr));
|
||
serveraddr.sin_family = AF_INET;
|
||
serveraddr.sin_port = htons(SERVER_PORT);
|
||
|
||
if (::bind(serverSocket, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
|
||
{
|
||
closesocket(serverSocket);
|
||
throw Error("gdb: bind() failed");
|
||
}
|
||
if (::listen(serverSocket, 5) < 0)
|
||
{
|
||
closesocket(serverSocket);
|
||
throw Error("gdb: listen() failed");
|
||
}
|
||
EventManager::listen(Event::Resume, emuEventCallback);
|
||
EventManager::listen(Event::Terminate, emuEventCallback);
|
||
}
|
||
|
||
void term()
|
||
{
|
||
stop();
|
||
if (VALID(clientSocket))
|
||
{
|
||
closesocket(clientSocket);
|
||
clientSocket = INVALID_SOCKET;
|
||
}
|
||
if (VALID(serverSocket))
|
||
{
|
||
closesocket(serverSocket);
|
||
serverSocket = INVALID_SOCKET;
|
||
}
|
||
}
|
||
|
||
void run()
|
||
{
|
||
DEBUG_LOG(COMMON, "GdbServer starting");
|
||
thread = std::thread(&GdbServer::serverThread, this);
|
||
}
|
||
|
||
void stop()
|
||
{
|
||
if (thread.joinable())
|
||
{
|
||
DEBUG_LOG(COMMON, "GdbServer stopping");
|
||
agent.resetAgent();
|
||
stopRequested = true;
|
||
thread.join();
|
||
}
|
||
}
|
||
|
||
bool isRunning() const {
|
||
return thread.joinable();
|
||
}
|
||
|
||
// called on the emu thread
|
||
void debugTrap(u32 event)
|
||
{
|
||
if (!attached)
|
||
return;
|
||
agent.debugTrap(event);
|
||
reportException();
|
||
postDebugTrapNeeded = true;
|
||
throw Stop();
|
||
}
|
||
|
||
private:
|
||
const u32 EXCEPT_NONE = 1;
|
||
|
||
void serverThread()
|
||
{
|
||
while (!stopRequested)
|
||
{
|
||
fd_set fds;
|
||
FD_ZERO(&fds);
|
||
sock_t max_fd = serverSocket;
|
||
FD_SET(serverSocket, &fds);
|
||
if (VALID(clientSocket))
|
||
{
|
||
max_fd = std::max(max_fd, clientSocket);
|
||
FD_SET(clientSocket, &fds);
|
||
}
|
||
timeval tv;
|
||
tv.tv_sec = 0;
|
||
tv.tv_usec = 100 * 1000;
|
||
if (::select(max_fd + 1, &fds, nullptr, nullptr, &tv) > 0)
|
||
{
|
||
if (FD_ISSET(serverSocket, &fds))
|
||
{
|
||
try {
|
||
acceptClientConnection();
|
||
} catch (const Error& e) {
|
||
ERROR_LOG(COMMON, "%s", e.what());
|
||
closesocket(serverSocket);
|
||
serverSocket = INVALID_SOCKET;
|
||
break;
|
||
}
|
||
}
|
||
else if (FD_ISSET(clientSocket, &fds))
|
||
{
|
||
readCommand();
|
||
}
|
||
}
|
||
}
|
||
if (VALID(clientSocket))
|
||
{
|
||
closesocket(clientSocket);
|
||
clientSocket = INVALID_SOCKET;
|
||
}
|
||
attached = false;
|
||
stopRequested = false;
|
||
}
|
||
|
||
void acceptClientConnection()
|
||
{
|
||
if (VALID(clientSocket))
|
||
closesocket(clientSocket);
|
||
sockaddr_in src_addr{};
|
||
socklen_t addr_len = sizeof(src_addr);
|
||
clientSocket = ::accept(serverSocket, (sockaddr *)&src_addr, &addr_len);
|
||
if (!VALID(clientSocket))
|
||
{
|
||
if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
|
||
throw Error("accept failed");
|
||
}
|
||
else
|
||
{
|
||
NOTICE_LOG(NETWORK, "gdb: client connection");
|
||
attached = true;
|
||
agent.interrupt();
|
||
}
|
||
}
|
||
|
||
void readCommand()
|
||
{
|
||
if (postDebugTrapNeeded)
|
||
{
|
||
postDebugTrapNeeded = false;
|
||
agent.postDebugTrap();
|
||
}
|
||
try {
|
||
std::string packet = recvPacket();
|
||
if (packet.empty())
|
||
return;
|
||
|
||
DEBUG_LOG(NETWORK, "gdb: recv %s", packet.c_str());
|
||
switch (packet[0])
|
||
{
|
||
case '!': // Enable extended mode
|
||
sendPacket("OK");
|
||
break;
|
||
case '?': // Sent when connection is first established to query the reason the target halted
|
||
reportException();
|
||
break;
|
||
case 'A': // Initialized argv[] array passed into program. not supported
|
||
sendPacket("E01");
|
||
break;;
|
||
case 'b': // Change the serial line speed to baud. deprecated
|
||
break;
|
||
case 'B': // Set or clear a breakpoint at addr. deprecated
|
||
break;
|
||
case 'c': // Continue at addr, which is the address to resume.
|
||
// If addr is omitted, resume at current address
|
||
sendContinue(packet);
|
||
break;
|
||
case 'C': // Continue with signal sig
|
||
sendContinue(packet);
|
||
break;
|
||
case 'd': // Toggle debug flag. deprecated
|
||
break;
|
||
case 'D': // Detach GDB from the remote system
|
||
sendPacket("OK");
|
||
agent.detach();
|
||
break;
|
||
case 'F': // File-I/O protocol extension not currently supported
|
||
break;;
|
||
case 'g': // Read general registers
|
||
readAllRegs();
|
||
break;
|
||
case 'G': // Write general registers
|
||
writeAllRegs(packet);
|
||
break;
|
||
case 'H': // Set thread for subsequent operations
|
||
sendPacket("OK");
|
||
break;
|
||
case 'i': // Step the remote target by a single clock cycle
|
||
case 'I': // Signal, then cycle step
|
||
// not supported
|
||
sendPacket("");
|
||
break;
|
||
case 'k': // Kill request. Stop process/system
|
||
agent.kill();
|
||
break;
|
||
case 'm': // Read length addressable memory units
|
||
readMem(packet);
|
||
break;
|
||
case 'M': // Write length addressable memory units
|
||
writeMem(packet);
|
||
break;
|
||
case 'p': // Read the value of register
|
||
readReg(packet);
|
||
break;
|
||
case 'P': // Write register
|
||
writeReg(packet);
|
||
break;
|
||
case 'q': // General query packets
|
||
query(packet);
|
||
break;
|
||
case 'Q': // General set packets
|
||
set(packet);
|
||
break;
|
||
case 'r': // Reset the entire system. Deprecated (use 'R' instead)
|
||
break;
|
||
case 'R': // Restart the program being debugged.
|
||
restart();
|
||
break;
|
||
case 's': // Single step
|
||
step(EXCEPT_NONE);
|
||
break;
|
||
case 'S': // Step with signal
|
||
step();
|
||
break;
|
||
case 't': // Search backwards. unsupported
|
||
break;
|
||
case 'T': // Find out if the thread is alive
|
||
sendPacket("OK");
|
||
break;
|
||
case 'v': // 'v' packets to control execution
|
||
vpacket(packet);
|
||
break;
|
||
case 'X': // Write binary data to memory
|
||
writeMemBin(packet);
|
||
break;
|
||
case 'z': // Remove a breakpoint/watchpoint.
|
||
removeMatchpoint(packet);
|
||
break;
|
||
case 'Z': // Insert a breakpoint/watchpoint.
|
||
insertMatchpoint(packet);
|
||
break;
|
||
case 3:
|
||
interrupt();
|
||
break;
|
||
default:
|
||
// Unknown commands are ignored
|
||
WARN_LOG(COMMON, "Unknown gdb command: %s", packet.c_str());
|
||
break;;
|
||
}
|
||
} catch (const Error& e) {
|
||
ERROR_LOG(COMMON, "%s", e.what());
|
||
closesocket(clientSocket);
|
||
clientSocket = INVALID_SOCKET;
|
||
attached = false;
|
||
}
|
||
}
|
||
|
||
void reportException()
|
||
{
|
||
char s[4];
|
||
sprintf(s, "S%02X", agent.currentException());
|
||
sendPacket(s);
|
||
}
|
||
|
||
void sendContinue(const std::string& pkt)
|
||
{
|
||
if (pkt[0] != 'c') {
|
||
WARN_LOG(COMMON, "Continue with signal not supported");
|
||
return;
|
||
}
|
||
|
||
if (pkt == "c")
|
||
agent.doContinue();
|
||
else
|
||
{
|
||
// Get the pc at which to resume
|
||
u32 addr;
|
||
if (sscanf(pkt.c_str(), "c%x", &addr) != 1)
|
||
{
|
||
WARN_LOG(COMMON, "Continue address invalid %s", pkt.c_str());
|
||
return;
|
||
}
|
||
agent.doContinue(addr);
|
||
}
|
||
}
|
||
|
||
void readAllRegs()
|
||
{
|
||
u32 *regs;
|
||
int c = agent.readAllRegs(®s);
|
||
std::string outpkt;
|
||
for (int i = 0; i < c; i++)
|
||
outpkt += pack(regs[i]);
|
||
sendPacket(outpkt);
|
||
}
|
||
|
||
void writeAllRegs(const std::string& pkt)
|
||
{
|
||
std::vector<u32> regs;
|
||
for (auto it = pkt.begin() + 1; it <= pkt.end() - 8; it += 8)
|
||
regs.push_back(unpack(&*it, 8));
|
||
|
||
agent.writeAllRegs(regs);
|
||
sendPacket("OK");
|
||
}
|
||
|
||
void readMem(const std::string& pkt)
|
||
{
|
||
u32 addr;
|
||
u32 len;
|
||
if (sscanf(pkt.c_str(), "m%x,%x:", &addr, &len) != 2)
|
||
{
|
||
WARN_LOG(COMMON, "readMem: invalid packet %s", pkt.c_str());
|
||
sendPacket("E01");
|
||
return;
|
||
}
|
||
const u8 *mem = agent.readMem(addr, len);
|
||
std::string outpkt;
|
||
for (u32 i = 0; i < len; i++)
|
||
{
|
||
char s[3];
|
||
sprintf(s,"%02x", mem[i]);
|
||
outpkt += s;
|
||
}
|
||
sendPacket(outpkt);
|
||
}
|
||
|
||
void writeMem(const std::string& pkt)
|
||
{
|
||
u32 addr;
|
||
u32 len;
|
||
if (sscanf(pkt.c_str(), "M%x,%x:", &addr, &len) != 2)
|
||
{
|
||
WARN_LOG(COMMON, "writeMem: invalid packet %s", pkt.c_str());
|
||
sendPacket("E01");
|
||
return;
|
||
}
|
||
std::vector<u8> data(len);
|
||
const char *p = &pkt[pkt.find(':')] + 1;
|
||
for (u32 i = 0; i < len; i++, p += 2)
|
||
{
|
||
u32 b;
|
||
sscanf(p,"%2x", &b);
|
||
data[i] = (u8)b;
|
||
}
|
||
agent.writeMem(addr, data);
|
||
sendPacket("OK");
|
||
}
|
||
|
||
void writeMemBin(const std::string& pkt)
|
||
{
|
||
u32 addr;
|
||
u32 len;
|
||
if (sscanf(pkt.c_str(), "X%x,%x:", &addr, &len) != 2)
|
||
{
|
||
WARN_LOG(COMMON, "writeMemBin invalid command: %s", pkt.c_str());
|
||
sendPacket("E01");
|
||
return;
|
||
}
|
||
const char *p = &pkt[pkt.find(':')] + 1;
|
||
std::vector<u8> data;
|
||
data.reserve(pkt.length() - (p - &pkt[0]));
|
||
for (; p <= &pkt.back(); p++)
|
||
{
|
||
u8 b = *p;
|
||
if (b == '}')
|
||
{
|
||
b = *++p ^ 0x20;
|
||
}
|
||
data.push_back(b);
|
||
}
|
||
agent.writeMem(addr, data);
|
||
sendPacket("OK");
|
||
}
|
||
|
||
void readReg(const std::string& pkt)
|
||
{
|
||
u32 regNum;
|
||
if (sscanf(pkt.c_str(), "p%x", ®Num) != 1)
|
||
{
|
||
WARN_LOG(COMMON, "readReg: invalid packet %s", pkt.c_str());
|
||
sendPacket("E01");
|
||
return;
|
||
}
|
||
u32 v = agent.readReg(regNum);
|
||
sendPacket(pack(v));
|
||
}
|
||
|
||
void writeReg(const std::string& pkt)
|
||
{
|
||
u32 regNum;
|
||
char vstr[9];
|
||
if (sscanf(pkt.c_str(), "P%x=%8s", ®Num, vstr) != 2)
|
||
{
|
||
WARN_LOG(COMMON, "writeReg: invalid packet %s", pkt.c_str());
|
||
sendPacket("E01");
|
||
return;
|
||
}
|
||
agent.writeReg(regNum, unpack(vstr, 8));
|
||
sendPacket("OK");
|
||
}
|
||
|
||
void query(const std::string& pkt)
|
||
{
|
||
if (pkt == "qC")
|
||
// Return the current thread ID. 0 is "any thread"
|
||
sendPacket("QC0.01");
|
||
else if (pkt.rfind("qCRC", 0) == 0)
|
||
{
|
||
WARN_LOG(COMMON, "CRC compute not supported %s", pkt.c_str());
|
||
sendPacket("E01");
|
||
}
|
||
else if (pkt == "qfThreadInfo")
|
||
// Obtain a list of all active thread IDs (first call)
|
||
sendPacket("m0");
|
||
else if (pkt == "qsThreadInfo")
|
||
// Obtain a list of all active thread IDs (subsequent calls -> 'l' == end of list)
|
||
sendPacket("l");
|
||
else if (pkt.rfind("qGetTLSAddr:", 0) == 0)
|
||
// Fetch the address associated with thread local storage
|
||
sendPacket("");
|
||
else if (pkt.rfind("qL", 0) == 0)
|
||
// Obtain thread information. deprecated
|
||
sendPacket("qM001");
|
||
else if (pkt == "qOffsets")
|
||
// Get section offsets. Not supported
|
||
sendPacket("");
|
||
else if (pkt.rfind("qP", 0) == 0)
|
||
// Returns information on thread. deprecated
|
||
sendPacket("");
|
||
else if (pkt.rfind("qRcmd,", 0) == 0)
|
||
{
|
||
std::string customCmd;
|
||
for (const char *p = pkt.c_str() + 6; *p != '\0'; p += 2)
|
||
customCmd += (char)unpack(p, 2);
|
||
DEBUG_LOG(COMMON, "query: custom command %s", customCmd.c_str());
|
||
if (customCmd == "reset")
|
||
restart();
|
||
else if (customCmd == "stack")
|
||
{
|
||
u32 len;
|
||
const u32 *data = agent.getStack(len);
|
||
len /= 4;
|
||
|
||
char reply[len * 9 * 2 + 1];
|
||
char *r = reply;
|
||
for (u32 i = 0; i < len; i++)
|
||
{
|
||
char n[10];
|
||
sprintf(n, "%08x ", *data++);
|
||
for (char *p = n; *p != 0; p++)
|
||
{
|
||
*r++ = packnb((*p >> 4) & 0xf);
|
||
*r++ = packnb(*p & 0xf);
|
||
}
|
||
}
|
||
*r = 0;
|
||
sendPacket(reply);
|
||
}
|
||
else
|
||
sendPacket("");
|
||
}
|
||
else if (pkt.rfind("qSupported", 0) == 0)
|
||
// Tell the remote stub about features supported by GDB,
|
||
// and query the stub for features it supports
|
||
sendPacket("PacketSize=10000");
|
||
else if (pkt.rfind("qSymbol:", 0) == 0)
|
||
// Notify the target that GDB is prepared to serve symbol lookup requests
|
||
sendPacket("OK");
|
||
else if (pkt.rfind("qThreadExtraInfo,", 0) == 0)
|
||
{
|
||
// Obtain from the target OS a printable string description of thread attributes
|
||
char s[19];
|
||
sprintf(s, "%02x%02x%02x%02x%02x%02x%02x%02x%02x", 'R', 'u', 'n', 'n', 'a', 'b', 'l', 'e', 0);
|
||
sendPacket(std::string(s, 18));
|
||
}
|
||
else if (pkt.rfind("qXfer:", 0) == 0)
|
||
// Read uninterpreted bytes from the target’s special data area identified by the keyword object
|
||
sendPacket("");
|
||
else if (pkt.rfind("qAttached", 0) == 0)
|
||
// Return an indication of whether the remote server attached to an existing process
|
||
// or created a new process
|
||
sendPacket("1"); // existing process
|
||
else if (pkt.rfind("qTfV", 0) == 0)
|
||
// request data about trace state variables
|
||
sendPacket("");
|
||
else if (pkt.rfind("qTfP", 0) == 0)
|
||
// request data about tracepoints
|
||
sendPacket("");
|
||
else if (pkt.rfind("qTStatus", 0) == 0)
|
||
// Ask the stub if there is a trace experiment running right now
|
||
sendPacket("");
|
||
else
|
||
WARN_LOG(COMMON, "query not supported %s", pkt.c_str());
|
||
}
|
||
|
||
void set(const std::string& pkt)
|
||
{
|
||
if (pkt.rfind("QPassSignals:", 0) == 0)
|
||
// Passing signals not supported
|
||
sendPacket("");
|
||
else if (pkt.rfind("QTDP", 0) == 0
|
||
|| pkt.rfind("QFrame", 0) == 0
|
||
|| pkt.rfind("QTStart", 0) == 0
|
||
|| pkt.rfind("QTStop", 0) == 0
|
||
|| pkt.rfind("QTinit", 0) == 0
|
||
|| pkt.rfind("QTro", 0) == 0)
|
||
// No tracepoint feature supported
|
||
sendPacket("");
|
||
else
|
||
WARN_LOG(COMMON, "set not supported %s", pkt.c_str());
|
||
}
|
||
|
||
void vpacket(const std::string& pkt)
|
||
{
|
||
if (pkt.rfind("vAttach;", 0) == 0)
|
||
sendPacket("S05");
|
||
else if (pkt.rfind("vCont?", 0) == 0)
|
||
// not supported
|
||
sendPacket("vCont;s;c");
|
||
else if (pkt.rfind("vCont", 0) == 0)
|
||
// not supported
|
||
WARN_LOG(COMMON, "vCont not supported %s", pkt.c_str());
|
||
else if (pkt.rfind("vFile:", 0) == 0)
|
||
// not supported
|
||
sendPacket("");
|
||
else if (pkt.rfind("vFlashErase:", 0) == 0)
|
||
// not supported
|
||
sendPacket("E01");
|
||
else if (pkt.rfind("vFlashWrite:", 0) == 0)
|
||
// not supported
|
||
sendPacket("E01");
|
||
else if (pkt.rfind("vFlashDone:", 0) == 0)
|
||
// not supported
|
||
sendPacket("E01");
|
||
else if (pkt.rfind("vRun;", 0) == 0)
|
||
{
|
||
if (pkt != "vRun;")
|
||
WARN_LOG(COMMON, "unexpected vRun args ignored: %s", pkt.c_str());
|
||
agent.restart();
|
||
sendPacket("S05");
|
||
}
|
||
else if (pkt.rfind("vKill", 0) == 0)
|
||
{
|
||
sendPacket("OK");
|
||
agent.kill();
|
||
}
|
||
else if (pkt.rfind("vMustReplyEmpty", 0) == 0)
|
||
// Reply empty packet
|
||
sendPacket("");
|
||
else
|
||
WARN_LOG(COMMON, "unknown v packet: %s", pkt.c_str());
|
||
}
|
||
|
||
void restart()
|
||
{
|
||
agent.restart();
|
||
}
|
||
|
||
void step(u32 what = 0)
|
||
{
|
||
agent.step();
|
||
sendPacket("S05");
|
||
}
|
||
|
||
void insertMatchpoint(const std::string& pkt)
|
||
{
|
||
u32 type;
|
||
u32 addr;
|
||
u32 len;
|
||
if (sscanf(pkt.c_str(), "Z%1d,%x,%1d", &type, &addr, &len) != 3) {
|
||
WARN_LOG(COMMON, "insertMatchpoint: unknown packet: %s", pkt.c_str());
|
||
sendPacket("E01");
|
||
}
|
||
switch (type) {
|
||
case 0: // soft bp
|
||
if (agent.insertMatchpoint(0, addr, len))
|
||
sendPacket("OK");
|
||
else
|
||
sendPacket("E01");
|
||
break;
|
||
case 1: // hardware bp
|
||
sendPacket("");
|
||
break;
|
||
case 2: // write watchpoint
|
||
sendPacket("");
|
||
break;
|
||
case 3: // read watchpoint
|
||
sendPacket("");
|
||
break;
|
||
case 4: // access watchpoint
|
||
sendPacket("");
|
||
break;
|
||
default:
|
||
sendPacket("");
|
||
break;
|
||
}
|
||
}
|
||
|
||
void removeMatchpoint(const std::string& pkt)
|
||
{
|
||
u32 type;
|
||
u32 addr;
|
||
u32 len;
|
||
if (sscanf(pkt.c_str(), "z%1d,%x,%1d", &type, &addr, &len) != 3) {
|
||
WARN_LOG(COMMON, "removeMatchpoint: unknown packet: %s", pkt.c_str());
|
||
sendPacket("E01");
|
||
}
|
||
switch (type) {
|
||
case 0: // soft bp
|
||
if (agent.removeMatchpoint(0, addr, len))
|
||
sendPacket("OK");
|
||
else
|
||
sendPacket("E01");
|
||
break;
|
||
case 1: // hardware bp
|
||
sendPacket("");
|
||
break;
|
||
case 2: // write watchpoint
|
||
sendPacket("");
|
||
break;
|
||
case 3: // read watchpoint
|
||
sendPacket("");
|
||
break;
|
||
case 4: // access watchpoint
|
||
sendPacket("");
|
||
break;
|
||
default:
|
||
sendPacket("");
|
||
break;
|
||
}
|
||
}
|
||
|
||
void interrupt()
|
||
{
|
||
u32 signal = agent.interrupt();
|
||
char s[10];
|
||
sprintf(s, "S%02x", signal);
|
||
sendPacket(s);
|
||
}
|
||
|
||
char recvChar()
|
||
{
|
||
char c;
|
||
int rc = ::recv(clientSocket, &c, 1, 0);
|
||
if (rc <= 0)
|
||
throw Error("gdb: I/O error");
|
||
return c;
|
||
}
|
||
|
||
void sendChar(char c)
|
||
{
|
||
std::unique_lock<std::mutex> lock(outMutex);
|
||
int rc = ::send(clientSocket, &c, 1, 0);
|
||
if (rc <= 0)
|
||
throw Error("gdb: I/O error");
|
||
}
|
||
|
||
u8 unpack(char c)
|
||
{
|
||
c = std::tolower(c);
|
||
if (c <= '9')
|
||
return c - '0';
|
||
else
|
||
return c - 'a' + 10;
|
||
}
|
||
|
||
char packnb(u8 b)
|
||
{
|
||
if (b < 10)
|
||
return '0' + b;
|
||
else
|
||
return 'a' + b - 10;
|
||
}
|
||
|
||
std::string packb(u8 v)
|
||
{
|
||
std::string s(1, packnb((v >> 4) & 0xf));
|
||
s += packnb(v & 0xf);
|
||
return s;
|
||
}
|
||
|
||
std::string pack(u32 v)
|
||
{
|
||
return packb(v & 0xff) + packb((v >> 8) & 0xff)
|
||
+ packb((v >> 16) & 0xff) + packb((v >> 24) & 0xff);
|
||
}
|
||
|
||
u32 unpack(const char *s, int l)
|
||
{
|
||
u32 r = 0;
|
||
for (int i = 0; i < l && *s != '\0'; i += 2, s += 2)
|
||
{
|
||
r |= (unpack(s[0]) << 4 | unpack(s[1])) << (i * 4);
|
||
}
|
||
return r;
|
||
}
|
||
|
||
std::string recvPacket()
|
||
{
|
||
std::string pkt;
|
||
// look for start character ('$') or BREAK
|
||
char c = recvChar();
|
||
if (c == 3)
|
||
return std::string("\03");
|
||
if (c != '$')
|
||
return pkt;
|
||
|
||
// read until '#'
|
||
u8 checksum = 0;
|
||
while (!stopRequested)
|
||
{
|
||
c = recvChar();
|
||
if (c == '$')
|
||
{
|
||
checksum = 0;
|
||
pkt.clear();
|
||
|
||
continue;
|
||
}
|
||
|
||
if (c == '#')
|
||
break;
|
||
|
||
checksum += (u8)c;
|
||
pkt.push_back(c);
|
||
}
|
||
if (stopRequested)
|
||
{
|
||
pkt.clear();
|
||
return pkt;
|
||
}
|
||
u8 recvchk = unpack(recvChar()) << 4;
|
||
recvchk |= unpack(recvChar());
|
||
|
||
// If the checksums don't match print a warning, and put the
|
||
// negative ack back to the client. Otherwise put a positive ack.
|
||
if (checksum != recvchk)
|
||
{
|
||
sendChar('-'); // Failed checksum
|
||
return "";
|
||
}
|
||
else
|
||
{
|
||
sendChar('+'); // Successful transfer
|
||
return pkt;
|
||
}
|
||
}
|
||
|
||
void sendPacket(const std::string& pkt)
|
||
{ DEBUG_LOG(NETWORK, "gdb: sending pkt");
|
||
std::unique_lock<std::mutex> lock(outMutex);
|
||
std::string data{'$'};
|
||
u8 checksum = 0;
|
||
for (char c : pkt)
|
||
{
|
||
if (c == '$' || c == '#' || c == '*' || c == '}')
|
||
{
|
||
c ^= 0x20;
|
||
checksum += (u8)'}';
|
||
data.push_back('}');
|
||
}
|
||
checksum += (u8)c;
|
||
data.push_back(c);
|
||
}
|
||
data.push_back('#');
|
||
char s[9];
|
||
sprintf(s, "%02x", checksum);
|
||
data += s;
|
||
DEBUG_LOG(NETWORK, "gdb: sent %s", data.c_str());
|
||
int ret = ::send(clientSocket, data.c_str(), data.length(), 0);
|
||
if (ret < (int)data.length())
|
||
throw Error("I/O error");
|
||
}
|
||
|
||
bool stopRequested = false;
|
||
bool attached = false;
|
||
bool postDebugTrapNeeded = false;
|
||
sock_t serverSocket = INVALID_SOCKET;
|
||
sock_t clientSocket = INVALID_SOCKET;
|
||
std::thread thread;
|
||
std::mutex outMutex;
|
||
public:
|
||
DebugAgent agent;
|
||
};
|
||
|
||
static GdbServer gdbServer;
|
||
|
||
void init()
|
||
{
|
||
gdbServer.init();
|
||
}
|
||
|
||
void term()
|
||
{
|
||
gdbServer.term();
|
||
}
|
||
|
||
void debugTrap(u32 event)
|
||
{
|
||
gdbServer.debugTrap(event);
|
||
}
|
||
|
||
void subroutineCall()
|
||
{
|
||
gdbServer.agent.subroutineCall();
|
||
}
|
||
|
||
void subroutineReturn()
|
||
{
|
||
gdbServer.agent.subroutineReturn();
|
||
}
|
||
|
||
static void emuEventCallback(Event event)
|
||
{
|
||
switch (event)
|
||
{
|
||
case Event::Resume:
|
||
if (!gdbServer.isRunning())
|
||
gdbServer.run();
|
||
break;
|
||
case Event::Terminate:
|
||
gdbServer.stop();
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
}
|
||
#endif
|