// Copyright 2013 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. // Originally written by Sven Peter for anergistic. #include #include #include #include #ifdef _WIN32 #include #include #else #include #include #include #include #endif #include "Common/Logging/Log.h" #include "Core/HW/CPU.h" #include "Core/HW/Memmap.h" #include "Core/Host.h" #include "Core/PowerPC/GDBStub.h" #include "Core/PowerPC/Gekko.h" #include "Core/PowerPC/PPCCache.h" #include "Core/PowerPC/PowerPC.h" #define GDB_BFR_MAX 10000 #define GDB_MAX_BP 10 #define GDB_STUB_START '$' #define GDB_STUB_END '#' #define GDB_STUB_ACK '+' #define GDB_STUB_NAK '-' static int tmpsock = -1; static int sock = -1; static u8 cmd_bfr[GDB_BFR_MAX]; static u32 cmd_len; static u32 sig = 0; static u32 send_signal = 0; static u32 step_break = 0; typedef struct { u32 active; u32 addr; u32 len; } gdb_bp_t; static gdb_bp_t bp_x[GDB_MAX_BP]; static gdb_bp_t bp_r[GDB_MAX_BP]; static gdb_bp_t bp_w[GDB_MAX_BP]; static gdb_bp_t bp_a[GDB_MAX_BP]; // private helpers static u8 hex2char(u8 hex) { if (hex >= '0' && hex <= '9') return hex - '0'; else if (hex >= 'a' && hex <= 'f') return hex - 'a' + 0xa; else if (hex >= 'A' && hex <= 'F') return hex - 'A' + 0xa; ERROR_LOG(GDB_STUB, "Invalid nibble: %c (%02x)", hex, hex); return 0; } static u8 nibble2hex(u8 n) { n &= 0xf; if (n < 0xa) return '0' + n; else return 'A' + n - 0xa; } static void mem2hex(u8* dst, u8* src, u32 len) { u8 tmp; while (len-- > 0) { tmp = *src++; *dst++ = nibble2hex(tmp >> 4); *dst++ = nibble2hex(tmp); } } static void hex2mem(u8* dst, u8* src, u32 len) { while (len-- > 0) { *dst++ = (hex2char(*src) << 4) | hex2char(*(src + 1)); src += 2; } } static u8 gdb_read_byte() { ssize_t res; u8 c = '+'; res = recv(sock, &c, 1, MSG_WAITALL); if (res != 1) { ERROR_LOG(GDB_STUB, "recv failed : %ld", res); gdb_deinit(); } return c; } static u8 gdb_calc_chksum() { u32 len = cmd_len; u8* ptr = cmd_bfr; u8 c = 0; while (len-- > 0) c += *ptr++; return c; } static gdb_bp_t* gdb_bp_ptr(u32 type) { switch (type) { case GDB_BP_TYPE_X: return bp_x; case GDB_BP_TYPE_R: return bp_x; case GDB_BP_TYPE_W: return bp_x; case GDB_BP_TYPE_A: return bp_x; default: return nullptr; } } static gdb_bp_t* gdb_bp_empty_slot(u32 type) { gdb_bp_t* p; u32 i; p = gdb_bp_ptr(type); if (p == nullptr) return nullptr; for (i = 0; i < GDB_MAX_BP; i++) { if (p[i].active == 0) return &p[i]; } return nullptr; } static gdb_bp_t* gdb_bp_find(u32 type, u32 addr, u32 len) { gdb_bp_t* p; u32 i; p = gdb_bp_ptr(type); if (p == nullptr) return nullptr; for (i = 0; i < GDB_MAX_BP; i++) { if (p[i].active == 1 && p[i].addr == addr && p[i].len == len) return &p[i]; } return nullptr; } static void gdb_bp_remove(u32 type, u32 addr, u32 len) { gdb_bp_t* p; do { p = gdb_bp_find(type, addr, len); if (p != nullptr) { DEBUG_LOG(GDB_STUB, "gdb: removed a breakpoint: %08x bytes at %08x", len, addr); p->active = 0; memset(p, 0, sizeof(gdb_bp_t)); } } while (p != nullptr); } static int gdb_bp_check(u32 addr, u32 type) { gdb_bp_t* p; u32 i; p = gdb_bp_ptr(type); if (p == nullptr) return 0; for (i = 0; i < GDB_MAX_BP; i++) { if (p[i].active == 1 && (addr >= p[i].addr && addr < p[i].addr + p[i].len)) return 1; } return 0; } static void gdb_nak() { const char nak = GDB_STUB_NAK; ssize_t res; res = send(sock, &nak, 1, 0); if (res != 1) ERROR_LOG(GDB_STUB, "send failed"); } static void gdb_ack() { const char ack = GDB_STUB_ACK; ssize_t res; res = send(sock, &ack, 1, 0); if (res != 1) ERROR_LOG(GDB_STUB, "send failed"); } static void gdb_read_command() { u8 c; u8 chk_read, chk_calc; cmd_len = 0; memset(cmd_bfr, 0, sizeof cmd_bfr); c = gdb_read_byte(); if (c == '+') { // ignore ack return; } else if (c == 0x03) { CPU::Break(); gdb_signal(SIGTRAP); return; } else if (c != GDB_STUB_START) { DEBUG_LOG(GDB_STUB, "gdb: read invalid byte %02x", c); return; } while ((c = gdb_read_byte()) != GDB_STUB_END) { cmd_bfr[cmd_len++] = c; if (cmd_len == sizeof cmd_bfr) { ERROR_LOG(GDB_STUB, "gdb: cmd_bfr overflow"); gdb_nak(); return; } } chk_read = hex2char(gdb_read_byte()) << 4; chk_read |= hex2char(gdb_read_byte()); chk_calc = gdb_calc_chksum(); if (chk_calc != chk_read) { ERROR_LOG(GDB_STUB, "gdb: invalid checksum: calculated %02x and read %02x for $%s# (length: %d)", chk_calc, chk_read, cmd_bfr, cmd_len); cmd_len = 0; gdb_nak(); return; } DEBUG_LOG(GDB_STUB, "gdb: read command %c with a length of %d: %s", cmd_bfr[0], cmd_len, cmd_bfr); gdb_ack(); } static int gdb_data_available() { struct timeval t; fd_set _fds, *fds = &_fds; FD_ZERO(fds); FD_SET(sock, fds); t.tv_sec = 0; t.tv_usec = 20; if (select(sock + 1, fds, nullptr, nullptr, &t) < 0) { ERROR_LOG(GDB_STUB, "select failed"); return 0; } if (FD_ISSET(sock, fds)) return 1; return 0; } static void gdb_reply(const char* reply) { u8 chk; u32 left; u8* ptr; int n; if (!gdb_active()) return; memset(cmd_bfr, 0, sizeof cmd_bfr); cmd_len = strlen(reply); if (cmd_len + 4 > sizeof cmd_bfr) ERROR_LOG(GDB_STUB, "cmd_bfr overflow in gdb_reply"); memcpy(cmd_bfr + 1, reply, cmd_len); cmd_len++; chk = gdb_calc_chksum(); cmd_len--; cmd_bfr[0] = GDB_STUB_START; cmd_bfr[cmd_len + 1] = GDB_STUB_END; cmd_bfr[cmd_len + 2] = nibble2hex(chk >> 4); cmd_bfr[cmd_len + 3] = nibble2hex(chk); DEBUG_LOG(GDB_STUB, "gdb: reply (len: %d): %s", cmd_len, cmd_bfr); ptr = cmd_bfr; left = cmd_len + 4; while (left > 0) { n = send(sock, ptr, left, 0); if (n < 0) { ERROR_LOG(GDB_STUB, "gdb: send failed"); return gdb_deinit(); } left -= n; ptr += n; } } static void gdb_handle_query() { DEBUG_LOG(GDB_STUB, "gdb: query '%s'", cmd_bfr + 1); if (!strcmp((const char*)(cmd_bfr + 1), "TStatus")) { return gdb_reply("T0"); } gdb_reply(""); } static void gdb_handle_set_thread() { if (memcmp(cmd_bfr, "Hg0", 3) == 0 || memcmp(cmd_bfr, "Hc-1", 4) == 0 || memcmp(cmd_bfr, "Hc0", 4) == 0 || memcmp(cmd_bfr, "Hc1", 4) == 0) return gdb_reply("OK"); gdb_reply("E01"); } static void gdb_handle_signal() { char bfr[128]; memset(bfr, 0, sizeof bfr); sprintf(bfr, "T%02x%02x:%08x;%02x:%08x;", sig, 64, PC, 1, GPR(1)); gdb_reply(bfr); } static void wbe32hex(u8* p, u32 v) { u32 i; for (i = 0; i < 8; i++) p[i] = nibble2hex(v >> (28 - 4 * i)); } static void wbe64hex(u8* p, u64 v) { u32 i; for (i = 0; i < 16; i++) p[i] = nibble2hex(v >> (60 - 4 * i)); } static u32 re32hex(u8* p) { u32 i; u32 res = 0; for (i = 0; i < 8; i++) res = (res << 4) | hex2char(p[i]); return res; } static u64 re64hex(u8* p) { u32 i; u64 res = 0; for (i = 0; i < 16; i++) res = (res << 4) | hex2char(p[i]); return res; } static void gdb_read_register() { static u8 reply[64]; u32 id; memset(reply, 0, sizeof reply); id = hex2char(cmd_bfr[1]); if (cmd_bfr[2] != '\0') { id <<= 4; id |= hex2char(cmd_bfr[2]); } switch (id) { case 0 ... 31: wbe32hex(reply, GPR(id)); break; case 32 ... 63: wbe64hex(reply, riPS0(id - 32)); break; case 64: wbe32hex(reply, PC); break; case 65: wbe32hex(reply, MSR.Hex); break; case 66: wbe32hex(reply, PowerPC::GetCR()); break; case 67: wbe32hex(reply, LR); break; case 68: wbe32hex(reply, CTR); break; case 69: wbe32hex(reply, PowerPC::ppcState.spr[SPR_XER]); break; case 70: wbe32hex(reply, 0x0BADC0DE); break; case 71: wbe32hex(reply, FPSCR.Hex); break; default: return gdb_reply("E01"); break; } gdb_reply((char*)reply); } static void gdb_read_registers() { static u8 bfr[GDB_BFR_MAX - 4]; u8* bufptr = bfr; u32 i; memset(bfr, 0, sizeof bfr); for (i = 0; i < 32; i++) { wbe32hex(bufptr + i * 8, GPR(i)); } bufptr += 32 * 8; gdb_reply((char*)bfr); } static void gdb_write_registers() { u32 i; u8* bufptr = cmd_bfr; for (i = 0; i < 32; i++) { GPR(i) = re32hex(bufptr + i * 8); } bufptr += 32 * 8; gdb_reply("OK"); } static void gdb_write_register() { u32 id; u8* bufptr = cmd_bfr + 3; id = hex2char(cmd_bfr[1]); if (cmd_bfr[2] != '=') { ++bufptr; id <<= 4; id |= hex2char(cmd_bfr[2]); } switch (id) { case 0 ... 31: GPR(id) = re32hex(bufptr); break; case 32 ... 63: riPS0(id - 32) = re64hex(bufptr); break; case 64: PC = re32hex(bufptr); break; case 65: MSR.Hex = re32hex(bufptr); break; case 66: PowerPC::SetCR(re32hex(bufptr)); break; case 67: LR = re32hex(bufptr); break; case 68: CTR = re32hex(bufptr); break; case 69: PowerPC::ppcState.spr[SPR_XER] = re32hex(bufptr); break; case 70: // do nothing, we dont have MQ break; case 71: FPSCR.Hex = re32hex(bufptr); break; default: return gdb_reply("E01"); break; } gdb_reply("OK"); } static void gdb_read_mem() { static u8 reply[GDB_BFR_MAX - 4]; u32 addr, len; u32 i; i = 1; addr = 0; while (cmd_bfr[i] != ',') addr = (addr << 4) | hex2char(cmd_bfr[i++]); i++; len = 0; while (i < cmd_len) len = (len << 4) | hex2char(cmd_bfr[i++]); DEBUG_LOG(GDB_STUB, "gdb: read memory: %08x bytes from %08x", len, addr); if (len * 2 > sizeof reply) gdb_reply("E01"); u8* data = Memory::GetPointer(addr); if (!data) return gdb_reply("E0"); mem2hex(reply, data, len); reply[len * 2] = '\0'; gdb_reply((char*)reply); } static void gdb_write_mem() { u32 addr, len; u32 i; i = 1; addr = 0; while (cmd_bfr[i] != ',') addr = (addr << 4) | hex2char(cmd_bfr[i++]); i++; len = 0; while (cmd_bfr[i] != ':') len = (len << 4) | hex2char(cmd_bfr[i++]); DEBUG_LOG(GDB_STUB, "gdb: write memory: %08x bytes to %08x", len, addr); u8* dst = Memory::GetPointer(addr); if (!dst) return gdb_reply("E00"); hex2mem(dst, cmd_bfr + i + 1, len); gdb_reply("OK"); } // forces a break on next instruction check void gdb_break() { step_break = 1; send_signal = 1; } static void gdb_step() { gdb_break(); } static void gdb_continue() { send_signal = 1; } bool gdb_add_bp(u32 type, u32 addr, u32 len) { gdb_bp_t* bp; bp = gdb_bp_empty_slot(type); if (bp == nullptr) return false; bp->active = 1; bp->addr = addr; bp->len = len; DEBUG_LOG(GDB_STUB, "gdb: added %d breakpoint: %08x bytes at %08x", type, bp->len, bp->addr); return true; } static void _gdb_add_bp() { u32 type; u32 i, addr = 0, len = 0; type = hex2char(cmd_bfr[1]); switch (type) { case 0: case 1: type = GDB_BP_TYPE_X; break; case 2: type = GDB_BP_TYPE_W; break; case 3: type = GDB_BP_TYPE_R; break; case 4: type = GDB_BP_TYPE_A; break; default: return gdb_reply("E01"); } i = 3; while (cmd_bfr[i] != ',') addr = addr << 4 | hex2char(cmd_bfr[i++]); i++; while (i < cmd_len) len = len << 4 | hex2char(cmd_bfr[i++]); if (!gdb_add_bp(type, addr, len)) return gdb_reply("E02"); gdb_reply("OK"); } static void gdb_remove_bp() { u32 type, addr, len, i; type = hex2char(cmd_bfr[1]); switch (type) { case 0: case 1: type = GDB_BP_TYPE_X; break; case 2: type = GDB_BP_TYPE_W; break; case 3: type = GDB_BP_TYPE_R; break; case 4: type = GDB_BP_TYPE_A; break; default: return gdb_reply("E01"); } addr = 0; len = 0; i = 3; while (cmd_bfr[i] != ',') addr = (addr << 4) | hex2char(cmd_bfr[i++]); i++; while (i < cmd_len) len = (len << 4) | hex2char(cmd_bfr[i++]); gdb_bp_remove(type, addr, len); gdb_reply("OK"); } void gdb_handle_exception() { while (gdb_active()) { if (!gdb_data_available()) continue; gdb_read_command(); if (cmd_len == 0) continue; switch (cmd_bfr[0]) { case 'q': gdb_handle_query(); break; case 'H': gdb_handle_set_thread(); break; case '?': gdb_handle_signal(); break; case 'k': gdb_deinit(); INFO_LOG(GDB_STUB, "killed by gdb"); return; case 'g': gdb_read_registers(); break; case 'G': gdb_write_registers(); break; case 'p': gdb_read_register(); break; case 'P': gdb_write_register(); break; case 'm': gdb_read_mem(); break; case 'M': gdb_write_mem(); PowerPC::ppcState.iCache.Reset(); Host_UpdateDisasmDialog(); break; case 's': gdb_step(); return; case 'C': case 'c': gdb_continue(); return; case 'z': gdb_remove_bp(); break; case 'Z': _gdb_add_bp(); break; default: gdb_reply(""); break; } } } #ifdef _WIN32 WSADATA InitData; #endif // exported functions static void gdb_init_generic(int domain, const sockaddr* server_addr, socklen_t server_addrlen, sockaddr* client_addr, socklen_t* client_addrlen); #ifndef _WIN32 void gdb_init_local(const char* socket) { unlink(socket); sockaddr_un addr = {}; addr.sun_family = AF_UNIX; strcpy(addr.sun_path, socket); gdb_init_generic(PF_LOCAL, (const sockaddr*)&addr, sizeof(addr), NULL, NULL); } #endif void gdb_init(u32 port) { sockaddr_in saddr_server = {}; sockaddr_in saddr_client; saddr_server.sin_family = AF_INET; saddr_server.sin_port = htons(port); saddr_server.sin_addr.s_addr = INADDR_ANY; socklen_t client_addrlen = sizeof(saddr_client); gdb_init_generic(PF_INET, (const sockaddr*)&saddr_server, sizeof(saddr_server), (sockaddr*)&saddr_client, &client_addrlen); saddr_client.sin_addr.s_addr = ntohl(saddr_client.sin_addr.s_addr); } static void gdb_init_generic(int domain, const sockaddr* server_addr, socklen_t server_addrlen, sockaddr* client_addr, socklen_t* client_addrlen) { int on; #ifdef _WIN32 WSAStartup(MAKEWORD(2, 2), &InitData); #endif memset(bp_x, 0, sizeof bp_x); memset(bp_r, 0, sizeof bp_r); memset(bp_w, 0, sizeof bp_w); memset(bp_a, 0, sizeof bp_a); tmpsock = socket(domain, SOCK_STREAM, 0); if (tmpsock == -1) ERROR_LOG(GDB_STUB, "Failed to create gdb socket"); on = 1; if (setsockopt(tmpsock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on) < 0) ERROR_LOG(GDB_STUB, "Failed to setsockopt"); if (bind(tmpsock, server_addr, server_addrlen) < 0) ERROR_LOG(GDB_STUB, "Failed to bind gdb socket"); if (listen(tmpsock, 1) < 0) ERROR_LOG(GDB_STUB, "Failed to listen to gdb socket"); INFO_LOG(GDB_STUB, "Waiting for gdb to connect..."); sock = accept(tmpsock, client_addr, client_addrlen); if (sock < 0) ERROR_LOG(GDB_STUB, "Failed to accept gdb client"); INFO_LOG(GDB_STUB, "Client connected."); close(tmpsock); tmpsock = -1; } void gdb_deinit() { if (tmpsock != -1) { shutdown(tmpsock, SHUT_RDWR); tmpsock = -1; } if (sock != -1) { shutdown(sock, SHUT_RDWR); sock = -1; } #ifdef _WIN32 WSACleanup(); #endif } bool gdb_active() { return tmpsock != -1 || sock != -1; } int gdb_signal(u32 s) { if (sock == -1) return 1; sig = s; if (send_signal) { gdb_handle_signal(); send_signal = 0; } return 0; } int gdb_bp_x(u32 addr) { if (sock == -1) return 0; if (step_break) { step_break = 0; DEBUG_LOG(GDB_STUB, "Step was successful."); return 1; } return gdb_bp_check(addr, GDB_BP_TYPE_X); } int gdb_bp_r(u32 addr) { if (sock == -1) return 0; return gdb_bp_check(addr, GDB_BP_TYPE_R); } int gdb_bp_w(u32 addr) { if (sock == -1) return 0; return gdb_bp_check(addr, GDB_BP_TYPE_W); } int gdb_bp_a(u32 addr) { if (sock == -1) return 0; return gdb_bp_check(addr, GDB_BP_TYPE_A); }