begin debugger work

This commit is contained in:
CocoSimone
2022-08-26 23:01:05 +02:00
parent 477e8b7884
commit 631b864641
7 changed files with 216 additions and 744 deletions

546
external/gdbstub.h vendored
View File

@@ -1,546 +0,0 @@
//
// 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 <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>
#include <netinet/in.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) {
// TODO: investigate
}
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;
}
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

@@ -141,4 +141,5 @@ ImDrawData* Window::Present(n64::Core& core) {
void Window::Render(n64::Core& core) { void Window::Render(n64::Core& core) {
MainMenuBar(core); MainMenuBar(core);
DebuggerWindow(core);
} }

View File

@@ -21,7 +21,7 @@ private:
void InitImgui(); void InitImgui();
void Render(n64::Core& core); void Render(n64::Core& core);
void MainMenuBar(n64::Core& core); void MainMenuBar(n64::Core& core);
void DebuggerWindow(n64::Core& core); void DebuggerWindow(n64::Core& core) const;
VkPhysicalDevice physicalDevice{}; VkPhysicalDevice physicalDevice{};
VkDevice device{}; VkDevice device{};

View File

@@ -1,149 +1,226 @@
#pragma once #pragma once
#define GDBSTUB_IMPLEMENTATION
#include <gdbstub.h>
#include <util.hpp> #include <util.hpp>
#include <map>
#include <functional>
struct Core; struct Core;
#define GDB_CPU_PORT 1337 const std::string gprStr[32] = {
"zero", "at", "v0", "v1",
struct Breakpoint { "a0", "a1", "a2", "a3",
u32 addr; "t0", "t1", "t2", "t3",
Breakpoint* next; "t4", "t5", "t6", "t7",
"s0", "s1", "s2", "s3",
"s4", "s5", "s6", "s7",
"t8", "t9", "k0", "k1",
"gp", "sp", "s8", "ra"
}; };
struct DebuggerState { std::string special(u32 instr) {
gdbstub_t* gdb; u8 mask = (instr & 0x3F);
bool broken; u8 sa = (instr >> 6) & 0x1f;
int steps; switch (mask) { // TODO: named constants for clearer code
Breakpoint* breakpoints; case 0:
bool enabled; if (mask != 0) {
}; return fmt::format("sll {}, {}, {:02X}", gprStr[RD(instr)], gprStr[RT(instr)], sa);
inline bool CheckBreakpoint(DebuggerState& state, u32 addr) {
Breakpoint* cur = state.breakpoints;
while (cur != NULL) {
if (cur->addr == addr) {
util::print("Hit breakpoint at 0x{:08X}\n", addr);
return true;
} }
cur = cur->next; return "nop";
case 0x02: return fmt::format("srl {}, {}, {:02X}", gprStr[RD(instr)], gprStr[RT(instr)], sa);
case 0x03: return fmt::format("sra {}, {}, {:02X}", gprStr[RD(instr)], gprStr[RT(instr)], sa);
case 0x04: return fmt::format("sllv {}, {}, {}", gprStr[RD(instr)], gprStr[RT(instr)], gprStr[RS(instr)]);
case 0x06: return fmt::format("srlv {}, {}, {}", gprStr[RD(instr)], gprStr[RT(instr)], gprStr[RS(instr)]);
case 0x07: return fmt::format("srav {}, {}, {}", gprStr[RD(instr)], gprStr[RT(instr)], gprStr[RS(instr)]);
case 0x08: return fmt::format("jr {}", gprStr[RS(instr)]);
case 0x09: return fmt::format("jalr {}, {}", gprStr[RD(instr)], gprStr[RS(instr)]);
case 0x0C: return fmt::format("syscall");
case 0x0D: return fmt::format("break");
case 0x0F: return fmt::format("sync");
case 0x10: return fmt::format("mfhi {}", gprStr[RD(instr)]);
case 0x11: return fmt::format("mthi {}", gprStr[RS(instr)]);
case 0x12: return fmt::format("mflo {}", gprStr[RD(instr)]);
case 0x13: return fmt::format("mtlo {}", gprStr[RS(instr)]);
case 0x14: return fmt::format("dsllv {}, {}, {}", gprStr[RD(instr)], gprStr[RT(instr)], gprStr[RS(instr)]);
case 0x16: return fmt::format("dsrlv {}, {}, {}", gprStr[RD(instr)], gprStr[RT(instr)], gprStr[RS(instr)]);
case 0x17: return fmt::format("dsrav {}, {}, {}", gprStr[RD(instr)], gprStr[RT(instr)], gprStr[RS(instr)]);
case 0x18: return fmt::format("mult {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x19: return fmt::format("multu {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x1A: return fmt::format("div {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x1B: return fmt::format("divu {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x1C: return fmt::format("dmult {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x1D: return fmt::format("dmultu {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x1E: return fmt::format("ddiv {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x1F: return fmt::format("ddivu {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x20: return fmt::format("add {}, {}, {}", gprStr[RD(instr)], gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x21: return fmt::format("addu {}, {}, {}", gprStr[RD(instr)], gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x22: return fmt::format("sub {}, {}, {}", gprStr[RD(instr)], gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x23: return fmt::format("subu {}, {}, {}", gprStr[RD(instr)], gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x24: return fmt::format("and {}, {}, {}", gprStr[RD(instr)], gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x25: return fmt::format("or {}, {}, {}", gprStr[RD(instr)], gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x26: return fmt::format("xor {}, {}, {}", gprStr[RD(instr)], gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x27: return fmt::format("nor {}, {}, {}", gprStr[RD(instr)], gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x2A: return fmt::format("slt {}, {}, {}", gprStr[RD(instr)], gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x2B: return fmt::format("sltu {}, {}, {}", gprStr[RD(instr)], gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x2C: return fmt::format("dadd {}, {}, {}", gprStr[RD(instr)], gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x2D: return fmt::format("daddu {}, {}, {}", gprStr[RD(instr)], gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x2E: return fmt::format("dsub {}, {}, {}", gprStr[RD(instr)], gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x2F: return fmt::format("dsubu {}, {}, {}", gprStr[RD(instr)], gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x34: return fmt::format("teq {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
case 0x38: return fmt::format("dsll {}, {}, {:02X}", gprStr[RD(instr)], gprStr[RS(instr)], sa);
case 0x3A: return fmt::format("dsrl {}, {}, {:02X}", gprStr[RD(instr)], gprStr[RS(instr)], sa);
case 0x3B: return fmt::format("dsra {}, {}, {:02X}", gprStr[RD(instr)], gprStr[RS(instr)], sa);
case 0x3C: return fmt::format("dsll32 {}, {}, {:02X}", gprStr[RD(instr)], gprStr[RS(instr)], sa);
case 0x3E: return fmt::format("dsrl32 {}, {}, {:02X}", gprStr[RD(instr)], gprStr[RS(instr)], sa);
case 0x3F: return fmt::format("dsra32 {}, {}, {:02X}", gprStr[RD(instr)], gprStr[RS(instr)], sa);
default:
return fmt::format("INVALID ({:08X})\n", instr);
} }
return false;
} }
const char* target_xml = std::string regimm(u32 instr) {
"<?xml version=\"1.0\"?>" u8 mask = ((instr >> 16) & 0x1F);
"<!DOCTYPE feature SYSTEM \"gdb-target.dtd\">" // 000r_rccc
"<target version=\"1.0\">" switch (mask) { // TODO: named constants for clearer code
"<architecture>mips:4000</architecture>" case 0x00: return fmt::format("bltz {}", gprStr[RS(instr)]);
"<osabi>none</osabi>" case 0x01: return fmt::format("bgez {}", gprStr[RS(instr)]);
"<feature name=\"org.gnu.gdb.mips.cpu\">" case 0x02: return fmt::format("bltzl {}", gprStr[RS(instr)]);
" <reg name=\"r0\" bitsize=\"64\" regnum=\"0\"/>" case 0x03: return fmt::format("bgezl {}", gprStr[RS(instr)]);
" <reg name=\"r1\" bitsize=\"64\"/>" case 0x10: return fmt::format("bltzal {}", gprStr[RS(instr)]);
" <reg name=\"r2\" bitsize=\"64\"/>" case 0x11: return fmt::format("bgezal {}", gprStr[RS(instr)]);
" <reg name=\"r3\" bitsize=\"64\"/>" case 0x12: return fmt::format("bltzall {}", gprStr[RS(instr)]);
" <reg name=\"r4\" bitsize=\"64\"/>" case 0x13: return fmt::format("bgezall {}", gprStr[RS(instr)]);
" <reg name=\"r5\" bitsize=\"64\"/>" default: return fmt::format("INVALID {:08X}", instr);
" <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 = const std::map<u8, std::function<std::string(u32)>> cpuInstr = {
"<?xml version=\"1.0\"?>" {0b000000, special},
"<memory-map>" {0b000001, regimm},
"<!-- KUSEG - TLB mapped, treat it as a giant block of RAM. Not ideal, but not sure how else to deal with it -->" {0b000010, [](u32 instr) {
"<memory type=\"ram\" start=\"0x0000000000000000\" length=\"0x80000000\"/>" return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b000011, [](u32 instr) {
return fmt::format("jal {:08X}", instr & 0x3FFFFFF);
}}, {0b000100, [](u32 instr) {
return fmt::format("beq {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
}}, {0b000101, [](u32 instr) {
return fmt::format("bne {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
}}, {0b000110, [](u32 instr) {
return fmt::format("blez {}", gprStr[RS(instr)]);
}}, {0b000111, [](u32 instr) {
return fmt::format("bgtz {}", gprStr[RS(instr)]);
}}, {0b001000, [](u32 instr) {
return fmt::format("addi {}, {:04X}", gprStr[RS(instr)], instr & 0xFFFF);
}}, {0b001001, [](u32 instr) {
return fmt::format("addiu {}, {:04X}", gprStr[RS(instr)], instr & 0x3FFFFFF);
}}, {0b001010, [](u32 instr) {
return fmt::format("slti {}, {}, {:04X}", gprStr[RT(instr)], gprStr[RS(instr)], instr & 0xFFFF);
}}, {0b001011, [](u32 instr) {
return fmt::format("sltiu {}, {}, {:04X}", gprStr[RT(instr)], gprStr[RS(instr)], instr & 0xFFFF);
}}, {0b001100, [](u32 instr) {
return fmt::format("andi {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
}}, {0b001101, [](u32 instr) {
return fmt::format("ori {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
}}, {0b001110, [](u32 instr) {
return fmt::format("xori {}", gprStr[RS(instr)]);
}}, {0b001111, [](u32 instr) {
return fmt::format("lui {}", gprStr[RS(instr)]);
}}, {0b010000, /*cop0decode*/ },
{0b010001, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b010010, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b010011, [](u32 instr) {
return fmt::format("jal {:08X}", instr & 0x3FFFFFF);
}}, {0b010100, [](u32 instr) {
return fmt::format("beq {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
}}, {0b010101, [](u32 instr) {
return fmt::format("bne {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
}}, {0b010110, [](u32 instr) {
return fmt::format("blez {}", gprStr[RS(instr)]);
}}, {0b010111, [](u32 instr) {
return fmt::format("bgtz {}", gprStr[RS(instr)]);
}}, {0b011000, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b011001, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b011010, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b011011, [](u32 instr) {
return fmt::format("jal {:08X}", instr & 0x3FFFFFF);
}}, {0b011100, [](u32 instr) {
return fmt::format("beq {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
}}, {0b011101, [](u32 instr) {
return fmt::format("bne {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
}}, {0b011110, [](u32 instr) {
return fmt::format("blez {}", gprStr[RS(instr)]);
}}, {0b011111, [](u32 instr) {
return fmt::format("bgtz {}", gprStr[RS(instr)]);
}}, {0b100000, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b100001, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b100010, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b100011, [](u32 instr) {
return fmt::format("jal {:08X}", instr & 0x3FFFFFF);
}}, {0b100100, [](u32 instr) {
return fmt::format("beq {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
}}, {0b100101, [](u32 instr) {
return fmt::format("bne {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
}}, {0b100110, [](u32 instr) {
return fmt::format("blez {}", gprStr[RS(instr)]);
}}, {0b100111, [](u32 instr) {
return fmt::format("bgtz {}", gprStr[RS(instr)]);
}}, {0b101000, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b101001, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b101010, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b101011, [](u32 instr) {
return fmt::format("jal {:08X}", instr & 0x3FFFFFF);
}}, {0b101100, [](u32 instr) {
return fmt::format("beq {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
}}, {0b101101, [](u32 instr) {
return fmt::format("bne {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
}}, {0b101110, [](u32 instr) {
return fmt::format("blez {}", gprStr[RS(instr)]);
}}, {0b101111, [](u32 instr) {
return fmt::format("bgtz {}", gprStr[RS(instr)]);
}}, {0b110000, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b110001, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b110010, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b110011, [](u32 instr) {
return fmt::format("jal {:08X}", instr & 0x3FFFFFF);
}}, {0b110100, [](u32 instr) {
return fmt::format("beq {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
}}, {0b110101, [](u32 instr) {
return fmt::format("bne {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
}}, {0b110110, [](u32 instr) {
return fmt::format("blez {}", gprStr[RS(instr)]);
}}, {0b110111, [](u32 instr) {
return fmt::format("bgtz {}", gprStr[RS(instr)]);
}}, {0b111000, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b111001, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b111010, [](u32 instr) {
return fmt::format("j {:08X}", instr & 0x3FFFFFF);
}}, {0b111011, [](u32 instr) {
return fmt::format("jal {:08X}", instr & 0x3FFFFFF);
}}, {0b111100, [](u32 instr) {
return fmt::format("beq {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
}}, {0b111101, [](u32 instr) {
return fmt::format("bne {}, {}", gprStr[RS(instr)], gprStr[RT(instr)]);
}}, {0b111110, [](u32 instr) {
return fmt::format("blez {}", gprStr[RS(instr)]);
}}, {0b111111, [](u32 instr) {
return fmt::format("bgtz {}", gprStr[RS(instr)]);
}}
};
"<!-- KSEG0 hardware mapped, full copy of the memory map goes here -->" // TODO finish struct Debugger {
"<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 DebugStart(void* user_data);
void DebugStop(void* user_data);
void DebugStep(void* user_data);
void DebugSetBreakpoint(void* user_data, u32 address);
void DebugClearBreakpoint(void* user_data, u32 address);
ssize_t DebugGetMemory(void* user_data, char* buffer, size_t length, u32 address, size_t bytes);
ssize_t DebugGetRegisterValue(void* user_data, char * buffer, size_t buffer_length, int reg);
ssize_t DebugGetGeneralRegisters(void* user_data, char * buffer, size_t buffer_length);

View File

@@ -38,7 +38,7 @@ void Window::MainMenuBar(n64::Core& core) {
ImGui::PopFont(); ImGui::PopFont();
} }
void Window::DebuggerWindow(n64::Core& core) { void Window::DebuggerWindow(n64::Core& core) const {
ImGui::PushFont(uiFont); ImGui::PushFont(uiFont);
ImGui::Begin("Debugger"); ImGui::Begin("Debugger");
if(ImGui::Button("Step")) { if(ImGui::Button("Step")) {

View File

@@ -56,16 +56,6 @@ void Core::Run(Window& window) {
} }
for(;cycles <= mmio.vi.cyclesPerHalfline; cycles++) { for(;cycles <= mmio.vi.cyclesPerHalfline; cycles++) {
#ifndef NDEBUG
if (debuggerState.enabled && CheckBreakpoint(debuggerState, cpu.regs.pc)) {
DebuggerBreakpointHit();
}
while (debuggerState.broken) {
SDL_Delay(1000);
DebuggerTick();
}
#endif
cpu.Step(mem); cpu.Step(mem);
mmio.rsp.Step(mmio.mi, cpu.regs, mmio.rdp); mmio.rsp.Step(mmio.mi, cpu.regs, mmio.rdp);
mmio.rsp.Step(mmio.mi, cpu.regs, mmio.rdp); mmio.rsp.Step(mmio.mi, cpu.regs, mmio.rdp);
@@ -170,49 +160,4 @@ void Core::UpdateController(const u8* state) {
} }
} }
} }
void Core::DebuggerInit() {
gdbstub_config_t config;
memset(&config, 0, sizeof(gdbstub_config_t));
config.port = GDB_CPU_PORT;
config.user_data = this;
config.start = (gdbstub_start_t) DebugStart;
config.stop = (gdbstub_stop_t) DebugStop;
config.step = (gdbstub_step_t) DebugStep;
config.set_breakpoint = (gdbstub_set_breakpoint_t) DebugSetBreakpoint;
config.clear_breakpoint = (gdbstub_clear_breakpoint_t) DebugClearBreakpoint;
config.get_memory = (gdbstub_get_memory_t) DebugGetMemory;
config.get_register_value = (gdbstub_get_register_value_t) DebugGetRegisterValue;
config.get_general_registers = (gdbstub_get_general_registers_t) DebugGetGeneralRegisters;
config.target_config = target_xml;
config.target_config_length = strlen(target_xml);
printf("Sizeof target: %zu\n", config.target_config_length);
config.memory_map = memory_map;
config.memory_map_length = strlen(memory_map);
printf("Sizeof memory map: %zu\n", config.memory_map_length);
debuggerState.gdb = gdbstub_init(config);
if (!debuggerState.gdb) {
util::panic("Failed to initialize GDB stub!");
}
}
void Core::DebuggerTick() const {
gdbstub_tick(debuggerState.gdb);
}
void Core::DebuggerBreakpointHit() {
debuggerState.broken = true;
gdbstub_breakpoint_hit(debuggerState.gdb);
}
void Core::DebuggerCleanup() const {
if (debuggerState.enabled) {
gdbstub_term(debuggerState.gdb);
}
}
} }

View File

@@ -19,11 +19,6 @@ struct Core {
void TogglePause() { pause = !pause; } void TogglePause() { pause = !pause; }
VI& GetVI() { return mem.mmio.vi; } VI& GetVI() { return mem.mmio.vi; }
void DebuggerInit();
void DebuggerTick() const;
void DebuggerBreakpointHit();
void DebuggerCleanup() const;
bool pause = true; bool pause = true;
bool romLoaded = false; bool romLoaded = false;
SDL_GameController* gamepad; SDL_GameController* gamepad;