From 2a9a9e37b0068a9d371b851a7917f711b7ed8b9d Mon Sep 17 00:00:00 2001 From: CocoSimone Date: Sat, 11 Feb 2023 13:45:55 +0100 Subject: [PATCH] add gdbstub --- external/gdbstub/LICENSE | 21 ++ external/gdbstub/gdbstub.h | 553 +++++++++++++++++++++++++++++++++++++ src/CMakeLists.txt | 1 + src/backend/Core.cpp | 5 +- src/backend/Core.hpp | 17 +- src/backend/Debugger.cpp | 274 ++++++++++++++++++ src/backend/Debugger.hpp | 34 +++ 7 files changed, 900 insertions(+), 5 deletions(-) create mode 100644 external/gdbstub/LICENSE create mode 100644 external/gdbstub/gdbstub.h create mode 100644 src/backend/Debugger.cpp create mode 100644 src/backend/Debugger.hpp diff --git a/external/gdbstub/LICENSE b/external/gdbstub/LICENSE new file mode 100644 index 00000000..4c897034 --- /dev/null +++ b/external/gdbstub/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Stephen Lane-Walsh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/external/gdbstub/gdbstub.h b/external/gdbstub/gdbstub.h new file mode 100644 index 00000000..011ee824 --- /dev/null +++ b/external/gdbstub/gdbstub.h @@ -0,0 +1,553 @@ +// +// gdbstub version 1.1.1 +// +// MIT License +// +// Copyright (c) 2020 Stephen Lane-Walsh +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#ifndef GDBSTUB_H +#define GDBSTUB_H + +#include +#include + +typedef void (*gdbstub_connected_t)(void * user_data); +typedef void (*gdbstub_disconnected_t)(void * user_data); +typedef void (*gdbstub_start_t)(void * user_data); +typedef void (*gdbstub_stop_t)(void * user_data); +typedef void (*gdbstub_step_t)(void * user_data); +typedef void (*gdbstub_set_breakpoint_t)(void * user_data, uint32_t address); +typedef void (*gdbstub_clear_breakpoint_t)(void * user_data, uint32_t address); +typedef ssize_t (*gdbstub_get_memory_t)(void * user_data, char * buffer, size_t buffer_length, uint32_t address, size_t length); +typedef ssize_t (*gdbstub_get_register_value_t)(void * user_data, char * buffer, size_t buffer_length, int reg); +typedef ssize_t (*gdbstub_get_general_registers_t)(void * user_data, char * buffer, size_t buffer_length); + +typedef struct gdbstub_config gdbstub_config_t; + +struct gdbstub_config +{ + uint16_t port; + + void * user_data; + + gdbstub_connected_t connected; + + gdbstub_disconnected_t disconnected; + + gdbstub_start_t start; + + gdbstub_stop_t stop; + + gdbstub_step_t step; + + gdbstub_set_breakpoint_t set_breakpoint; + + gdbstub_clear_breakpoint_t clear_breakpoint; + + gdbstub_get_memory_t get_memory; + + gdbstub_get_register_value_t get_register_value; + + gdbstub_get_general_registers_t get_general_registers; + + const char * target_config; + size_t target_config_length; + + const char * memory_map; + size_t memory_map_length; +}; + +typedef struct gdbstub gdbstub_t; + +gdbstub_t * gdbstub_init(gdbstub_config_t config); + +void gdbstub_term(gdbstub_t * gdb); + +void gdbstub_tick(gdbstub_t * gdb); + +void gdbstub_breakpoint_hit(gdbstub_t * gdb); + +#endif // GDBSTUB_H + +#ifdef GDBSTUB_IMPLEMENTATION + +#include +#include +#include +#include +#include +#include + +#if defined(WIN32) +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#pragma comment (lib, "Ws2_32.lib") +#define close closesocket +#else +#include +#endif + +#define GDBSTUB_BUFFER_LENGTH 4096 +#define GDBSTUB_PACKET_LENGTH 2048 + +typedef enum +{ + GDB_STATE_NO_PACKET, + GDB_STATE_IN_PACKET, + GDB_STATE_IN_CHECKSUM, + +} gdbstate_t; + +struct gdbstub +{ + gdbstub_config_t config; + + int server; + int client; + + char buffer[GDBSTUB_BUFFER_LENGTH]; + ssize_t buffer_length; + + gdbstate_t state; + char packet[GDBSTUB_BUFFER_LENGTH]; + ssize_t packet_length; + uint8_t packet_checksum; + + char checksum[2]; + ssize_t checksum_length; + +}; + +void _gdbstub_recv(gdbstub_t * gdb); + +void _gdbstub_send(gdbstub_t * gdb, const char * data, size_t data_length); + +void _gdbstub_send_paged(gdbstub_t * gdb, int offset, int length, const char * data, size_t data_length); + +void _gdbstub_process_packet(gdbstub_t * gdb); + +gdbstub_t * gdbstub_init(gdbstub_config_t config) +{ + gdbstub_t * gdb = (gdbstub_t *)malloc(sizeof(gdbstub_t)); + if (!gdb) { + fprintf(stderr, "out of memory\n"); + return NULL; + } + + gdb->config = config; + gdb->server = -1; + gdb->client = -1; + gdb->state = GDB_STATE_NO_PACKET; + gdb->packet_length = 0; + gdb->packet_checksum = 0; + gdb->checksum_length = 0; + + int result; + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(gdb->config.port); + addr.sin_addr.s_addr = INADDR_ANY; + + gdb->server = socket(AF_INET, SOCK_STREAM, 0); + if (gdb->server < 0) { + perror("socket failed"); + free(gdb); + return NULL; + } + + result = fcntl(gdb->server, F_SETFL, fcntl(gdb->server, F_GETFL, 0) | O_NONBLOCK); + if (result < 0) { + perror("fcntl O_NONBLOCK failed"); + free(gdb); + return NULL; + } + + int reuse = 1; + result = setsockopt(gdb->server, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)); + if (result < 0) { + perror("setsockopt SO_REUSEADDR failed"); + free(gdb); + return NULL; + } + +#ifdef SO_REUSEPORT + result = setsockopt(gdb->server, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)); + if (result < 0) { + perror("setsockopt SO_REUSEPORT failed"); + free(gdb); + return NULL; + } +#endif + + result = bind(gdb->server, (struct sockaddr *)&addr, sizeof(addr)); + if (result < 0) { + perror("bind failed"); + free(gdb); + return NULL; + } + + result = listen(gdb->server, 1); + if (result < 0) { + perror("listen failed"); + free(gdb); + return NULL; + } + + printf("listening for gdb on port %hu\n", gdb->config.port); + return gdb; +} + +void gdbstub_term(gdbstub_t * gdb) +{ + if (gdb) { + if (gdb->client >= 0) { + close(gdb->client); + gdb->client = -1; + } + + if (gdb->server >= 0) { + close(gdb->server); + gdb->server = -1; + } + + free(gdb); + } +} + +void gdbstub_tick(gdbstub_t * gdb) +{ + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + + if (gdb->client < 0) { + gdb->client = accept(gdb->server, (struct sockaddr *)&addr, &addrlen); + if (gdb->client >= 0) { + printf("accepted gdb connection\n"); + fcntl(gdb->client, F_SETFL, fcntl(gdb->client, F_GETFL, 0) | O_NONBLOCK); + + if (gdb->config.connected) { + gdb->config.connected(gdb->config.user_data); + } + } + } + else { + _gdbstub_recv(gdb); + } +} + +void gdbstub_breakpoint_hit(gdbstub_t * gdb) { + _gdbstub_send(gdb, "T05", 3); +} + +void _gdbstub_send(gdbstub_t * gdb, const char * data, size_t data_length) +{ + uint8_t checksum = 0; + for (size_t i = 0; i < data_length; ++i) { + checksum += data[i]; + } + + gdb->packet_length = snprintf(gdb->packet, GDBSTUB_PACKET_LENGTH, "$%s#%02x", data, checksum); + + int bytes = send(gdb->client, gdb->packet, gdb->packet_length, 0); + +# if defined(GDBSTUB_DEBUG) + printf("gdbstub sent '%s'\n", gdb->packet); +# endif + + if (bytes < 0) { + perror("lost gdb connection"); + close(gdb->client); + gdb->client = -1; + gdb->state = GDB_STATE_NO_PACKET; + + if (gdb->config.disconnected) { + gdb->config.disconnected(gdb->config.user_data); + } + } +} + +void _gdbstub_send_paged(gdbstub_t * gdb, int offset, int length, const char * data, size_t data_length) +{ + if (length > GDBSTUB_PACKET_LENGTH - 6) { + length = GDBSTUB_PACKET_LENGTH - 6; + } + + if (length < (data_length - offset)) { + // Any page but the last + gdb->buffer_length = snprintf(gdb->buffer, GDBSTUB_BUFFER_LENGTH, "m%.*s", length, data + offset); + _gdbstub_send(gdb, gdb->buffer, gdb->buffer_length); + } + else if (offset != 0) { + // The last page + gdb->buffer_length = snprintf(gdb->buffer, GDBSTUB_BUFFER_LENGTH, "l%.*s", length, data + offset); + _gdbstub_send(gdb, gdb->buffer, gdb->buffer_length); + } + else { + // The only page + gdb->buffer_length = snprintf(gdb->buffer, GDBSTUB_BUFFER_LENGTH, "l%s", data); + _gdbstub_send(gdb, gdb->buffer, gdb->buffer_length); + } +} + +void _gdbstub_recv(gdbstub_t * gdb) +{ + gdb->buffer_length = recv(gdb->client, gdb->buffer, GDBSTUB_BUFFER_LENGTH, 0); + if (gdb->buffer_length < 0 && errno != EAGAIN) { + perror("lost gdb connection"); + close(gdb->client); + gdb->client = -1; + gdb->state = GDB_STATE_NO_PACKET; + + if (gdb->config.disconnected) { + gdb->config.disconnected(gdb->config.user_data); + } + return; + } + + for (ssize_t i = 0; i < gdb->buffer_length; ++i) { + char c = gdb->buffer[i]; + + switch (gdb->state) + { + case GDB_STATE_NO_PACKET: + if (c == '$') { + gdb->state = GDB_STATE_IN_PACKET; + gdb->packet_length = 0; + gdb->packet_checksum = 0; + } + else if (c == 3) { + // GDB Interrupt '0x03' Packet + gdb->config.stop(gdb->config.user_data); + _gdbstub_send(gdb, "S00", 3); + } + break; + case GDB_STATE_IN_PACKET: + if (c == '#') { + gdb->state = GDB_STATE_IN_CHECKSUM; + gdb->checksum_length = 0; + } + else { + gdb->packet[gdb->packet_length++] = c; + gdb->packet_checksum += c; + } + break; + case GDB_STATE_IN_CHECKSUM: + gdb->checksum[gdb->checksum_length++] = c; + if (gdb->checksum_length == 2) { + int checksum; + sscanf(gdb->checksum, "%2x", &checksum); + if (gdb->packet_checksum != checksum) { + // freak out? + } + + send(gdb->client, "+", 1, 0); + + gdb->packet[gdb->packet_length] = '\0'; +# if defined(GDBSTUB_DEBUG) + printf("gdbstub received '$%s#%c%c'\n", gdb->packet, gdb->checksum[0], gdb->checksum[1]); +# endif + + _gdbstub_process_packet(gdb); + + gdb->state = GDB_STATE_NO_PACKET; + } + } + } +} + +void _gdbstub_process_packet(gdbstub_t * gdb) +{ + switch (gdb->packet[0]) + { + case 'c': + // Continue execution + if (gdb->config.start) { + gdb->config.start(gdb->config.user_data); + } + return; + case 'D': + // Disconnect + printf("gdb disconnected\n"); + _gdbstub_send(gdb, "OK", 2); + close(gdb->client); + gdb->client = -1; + gdb->state = GDB_STATE_NO_PACKET; + + if (gdb->config.disconnected) { + gdb->config.disconnected(gdb->config.user_data); + } + return; + case 'g': + // Get general registers + if (gdb->config.get_general_registers) { + gdb->buffer_length = gdb->config.get_general_registers(gdb->config.user_data, gdb->buffer, GDBSTUB_BUFFER_LENGTH); + _gdbstub_send(gdb, gdb->buffer, gdb->buffer_length); + } + else { + _gdbstub_send(gdb, "", 0); + } + return; + case 'H': + // Set active thread + _gdbstub_send(gdb, "OK", 2); + return; + case 'm': + // Read memory + if (gdb->config.get_memory) { + int address, length; + sscanf(gdb->packet + 1, "%x,%x", &address, &length); + gdb->buffer_length = gdb->config.get_memory(gdb->config.user_data, gdb->buffer, GDBSTUB_BUFFER_LENGTH, address, length); + _gdbstub_send(gdb, gdb->buffer, gdb->buffer_length); + } + else { + _gdbstub_send(gdb, "", 0); + } + return; + case 'p': + // Read the value of register n + if (gdb->config.get_register_value) { + int reg; + sscanf(gdb->packet + 1, "%x", ®); + gdb->buffer_length = gdb->config.get_register_value(gdb->config.user_data, gdb->buffer, GDBSTUB_BUFFER_LENGTH, reg); + _gdbstub_send(gdb, gdb->buffer, gdb->buffer_length); + } + else { + _gdbstub_send(gdb, "", 0); + } + return; + case 'q': + // Check for available features + if (strncmp(gdb->packet, "qSupported", 10) == 0) { + strcpy(gdb->buffer, "PacketSize=1024"); + gdb->buffer_length = 15; + + if (gdb->config.target_config) { + strcpy(gdb->buffer + gdb->buffer_length, ";qXfer:features:read+"); + gdb->buffer_length += 21; + } + + if (gdb->config.memory_map) { + strcpy(gdb->buffer + gdb->buffer_length, ";qXfer:memory-map:read+"); + gdb->buffer_length += 23; + } + + _gdbstub_send(gdb, gdb->buffer, gdb->buffer_length); + return; + } + // We have no thread ID + else if (gdb->packet[1] == 'C') { + _gdbstub_send(gdb, "QC00", 4); + return; + } + // We are always "attached" to an existing process + else if (strncmp(gdb->packet, "qAttached", 9) == 0) { + _gdbstub_send(gdb, "1", 1); + return; + } + // There is no trace running + else if (strncmp(gdb->packet, "qTStatus", 8) == 0) { + _gdbstub_send(gdb, "T0", 2); + return; + } + // Target configuration XML + else if (strncmp(gdb->packet, "qXfer:features:read:target.xml:", 31) == 0) { + int offset, length; + sscanf(gdb->packet + 31, "%x,%x", &offset, &length); + _gdbstub_send_paged(gdb, offset, length, gdb->config.target_config, gdb->config.target_config_length); + return; + } + // Memory map XML + else if (strncmp(gdb->packet, "qXfer:memory-map:read::", 23) == 0) { + int offset, length; + sscanf(gdb->packet + 23, "%x,%x", &offset, &length); + _gdbstub_send_paged(gdb, offset, length, gdb->config.memory_map, gdb->config.memory_map_length); + return; + } + // Trace control operations + else if (strncmp(gdb->packet, "qTfP", 4) == 0) { + _gdbstub_send(gdb, "", 0); + return; + } + else if (strncmp(gdb->packet, "qTfV", 4) == 0) { + _gdbstub_send(gdb, "", 0); + return; + } + else if (strncmp(gdb->packet, "qTsP", 4) == 0) { + _gdbstub_send(gdb, "", 0); + return; + } + // Thread #0 + else if (strncmp(gdb->packet, "qfThreadInfo", 12) == 0) { + _gdbstub_send(gdb, "lm0", 3); + return; + } + // GDB is ready to serve symbol requests + else if (strncmp(gdb->packet, "qSymbol", 7) == 0) { + _gdbstub_send(gdb, "OK", 2); + return; + } + break; + case 's': + // Single execution step + if (gdb->config.step) { + gdb->config.step(gdb->config.user_data); + } + _gdbstub_send(gdb, "T05", 3); + return; + case 'v': + // Various remote operations, not supported + _gdbstub_send(gdb, "", 0); + return; + case 'z': + // Remove breakpoint + if (gdb->config.clear_breakpoint) { + uint32_t address; + sscanf(gdb->packet, "z0,%x", &address); + gdb->config.clear_breakpoint(gdb->config.user_data, address); + } + _gdbstub_send(gdb, "OK", 2); + break; + case 'Z': + // Add breakpoint + if (gdb->config.set_breakpoint) { + uint32_t address; + sscanf(gdb->packet, "Z0,%x", &address); + gdb->config.set_breakpoint(gdb->config.user_data, address); + } + _gdbstub_send(gdb, "OK", 2); + break; + case '?': + // Break immediately + if (gdb->config.stop) { + gdb->config.stop(gdb->config.user_data); + } + // TODO: improve + _gdbstub_send(gdb, "S00", 3); + return; + } + + fprintf(stderr, "unknown gdb command '%s'\n", gdb->packet); +} + +#endif // GDBSTUB_IMPLEMENTATION diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a4c16c96..595d6995 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,6 +35,7 @@ include_directories( ../external/imgui/imgui ../external/imgui/imgui/backends ../external/discord-rpc/include + ../external/gdbstub ${SDL2_INCLUDE_DIRS} ) diff --git a/src/backend/Core.cpp b/src/backend/Core.cpp index a4e7e9a0..68e07f01 100644 --- a/src/backend/Core.cpp +++ b/src/backend/Core.cpp @@ -49,7 +49,7 @@ void Core::Run(Window& window, float volumeL, float volumeR) { } for(;cycles <= mmio.vi.cyclesPerHalfline; cycles++, frameCycles++) { - int cpuCount = CpuStep(); + int cpuCount = CpuStep(*this); int oldCpuCount = cpuCount; while(cpuCount--) { if (!mmio.rsp.spStatus.halt) { @@ -87,6 +87,9 @@ void Core::Run(Window& window, float volumeL, float volumeR) { UpdateScreenParallelRdpNoGame(*this, window); } } + + if(debugger.enabled && romLoaded && !pause) + debugger.tick(); } #define GET_BUTTON(gamepad, i) SDL_GameControllerGetButton(gamepad, i) diff --git a/src/backend/Core.hpp b/src/backend/Core.hpp index 06ab1898..456a931c 100644 --- a/src/backend/Core.hpp +++ b/src/backend/Core.hpp @@ -5,6 +5,7 @@ #include #include #include +#include struct Window; @@ -40,10 +41,17 @@ struct Core { } } - int CpuStep() { - switch(cpuType) { - case CpuType::Dynarec: return cpuDynarec->Step(mem); - case CpuType::Interpreter: cpuInterp->Step(mem); return 1; + static int CpuStep(Core& core) { + if (core.debugger.enabled && core.debugger.checkBreakpoint(core.CpuGetRegs().pc)) { + core.debugger.breakpointHit(); + } + while (core.debugger.broken) { + usleep(1000); + core.debugger.tick(); + } + switch(core.cpuType) { + case CpuType::Dynarec: return core.cpuDynarec->Step(core.mem); + case CpuType::Interpreter: core.cpuInterp->Step(core.mem); return 1; case CpuType::NONE: return 0; } } @@ -62,5 +70,6 @@ struct Core { CpuType cpuType = CpuType::NONE; Interpreter* cpuInterp = nullptr; JIT::Dynarec* cpuDynarec = nullptr; + Debugger debugger{*this}; }; } diff --git a/src/backend/Debugger.cpp b/src/backend/Debugger.cpp new file mode 100644 index 00000000..3f1bad77 --- /dev/null +++ b/src/backend/Debugger.cpp @@ -0,0 +1,274 @@ +#include +#define GDBSTUB_IMPLEMENTATION +#include +#include +#include + +const char* target_xml = + "" + "" + "" + "mips:4000" + "none" + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + " " + " " + " " + " " + "" + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + +const char* memory_map = + "" + "" + "" + "" + + "" // TODO finish + "" // RDRAM + "" // RSP DMEM + "" // RSP IMEM + "" // PIF ROM + + "" // TODO finish + "" // RDRAM + "" // RSP DMEM + "" // RSP IMEM + "" // PIF ROM + ""; + +void n64_debug_start(void* user_data) { + auto* debugger = (Debugger*)user_data; + debugger->broken = false; +} + +void n64_debug_stop(void* user_data) { + auto* debugger = (Debugger*)user_data; + debugger->broken = true; +} + +void n64_debug_step(void* user_data) { + auto* debugger = (Debugger*)user_data; + bool old_broken = debugger->broken; + debugger->broken = false; + n64::Core::CpuStep(debugger->core); + debugger->broken = old_broken; + debugger->steps += 2; +} + +void n64_debug_set_breakpoint(void* user_data, u32 address) { + auto* debugger = (Debugger*)user_data; + auto* breakpoint = (Breakpoint*)malloc(sizeof(Breakpoint)); + breakpoint->address = address; + breakpoint->next = nullptr; + + // Special case for this being the first breakpoint + if (debugger->breakpoints == nullptr) { + debugger->breakpoints = breakpoint; + } else { + // Find end of the list + auto* tail = debugger->breakpoints; + while (tail->next != nullptr) { + tail = tail->next; + } + + tail->next = breakpoint; + } +} + +void n64_debug_clear_breakpoint(void* user_data, u32 address) { + auto* debugger = (Debugger*)user_data; + if (debugger->breakpoints == nullptr) { + return; // No breakpoints set at all + } else if (debugger->breakpoints->address == address) { + // Special case for the first breakpoint being the one we want to clear + auto* next = debugger->breakpoints->next; + free(debugger->breakpoints); + debugger->breakpoints = next; + } else { + // Find the breakpoint somewhere in the list and free it + auto* iter = debugger->breakpoints; + while (iter->next != nullptr) { + if (iter->next->address == address) { + auto* next = iter->next->next; + free(iter->next); + iter->next = next; + } + } + } +} + +ssize_t n64_debug_get_memory(void* user_data, char* buffer, size_t length, u32 address, size_t bytes) { + auto* debugger = (Debugger*)user_data; + printf("Checking memory at address 0x%08X\n", address); + int printed = 0; + u32 paddr; + if(!n64::MapVAddr(debugger->core.CpuGetRegs(), LOAD, address, paddr)) { + return 0; + } + + for (int i = 0; i < bytes; i++) { + u8 value = debugger->core.mem.Read8(debugger->core.CpuGetRegs(), paddr + i); + printed += snprintf(buffer + (i*2), length, "%02X", value); + } + printf("Get memory: %ld bytes from 0x%08X: %d\n", bytes, paddr, printed); + return printed + 1; +} + +ssize_t n64_debug_get_register_value(void* user_data, char * buffer, size_t buffer_length, int reg) { + auto* debugger = (Debugger*)user_data; + switch (reg) { + case 0 ... 31: + return snprintf(buffer, buffer_length, "%016lx", debugger->core.CpuGetRegs().gpr[reg]); + case 32: + return snprintf(buffer, buffer_length, "%08x", debugger->core.CpuGetRegs().cop0.status.raw); + case 33: + return snprintf(buffer, buffer_length, "%016lx", debugger->core.CpuGetRegs().lo); + case 34: + return snprintf(buffer, buffer_length, "%016lx", debugger->core.CpuGetRegs().hi); + case 35: + return snprintf(buffer, buffer_length, "%016lx", debugger->core.CpuGetRegs().cop0.badVaddr); + case 36: + return snprintf(buffer, buffer_length, "%08x", debugger->core.CpuGetRegs().cop0.cause.raw); + case 37: + printf("Sending PC: 0x%016lX\n", debugger->core.CpuGetRegs().pc); + return snprintf(buffer, buffer_length, "%016lx", debugger->core.CpuGetRegs().pc); + case 38 ... 71: // TODO FPU stuff + return snprintf(buffer, buffer_length, "%08x", 0); + default: + Util::panic("Debug get register %d value\n", reg); + } +} + +ssize_t n64_debug_get_general_registers(void* user_data, char * buffer, size_t buffer_length) { + auto* debugger = (Debugger*)user_data; + printf("The buffer length is %ld!\n", buffer_length); + ssize_t printed = 0; + for (int i = 0; i < 32; i++) { + int ofs = i * 16; // 64 bit regs take up 16 ascii chars to print in hex + if (ofs + 16 > buffer_length) { + Util::panic("Too big!\n"); + } + u64 reg = debugger->core.CpuGetRegs().gpr[i]; + printed += snprintf(buffer + ofs, buffer_length - ofs, "%016lx", reg); + } + return printed; +} + +Debugger::Debugger(n64::Core& core) : core(core) { + gdbstub_config_t config; + memset(&config, 0, sizeof(gdbstub_config_t)); + config.port = 1337; + config.user_data = this; + config.start = (gdbstub_start_t) n64_debug_start; + config.stop = (gdbstub_stop_t) n64_debug_stop; + config.step = (gdbstub_step_t) n64_debug_step; + config.set_breakpoint = (gdbstub_set_breakpoint_t) n64_debug_set_breakpoint; + config.clear_breakpoint = (gdbstub_clear_breakpoint_t) n64_debug_clear_breakpoint; + config.get_memory = (gdbstub_get_memory_t) n64_debug_get_memory; + config.get_register_value = (gdbstub_get_register_value_t) n64_debug_get_register_value; + config.get_general_registers = (gdbstub_get_general_registers_t) n64_debug_get_general_registers; + + config.target_config = target_xml; + config.target_config_length = strlen(target_xml); + + printf("Sizeof target: %ld\n", config.target_config_length); + + config.memory_map = memory_map; + config.memory_map_length = strlen(memory_map); + + printf("Sizeof memory map: %ld\n", config.memory_map_length); + + gdb = gdbstub_init(config); + if (!gdb) { + Util::panic("Failed to initialize GDB stub!"); + } +} + +Debugger::~Debugger() { + if(enabled) { + gdbstub_term(gdb); + } +} + +void Debugger::tick() const { + gdbstub_tick(gdb); +} + +void Debugger::breakpointHit() { + broken = true; + gdbstub_breakpoint_hit(gdb); +} diff --git a/src/backend/Debugger.hpp b/src/backend/Debugger.hpp new file mode 100644 index 00000000..53d99728 --- /dev/null +++ b/src/backend/Debugger.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#include + +struct Breakpoint { + u32 address = 0; + Breakpoint* next = nullptr; +}; + +namespace n64 { struct Core; } + +struct Debugger { + Debugger(n64::Core& core); + ~Debugger(); + bool broken = false, enabled = true; + int steps = 0; + gdbstub_t* gdb; + Breakpoint* breakpoints = nullptr; + n64::Core& core; + + [[nodiscard]] inline bool checkBreakpoint(u32 address) const { + auto* cur = breakpoints; + while (cur != nullptr) { + if (cur->address == address) { + return true; + } + cur = cur->next; + } + return false; + } + + void tick() const; + void breakpointHit(); +}; \ No newline at end of file