diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f9fdb1dcd..c8034aeb6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,8 @@ cmake_minimum_required(VERSION 2.8.12) +# uncomment next line if you want to build with GDB stub +# add_definitions(-DWITH_GDB_DEBUGGER) + set(ASMJIT_STATIC TRUE) if (NOT CMAKE_BUILD_TYPE) diff --git a/Utilities/GDBDebugServer.cpp b/Utilities/GDBDebugServer.cpp new file mode 100644 index 0000000000..b5ed6495c2 --- /dev/null +++ b/Utilities/GDBDebugServer.cpp @@ -0,0 +1,820 @@ +#include "stdafx.h" +#ifdef WITH_GDB_DEBUGGER + +#include "GDBDebugServer.h" +#include "Log.h" +#include "Config.h" +#include +#include "Emu/Memory/Memory.h" +#include "Emu/System.h" +#include "Emu/IdManager.h" +#include "Emu/CPU/CPUThread.h" +#include "Emu/Cell/PPUThread.h" +#include "Emu/Cell/RawSPUThread.h" +#include "Emu/Cell/SPUThread.h" + +#ifndef _WIN32 +#include"fcntl.h" +#endif + +extern void ppu_set_breakpoint(u32 addr); +extern void ppu_remove_breakpoint(u32 addr); + +logs::channel gdbDebugServer("gdbDebugServer", logs::level::notice); + +int sock_init(void) +{ +#ifdef _WIN32 + WSADATA wsa_data; + return WSAStartup(MAKEWORD(1, 1), &wsa_data); +#else + return 0; +#endif +} + +int sock_quit(void) +{ +#ifdef _WIN32 + return WSACleanup(); +#else + return 0; +#endif +} + +#ifndef _WIN32 +int closesocket(socket_t s) { + return close(s); +} +const int SOCKET_ERROR = -1; +const socket_t INVALID_SOCKET = -1; +#define sscanf_s sscanf +#define HEX_U32 "x" +#define HEX_U64 "lx" +#else +#define HEX_U32 "lx" +#define HEX_U64 "llx" +#endif + +bool check_errno_again() { +#ifdef _WIN32 + int err = GetLastError(); + return (err == WSAEWOULDBLOCK); +#else + int err = errno; + return (err == EAGAIN) || (err == EWOULDBLOCK); +#endif +} + +cfg::int_entry<1, 65535> g_cfg_gdb_server_port(cfg::root.misc, "Port", 2345); + +std::string u32_to_hex(u32 i) { + return fmt::format("%" HEX_U32, i); +} + +std::string u64_to_padded_hex(u64 value) { + return fmt::format("%.16" HEX_U64, value); +} + +std::string u32_to_padded_hex(u32 value) { + return fmt::format("%.8" HEX_U32, value); +} + +u8 hex_to_u8(std::string val) { + u8 result; + sscanf_s(val.c_str(), "%02hhX", &result); + return result; +} + +u32 hex_to_u32(std::string val) { + u32 result; + sscanf_s(val.c_str(), "%" HEX_U32, &result); + return result; +} + +u64 hex_to_u64(std::string val) { + u64 result; + sscanf_s(val.c_str(), "%" HEX_U64, &result); + return result; +} + +void GDBDebugServer::start_server() +{ + server_socket = socket(AF_INET, SOCK_STREAM, 0); + + if (server_socket == INVALID_SOCKET) { + gdbDebugServer.error("Error creating server socket"); + return; + } + +#ifdef WIN32 + { + int mode = 1; + ioctlsocket(server_socket, FIONBIO, (u_long FAR *)&mode); + } +#else + fcntl(server_socket, F_SETFL, fcntl(server_socket, F_GETFL) | O_NONBLOCK); +#endif + + int err; + + sockaddr_in server_saddr; + server_saddr.sin_family = AF_INET; + int port = g_cfg_gdb_server_port; + server_saddr.sin_port = htons(port); + server_saddr.sin_addr.s_addr = htonl(INADDR_ANY); + + err = bind(server_socket, (struct sockaddr *) &server_saddr, sizeof(server_saddr)); + if (err == SOCKET_ERROR) { + gdbDebugServer.error("Error binding to port %d", port); + return; + } + + err = listen(server_socket, 1); + if (err == SOCKET_ERROR) { + gdbDebugServer.error("Error listening on port %d", port); + return; + } + + gdbDebugServer.success("GDB Debug Server listening on port %d", port); +} + +int GDBDebugServer::read(void * buf, int cnt) +{ + while (!stop) { + int result = recv(client_socket, reinterpret_cast(buf), cnt, 0); + + if (result == SOCKET_ERROR) { + if (check_errno_again()) { + thread_ctrl::wait_for(50); + continue; + } + + gdbDebugServer.error("Error during socket read"); + fmt::throw_exception("Error during socket read" HERE); + } + return result; + } + return 0; +} + +char GDBDebugServer::read_char() +{ + char result; + int cnt = read(&result, 1); + if (!cnt) { + fmt::throw_exception("Tried to read char, but no data was available" HERE); + } + return result; +} + +u8 GDBDebugServer::read_hexbyte() +{ + char buf[2]; + read(buf, 2); + return static_cast(strtol(buf, nullptr, 16)); +} + +void GDBDebugServer::try_read_cmd(gdb_cmd & out_cmd) +{ + char c = read_char(); + //interrupt + if (UNLIKELY(c == 0x03)) { + out_cmd.cmd = "\0x03"; + out_cmd.data = ""; + out_cmd.checksum = 0; + return; + } + if (UNLIKELY(c != '$')) { + //gdb starts conversation with + for some reason + if (c == '+') { + c = read_char(); + } + if (c != '$') { + fmt::throw_exception("Expected start of packet character '$', got '%c' instead" HERE, c); + } + } + //clear packet data + out_cmd.cmd = ""; + out_cmd.data = ""; + out_cmd.checksum = 0; + bool cmd_part = true; + u8 checksum = 0; + while(true) { + c = read_char(); + if (c == '#') { + break; + } + checksum = (checksum + reinterpret_cast(c)) % 256; + //escaped char + if (c == '}') { + c = read_char() ^ 0x20; + checksum = (checksum + reinterpret_cast(c)) % 256; + } + //cmd-data splitters + if (cmd_part && ((c == ':') || (c == '.') || (c == ';'))) { + cmd_part = false; + } + if (cmd_part) { + out_cmd.cmd += c; + //only q and v commands can have multi-char command + if ((out_cmd.cmd.length() == 1) && (c != 'q') && (c != 'v')) { + cmd_part = false; + } + } else { + out_cmd.data += c; + } + } + out_cmd.checksum = read_hexbyte(); + if (out_cmd.checksum != checksum) { + throw new wrong_checksum_exception("Wrong checksum for packet" HERE); + } +} + +bool GDBDebugServer::read_cmd(gdb_cmd & out_cmd) +{ + while (true) { + try { + try_read_cmd(out_cmd); + ack(true); + return true; + } + catch (wrong_checksum_exception) { + ack(false); + } + catch (std::runtime_error e) { + gdbDebugServer.error(e.what()); + return false; + } + } +} + +void GDBDebugServer::send(const char * buf, int cnt) +{ + gdbDebugServer.trace("Sending %s (%d bytes)", buf, cnt); + while (!stop) { + int res = ::send(client_socket, buf, cnt, 0); + if (res == SOCKET_ERROR) { + if (check_errno_again()) { + thread_ctrl::wait_for(50); + continue; + } + gdbDebugServer.error("Failed sending %d bytes", cnt); + return; + } + return; + } +} + +void GDBDebugServer::send_char(char c) +{ + send(&c, 1); +} + +void GDBDebugServer::ack(bool accepted) +{ + send_char(accepted ? '+' : '-'); +} + +void GDBDebugServer::send_cmd(const std::string & cmd) +{ + u8 checksum = 0; + std::string buf; + buf.reserve(cmd.length() + 4); + buf += "$"; + for (int i = 0; i < cmd.length(); ++i) { + checksum = (checksum + append_encoded_char(cmd[i], buf)) % 256; + } + buf += "#"; + buf += to_hexbyte(checksum); + send(buf.c_str(), static_cast(buf.length())); +} + +bool GDBDebugServer::send_cmd_ack(const std::string & cmd) +{ + while (true) { + send_cmd(cmd); + char c = read_char(); + if (LIKELY(c == '+')) { + return true; + } + if (UNLIKELY(c != '-')) { + gdbDebugServer.error("Wrong acknowledge character received %c", c); + return false; + } + gdbDebugServer.warning("Client rejected our cmd"); + } +} + +u8 GDBDebugServer::append_encoded_char(char c, std::string & str) +{ + u8 checksum = 0; + if (UNLIKELY((c == '#') || (c == '$') || (c == '}'))) { + str += '}'; + c ^= 0x20; + checksum = '}'; + } + checksum = (checksum + reinterpret_cast(c)) % 256; + str += c; + return checksum; +} + +std::string GDBDebugServer::to_hexbyte(u8 i) +{ + std::string result = "00"; + u8 i1 = i & 0xF; + u8 i2 = i >> 4; + result[0] = i2 > 9 ? 'a' + i2 - 10 : '0' + i2; + result[1] = i1 > 9 ? 'a' + i1 - 10 : '0' + i1; + return result; +} + +bool GDBDebugServer::select_thread(u64 id) +{ + //in case we have none at all + selected_thread.reset(); + const auto on_select = [&](u32, cpu_thread& cpu) + { + return (id == ALL_THREADS) || (id == ANY_THREAD) || (cpu.id == id); + }; + if (auto ppu = idm::select(on_select)) { + selected_thread = ppu.ptr; + return true; + } + return false; +} + +std::string GDBDebugServer::get_reg(std::shared_ptr thread, u32 rid) +{ + std::string result; + //ids from gdb/features/rs6000/powerpc-64.c + //pc + switch (rid) { + case 64: + return u64_to_padded_hex(thread->cia); + //msr? + case 65: + return std::string(16, 'x'); + case 66: + return u32_to_padded_hex(thread->cr_pack()); + case 67: + return u64_to_padded_hex(thread->lr); + case 68: + return u64_to_padded_hex(thread->ctr); + //xer + case 69: + return std::string(8, 'x'); + //fpscr + case 70: + return std::string(8, 'x'); + default: + if (rid > 70) return ""; + return (rid > 31) + ? u64_to_padded_hex(reinterpret_cast(thread->fpr[rid - 32])) //fpr + : u64_to_padded_hex(thread->gpr[rid]); //gpr + } +} + +bool GDBDebugServer::set_reg(std::shared_ptr thread, u32 rid, std::string value) +{ + switch (rid) { + case 64: + thread->cia = static_cast(hex_to_u64(value)); + return true; + //msr? + case 65: + return true; + case 66: + thread->cr_unpack(hex_to_u32(value)); + return true; + case 67: + thread->lr = hex_to_u64(value); + return true; + case 68: + thread->ctr = hex_to_u64(value); + return true; + //xer + case 69: + return true; + //fpscr + case 70: + return true; + default: + if (rid > 70) return false; + if (rid > 31) { + u64 val = hex_to_u64(value); + thread->fpr[rid - 32] = reinterpret_cast(val); + } else { + thread->gpr[rid] = hex_to_u64(value); + } + return true; + } +} + +u32 GDBDebugServer::get_reg_size(std::shared_ptr thread, u32 rid) +{ + switch (rid) { + case 66: + case 69: + case 70: + return 4; + default: + if (rid > 70) { + return 0; + } + return 8; + } +} + +bool GDBDebugServer::send_reason() +{ + return send_cmd_ack("S05"); +} + +bool GDBDebugServer::cmd_extended_mode(gdb_cmd & cmd) +{ + return send_cmd_ack("OK"); +} + +bool GDBDebugServer::cmd_reason(gdb_cmd & cmd) +{ + return send_reason(); +} + +bool GDBDebugServer::cmd_supported(gdb_cmd & cmd) +{ + return send_cmd_ack("PacketSize=1200"); +} + +bool GDBDebugServer::cmd_thread_info(gdb_cmd & cmd) +{ + std::string result = ""; + const auto on_select = [&](u32, cpu_thread& cpu) + { + if (result.length()) { + result += ","; + } + result += u64_to_padded_hex(static_cast(cpu.id)); + }; + idm::select(on_select); + idm::select(on_select); + idm::select(on_select); + idm::select(on_select); + + //todo: this may exceed max command length + result = "m" + result + "l"; + + return send_cmd_ack(result);; +} + +bool GDBDebugServer::cmd_current_thread(gdb_cmd & cmd) +{ + return send_cmd_ack(selected_thread.expired() ? "" : u64_to_padded_hex(selected_thread.lock()->id)); +} + +bool GDBDebugServer::cmd_read_register(gdb_cmd & cmd) +{ + select_thread(general_ops_thread_id); + auto th = selected_thread.lock(); + if (th->id_type() == 1) { + auto ppu = std::static_pointer_cast(th); + u32 rid = hex_to_u32(cmd.data); + std::string result = get_reg(ppu, rid); + if (!result.length()) { + gdbDebugServer.warning("Wrong register id %d", rid); + return send_cmd_ack("E01"); + } + return send_cmd_ack(result); + } + gdbDebugServer.warning("Unimplemented thread type %d", th->id_type()); + return send_cmd_ack(""); +} + +bool GDBDebugServer::cmd_write_register(gdb_cmd & cmd) +{ + select_thread(general_ops_thread_id); + auto th = selected_thread.lock(); + if (th->id_type() == 1) { + auto ppu = std::static_pointer_cast(th); + size_t eq_pos = cmd.data.find('='); + if (eq_pos == std::string::npos) { + gdbDebugServer.warning("Wrong write_register cmd data %s", cmd.data.c_str()); + return send_cmd_ack("E02"); + } + u32 rid = hex_to_u32(cmd.data.substr(0, eq_pos)); + std::string value = cmd.data.substr(eq_pos + 1); + if (!set_reg(ppu, rid, value)) { + gdbDebugServer.warning("Wrong register id %d", rid); + return send_cmd_ack("E01"); + } + return send_cmd_ack("OK"); + } + gdbDebugServer.warning("Unimplemented thread type %d", th->id_type()); + return send_cmd_ack(""); +} + +bool GDBDebugServer::cmd_read_memory(gdb_cmd & cmd) +{ + size_t s = cmd.data.find(','); + u32 addr = hex_to_u32(cmd.data.substr(0, s)); + u32 len = hex_to_u32(cmd.data.substr(s + 1)); + std::string result; + result.reserve(len * 2); + for (u32 i = 0; i < len; ++i) { + if (vm::check_addr(addr + i)) { + result += to_hexbyte(vm::read8(addr + i)); + } else { + break; + //result += "xx"; + } + } + if (len && !result.length()) { + //nothing read + return send_cmd_ack("E01"); + } + return send_cmd_ack(result); +} + +bool GDBDebugServer::cmd_write_memory(gdb_cmd & cmd) +{ + size_t s = cmd.data.find(','); + size_t s2 = cmd.data.find(':'); + if ((s == std::string::npos) || (s2 == std::string::npos)) { + gdbDebugServer.warning("Malformed write memory request received: %s", cmd.data.c_str()); + return send_cmd_ack("E01"); + } + u32 addr = hex_to_u32(cmd.data.substr(0, s)); + u32 len = hex_to_u32(cmd.data.substr(s + 1, s2 - s - 1)); + const char* data_ptr = (cmd.data.c_str()) + s2 + 1; + for (u32 i = 0; i < len; ++i) { + if (vm::check_addr(addr + i)) { + u8 val; + int res = sscanf_s(data_ptr, "%02hhX", &val); + if (!res) { + gdbDebugServer.warning("Couldn't read u8 from string %s", data_ptr); + return send_cmd_ack("E02"); + } + data_ptr += 2; + vm::write8(addr + i, val); + } else { + return send_cmd_ack("E03"); + } + } + return send_cmd_ack("OK"); +} + +bool GDBDebugServer::cmd_read_all_registers(gdb_cmd & cmd) +{ + std::string result; + select_thread(general_ops_thread_id); + + auto th = selected_thread.lock(); + if (th->id_type() == 1) { + auto ppu = std::static_pointer_cast(th); + //68 64-bit registers, and 3 32-bit + result.reserve(68*16 + 3*8); + for (int i = 0; i < 71; ++i) { + result += get_reg(ppu, i); + } + return send_cmd_ack(result); + } + gdbDebugServer.warning("Unimplemented thread type %d", th->id_type()); + return send_cmd_ack(""); +} + +bool GDBDebugServer::cmd_write_all_registers(gdb_cmd & cmd) +{ + select_thread(general_ops_thread_id); + auto th = selected_thread.lock(); + if (th->id_type() == 1) { + auto ppu = std::static_pointer_cast(th); + int ptr = 0; + for (int i = 0; i < 71; ++i) { + int sz = get_reg_size(ppu, i); + set_reg(ppu, i, cmd.data.substr(ptr, sz * 2)); + ptr += sz * 2; + } + return send_cmd_ack("OK"); + } + gdbDebugServer.warning("Unimplemented thread type %d", th->id_type()); + return send_cmd_ack("E01"); +} + +bool GDBDebugServer::cmd_set_thread_ops(gdb_cmd & cmd) +{ + char type = cmd.data[0]; + std::string thread = cmd.data.substr(1); + u64 id = thread == "-1" ? ALL_THREADS : hex_to_u64(thread); + if (type == 'c') { + continue_ops_thread_id = id; + } else { + general_ops_thread_id = id; + } + if (select_thread(id)) { + return send_cmd_ack("OK"); + } + gdbDebugServer.warning("Client asked to use thread %llx for %s, but no matching thread was found", id, type == 'c' ? "continue ops" : "general ops"); + return send_cmd_ack("E01"); +} + +bool GDBDebugServer::cmd_attached_to_what(gdb_cmd & cmd) +{ + //creating processes from client is not available yet + return send_cmd_ack("1"); +} + +bool GDBDebugServer::cmd_kill(gdb_cmd & cmd) +{ + Emu.Stop(); + return true; +} + +bool GDBDebugServer::cmd_continue_support(gdb_cmd & cmd) +{ + return send_cmd_ack("vCont;c;s;C;S"); +} + +bool GDBDebugServer::cmd_vcont(gdb_cmd & cmd) +{ + //todo: handle multiple actions and thread ids + this->from_breakpoint = false; + if (cmd.data[1] == 'c') { + select_thread(continue_ops_thread_id); + auto ppu = std::static_pointer_cast(selected_thread.lock()); + ppu->state -= cpu_flag::dbg_pause; + if (Emu.IsPaused()) { + Emu.Resume(); + } + thread_ctrl::wait(); + //we are in all-stop mode + Emu.Pause(); + return send_reason(); + } else if (cmd.data[1] == 's') { + select_thread(continue_ops_thread_id); + auto ppu = std::static_pointer_cast(selected_thread.lock()); + ppu->state += cpu_flag::dbg_step; + ppu->state -= cpu_flag::dbg_pause; + if (Emu.IsPaused()) { + Emu.Resume(); + } else { + ppu->notify(); + } + thread_ctrl::wait(); + //we are in all-stop mode + Emu.Pause(); + return send_reason(); + } + return send_cmd_ack(""); +} + +static const u32 INVALID_PTR = 0xffffffff; + +bool GDBDebugServer::cmd_set_breakpoint(gdb_cmd & cmd) +{ + char type = cmd.data[0]; + //software breakpoint + if (type == '0') { + u32 addr = INVALID_PTR; + if (cmd.data.find(';') != std::string::npos) { + gdbDebugServer.warning("Received request to set breakpoint with condition, but they are not supported"); + return send_cmd_ack("E01"); + } + sscanf_s(cmd.data.c_str(), "0,%x", &addr); + if (addr == INVALID_PTR) { + gdbDebugServer.warning("Can't parse breakpoint request, data: %s", cmd.data.c_str()); + return send_cmd_ack("E02"); + } + ppu_set_breakpoint(addr); + return send_cmd_ack("OK"); + } + //other breakpoint types are not supported + return send_cmd_ack(""); +} + +bool GDBDebugServer::cmd_remove_breakpoint(gdb_cmd & cmd) +{ + char type = cmd.data[0]; + //software breakpoint + if (type == '0') { + u32 addr = INVALID_PTR; + sscanf_s(cmd.data.c_str(), "0,%x", &addr); + if (addr == INVALID_PTR) { + gdbDebugServer.warning("Can't parse breakpoint remove request, data: %s", cmd.data.c_str()); + return send_cmd_ack("E01"); + } + ppu_remove_breakpoint(addr); + return send_cmd_ack("OK"); + } + //other breakpoint types are not supported + return send_cmd_ack(""); + +} + +#define PROCESS_CMD(cmds,handler) if (cmd.cmd == cmds) { if (!handler(cmd)) break; else continue; } + +void GDBDebugServer::on_task() +{ + sock_init(); + + start_server(); + + while (!stop) { + sockaddr_in client; + socklen_t client_len = sizeof(client); + client_socket = accept(server_socket, (struct sockaddr *) &client, &client_len); + + if (client_socket == INVALID_SOCKET) { + if (check_errno_again()) { + thread_ctrl::wait_for(50); + continue; + } + + gdbDebugServer.error("Could not establish new connection\n"); + return; + } + //stop immediately + Emu.Pause(); + + try { + char hostbuf[32]; + inet_ntop(client.sin_family, reinterpret_cast(&client.sin_addr), hostbuf, 32); + gdbDebugServer.success("Got connection to GDB debug server from %s:%d", hostbuf, client.sin_port); + + gdb_cmd cmd; + + while (!stop) { + if (!read_cmd(cmd)) { + break; + } + gdbDebugServer.trace("Command %s with data %s received", cmd.cmd.c_str(), cmd.data.c_str()); + PROCESS_CMD("!", cmd_extended_mode); + PROCESS_CMD("?", cmd_reason); + PROCESS_CMD("qSupported", cmd_supported); + PROCESS_CMD("qfThreadInfo", cmd_thread_info); + PROCESS_CMD("qC", cmd_current_thread); + PROCESS_CMD("p", cmd_read_register); + PROCESS_CMD("P", cmd_write_register); + PROCESS_CMD("m", cmd_read_memory); + PROCESS_CMD("M", cmd_write_memory); + PROCESS_CMD("g", cmd_read_all_registers); + PROCESS_CMD("G", cmd_write_all_registers); + PROCESS_CMD("H", cmd_set_thread_ops); + PROCESS_CMD("qAttached", cmd_attached_to_what); + PROCESS_CMD("k", cmd_kill); + PROCESS_CMD("vCont?", cmd_continue_support); + PROCESS_CMD("vCont", cmd_vcont); + PROCESS_CMD("z", cmd_remove_breakpoint); + PROCESS_CMD("Z", cmd_set_breakpoint); + + gdbDebugServer.trace("Unsupported command received %s", cmd.cmd.c_str()); + if (!send_cmd_ack("")) { + break; + } + } + } + catch (std::runtime_error& e) + { + if (client_socket) { + closesocket(client_socket); + client_socket = 0; + } + gdbDebugServer.error(e.what()); + } + } +} + +#undef PROCESS_CMD + +void GDBDebugServer::on_exit() +{ + if (server_socket) { + closesocket(server_socket); + } + if (client_socket) { + closesocket(client_socket); + } + sock_quit(); +} + +std::string GDBDebugServer::get_name() const +{ + return "GDBDebugger"; +} + +void GDBDebugServer::on_stop() +{ + this->stop = true; + //just in case we are waiting for breakpoint + this->notify(); + named_thread::on_stop(); +} + +u32 g_gdb_debugger_id = 0; + +#ifndef _WIN32 +#undef sscanf_s +#endif + +#undef HEX_U32 +#undef HEX_U64 + +#endif diff --git a/Utilities/GDBDebugServer.h b/Utilities/GDBDebugServer.h new file mode 100644 index 0000000000..7ce7e06476 --- /dev/null +++ b/Utilities/GDBDebugServer.h @@ -0,0 +1,131 @@ +#pragma once + +#ifdef WITH_GDB_DEBUGGER + +#include "Thread.h" +#include +#include "Emu/CPU/CPUThread.h" +#include "Emu/Cell/PPUThread.h" + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#endif + +#ifdef _WIN32 +using socket_t = SOCKET; +#else +using socket_t = int; +#endif + +typedef struct gdb_cmd { + std::string cmd; + std::string data; + u8 checksum; +} gdb_cmd; + +class wrong_checksum_exception : public std::runtime_error { +public: + wrong_checksum_exception(char const* const message) : runtime_error(message) {} +}; + +const u64 ALL_THREADS = 0xffffffffffffffff; +const u64 ANY_THREAD = 0; + +class GDBDebugServer : public named_thread { + + socket_t server_socket; + socket_t client_socket; + std::weak_ptr selected_thread; + u64 continue_ops_thread_id = ANY_THREAD; + u64 general_ops_thread_id = ANY_THREAD; + + //initialize server socket and start listening + void start_server(); + //read at most cnt bytes to buf, returns nubmer of bytes actually read + int read(void* buf, int cnt); + //reads one character + char read_char(); + //reads pairs of hex characters and returns their integer value + u8 read_hexbyte(); + //tries to read command, throws exceptions if anything goes wrong + void try_read_cmd(gdb_cmd& out_cmd); + //reads commands until receiveing one with valid checksum + //in case of other exception (i.e. wrong first char of command) + //it will log exception text and return false + //in that case best for caller would be to stop reading, because + //chance of getting correct command is low + bool read_cmd(gdb_cmd& out_cmd); + //send cnt bytes from buf to client + void send(const char* buf, int cnt); + //send character to client + void send_char(char c); + //acknowledge packet, either as accepted or declined + void ack(bool accepted); + //sends command body cmd to client + void send_cmd(const std::string & cmd); + //sends command to client until receives positive acknowledgement + //returns false in case some error happened, and command wasn't sent + bool send_cmd_ack(const std::string & cmd); + //appends encoded char c to string str, and returns checksum. encoded byte can occupy 2 bytes + static u8 append_encoded_char(char c, std::string& str); + //convert u8 to 2 byte hexademical representation + static std::string to_hexbyte(u8 i); + //choose thread, support ALL_THREADS and ANY_THREAD values, returns true if some thread was selected + bool select_thread(u64 id); + //returns register value as hex string by register id (in gdb), in case of wrong id returns empty string + static std::string get_reg(std::shared_ptr thread, u32 rid); + //sets register value to hex string by register id (in gdb), in case of wrong id returns false + static bool set_reg(std::shared_ptr thread, u32 rid, std::string value); + //returns size of register with id rid in bytes, zero if invalid rid is provided + static u32 get_reg_size(std::shared_ptr thread, u32 rid); + //send reason of stop, returns false if sending response failed + bool send_reason(); + + //commands + bool cmd_extended_mode(gdb_cmd& cmd); + bool cmd_reason(gdb_cmd& cmd); + bool cmd_supported(gdb_cmd& cmd); + bool cmd_thread_info(gdb_cmd& cmd); + bool cmd_current_thread(gdb_cmd& cmd); + bool cmd_read_register(gdb_cmd& cmd); + bool cmd_write_register(gdb_cmd& cmd); + bool cmd_read_memory(gdb_cmd& cmd); + bool cmd_write_memory(gdb_cmd& cmd); + bool cmd_read_all_registers(gdb_cmd& cmd); + bool cmd_write_all_registers(gdb_cmd& cmd); + bool cmd_set_thread_ops(gdb_cmd& cmd); + bool cmd_attached_to_what(gdb_cmd& cmd); + bool cmd_kill(gdb_cmd& cmd); + bool cmd_continue_support(gdb_cmd& cmd); + bool cmd_vcont(gdb_cmd& cmd); + bool cmd_set_breakpoint(gdb_cmd& cmd); + bool cmd_remove_breakpoint(gdb_cmd& cmd); + +protected: + void on_task() override final; + void on_exit() override final; + +public: + static const u32 id_base = 1; + static const u32 id_step = 1; + static const u32 id_count = 0x100000; + + bool from_breakpoint = true; + bool stop = false; + + virtual std::string get_name() const; + virtual void on_stop() override final; +}; + +extern u32 g_gdb_debugger_id; + +#endif diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp index e05caeebd8..bbde4b87be 100644 --- a/rpcs3/Emu/CPU/CPUThread.cpp +++ b/rpcs3/Emu/CPU/CPUThread.cpp @@ -2,6 +2,8 @@ #include "Emu/System.h" #include "Emu/Memory/vm.h" #include "CPUThread.h" +#include "Emu/IdManager.h" +#include "Utilities/GDBDebugServer.h" DECLARE(cpu_thread::g_threads_created){0}; DECLARE(cpu_thread::g_threads_deleted){0}; @@ -92,6 +94,12 @@ cpu_thread::cpu_thread(u32 id) bool cpu_thread::check_state() { +#ifdef WITH_GDB_DEBUGGER + if (test(state, cpu_flag::dbg_pause)) { + fxm::get()->notify(); + } +#endif + bool cpu_sleep_called = false; bool cpu_flag_memory = false; diff --git a/rpcs3/Emu/Cell/PPUModule.cpp b/rpcs3/Emu/Cell/PPUModule.cpp index 8d506b1921..b6223838fd 100644 --- a/rpcs3/Emu/Cell/PPUModule.cpp +++ b/rpcs3/Emu/Cell/PPUModule.cpp @@ -112,6 +112,9 @@ LOG_CHANNEL(sys_libc); LOG_CHANNEL(sys_lv2dbg); LOG_CHANNEL(libnet); LOG_CHANNEL(sysPrxForUser); +#ifdef WITH_GDB_DEBUGGER +LOG_CHANNEL(gdbDebugServer); +#endif cfg::bool_entry g_cfg_hook_ppu_funcs(cfg::root.core, "Hook static functions"); cfg::bool_entry g_cfg_load_liblv2(cfg::root.core, "Load liblv2.sprx only"); diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index c1be100a28..b97b88ba9f 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -11,6 +11,7 @@ #include "PPUModule.h" #include "lv2/sys_sync.h" #include "lv2/sys_prx.h" +#include "Utilities/GDBDebugServer.h" #ifdef LLVM_AVAILABLE #include "restore_new.h" @@ -195,7 +196,11 @@ extern void ppu_register_function_at(u32 addr, u32 size, ppu_function_t ptr) static bool ppu_break(ppu_thread& ppu, ppu_opcode_t op) { // Pause and wait if necessary - if (!ppu.state.test_and_set(cpu_flag::dbg_pause) && ppu.check_state()) + bool status = ppu.state.test_and_set(cpu_flag::dbg_pause); +#ifdef WITH_GDB_DEBUGGER + fxm::get()->notify(); +#endif + if (!status && ppu.check_state()) { return false; } @@ -248,6 +253,38 @@ void ppu_thread::on_init(const std::shared_ptr& _this) } } +//sets breakpoint, does nothing if there is a breakpoint there already +extern void ppu_set_breakpoint(u32 addr) +{ + if (g_cfg_ppu_decoder.get() == ppu_decoder_type::llvm) + { + return; + } + + const auto _break = ::narrow(reinterpret_cast(&ppu_break)); + + if (ppu_ref(addr / 4) != _break) + { + ppu_ref(addr / 4) = _break; + } +} + +//removes breakpoint, does nothing if there is no breakpoint at location +extern void ppu_remove_breakpoint(u32 addr) +{ + if (g_cfg_ppu_decoder.get() == ppu_decoder_type::llvm) + { + return; + } + + const auto _break = ::narrow(reinterpret_cast(&ppu_break)); + + if (ppu_ref(addr / 4) == _break) + { + ppu_ref(addr / 4) = ppu_cache(addr); + } +} + std::string ppu_thread::get_name() const { return fmt::format("PPU[0x%x] Thread (%s)", id, m_name); diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index b47edb5b8e..77a0f4af80 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -26,6 +26,8 @@ #include +#include "Utilities/GDBDebugServer.h" + system_type g_system; cfg::bool_entry g_cfg_autostart(cfg::root.misc, "Always start after boot", true); @@ -100,7 +102,10 @@ void Emulator::Init() fs::create_dir(dev_hdd1 + "game/"); fs::create_path(dev_hdd1); fs::create_path(dev_usb); - + +#ifdef WITH_GDB_DEBUGGER + fxm::make(); +#endif // Initialize patch engine fxm::make_always()->append(fs::get_config_dir() + "/patch.yml"); } @@ -488,6 +493,12 @@ void Emulator::Stop() rpcs3::on_stop()(); +#ifdef WITH_GDB_DEBUGGER + //fxm for some reason doesn't call on_stop + fxm::get()->on_stop(); + fxm::remove(); +#endif + auto e_stop = std::make_exception_ptr(cpu_flag::dbg_global_stop); auto on_select = [&](u32, cpu_thread& cpu) diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 8d720c1a64..fde5629563 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -80,6 +80,9 @@ NotUsing + + Use + NotUsing @@ -406,6 +409,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 2e4bb036e3..10cda3e8d2 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -908,6 +908,9 @@ Loader + + Utilities + Utilities @@ -1741,6 +1744,9 @@ Loader + + Utilities + Utilities