mirror of
https://github.com/RKX1209/nsemu.git
synced 2024-05-11 00:55:24 -04:00
539 lines
16 KiB
C++
539 lines
16 KiB
C++
/* nsemu - LGPL - Copyright 2018 rkx1209<rkx1209dev@gmail.com> */
|
|
/* TODO: Move host dependent functions to host_util.hpp */
|
|
#include "Nsemu.hpp"
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
|
|
namespace GdbStub {
|
|
|
|
static int server_fd, client_fd;
|
|
|
|
uint8_t cmd_buf[GDB_BUFFER_SIZE], last_packet[GDB_BUFFER_SIZE + 4];
|
|
|
|
static int buf_index, line_sum, checksum, last_packet_len;
|
|
|
|
static RSState state = RS_IDLE;
|
|
|
|
volatile bool enabled = false;
|
|
volatile bool step = false;
|
|
volatile bool cont = false;
|
|
|
|
std::vector<Breakpoint> bp_list;
|
|
std::vector<Watchpoint> wp_list;
|
|
|
|
Breakpoint::Breakpoint(uint64_t a, unsigned int l, int t) : addr(a), len(l), type(t) {
|
|
oldop = ARMv8::ReadInst (a);
|
|
}
|
|
Watchpoint::Watchpoint(uint64_t a, unsigned int l, int t) : addr(a), len(l), type(t) {
|
|
}
|
|
|
|
static inline bool IsXdigit(char ch) {
|
|
return ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F') || ('0' <= ch && ch <= '9');
|
|
}
|
|
|
|
static inline int FromHex(int v)
|
|
{
|
|
if (v >= '0' && v <= '9')
|
|
return v - '0';
|
|
else if (v >= 'A' && v <= 'F')
|
|
return v - 'A' + 10;
|
|
else if (v >= 'a' && v <= 'f')
|
|
return v - 'a' + 10;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static inline int ToHex(int v)
|
|
{
|
|
if (v < 10)
|
|
return v + '0';
|
|
else
|
|
return v - 10 + 'a';
|
|
}
|
|
|
|
static void MemToHex(char *buf, const uint8_t *mem, int len)
|
|
{
|
|
char *q;
|
|
q = buf;
|
|
for(int i = 0; i < len; i++) {
|
|
int c = mem[i];
|
|
*q++ = ToHex(c >> 4);
|
|
*q++ = ToHex(c & 0xf);
|
|
}
|
|
*q = '\0';
|
|
}
|
|
|
|
void Init() {
|
|
struct sockaddr_in addr;
|
|
|
|
server_fd = socket (AF_INET, SOCK_STREAM, 0);
|
|
if (server_fd < 0) {
|
|
ns_abort ("socket failed\n");
|
|
}
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(1234);
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
|
|
if (::bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
|
|
ns_abort ("bind failed");
|
|
}
|
|
|
|
if (listen(server_fd, 5) != 0) {
|
|
ns_abort ("listen failed");
|
|
}
|
|
client_fd = accept(server_fd, nullptr, nullptr);
|
|
if (client_fd < 0) {
|
|
ns_abort ("Failed to accept gdb client\n");
|
|
}
|
|
enabled = true;
|
|
}
|
|
|
|
uint8_t ReadByte() {
|
|
uint8_t byte;
|
|
auto size = recv (client_fd, &byte, 1, MSG_WAITALL);
|
|
if (size != 1) {
|
|
ns_abort ("Failed recv :%ld", size);
|
|
}
|
|
return byte;
|
|
}
|
|
|
|
void WriteBytes(uint8_t *buf, size_t len) {
|
|
if (send (client_fd, buf, len, 0) == -1) {
|
|
ns_abort ("Failed send\n");
|
|
}
|
|
}
|
|
|
|
void SendPacket(uint8_t *buf, size_t len) {
|
|
int csum;
|
|
uint8_t *p;
|
|
for (;;) {
|
|
p = last_packet;
|
|
*(p++) = '$';
|
|
memcpy(p, buf, len);
|
|
p += len;
|
|
csum = 0;
|
|
for(int i = 0; i < len; i++) {
|
|
csum += buf[i];
|
|
}
|
|
*(p++) = '#';
|
|
*(p++) = ToHex((csum >> 4) & 0xf);
|
|
*(p++) = ToHex((csum) & 0xf);
|
|
|
|
last_packet_len = p - last_packet;
|
|
WriteBytes (last_packet, last_packet_len);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void WritePacket(std::string buf) {
|
|
SendPacket ((uint8_t *)buf.c_str(), buf.size());
|
|
}
|
|
|
|
static int IsQueryPacket (const char *p, const char *query, char separator)
|
|
{
|
|
unsigned int query_len = strlen(query);
|
|
|
|
return strncmp(p, query, query_len) == 0 && (p[query_len] == '\0' || p[query_len] == separator);
|
|
}
|
|
|
|
static int TargetMemoryRW(uint64_t addr, uint8_t *buf, int len, bool is_write) {
|
|
if ((int64_t)addr < 0) {
|
|
return -1; //FIXME: Correct validation is required
|
|
}
|
|
if (is_write) {
|
|
ARMv8::GdbWriteBytes (addr, buf, len);
|
|
} else {
|
|
ARMv8::GdbReadBytes (addr, buf, len);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ReadRegister (uint8_t *buf, int reg) {
|
|
if (reg < GPR_ZERO) {
|
|
*(uint64_t*)buf = X(reg);
|
|
} else {
|
|
switch(reg) {
|
|
case 31:
|
|
*(uint64_t *)buf = X(GPR_SP);
|
|
break;
|
|
case 32:
|
|
*(uint64_t *)buf = X(PC_IDX);
|
|
break;
|
|
default:
|
|
*(uint64_t *)buf = 0xdeadbeef;
|
|
break;
|
|
}
|
|
}
|
|
return sizeof(uint64_t);
|
|
}
|
|
|
|
static void SendSignal(uint32_t signal) {
|
|
char buf[GDB_BUFFER_SIZE];
|
|
snprintf(buf, sizeof(buf), "T%02xthread:%02x;", signal, 1);
|
|
WritePacket(buf);
|
|
}
|
|
|
|
void Trap() {
|
|
cont = false;
|
|
SendSignal(GDB_SIGNAL_TRAP);
|
|
}
|
|
|
|
static void Stepi() {
|
|
step = true;
|
|
}
|
|
|
|
static int SwBreakpointInsert(unsigned long addr, unsigned long len, int type) {
|
|
Breakpoint bp(addr, len, type);
|
|
bp_list.push_back(bp);
|
|
ns_print("[Add bp] 0x%lx, %u, %d, (oldop: 0x%08lx)\n", addr, len, type, bp.oldop);
|
|
ARMv8::WriteU32(addr, BRK_0x0_INST);
|
|
return 0;
|
|
}
|
|
|
|
static int SwBreakpointRemove(unsigned long addr, unsigned long len, int type) {
|
|
if (bp_list.empty()) {
|
|
return -1;
|
|
}
|
|
|
|
auto bp_it = std::find(bp_list.begin(), bp_list.end(), Breakpoint(addr, len, type));
|
|
if (bp_it == bp_list.end()) {
|
|
ns_print ("Breakpoint not found\n");
|
|
return -1;
|
|
}
|
|
ns_print("[Remove bp] 0x%lx, %u, %d, (oldop: 0x%08lx)\n", addr, len, type, (*bp_it).oldop);
|
|
ARMv8::WriteU32((*bp_it).addr, (*bp_it).oldop); // Restore an original operation
|
|
bp_list.erase(bp_it);
|
|
return 0;
|
|
}
|
|
|
|
static int WatchpointInsert(unsigned long addr, unsigned long len, int type) {
|
|
Watchpoint wp(addr, len, type);
|
|
wp_list.push_back(wp);
|
|
ns_print("[Add wp] 0x%lx, %u, %d\n", addr, len, type);
|
|
return 0;
|
|
}
|
|
|
|
static int WatchpointRemove(unsigned long addr, unsigned long len, int type) {
|
|
if (wp_list.empty()) {
|
|
return -1;
|
|
}
|
|
|
|
auto wp_it = std::find(wp_list.begin(), wp_list.end(), Watchpoint(addr, len, type));
|
|
if (wp_it == wp_list.end()) {
|
|
ns_print ("Watchpoint not found\n");
|
|
return -1;
|
|
}
|
|
ns_print("[Remove wp] 0x%lx, %u, %d\n", addr, len, type);
|
|
wp_list.erase(wp_it);
|
|
return 0;
|
|
}
|
|
|
|
static void HitWatchpoint(unsigned long addr, int type) {
|
|
cont = false;
|
|
step = false;
|
|
char buf[GDB_BUFFER_SIZE];
|
|
const char *type_s;
|
|
if (type == GDB_WATCHPOINT_READ) {
|
|
type_s = "r";
|
|
} else if (type == GDB_WATCHPOINT_ACCESS) {
|
|
type_s = "a";
|
|
} else {
|
|
type_s = "";
|
|
}
|
|
|
|
snprintf(buf, sizeof(buf), "T%02xthread:%02x;%swatch:%lx;", GDB_SIGNAL_TRAP, 1, type_s, addr);
|
|
WritePacket(buf);
|
|
}
|
|
|
|
void NotifyMemAccess(unsigned long addr, size_t len, bool read) {
|
|
int type = read ? GDB_WATCHPOINT_READ : GDB_WATCHPOINT_WRITE;
|
|
for (int i = 0; i < wp_list.size(); i++) {
|
|
Watchpoint wp = wp_list[i];
|
|
if (addr <= wp.addr && wp.addr + wp.len <= addr + len) {
|
|
if (wp.type == GDB_WATCHPOINT_ACCESS || wp.type == type) {
|
|
HitWatchpoint (addr, wp.type);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int BreakpointInsert(unsigned long addr, unsigned long len, int type) {
|
|
int err = -1;
|
|
switch (type) {
|
|
case GDB_BREAKPOINT_SW:
|
|
case GDB_BREAKPOINT_HW:
|
|
return SwBreakpointInsert (addr, len, type);
|
|
case GDB_WATCHPOINT_WRITE:
|
|
case GDB_WATCHPOINT_READ:
|
|
case GDB_WATCHPOINT_ACCESS:
|
|
return WatchpointInsert (addr, len, type);
|
|
default:
|
|
break;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int BreakpointRemove(unsigned long addr, unsigned long len, int type) {
|
|
int err = -1;
|
|
switch (type) {
|
|
case GDB_BREAKPOINT_SW:
|
|
case GDB_BREAKPOINT_HW:
|
|
return SwBreakpointRemove (addr, len, type);
|
|
case GDB_WATCHPOINT_WRITE:
|
|
case GDB_WATCHPOINT_READ:
|
|
case GDB_WATCHPOINT_ACCESS:
|
|
return WatchpointRemove (addr, len, type);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static void Continue() {
|
|
cont = true;
|
|
}
|
|
|
|
static RSState HandleCommand(char *line_buf) {
|
|
const char *p;
|
|
uint32_t thread;
|
|
int ch, reg_size, type, res;
|
|
char buf[GDB_BUFFER_SIZE];
|
|
uint8_t mem_buf[GDB_BUFFER_SIZE];
|
|
unsigned long addr, len;
|
|
|
|
p = line_buf;
|
|
ch = *p++;
|
|
switch(ch) {
|
|
case '?':
|
|
/* XXX: Is it correct to fix thread id to '1'? */
|
|
SendSignal(GDB_SIGNAL_TRAP);
|
|
break;
|
|
case 'c':
|
|
if (*p != '\0') {
|
|
addr = strtol(p, (char **)&p, 16);
|
|
}
|
|
Continue ();
|
|
break;
|
|
case 'g':
|
|
len = 0;
|
|
for (addr = 0; addr < 33; addr++) {
|
|
reg_size = ReadRegister (mem_buf + len, addr);
|
|
len += reg_size;
|
|
}
|
|
MemToHex(buf, mem_buf, len);
|
|
WritePacket(buf);
|
|
break;
|
|
case 'm':
|
|
addr = strtoul(p, (char **)&p, 16);
|
|
if (*p == ',')
|
|
p++;
|
|
len = strtol(p, NULL, 16);
|
|
|
|
/* memtohex() doubles the required space */
|
|
if (len > GDB_BUFFER_SIZE / 2) {
|
|
WritePacket ("E22");
|
|
break;
|
|
}
|
|
|
|
if (TargetMemoryRW (addr, mem_buf, len, false) != 0) {
|
|
WritePacket ("E14");
|
|
} else {
|
|
MemToHex(buf, mem_buf, len);
|
|
WritePacket(buf);
|
|
}
|
|
break;
|
|
case 'p':
|
|
addr = strtol(p, (char **)&p, 16);
|
|
reg_size = ReadRegister (mem_buf, addr);
|
|
if (reg_size) {
|
|
MemToHex(buf, mem_buf, reg_size);
|
|
WritePacket(buf);
|
|
} else {
|
|
WritePacket("E14");
|
|
}
|
|
break;
|
|
case 'q':
|
|
case 'Q':
|
|
if (IsQueryPacket(p, "Supported", ':')) {
|
|
snprintf(buf, sizeof(buf), "PacketSize=%x", GDB_BUFFER_SIZE);
|
|
WritePacket(buf);
|
|
break;
|
|
} else if (strcmp(p, "C") == 0) {
|
|
WritePacket("QC1");
|
|
break;
|
|
}
|
|
goto unknown_command;
|
|
case 'z':
|
|
case 'Z':
|
|
type = strtol(p, (char **)&p, 16);
|
|
if (*p == ',')
|
|
p++;
|
|
addr = strtol(p, (char **)&p, 16);
|
|
if (*p == ',')
|
|
p++;
|
|
len = strtol(p, (char **)&p, 16);
|
|
if (ch == 'Z') {
|
|
res = BreakpointInsert(addr, len, type);
|
|
} else {
|
|
res = BreakpointRemove(addr, len, type);
|
|
}
|
|
if (res >= 0)
|
|
WritePacket("OK");
|
|
else
|
|
WritePacket("E22");
|
|
break;
|
|
case 's':
|
|
Stepi();
|
|
break;
|
|
case 'H':
|
|
type = *p++;
|
|
thread = strtol(p, (char **)&p, 16);
|
|
if (thread == -1 || thread == 0) {
|
|
WritePacket("OK");
|
|
break;
|
|
}
|
|
switch (type) {
|
|
case 'c':
|
|
WritePacket("OK");
|
|
break;
|
|
case 'g':
|
|
WritePacket("OK");
|
|
break;
|
|
default:
|
|
WritePacket("E22");
|
|
break;
|
|
}
|
|
break;
|
|
case 'T':
|
|
thread = strtol(p, (char **)&p, 16);
|
|
WritePacket("OK");
|
|
break;
|
|
default:
|
|
unknown_command:
|
|
/* put empty packet */
|
|
buf[0] = '\0';
|
|
WritePacket(buf);
|
|
break;
|
|
}
|
|
return RS_IDLE;
|
|
}
|
|
|
|
void HandlePacket() {
|
|
uint8_t ch = ReadByte();
|
|
uint8_t reply;
|
|
switch (state) {
|
|
case RS_IDLE:
|
|
if (ch == '$') {
|
|
/* start of command packet */
|
|
buf_index = 0;
|
|
line_sum = 0;
|
|
state = RS_GETLINE;
|
|
} else {
|
|
//printf("%s: received garbage between packets: 0x%x\n", __func__, ch);
|
|
}
|
|
break;
|
|
case RS_GETLINE:
|
|
if (ch == '}') {
|
|
state = RS_GETLINE_ESC;
|
|
line_sum += ch;
|
|
} else if (ch == '*') {
|
|
/* start run length encoding sequence */
|
|
state = RS_GETLINE_RLE;
|
|
line_sum += ch;
|
|
} else if (ch == '#') {
|
|
/* end of command, start of checksum*/
|
|
state = RS_CHKSUM1;
|
|
} else if (buf_index >= sizeof(cmd_buf) - 1) {
|
|
state = RS_IDLE;
|
|
ns_print ("Gdb: command buffer overrun, dropping command\n");
|
|
} else {
|
|
/* unescaped command character */
|
|
cmd_buf[buf_index++] = ch;
|
|
line_sum += ch;
|
|
}
|
|
break;
|
|
case RS_GETLINE_ESC:
|
|
if (ch == '#') {
|
|
/* unexpected end of command in escape sequence */
|
|
state = RS_CHKSUM1;
|
|
} else if (buf_index >= sizeof(cmd_buf) - 1) {
|
|
/* command buffer overrun */
|
|
state = RS_IDLE;
|
|
ns_print ("Gdb: command buffer overrun, dropping command\n");
|
|
} else {
|
|
/* parse escaped character and leave escape state */
|
|
cmd_buf[buf_index++] = ch ^ 0x20;
|
|
line_sum += ch;
|
|
state = RS_GETLINE;
|
|
}
|
|
break;
|
|
case RS_GETLINE_RLE:
|
|
if (ch < ' ') {
|
|
/* invalid RLE count encoding */
|
|
state = RS_GETLINE;
|
|
ns_print ("Gdb: got invalid RLE count: 0x%x\n", ch);
|
|
} else {
|
|
/* decode repeat length */
|
|
int repeat = (unsigned char)ch - ' ' + 3;
|
|
if (buf_index + repeat >= sizeof(cmd_buf) - 1) {
|
|
/* that many repeats would overrun the command buffer */
|
|
ns_print ("Gdb: command buffer overrun, dropping command\n");
|
|
state = RS_IDLE;
|
|
} else if (buf_index < 1) {
|
|
/* got a repeat but we have nothing to repeat */
|
|
ns_print ("Gdb: got invalid RLE sequence\n");
|
|
state = RS_GETLINE;
|
|
} else {
|
|
/* repeat the last character */
|
|
memset(cmd_buf + buf_index,
|
|
cmd_buf[buf_index - 1], repeat);
|
|
buf_index += repeat;
|
|
line_sum += ch;
|
|
state = RS_GETLINE;
|
|
}
|
|
}
|
|
break;
|
|
case RS_CHKSUM1:
|
|
/* get high hex digit of checksum */
|
|
if (!IsXdigit(ch)) {
|
|
ns_print("Gdb: got invalid command checksum digit\n");
|
|
state = RS_GETLINE;
|
|
break;
|
|
}
|
|
cmd_buf[buf_index] = '\0';
|
|
checksum = FromHex(ch) << 4;
|
|
state = RS_CHKSUM2;
|
|
break;
|
|
case RS_CHKSUM2:
|
|
/* get low hex digit of checksum */
|
|
if (!IsXdigit(ch)) {
|
|
ns_print("Gdb: got invalid command checksum digit\n");
|
|
state = RS_GETLINE;
|
|
break;
|
|
}
|
|
checksum |= FromHex(ch);
|
|
|
|
if (checksum != (line_sum & 0xff)) {
|
|
ns_print("Gdb: got command packet with incorrect checksum\n");
|
|
/* send NAK reply */
|
|
reply = '-';
|
|
WriteBytes (&reply, 1);
|
|
state = RS_IDLE;
|
|
} else {
|
|
/* send ACK reply */
|
|
reply = '+';
|
|
WriteBytes (&reply, 1);
|
|
state = HandleCommand ((char *)cmd_buf);
|
|
}
|
|
break;
|
|
default:
|
|
ns_print("Gdb: got unknown status\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
};
|