add gdbstub

This commit is contained in:
CocoSimone
2023-02-11 13:45:55 +01:00
parent 78effc5012
commit 2a9a9e37b0
7 changed files with 900 additions and 5 deletions

21
external/gdbstub/LICENSE vendored Normal file
View File

@@ -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.

553
external/gdbstub/gdbstub.h vendored Normal file
View File

@@ -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 <stdint.h>
#include <sys/types.h>
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 <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(WIN32)
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment (lib, "Ws2_32.lib")
#define close closesocket
#else
#include <unistd.h>
#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", &reg);
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

View File

@@ -35,6 +35,7 @@ include_directories(
../external/imgui/imgui
../external/imgui/imgui/backends
../external/discord-rpc/include
../external/gdbstub
${SDL2_INCLUDE_DIRS}
)

View File

@@ -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)

View File

@@ -5,6 +5,7 @@
#include <string>
#include <backend/core/Dynarec.hpp>
#include <backend/core/registers/Registers.hpp>
#include <Debugger.hpp>
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};
};
}

274
src/backend/Debugger.cpp Normal file
View File

@@ -0,0 +1,274 @@
#include <Debugger.hpp>
#define GDBSTUB_IMPLEMENTATION
#include <gdbstub.h>
#include <log.hpp>
#include <Core.hpp>
const char* target_xml =
"<?xml version=\"1.0\"?>"
"<!DOCTYPE feature SYSTEM \"gdb-target.dtd\">"
"<target version=\"1.0\">"
"<architecture>mips:4000</architecture>"
"<osabi>none</osabi>"
"<feature name=\"org.gnu.gdb.mips.cpu\">"
" <reg name=\"r0\" bitsize=\"64\" regnum=\"0\"/>"
" <reg name=\"r1\" bitsize=\"64\"/>"
" <reg name=\"r2\" bitsize=\"64\"/>"
" <reg name=\"r3\" bitsize=\"64\"/>"
" <reg name=\"r4\" bitsize=\"64\"/>"
" <reg name=\"r5\" bitsize=\"64\"/>"
" <reg name=\"r6\" bitsize=\"64\"/>"
" <reg name=\"r7\" bitsize=\"64\"/>"
" <reg name=\"r8\" bitsize=\"64\"/>"
" <reg name=\"r9\" bitsize=\"64\"/>"
" <reg name=\"r10\" bitsize=\"64\"/>"
" <reg name=\"r11\" bitsize=\"64\"/>"
" <reg name=\"r12\" bitsize=\"64\"/>"
" <reg name=\"r13\" bitsize=\"64\"/>"
" <reg name=\"r14\" bitsize=\"64\"/>"
" <reg name=\"r15\" bitsize=\"64\"/>"
" <reg name=\"r16\" bitsize=\"64\"/>"
" <reg name=\"r17\" bitsize=\"64\"/>"
" <reg name=\"r18\" bitsize=\"64\"/>"
" <reg name=\"r19\" bitsize=\"64\"/>"
" <reg name=\"r20\" bitsize=\"64\"/>"
" <reg name=\"r21\" bitsize=\"64\"/>"
" <reg name=\"r22\" bitsize=\"64\"/>"
" <reg name=\"r23\" bitsize=\"64\"/>"
" <reg name=\"r24\" bitsize=\"64\"/>"
" <reg name=\"r25\" bitsize=\"64\"/>"
" <reg name=\"r26\" bitsize=\"64\"/>"
" <reg name=\"r27\" bitsize=\"64\"/>"
" <reg name=\"r28\" bitsize=\"64\"/>"
" <reg name=\"r29\" bitsize=\"64\"/>"
" <reg name=\"r30\" bitsize=\"64\"/>"
" <reg name=\"r31\" bitsize=\"64\"/>"
" <reg name=\"lo\" bitsize=\"64\" regnum=\"33\"/>"
" <reg name=\"hi\" bitsize=\"64\" regnum=\"34\"/>"
" <reg name=\"pc\" bitsize=\"64\" regnum=\"37\"/>"
" </feature>"
"<feature name=\"org.gnu.gdb.mips.cp0\">"
" <reg name=\"status\" bitsize=\"32\" regnum=\"32\"/>"
" <reg name=\"badvaddr\" bitsize=\"32\" regnum=\"35\"/>"
" <reg name=\"cause\" bitsize=\"32\" regnum=\"36\"/>"
" </feature>"
"<!-- TODO fix the sizes here. How do we deal with configurable sizes? -->"
"<feature name=\"org.gnu.gdb.mips.fpu\">"
" <reg name=\"f0\" bitsize=\"32\" type=\"ieee_single\" regnum=\"38\"/>"
" <reg name=\"f1\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f2\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f3\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f4\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f5\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f6\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f7\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f8\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f9\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f10\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f11\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f12\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f13\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f14\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f15\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f16\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f17\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f18\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f19\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f20\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f21\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f22\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f23\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f24\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f25\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f26\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f27\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f28\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f29\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f30\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"f31\" bitsize=\"32\" type=\"ieee_single\"/>"
" <reg name=\"fcsr\" bitsize=\"32\" group=\"float\"/>"
" <reg name=\"fir\" bitsize=\"32\" group=\"float\"/>"
" </feature>"
"</target>";
const char* memory_map =
"<?xml version=\"1.0\"?>"
"<memory-map>"
"<!-- KUSEG - TLB mapped, treat it as a giant block of RAM. Not ideal, but not sure how else to deal with it -->"
"<memory type=\"ram\" start=\"0x0000000000000000\" length=\"0x80000000\"/>"
"<!-- KSEG0 hardware mapped, full copy of the memory map goes here -->" // TODO finish
"<memory type=\"ram\" start=\"0xffffffff80000000\" length=\"0x800000\"/>" // RDRAM
"<memory type=\"ram\" start=\"0xffffffff84000000\" length=\"0x1000\"/>" // RSP DMEM
"<memory type=\"ram\" start=\"0xffffffff84001000\" length=\"0x1000\"/>" // RSP IMEM
"<memory type=\"rom\" start=\"0xffffffff9fc00000\" length=\"0x7c0\"/>" // PIF ROM
"<!-- KSEG1 hardware mapped, full copy of the memory map goes here -->" // TODO finish
"<memory type=\"ram\" start=\"0xffffffffa0000000\" length=\"0x800000\"/>" // RDRAM
"<memory type=\"ram\" start=\"0xffffffffa4000000\" length=\"0x1000\"/>" // RSP DMEM
"<memory type=\"ram\" start=\"0xffffffffa4001000\" length=\"0x1000\"/>" // RSP IMEM
"<memory type=\"rom\" start=\"0xffffffffbfc00000\" length=\"0x7c0\"/>" // PIF ROM
"</memory-map>";
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);
}

34
src/backend/Debugger.hpp Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
#include <common.hpp>
#include <gdbstub.h>
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();
};