diff options
-rw-r--r-- | scripts/src/osd/modules.lua | 1 | ||||
-rw-r--r-- | src/devices/cpu/arm7/arm7.cpp | 2 | ||||
-rw-r--r-- | src/emu/debug/debugcpu.cpp | 6 | ||||
-rw-r--r-- | src/emu/debug/debugcpu.h | 6 | ||||
-rw-r--r-- | src/emu/distate.h | 1 | ||||
-rw-r--r-- | src/osd/modules/debugger/debuggdbstub.cpp | 1133 | ||||
-rw-r--r-- | src/osd/modules/lib/osdobj_common.cpp | 2 | ||||
-rw-r--r-- | src/osd/modules/lib/osdobj_common.h | 2 |
8 files changed, 1153 insertions, 0 deletions
diff --git a/scripts/src/osd/modules.lua b/scripts/src/osd/modules.lua index 1376be2b1d5..ed4b53e8a5f 100644 --- a/scripts/src/osd/modules.lua +++ b/scripts/src/osd/modules.lua @@ -68,6 +68,7 @@ function osdmodulesbuild() MAME_DIR .. "src/osd/modules/debugger/none.cpp", MAME_DIR .. "src/osd/modules/debugger/debugwin.cpp", MAME_DIR .. "src/osd/modules/debugger/debugimgui.cpp", + MAME_DIR .. "src/osd/modules/debugger/debuggdbstub.cpp", MAME_DIR .. "src/osd/modules/font/font_sdl.cpp", MAME_DIR .. "src/osd/modules/font/font_windows.cpp", MAME_DIR .. "src/osd/modules/font/font_dwrite.cpp", diff --git a/src/devices/cpu/arm7/arm7.cpp b/src/devices/cpu/arm7/arm7.cpp index 154d6ad405e..4cf3b5fe351 100644 --- a/src/devices/cpu/arm7/arm7.cpp +++ b/src/devices/cpu/arm7/arm7.cpp @@ -656,6 +656,8 @@ void arm7_cpu_device::device_start() state_add( ARM7_R13, "R13", m_r[13]).formatstr("%08X"); state_add( ARM7_R14, "R14", m_r[14]).formatstr("%08X"); state_add( ARM7_R15, "R15", m_r[15]).formatstr("%08X"); + /* Current Status Program Register */ + state_add( ARM7_CPSR, "CPSR", m_r[eCPSR]).formatstr("%08X"); /* FIRQ Mode Shadowed Registers */ state_add( ARM7_FR8, "FR8", m_r[eR8_FIQ] ).formatstr("%08X"); state_add( ARM7_FR9, "FR9", m_r[eR9_FIQ] ).formatstr("%08X"); diff --git a/src/emu/debug/debugcpu.cpp b/src/emu/debug/debugcpu.cpp index 7bb39bc112f..e49c5bfdb61 100644 --- a/src/emu/debug/debugcpu.cpp +++ b/src/emu/debug/debugcpu.cpp @@ -1299,6 +1299,8 @@ device_debug::device_debug(device_t &device) , m_pc_history_index(0) , m_bplist(nullptr) , m_rplist(nullptr) + , m_triggered_breakpoint(nullptr) + , m_triggered_watchpoint(nullptr) , m_trace(nullptr) , m_hotspot_threshhold(0) , m_track_pc_set() @@ -2551,7 +2553,10 @@ void device_debug::breakpoint_check(offs_t pc) // print a notification, unless the action made us go again if (debugcpu.is_stopped()) + { m_device.machine().debugger().console().printf("Stopped at breakpoint %X\n", bp->m_index); + m_triggered_breakpoint = bp; + } break; } @@ -3071,6 +3076,7 @@ void device_debug::watchpoint::triggered(read_or_write type, offs_t address, u64 pc); debug.console().printf("%s\n", buffer); m_debugInterface->compute_debug_flags(); + m_debugInterface->set_triggered_watchpoint(this); } debug.cpu().set_within_instruction(false); diff --git a/src/emu/debug/debugcpu.h b/src/emu/debug/debugcpu.h index adaf6a2ba46..0feeee51386 100644 --- a/src/emu/debug/debugcpu.h +++ b/src/emu/debug/debugcpu.h @@ -223,6 +223,7 @@ public: void breakpoint_clear_all(); bool breakpoint_enable(int index, bool enable = true); void breakpoint_enable_all(bool enable = true); + breakpoint *triggered_breakpoint(void) { breakpoint *ret = m_triggered_breakpoint; m_triggered_breakpoint = nullptr; return ret; } // watchpoints int watchpoint_space_count() const { return m_wplist.size(); } @@ -232,6 +233,8 @@ public: void watchpoint_clear_all(); bool watchpoint_enable(int index, bool enable = true); void watchpoint_enable_all(bool enable = true); + void set_triggered_watchpoint(watchpoint *wp) { m_triggered_watchpoint = wp; } + watchpoint *triggered_watchpoint(void) { watchpoint *ret = m_triggered_watchpoint; m_triggered_watchpoint = nullptr; return ret; } // registerpoints registerpoint *registerpoint_first() const { return m_rplist; } @@ -341,6 +344,9 @@ private: std::vector<std::vector<std::unique_ptr<watchpoint>>> m_wplist; // watchpoint lists for each address space registerpoint * m_rplist; // list of registerpoints + breakpoint * m_triggered_breakpoint; // latest breakpoint that was triggered + watchpoint * m_triggered_watchpoint; // latest watchpoint that was triggered + // tracing class tracer { diff --git a/src/emu/distate.h b/src/emu/distate.h index f97ec8e3c2e..5951a0143d4 100644 --- a/src/emu/distate.h +++ b/src/emu/distate.h @@ -64,6 +64,7 @@ public: int index() const { return m_index; } void *dataptr() const { return entry_baseptr(); } u64 datamask() const { return m_datamask; } + u8 datasize() const { return m_datasize; } const char *symbol() const { return m_symbol.c_str(); } bool visible() const { return ((m_flags & DSF_NOSHOW) == 0); } bool writeable() const { return ((m_flags & DSF_READONLY) == 0); } diff --git a/src/osd/modules/debugger/debuggdbstub.cpp b/src/osd/modules/debugger/debuggdbstub.cpp new file mode 100644 index 00000000000..d345f4ec602 --- /dev/null +++ b/src/osd/modules/debugger/debuggdbstub.cpp @@ -0,0 +1,1133 @@ +// license:BSD-3-Clause +// copyright-holders:Ramiro Polla +//============================================================ +// +// debuggdbstub.cpp - GDB stub debugger +// +//============================================================ + +#include "emu.h" +#include "debug/debugcpu.h" +#include "debug_module.h" +#include "debugger.h" +#include "modules/lib/osdobj_common.h" +#include "modules/osdmodule.h" + +#include <cinttypes> + +//------------------------------------------------------------------------- +#define MAX_PACKET_SIZE 16384 + +//------------------------------------------------------------------------- +enum gdb_register_type +{ + TYPE_INT, + TYPE_CODE_POINTER, + TYPE_DATA_POINTER, + TYPE_I387_EXT, +}; +static const char *const gdb_register_type_str[] = { + "int", + "code_ptr", + "data_ptr", + "i387_ext", +}; +struct gdb_register_map +{ + const char *arch; + const char *feature; + struct gdb_register_description + { + const char *state_name; + const char *gdb_name; + bool stop_packet; + gdb_register_type gdb_type; + }; + std::vector<gdb_register_description> registers; +}; + +//------------------------------------------------------------------------- +static const gdb_register_map gdb_register_map_i486 = +{ + "i386", + "org.gnu.gdb.i386.core", + { + { "EAX", "eax", false, TYPE_INT }, + { "ECX", "ecx", false, TYPE_INT }, + { "EDX", "edx", false, TYPE_INT }, + { "EBX", "ebx", false, TYPE_INT }, + { "ESP", "esp", true, TYPE_DATA_POINTER }, + { "EBP", "ebp", true, TYPE_DATA_POINTER }, + { "ESI", "esi", false, TYPE_INT }, + { "EDI", "edi", false, TYPE_INT }, + { "EIP", "eip", true, TYPE_CODE_POINTER }, + { "EFLAGS", "eflags", false, TYPE_INT }, // TODO describe bitfield + { "CS", "cs", false, TYPE_INT }, + { "SS", "ss", false, TYPE_INT }, + { "DS", "ds", false, TYPE_INT }, + { "ES", "es", false, TYPE_INT }, + { "FS", "fs", false, TYPE_INT }, + { "GS", "gs", false, TYPE_INT }, + // TODO fix x87 registers! + // The x87 registers are just plain wrong for a few reasons: + // - The st* registers use a dummy variable in i386_device, so we + // don't retrieve the real value (also the bitsize is wrong); + // - The seg/off/op registers don't seem to be exported in the + // state. + { "ST0", "st0", false, TYPE_I387_EXT }, + { "ST1", "st1", false, TYPE_I387_EXT }, + { "ST2", "st2", false, TYPE_I387_EXT }, + { "ST3", "st3", false, TYPE_I387_EXT }, + { "ST4", "st4", false, TYPE_I387_EXT }, + { "ST5", "st5", false, TYPE_I387_EXT }, + { "ST6", "st6", false, TYPE_I387_EXT }, + { "ST7", "st7", false, TYPE_I387_EXT }, + { "x87_CW", "fctrl", false, TYPE_INT }, + { "x87_SW", "fstat", false, TYPE_INT }, + { "x87_TAG", "ftag", false, TYPE_INT }, + { "EAX", "fiseg", false, TYPE_INT }, + { "EAX", "fioff", false, TYPE_INT }, + { "EAX", "foseg", false, TYPE_INT }, + { "EAX", "fooff", false, TYPE_INT }, + { "EAX", "fop", false, TYPE_INT }, + } +}; + +//------------------------------------------------------------------------- +static const gdb_register_map gdb_register_map_arm7 = +{ + "arm", + "org.gnu.gdb.arm.core", + { + { "R0", "r0", false, TYPE_INT }, + { "R1", "r1", false, TYPE_INT }, + { "R2", "r2", false, TYPE_INT }, + { "R3", "r3", false, TYPE_INT }, + { "R4", "r4", false, TYPE_INT }, + { "R5", "r5", false, TYPE_INT }, + { "R6", "r6", false, TYPE_INT }, + { "R7", "r7", false, TYPE_INT }, + { "R8", "r8", false, TYPE_INT }, + { "R9", "r9", false, TYPE_INT }, + { "R10", "r10", false, TYPE_INT }, + { "R11", "r11", false, TYPE_INT }, + { "R12", "r12", false, TYPE_INT }, + { "R13", "sp", true, TYPE_DATA_POINTER }, + { "R14", "lr", true, TYPE_INT }, + { "R15", "pc", true, TYPE_CODE_POINTER }, + { "CPSR", "cpsr", false, TYPE_INT }, // TODO describe bitfield + } +}; + +//------------------------------------------------------------------------- +static const gdb_register_map gdb_register_map_ppc601 = +{ + "powerpc:common", + "org.gnu.gdb.power.core", + { + { "R0", "r0", false, TYPE_INT }, + { "R1", "r1", false, TYPE_INT }, + { "R2", "r2", false, TYPE_INT }, + { "R3", "r3", false, TYPE_INT }, + { "R4", "r4", false, TYPE_INT }, + { "R5", "r5", false, TYPE_INT }, + { "R6", "r6", false, TYPE_INT }, + { "R7", "r7", false, TYPE_INT }, + { "R8", "r8", false, TYPE_INT }, + { "R9", "r9", false, TYPE_INT }, + { "R10", "r10", false, TYPE_INT }, + { "R11", "r11", false, TYPE_INT }, + { "R12", "r12", false, TYPE_INT }, + { "R13", "r13", false, TYPE_INT }, + { "R14", "r14", false, TYPE_INT }, + { "R15", "r15", false, TYPE_INT }, + { "R16", "r16", false, TYPE_INT }, + { "R17", "r17", false, TYPE_INT }, + { "R18", "r18", false, TYPE_INT }, + { "R19", "r19", false, TYPE_INT }, + { "R20", "r20", false, TYPE_INT }, + { "R21", "r21", false, TYPE_INT }, + { "R22", "r22", false, TYPE_INT }, + { "R23", "r23", false, TYPE_INT }, + { "R24", "r24", false, TYPE_INT }, + { "R25", "r25", false, TYPE_INT }, + { "R26", "r26", false, TYPE_INT }, + { "R27", "r27", false, TYPE_INT }, + { "R28", "r28", false, TYPE_INT }, + { "R29", "r29", false, TYPE_INT }, + { "R30", "r30", false, TYPE_INT }, + { "R31", "r31", false, TYPE_INT }, + { "PC", "pc", true, TYPE_CODE_POINTER }, + { "MSR", "msr", false, TYPE_INT }, + { "CR", "cr", false, TYPE_INT }, + { "LR", "lr", true, TYPE_CODE_POINTER }, + { "CTR", "ctr", false, TYPE_INT }, + { "XER", "xer", false, TYPE_INT }, + } +}; + +//------------------------------------------------------------------------- +static const std::map<std::string, const gdb_register_map &> gdb_register_maps = { + { "i486", gdb_register_map_i486 }, + { "arm7_le", gdb_register_map_arm7 }, + { "ppc601", gdb_register_map_ppc601 }, +}; + +//------------------------------------------------------------------------- +class debug_gdbstub : public osd_module, public debug_module +{ +public: + debug_gdbstub() + : osd_module(OSD_DEBUG_PROVIDER, "gdbstub"), debug_module(), + m_machine(nullptr), + m_maincpu(nullptr), + m_state(nullptr), + m_memory(nullptr), + m_address_space(nullptr), + m_debugger_cpu(nullptr), + m_debugger_port(0), + m_socket(OPEN_FLAG_WRITE | OPEN_FLAG_CREATE), + m_is_be(false), + m_initialized(false), + m_dettached(false), + m_extended_mode(false), + m_send_stop_packet(false), + m_target_xml_sent(false), + m_triggered_breakpoint(nullptr), + m_triggered_watchpoint(nullptr), + m_readbuf_len(0), + m_readbuf_offset(0), + m_packet_len(0), + m_packet_checksum(0), + m_recv_checksum(0) + { + } + + virtual ~debug_gdbstub() { } + + virtual int init(const osd_options &options) override; + virtual void exit() override; + + virtual void init_debugger(running_machine &machine) override; + virtual void wait_for_debugger(device_t &device, bool firststop) override; + virtual void debugger_update() override; + + std::string get_register_string(int gdb_regnum); + bool parse_register_string(uint64_t *pvalue, const char *buf, int gdb_regnum); + + bool parse_zZ(int *ptype, uint64_t *paddress, int *pkind, const char *buf); + + void set_register_value(int gdb_regnum, uint64_t value); + + bool is_thread_id_ok(const char *buf); + + void handle_character(char ch); + void send_nack(void); + void send_ack(void); + void handle_packet(void); + + enum cmd_reply + { + REPLY_NONE, + REPLY_OK, + REPLY_ENN, + REPLY_UNSUPPORTED, + }; + + cmd_reply handle_exclamation(const char *buf); + cmd_reply handle_question(const char *buf); + cmd_reply handle_c(const char *buf); + cmd_reply handle_D(const char *buf); + cmd_reply handle_g(const char *buf); + cmd_reply handle_G(const char *buf); + cmd_reply handle_H(const char *buf); + cmd_reply handle_i(const char *buf); + cmd_reply handle_I(const char *buf); + cmd_reply handle_k(const char *buf); + cmd_reply handle_m(const char *buf); + cmd_reply handle_M(const char *buf); + cmd_reply handle_p(const char *buf); + cmd_reply handle_P(const char *buf); + cmd_reply handle_q(const char *buf); + cmd_reply handle_s(const char *buf); + cmd_reply handle_z(const char *buf); + cmd_reply handle_Z(const char *buf); + + enum readbuf_state + { + PACKET_START, + PACKET_DATA, + PACKET_CHECKSUM1, + PACKET_CHECKSUM2, + }; + + readbuf_state m_readbuf_state; + + void generate_target_xml(void); + + int readchar(void); + + void send_reply(const char *str); + void send_stop_packet(void); + +private: + running_machine *m_machine; + device_t *m_maincpu; + device_state_interface *m_state; + device_memory_interface *m_memory; + address_space *m_address_space; + debugger_cpu *m_debugger_cpu; + int m_debugger_port; + emu_file m_socket; + bool m_is_be; + bool m_initialized; + bool m_dettached; + bool m_extended_mode; + bool m_send_stop_packet; + bool m_target_xml_sent; + + struct gdb_register + { + std::string gdb_name; + int gdb_regnum; + gdb_register_type gdb_type; + int gdb_bitsize; + int state_index; + }; + std::vector<gdb_register> m_gdb_registers; + std::set<int> m_stop_reply_registers; + std::string m_gdb_arch; + std::string m_gdb_feature; + + std::map<offs_t, uint64_t> m_address_map; + + device_debug::breakpoint *m_triggered_breakpoint; + device_debug::watchpoint *m_triggered_watchpoint; + + std::string m_target_xml; + + uint8_t m_readbuf[512]; + uint32_t m_readbuf_len; + uint32_t m_readbuf_offset; + + uint8_t m_packet_buf[MAX_PACKET_SIZE+1]; + int m_packet_len; + uint8_t m_packet_checksum; + uint8_t m_recv_checksum; +}; + +//------------------------------------------------------------------------- +int debug_gdbstub::init(const osd_options &options) +{ + m_debugger_port = options.debugger_port(); + return 0; +} + +//------------------------------------------------------------------------- +void debug_gdbstub::exit(void) +{ +} + +//------------------------------------------------------------------------- +void debug_gdbstub::init_debugger(running_machine &machine) +{ + std::string socket_name = string_format("socket.localhost:%d", m_debugger_port); + osd_file::error filerr = m_socket.open(socket_name.c_str()); + if ( filerr != osd_file::error::NONE ) + return; + + m_machine = &machine; + + osd_printf_info("gdbstub: listening on port %d\n", m_debugger_port); +} + +//------------------------------------------------------------------------- +int debug_gdbstub::readchar(void) +{ + // NOTE: we don't use m_socket.getc() because it does not work with + // sockets (it assumes seeking is possible). + + if ( !m_socket.is_open() ) + return -1; + + if ( m_readbuf_offset == m_readbuf_len ) + { + m_readbuf_offset = 0; + m_readbuf_len = m_socket.read(m_readbuf, sizeof(m_readbuf)); + if ( m_readbuf_len == 0 ) + return -1; + } + + return (int) m_readbuf[m_readbuf_offset++]; +} + +//------------------------------------------------------------------------- +static std::string escape_packet(const std::string src) +{ + std::string result; + result.reserve(src.length()); + for ( char ch: src ) + { + if ( ch == '#' || ch == '$' || ch == '}' ) + { + result += '}'; + ch ^= 0x20; + } + result += ch; + } + return result; +} + +//------------------------------------------------------------------------- +void debug_gdbstub::generate_target_xml(void) +{ + std::string target_xml; + target_xml += "<?xml version=\"1.0\"?>\n"; + target_xml += "<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n"; + target_xml += "<target version=\"1.0\">\n"; + target_xml += string_format("<architecture>%s</architecture>\n", m_gdb_arch.c_str()); + target_xml += string_format(" <feature name=\"%s\">\n", m_gdb_feature.c_str()); + for ( const auto ®: m_gdb_registers ) + if ( !reg.gdb_name.empty() ) + target_xml += string_format(" <reg name=\"%s\" bitsize=\"%d\" type=\"%s\"/>\n", reg.gdb_name.c_str(), reg.gdb_bitsize, gdb_register_type_str[reg.gdb_type]); + target_xml += " </feature>\n"; + target_xml += "</target>\n"; + m_target_xml = escape_packet(target_xml); +} + +//------------------------------------------------------------------------- +void debug_gdbstub::wait_for_debugger(device_t &device, bool firststop) +{ + if ( m_dettached ) + return; + + if ( firststop && !m_initialized ) + { + m_maincpu = m_machine->root_device().subdevice(":maincpu"); + const char *cpuname = m_maincpu->shortname(); + auto it = gdb_register_maps.find(cpuname); + if ( it == gdb_register_maps.end() ) + fatalerror("gdbstub: cpuname %s not found in gdb stub descriptions\n", cpuname); + + m_state = &m_maincpu->state(); + m_memory = &m_maincpu->memory(); + m_address_space = &m_memory->space(AS_PROGRAM); + m_debugger_cpu = &m_machine->debugger().cpu(); + + m_is_be = m_address_space->endianness() == ENDIANNESS_BIG; + + const gdb_register_map ®ister_map = it->second; + m_gdb_arch = register_map.arch; + m_gdb_feature = register_map.feature; + int cur_gdb_regnum = 0; + for ( const auto ®: register_map.registers ) + { + bool added = false; + for ( const auto &entry: m_state->state_entries() ) + { + const char *symbol = entry->symbol(); + if ( strcmp(symbol, reg.state_name) == 0 ) + { + gdb_register new_reg; + new_reg.gdb_name = reg.gdb_name; + new_reg.gdb_regnum = cur_gdb_regnum; + new_reg.gdb_type = reg.gdb_type; + new_reg.gdb_bitsize = entry->datasize() * 8; + new_reg.state_index = entry->index(); + m_gdb_registers.push_back(std::move(new_reg)); + if ( reg.stop_packet ) + m_stop_reply_registers.insert(cur_gdb_regnum); + added = true; + cur_gdb_regnum++; + break; + } + } + if ( !added ) + osd_printf_info("gdbstub: could not find register [%s]\n", reg.gdb_name); + } + +#if 0 + for ( const auto ®: m_gdb_registers ) + if ( !reg.gdb_name.empty() ) + osd_printf_info(" %3d (%d) %d %d [%s]\n", reg.gdb_regnum, reg.state_index, reg.gdb_bitsize, reg.gdb_type, reg.gdb_name.c_str()); +#endif + + m_initialized = true; + } + else + { + device_debug *debug = m_debugger_cpu->get_visible_cpu()->debug(); + m_triggered_watchpoint = debug->triggered_watchpoint(); + m_triggered_breakpoint = debug->triggered_breakpoint(); + if ( m_send_stop_packet ) + { + send_stop_packet(); + m_send_stop_packet = false; + } + } + + while ( m_debugger_cpu->is_stopped() ) + { + int ch = readchar(); + if ( ch < 0 ) + { + // TODO add support for timeout in *_osd_socket. + // To prevent 100% cpu usage while waiting for data to + // arrive from the socket, we sleep for 1 millisecond. + osd_sleep(osd_ticks_per_second() / 1000); + continue; + } + handle_character((char) ch); + } +} + +//------------------------------------------------------------------------- +void debug_gdbstub::debugger_update(void) +{ + while ( true ) + { + int ch = readchar(); + if ( ch < 0 ) + break; + handle_character((char) ch); + } +} + +//------------------------------------------------------------------------- +void debug_gdbstub::send_nack(void) +{ + m_socket.puts("-"); +} + +//------------------------------------------------------------------------- +void debug_gdbstub::send_ack(void) +{ + m_socket.puts("+"); +} + +//------------------------------------------------------------------------- +void debug_gdbstub::send_reply(const char *str) +{ + size_t length = strlen(str); + + uint8_t checksum = 0; + for ( size_t i = 0; i < length; i++ ) + checksum += str[i]; + + std::string reply = string_format("$%s#%02x", str, checksum); + m_socket.puts(reply.c_str()); +} + + +//------------------------------------------------------------------------- +// Enable extended mode. +debug_gdbstub::cmd_reply debug_gdbstub::handle_exclamation(const char */*buf*/) +{ + m_extended_mode = true; + return REPLY_OK; +} + +//------------------------------------------------------------------------- +// Indicate the reason the target halted. +debug_gdbstub::cmd_reply debug_gdbstub::handle_question(const char */*buf*/) +{ + send_stop_packet(); + return REPLY_NONE; +} + +//------------------------------------------------------------------------- +// Continue at addr. +debug_gdbstub::cmd_reply debug_gdbstub::handle_c(const char *buf) +{ + // We don't support continuing with addr. + if ( *buf != '\0' ) + return REPLY_UNSUPPORTED; + + m_debugger_cpu->get_visible_cpu()->debug()->go(); + m_send_stop_packet = true; + return REPLY_NONE; +} + +//------------------------------------------------------------------------- +// Detach. +debug_gdbstub::cmd_reply debug_gdbstub::handle_D(const char *buf) +{ + // We don't support dettaching with pid. + if ( *buf != '\0' ) + return REPLY_UNSUPPORTED; + + m_debugger_cpu->get_visible_cpu()->debug()->go(); + m_dettached = true; + + return REPLY_OK; +} + +//------------------------------------------------------------------------- +// Read general registers. +debug_gdbstub::cmd_reply debug_gdbstub::handle_g(const char *buf) +{ + if ( !m_target_xml_sent ) + return REPLY_ENN; + if ( *buf != '\0' ) + return REPLY_UNSUPPORTED; + std::string reply; + for ( const auto ®: m_gdb_registers ) + if ( !reg.gdb_name.empty() ) + reply += get_register_string(reg.gdb_regnum); + send_reply(reply.c_str()); + return REPLY_NONE; +} + +//------------------------------------------------------------------------- +// Write general registers. +debug_gdbstub::cmd_reply debug_gdbstub::handle_G(const char *buf) +{ + if ( !m_target_xml_sent ) + return REPLY_ENN; + for ( const auto ®: m_gdb_registers ) + { + uint64_t value; + if ( !parse_register_string(&value, buf, reg.gdb_regnum) ) + return REPLY_ENN; + set_register_value(reg.gdb_regnum, value); + buf += (reg.gdb_bitsize / 8) * 2; + } + if ( *buf != '\0' ) + return REPLY_ENN; + return REPLY_OK; +} + +//------------------------------------------------------------------------- +// Set thread for subsequent operations. +debug_gdbstub::cmd_reply debug_gdbstub::handle_H(const char *buf) +{ + // accept threads 'any', 1, and 'all' + if ( (buf[0] == 'c' || buf[0] == 'g') && is_thread_id_ok(buf + 1) ) + return REPLY_OK; + // otherwise silently ignore + return REPLY_UNSUPPORTED; +} + +//------------------------------------------------------------------------- +// Kill request. +debug_gdbstub::cmd_reply debug_gdbstub::handle_k(const char */*buf*/) +{ + m_machine->schedule_exit(); + m_debugger_cpu->get_visible_cpu()->debug()->go(); + m_dettached = true; + m_socket.close(); + return REPLY_NONE; +} + +//------------------------------------------------------------------------- +// Read memory. +debug_gdbstub::cmd_reply debug_gdbstub::handle_m(const char *buf) +{ + uint64_t address; + uint64_t length; + if ( sscanf(buf, "%" PRIx64 ",%" PRIx64, &address, &length) != 2 ) + return REPLY_ENN; + + offs_t offset = address; + if ( !m_memory->translate(m_address_space->spacenum(), TRANSLATE_READ_DEBUG, offset) ) + return REPLY_ENN; + + std::string reply; + reply.reserve(length * 2); + for ( int i = 0; i < length; i++ ) + { + uint8_t value = m_address_space->read_byte(offset + i); + reply += string_format("%02x", value); + } + send_reply(reply.c_str()); + + return REPLY_NONE; +} + +//------------------------------------------------------------------------- +// Write memory. +debug_gdbstub::cmd_reply debug_gdbstub::handle_M(const char *buf) +{ + uint64_t address; + uint64_t length; + int buf_offset; + if ( sscanf(buf, "%" PRIx64 ",%" PRIx64 ":%n", &address, &length, &buf_offset) != 2 ) + return REPLY_ENN; + + offs_t offset = address; + if ( !m_memory->translate(m_address_space->spacenum(), TRANSLATE_READ_DEBUG, offset) ) + return REPLY_ENN; + + std::vector<uint8_t> data; + data.reserve(length); + buf += buf_offset; + for ( int i = 0; i < length; i++ ) + { + if ( sscanf(buf, "%02hhx", &data[i]) != 1 ) + return REPLY_ENN; + buf += 2; + } + if ( *buf != '\0' ) + return REPLY_ENN; + + for ( int i = 0; i < length; i++ ) + m_address_space->write_byte(offset + i, data[i]); + + return REPLY_OK; +} + +//------------------------------------------------------------------------- +// Read the value of register n. +debug_gdbstub::cmd_reply debug_gdbstub::handle_p(const char *buf) +{ + if ( !m_target_xml_sent ) + return REPLY_ENN; + int gdb_regnum; + if ( sscanf(buf, "%x", &gdb_regnum) != 1 || gdb_regnum >= m_gdb_registers.size() ) + return REPLY_ENN; + std::string reply = get_register_string(gdb_regnum); + send_reply(reply.c_str()); + return REPLY_NONE; +} + +//------------------------------------------------------------------------- +// Write register n… with value r…. +debug_gdbstub::cmd_reply debug_gdbstub::handle_P(const char *buf) +{ + if ( !m_target_xml_sent ) + return REPLY_ENN; + int gdb_regnum; + int buf_offset; + if ( sscanf(buf, "%x=%n", &gdb_regnum, &buf_offset) != 1 || gdb_regnum >= m_gdb_registers.size() ) + return REPLY_ENN; + buf += buf_offset; + uint64_t value; + if ( !parse_register_string(&value, buf, gdb_regnum) ) + return REPLY_ENN; + set_register_value(gdb_regnum, value); + return REPLY_OK; +} + +//------------------------------------------------------------------------- +// General query. +debug_gdbstub::cmd_reply debug_gdbstub::handle_q(const char *buf) +{ + // First check for packets that predate the qname:params convention. + if ( *buf == 'C' ) + { + // Return the current thread ID. + send_reply("QC1"); + return REPLY_NONE; + } + else if ( *buf == 'P' ) + { + // Returns information on thread-id. + return REPLY_UNSUPPORTED; + } + else if ( *buf == 'L' ) + { + // Obtain thread information from RTOS. + return REPLY_UNSUPPORTED; + } + + // Split name and parameters + const char *ptr = buf; + while ( *ptr != '\0' && *ptr != ':' ) + ptr++; + std::string name(buf, ptr-buf); + std::string params; + if ( *ptr != '\0' ) + params = ptr+1; + + if ( name == "Supported" ) + { + std::string reply = string_format("PacketSize=%x", MAX_PACKET_SIZE); + reply += ";qXfer:features:read+"; + send_reply(reply.c_str()); + return REPLY_NONE; + } + else if ( name == "Xfer" ) + { + // "features:read:target.xml:0,3fff" + if ( strncmp(params.c_str(), "features:read:", 14) == 0 ) + { + int offset = 0; + int length = 0; + if ( sscanf(params.c_str() + 14, "target.xml:%x,%x", &offset, &length) == 2 ) + { + if ( m_target_xml.empty() ) + generate_target_xml(); + length = std::min(length, (int) m_target_xml.length()-offset); + std::string reply; + if ( offset + length < m_target_xml.length() ) + reply += 'm'; + else + reply += 'l'; + reply += m_target_xml.substr(offset, length); + send_reply(reply.c_str()); + m_target_xml_sent = true; + return REPLY_NONE; + } + } + } + else if ( name == "fThreadInfo" ) + { + send_reply("m1"); + return REPLY_NONE; + } + else if ( name == "sThreadInfo" ) + { + send_reply("l"); + return REPLY_NONE; + } + + return REPLY_UNSUPPORTED; +} + +//------------------------------------------------------------------------- +// Single step, resuming at addr. +debug_gdbstub::cmd_reply debug_gdbstub::handle_s(const char *buf) +{ + // We don't support stepping with addr. + if ( *buf != '\0' ) + return REPLY_UNSUPPORTED; + + m_debugger_cpu->get_visible_cpu()->debug()->single_step(); + m_send_stop_packet = true; + return REPLY_NONE; +} + +//------------------------------------------------------------------------- +static bool remove_breakpoint(device_debug *debug, uint64_t address, int /*kind*/) +{ + device_debug::breakpoint *bp = debug->breakpoint_first(); + while ( bp != nullptr ) + { + if ( bp->address() == address ) + return debug->breakpoint_clear(bp->index()); + bp = bp->next(); + } + return false; +} + +//------------------------------------------------------------------------- +static bool remove_watchpoint(device_debug *debug, read_or_write type, uint64_t address, int kind) +{ + const auto &watchpoints = debug->watchpoint_vector(AS_PROGRAM); + for ( const auto &wp: watchpoints ) + if ( wp->type() == type && wp->address() == address && wp->length() == kind ) + return debug->watchpoint_clear(wp->index()); + return false; +} + +//------------------------------------------------------------------------- +bool debug_gdbstub::parse_zZ(int *ptype, uint64_t *paddress, int *pkind, const char *buf) +{ + int buf_offset; + if ( sscanf(buf, "%d,%" PRIx64 ",%x%n", ptype, paddress, pkind, &buf_offset) != 3 || buf[buf_offset] != '\0' ) + return false; + return true; +} + +//------------------------------------------------------------------------- +// Remove breakpoint or watchpoint. +debug_gdbstub::cmd_reply debug_gdbstub::handle_z(const char *buf) +{ + int type; + uint64_t address; + int kind; + if ( !parse_zZ(&type, &address, &kind, buf) ) + return REPLY_ENN; + + // watchpoints + offs_t offset = address; + if ( type == 2 || type == 3 || type == 4 ) + { + if ( !m_memory->translate(m_address_space->spacenum(), TRANSLATE_READ_DEBUG, offset) ) + return REPLY_ENN; + m_address_map.erase(offset); + } + + device_debug *debug = m_debugger_cpu->get_visible_cpu()->debug(); + switch ( type ) + { + // Note: software and hardware breakpoints are treated both the + // same way, and the 'kind' parameter is ignored. + case 0: // software breakpoint + case 1: // hardware breakpoint + return remove_breakpoint(debug, address, kind) ? REPLY_OK : REPLY_ENN; + case 2: + // write watchpoint + return remove_watchpoint(debug, read_or_write::WRITE, offset, kind) ? REPLY_OK : REPLY_ENN; + case 3: + // read watchpoint + return remove_watchpoint(debug, read_or_write::READ, offset, kind) ? REPLY_OK : REPLY_ENN; + case 4: + // access watchpoint + return remove_watchpoint(debug, read_or_write::READWRITE, offset, kind) ? REPLY_OK : REPLY_ENN; + } + + return REPLY_UNSUPPORTED; +} + +//------------------------------------------------------------------------- +// Insert breakpoint or watchpoint. +debug_gdbstub::cmd_reply debug_gdbstub::handle_Z(const char *buf) +{ + int type; + uint64_t address; + int kind; + if ( !parse_zZ(&type, &address, &kind, buf) ) + return REPLY_ENN; + + // watchpoints + offs_t offset = address; + if ( type == 2 || type == 3 || type == 4 ) + { + if ( !m_memory->translate(m_address_space->spacenum(), TRANSLATE_READ_DEBUG, offset) ) + return REPLY_ENN; + m_address_map[offset] = address; + } + + device_debug *debug = m_debugger_cpu->get_visible_cpu()->debug(); + switch ( type ) + { + // Note: software and hardware breakpoints are treated both the + // same way, and the 'kind' parameter is ignored. + case 0: // software breakpoint + case 1: // hardware breakpoint + debug->breakpoint_set(address); + return REPLY_OK; + case 2: + // write watchpoint + debug->watchpoint_set(*m_address_space, read_or_write::WRITE, offset, kind, nullptr, nullptr); + return REPLY_OK; + case 3: + // read watchpoint + debug->watchpoint_set(*m_address_space, read_or_write::READ, offset, kind, nullptr, nullptr); + return REPLY_OK; + case 4: + // access watchpoint + debug->watchpoint_set(*m_address_space, read_or_write::READWRITE, offset, kind, nullptr, nullptr); + return REPLY_OK; + } + + return REPLY_UNSUPPORTED; +} + + +//------------------------------------------------------------------------- +void debug_gdbstub::send_stop_packet(void) +{ + int signal = 5; // GDB_SIGNAL_TRAP + std::string reply = string_format("T%02x", signal); + if ( m_triggered_watchpoint != nullptr ) + { + switch ( m_triggered_watchpoint->type() ) + { + case read_or_write::WRITE: + reply += "watch"; + break; + case read_or_write::READ: + reply += "rwatch"; + break; + case read_or_write::READWRITE: + reply += "awatch"; + break; + } + offs_t offset = m_triggered_watchpoint->address(); + uint64_t address = m_address_map[offset]; + reply += string_format(":%" PRIx64 ";", address); + } + if ( m_target_xml_sent ) + for ( const auto &gdb_regnum: m_stop_reply_registers ) + reply += string_format("%02x:%s;", gdb_regnum, get_register_string(gdb_regnum)); + send_reply(reply.c_str()); +} + +//------------------------------------------------------------------------- +void debug_gdbstub::handle_packet(void) +{ + // For any command not supported by the stub, an empty response + // (‘$#00’) should be returned. That way it is possible to extend + // the protocol. A newer GDB can tell if a packet is supported + // based on that response. + cmd_reply reply = REPLY_UNSUPPORTED; + + const char *buf = (const char *) m_packet_buf+1; + switch ( m_packet_buf[0] ) + { + case '!': reply = handle_exclamation(buf); break; + case '?': reply = handle_question(buf); break; + case 'c': reply = handle_c(buf); break; + case 'D': reply = handle_D(buf); break; + case 'g': reply = handle_g(buf); break; + case 'G': reply = handle_G(buf); break; + case 'H': reply = handle_H(buf); break; + case 'k': reply = handle_k(buf); break; + case 'm': reply = handle_m(buf); break; + case 'M': reply = handle_M(buf); break; + case 'p': reply = handle_p(buf); break; + case 'P': reply = handle_P(buf); break; + case 'q': reply = handle_q(buf); break; + case 's': reply = handle_s(buf); break; + case 'z': reply = handle_z(buf); break; + case 'Z': reply = handle_Z(buf); break; + } + if ( reply == REPLY_OK ) + send_reply("OK"); + else if ( reply == REPLY_ENN ) + send_reply("E01"); + else if ( reply == REPLY_UNSUPPORTED ) + send_reply(""); +} + +//------------------------------------------------------------------------- +#define BYTESWAP_64(x) ((((x) << 56) & 0xFF00000000000000) \ + | (((x) << 40) & 0x00FF000000000000) \ + | (((x) << 24) & 0x0000FF0000000000) \ + | (((x) << 8) & 0x000000FF00000000) \ + | (((x) >> 8) & 0x00000000FF000000) \ + | (((x) >> 24) & 0x0000000000FF0000) \ + | (((x) >> 40) & 0x000000000000FF00) \ + | (((x) >> 56) & 0x00000000000000FF)) +#define BYTESWAP_32(x) ((((x) << 24) & 0xFF000000) \ + | (((x) << 8) & 0x00FF0000) \ + | (((x) >> 8) & 0x0000FF00) \ + | (((x) >> 24) & 0x000000FF)) +#define BYTESWAP_16(x) ((((x) << 8) & 0xFF00) \ + | (((x) >> 8) & 0x00FF)) + +//------------------------------------------------------------------------- +std::string debug_gdbstub::get_register_string(int gdb_regnum) +{ + const gdb_register ® = m_gdb_registers[gdb_regnum]; + const char *fmt = (reg.gdb_bitsize == 64) ? "%016" PRIx64 + : (reg.gdb_bitsize == 32) ? "%08" PRIx64 + : (reg.gdb_bitsize == 16) ? "%04" PRIx64 + : "%02" PRIx64; + uint64_t value = m_state->state_int(reg.state_index); + if ( !m_is_be ) + { + value = (reg.gdb_bitsize == 64) ? BYTESWAP_64(value) + : (reg.gdb_bitsize == 32) ? BYTESWAP_32(value) + : (reg.gdb_bitsize == 16) ? BYTESWAP_16(value) + : value; + } + return string_format(fmt, value); +} + +//------------------------------------------------------------------------- +bool debug_gdbstub::parse_register_string(uint64_t *pvalue, const char *buf, int gdb_regnum) +{ + const gdb_register ® = m_gdb_registers[gdb_regnum]; + const char *fmt = (reg.gdb_bitsize == 64) ? "%016" PRIx64 + : (reg.gdb_bitsize == 32) ? "%08" PRIx64 + : (reg.gdb_bitsize == 16) ? "%04" PRIx64 + : "%02" PRIx64; + uint64_t value; + if ( sscanf(buf, fmt, &value) != 1 ) + return false; + if ( !m_is_be ) + { + value = (reg.gdb_bitsize == 64) ? BYTESWAP_64(value) + : (reg.gdb_bitsize == 32) ? BYTESWAP_32(value) + : (reg.gdb_bitsize == 16) ? BYTESWAP_16(value) + : value; + } + *pvalue = value; + return true; +} + +//------------------------------------------------------------------------- +void debug_gdbstub::set_register_value(int gdb_regnum, uint64_t value) +{ + const gdb_register ® = m_gdb_registers[gdb_regnum]; + m_state->set_state_int(reg.state_index, value); +} + +//------------------------------------------------------------------------- +bool debug_gdbstub::is_thread_id_ok(const char *buf) +{ + // 'any' + if ( buf[0] == '0' && buf[1] == '\0' ) + return true; + // The thread id we reported. + if ( buf[0] == '1' && buf[1] == '\0' ) + return true; + // 'all' + if ( buf[0] == '-' && buf[1] == '1' && buf[2] == '\0' ) + return true; + return false; +} + +//------------------------------------------------------------------------- +void debug_gdbstub::handle_character(char ch) +{ + int8_t nibble; + switch ( m_readbuf_state ) + { + case PACKET_START: + if ( ch == '$' ) + { + m_packet_len = 0; + m_packet_checksum = 0; + m_readbuf_state = PACKET_DATA; + } + else if ( ch == '\x03' ) + { + m_debugger_cpu->set_execution_stopped(); + } + break; + case PACKET_DATA: + if ( ch == '#' ) + { + m_readbuf_state = PACKET_CHECKSUM1; + } + else if ( m_packet_len >= MAX_PACKET_SIZE ) + { + osd_printf_info("gdbstub: packet buffer overflow!\n"); + m_readbuf_state = PACKET_START; + } + else + { + m_packet_buf[m_packet_len++] = ch; + m_packet_checksum += ch; + } + break; + case PACKET_CHECKSUM1: + if ( sscanf(&ch, "%01hhx", &nibble) != 1 ) + { + osd_printf_info("gdbstub: invalid checksum!\n"); + m_readbuf_state = PACKET_START; + break; + } + m_recv_checksum = nibble; + m_readbuf_state = PACKET_CHECKSUM2; + break; + case PACKET_CHECKSUM2: + if ( sscanf(&ch, "%01hhx", &nibble) != 1 ) + { + osd_printf_info("gdbstub: invalid checksum!\n"); + m_readbuf_state = PACKET_START; + break; + } + m_recv_checksum <<= 4; + m_recv_checksum |= nibble; + if ( m_recv_checksum != m_packet_checksum ) + { + osd_printf_info("gdbstub: bad checksum!\n"); + send_nack(); + m_readbuf_state = PACKET_START; + break; + } + m_packet_buf[m_packet_len] = '\0'; + send_ack(); + handle_packet(); + m_readbuf_state = PACKET_START; + break; + } +} + +//------------------------------------------------------------------------- +MODULE_DEFINITION(DEBUG_GDBSTUB, debug_gdbstub) diff --git a/src/osd/modules/lib/osdobj_common.cpp b/src/osd/modules/lib/osdobj_common.cpp index d3f5f35192e..4e2d73f6336 100644 --- a/src/osd/modules/lib/osdobj_common.cpp +++ b/src/osd/modules/lib/osdobj_common.cpp @@ -40,6 +40,7 @@ const options_entry osd_options::s_option_entries[] = { nullptr, nullptr, OPTION_HEADER, "OSD DEBUGGING OPTIONS" }, { OSDOPTION_DEBUGGER, OSDOPTVAL_AUTO, OPTION_STRING, "debugger used: " }, + { OSDOPTION_DEBUGGER_PORT, "23946", OPTION_INTEGER, "port to use for gdbstub debugger" }, { OSDOPTION_DEBUGGER_FONT ";dfont", OSDOPTVAL_AUTO, OPTION_STRING, "font to use for debugger views" }, { OSDOPTION_DEBUGGER_FONT_SIZE ";dfontsize", "0", OPTION_FLOAT, "font size to use for debugger views" }, { OSDOPTION_WATCHDOG ";wdog", "0", OPTION_INTEGER, "force the program to terminate if no updates within specified number of seconds" }, @@ -235,6 +236,7 @@ void osd_common_t::register_options() REGISTER_MODULE(m_mod_man, DEBUG_WINDOWS); REGISTER_MODULE(m_mod_man, DEBUG_QT); REGISTER_MODULE(m_mod_man, DEBUG_IMGUI); + REGISTER_MODULE(m_mod_man, DEBUG_GDBSTUB); REGISTER_MODULE(m_mod_man, DEBUG_NONE); #endif diff --git a/src/osd/modules/lib/osdobj_common.h b/src/osd/modules/lib/osdobj_common.h index af4d2d332bc..37e043f53ad 100644 --- a/src/osd/modules/lib/osdobj_common.h +++ b/src/osd/modules/lib/osdobj_common.h @@ -37,6 +37,7 @@ #define OSDCOMMAND_LIST_NETWORK_ADAPTERS "listnetwork" #define OSDOPTION_DEBUGGER "debugger" +#define OSDOPTION_DEBUGGER_PORT "debugger_port" #define OSDOPTION_DEBUGGER_FONT "debugger_font" #define OSDOPTION_DEBUGGER_FONT_SIZE "debugger_font_size" #define OSDOPTION_WATCHDOG "watchdog" @@ -106,6 +107,7 @@ public: // debugging options const char *debugger() const { return value(OSDOPTION_DEBUGGER); } + int debugger_port() const { return int_value(OSDOPTION_DEBUGGER_PORT); } const char *debugger_font() const { return value(OSDOPTION_DEBUGGER_FONT); } float debugger_font_size() const { return float_value(OSDOPTION_DEBUGGER_FONT_SIZE); } int watchdog() const { return int_value(OSDOPTION_WATCHDOG); } |